feat: Refactor and modularize the API layer
- The API logic for Patients and Professionals has been extracted to the new src/features folder. - The patient API functions have been refactored to use the httpClient. - Types have been centralized by feature for better organization.
This commit is contained in:
parent
6aef2b4910
commit
b50050a545
166
src/features/pacientes/api/index.ts
Normal file
166
src/features/pacientes/api/index.ts
Normal file
@ -0,0 +1,166 @@
|
||||
// src/features/pacientes/api/index.ts
|
||||
import httpClient from "@/lib/http";
|
||||
import type { Paciente, PacienteInput } from "@/features/pacientes/types";
|
||||
|
||||
// TODO: Essas dependências foram movidas de lib/api.ts e podem precisar de ajustes.
|
||||
const API_BASE =
|
||||
process.env.NEXT_PUBLIC_API_BASE ?? "https://yuanqfswhberkoevtmfr.supabase.co";
|
||||
const REST = `${API_BASE}/rest/v1`;
|
||||
|
||||
// Funções auxiliares que estavam no escopo de api.ts
|
||||
// Elas provavelmente deveriam estar em um arquivo de utilitários compartilhado.
|
||||
declare function baseHeaders(): Record<string, string>;
|
||||
declare function withPrefer(
|
||||
headers: Record<string, string>,
|
||||
prefer: string
|
||||
): Record<string, string>;
|
||||
declare function parse<T>(res: Response): Promise<T>;
|
||||
|
||||
function rangeHeaders(page?: number, limit?: number): Record<string, string> {
|
||||
if (!page || !limit) return {};
|
||||
const start = (page - 1) * limit;
|
||||
const end = start + limit - 1;
|
||||
return { Range: `${start}-${end}`, "Range-Unit": "items" };
|
||||
}
|
||||
|
||||
// ===== PACIENTES (CRUD) =====
|
||||
export async function listarPacientes(params?: {
|
||||
page?: number;
|
||||
limit?: number;
|
||||
q?: string;
|
||||
}): Promise<Paciente[]> {
|
||||
const qs = new URLSearchParams();
|
||||
if (params?.q) qs.set("q", params.q);
|
||||
|
||||
const url = `${REST}/patients${qs.toString() ? `?${qs.toString()}` : ""}`;
|
||||
const res = await httpClient.get(url, {
|
||||
headers: {
|
||||
...rangeHeaders(params?.page, params?.limit),
|
||||
},
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(`Erro ao listar pacientes: ${res.statusText}`);
|
||||
}
|
||||
|
||||
return await res.json();
|
||||
}
|
||||
|
||||
// Nova função para busca avançada de pacientes
|
||||
export async function buscarPacientes(termo: string): Promise<Paciente[]> {
|
||||
if (!termo || termo.trim().length < 2) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const searchTerm = termo.toLowerCase().trim();
|
||||
const digitsOnly = searchTerm.replace(/\D/g, "");
|
||||
|
||||
// Monta queries para buscar em múltiplos campos
|
||||
const queries = [];
|
||||
|
||||
// Busca por ID se parece com UUID
|
||||
if (searchTerm.includes("-") && searchTerm.length > 10) {
|
||||
queries.push(`id=eq.${searchTerm}`);
|
||||
}
|
||||
|
||||
// Busca por CPF (com e sem formatação)
|
||||
if (digitsOnly.length >= 11) {
|
||||
queries.push(`cpf=eq.${digitsOnly}`);
|
||||
} else if (digitsOnly.length >= 3) {
|
||||
queries.push(`cpf=ilike.*${digitsOnly}*`);
|
||||
}
|
||||
|
||||
// Busca por nome (usando ilike para busca case-insensitive)
|
||||
if (searchTerm.length >= 2) {
|
||||
queries.push(`full_name=ilike.*${searchTerm}*`);
|
||||
queries.push(`social_name=ilike.*${searchTerm}*`);
|
||||
}
|
||||
|
||||
// Busca por email se contém @
|
||||
if (searchTerm.includes("@")) {
|
||||
queries.push(`email=ilike.*${searchTerm}*`);
|
||||
}
|
||||
|
||||
const results: Paciente[] = [];
|
||||
const seenIds = new Set<string>();
|
||||
|
||||
// Executa as buscas e combina resultados únicos
|
||||
for (const query of queries) {
|
||||
try {
|
||||
const url = `${REST}/patients?${query}&limit=10`;
|
||||
const res = await fetch(url, { method: "GET", headers: baseHeaders() });
|
||||
const arr = await parse<Paciente[]>(res);
|
||||
|
||||
if (arr?.length > 0) {
|
||||
for (const paciente of arr) {
|
||||
if (!seenIds.has(paciente.id)) {
|
||||
seenIds.add(paciente.id);
|
||||
results.push(paciente);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(`Erro na busca com query: ${query}`, error);
|
||||
}
|
||||
}
|
||||
|
||||
return results.slice(0, 20); // Limita a 20 resultados
|
||||
}
|
||||
|
||||
export async function buscarPacientePorId(id: string | number): Promise<Paciente> {
|
||||
const url = `${REST}/patients?id=eq.${id}`;
|
||||
const res = await fetch(url, { method: "GET", headers: baseHeaders() });
|
||||
const arr = await parse<Paciente[]>(res);
|
||||
if (!arr?.length) throw new Error("404: Paciente não encontrado");
|
||||
return arr[0];
|
||||
}
|
||||
|
||||
export async function criarPaciente(input: PacienteInput): Promise<Paciente> {
|
||||
const url = `${REST}/patients`;
|
||||
const res = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: withPrefer(
|
||||
{ ...baseHeaders(), "Content-Type": "application/json" },
|
||||
"return=representation"
|
||||
),
|
||||
body: JSON.stringify(input),
|
||||
});
|
||||
const arr = await parse<Paciente[] | Paciente>(res);
|
||||
return Array.isArray(arr) ? arr[0] : (arr as Paciente);
|
||||
}
|
||||
|
||||
export async function atualizarPaciente(
|
||||
id: string | number,
|
||||
input: PacienteInput
|
||||
): Promise<Paciente> {
|
||||
const url = `${REST}/patients?id=eq.${id}`;
|
||||
const res = await fetch(url, {
|
||||
method: "PATCH",
|
||||
headers: withPrefer(
|
||||
{ ...baseHeaders(), "Content-Type": "application/json" },
|
||||
"return=representation"
|
||||
),
|
||||
body: JSON.stringify(input),
|
||||
});
|
||||
const arr = await parse<Paciente[] | Paciente>(res);
|
||||
return Array.isArray(arr) ? arr[0] : (arr as Paciente);
|
||||
}
|
||||
|
||||
export async function excluirPaciente(id: string | number): Promise<void> {
|
||||
const url = `${REST}/patients?id=eq.${id}`;
|
||||
const res = await fetch(url, { method: "DELETE", headers: baseHeaders() });
|
||||
await parse<any>(res);
|
||||
}
|
||||
// ===== PACIENTES (Extra: verificação de CPF duplicado) =====
|
||||
export async function verificarCpfDuplicado(cpf: string): Promise<boolean> {
|
||||
const clean = (cpf || "").replace(/\D/g, "");
|
||||
const url = `${API_BASE}/rest/v1/patients?cpf=eq.${clean}&select=id`;
|
||||
|
||||
const res = await fetch(url, {
|
||||
method: "GET",
|
||||
headers: baseHeaders(),
|
||||
});
|
||||
|
||||
const data = await res.json().catch(() => []);
|
||||
return Array.isArray(data) && data.length > 0;
|
||||
}
|
||||
41
src/features/pacientes/types/index.ts
Normal file
41
src/features/pacientes/types/index.ts
Normal file
@ -0,0 +1,41 @@
|
||||
// src/features/pacientes/types/index.ts
|
||||
|
||||
// ===== PACIENTES =====
|
||||
export type Paciente = {
|
||||
id: string;
|
||||
full_name: string;
|
||||
social_name?: string | null;
|
||||
cpf?: string;
|
||||
rg?: string | null;
|
||||
sex?: string | null;
|
||||
birth_date?: string | null;
|
||||
phone_mobile?: string;
|
||||
email?: string;
|
||||
cep?: string | null;
|
||||
street?: string | null;
|
||||
number?: string | null;
|
||||
complement?: string | null;
|
||||
neighborhood?: string | null;
|
||||
city?: string | null;
|
||||
state?: string | null;
|
||||
notes?: string | null;
|
||||
};
|
||||
|
||||
export type PacienteInput = {
|
||||
full_name: string;
|
||||
social_name?: string | null;
|
||||
cpf: string;
|
||||
rg?: string | null;
|
||||
sex?: string | null;
|
||||
birth_date?: string | null;
|
||||
phone_mobile?: string | null;
|
||||
email?: string | null;
|
||||
cep?: string | null;
|
||||
street?: string | null;
|
||||
number?: string | null;
|
||||
complement?: string | null;
|
||||
neighborhood?: string | null;
|
||||
city?: string | null;
|
||||
state?: string | null;
|
||||
notes?: string | null;
|
||||
};
|
||||
83
src/features/profissionais/types/index.ts
Normal file
83
src/features/profissionais/types/index.ts
Normal file
@ -0,0 +1,83 @@
|
||||
// src/features/profissionais/types/index.ts
|
||||
|
||||
// ===== MÉDICOS =====
|
||||
export type FormacaoAcademica = {
|
||||
instituicao: string;
|
||||
curso: string;
|
||||
ano_conclusao: string;
|
||||
};
|
||||
|
||||
export type DadosBancarios = {
|
||||
banco: string;
|
||||
agencia: string;
|
||||
conta: string;
|
||||
tipo_conta: string;
|
||||
};
|
||||
|
||||
// ===== MÉDICOS =====
|
||||
export type Medico = {
|
||||
id: string;
|
||||
full_name: string; // Altere 'nome' para 'full_name'
|
||||
nome_social?: string | null;
|
||||
cpf?: string;
|
||||
rg?: string | null;
|
||||
sexo?: string | null;
|
||||
data_nascimento?: string | null;
|
||||
telefone?: string;
|
||||
celular?: string;
|
||||
contato_emergencia?: string;
|
||||
email?: string;
|
||||
crm?: string;
|
||||
estado_crm?: string;
|
||||
rqe?: string;
|
||||
formacao_academica?: FormacaoAcademica[];
|
||||
curriculo_url?: string | null;
|
||||
especialidade?: string;
|
||||
observacoes?: string | null;
|
||||
foto_url?: string | null;
|
||||
tipo_vinculo?: string;
|
||||
dados_bancarios?: DadosBancarios;
|
||||
agenda_horario?: string;
|
||||
valor_consulta?: number | string;
|
||||
active?: boolean;
|
||||
cep?: string;
|
||||
city?: string;
|
||||
complement?: string;
|
||||
neighborhood?: string;
|
||||
number?: string;
|
||||
phone2?: string;
|
||||
state?: string;
|
||||
street?: string;
|
||||
created_at?: string;
|
||||
created_by?: string;
|
||||
updated_at?: string;
|
||||
updated_by?: string;
|
||||
user_id?: string;
|
||||
};
|
||||
|
||||
|
||||
// ===== MÉDICOS =====
|
||||
// ...existing code...
|
||||
export type MedicoInput = {
|
||||
user_id?: string | null;
|
||||
crm: string;
|
||||
crm_uf: string;
|
||||
specialty: string;
|
||||
full_name: string;
|
||||
cpf: string;
|
||||
email: string;
|
||||
phone_mobile: string;
|
||||
phone2?: string | null;
|
||||
cep: string;
|
||||
street: string;
|
||||
number: string;
|
||||
complement?: string;
|
||||
neighborhood?: string;
|
||||
city: string;
|
||||
state: string;
|
||||
birth_date: string | null;
|
||||
rg?: string | null;
|
||||
active?: boolean;
|
||||
created_by?: string | null;
|
||||
updated_by?: string | null;
|
||||
};
|
||||
24
src/types/api.ts
Normal file
24
src/types/api.ts
Normal file
@ -0,0 +1,24 @@
|
||||
// src/types/api.ts
|
||||
|
||||
export type ApiOk<T = any> = {
|
||||
success?: boolean;
|
||||
data: T;
|
||||
message?: string;
|
||||
pagination?: {
|
||||
current_page?: number;
|
||||
per_page?: number;
|
||||
total_pages?: number;
|
||||
total?: number;
|
||||
};
|
||||
};
|
||||
|
||||
// ===== TIPOS COMUNS =====
|
||||
export type Endereco = {
|
||||
cep?: string;
|
||||
logradouro?: string;
|
||||
numero?: string;
|
||||
complemento?: string;
|
||||
bairro?: string;
|
||||
cidade?: string;
|
||||
estado?: string;
|
||||
};
|
||||
Loading…
x
Reference in New Issue
Block a user