From b50050a545663cd12f8afa6b7e5e4cc5cc3b073b Mon Sep 17 00:00:00 2001 From: M-Gabrielly Date: Tue, 7 Oct 2025 01:15:13 -0300 Subject: [PATCH] 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. --- src/features/pacientes/api/index.ts | 166 ++++++++++++++++++++++ src/features/pacientes/types/index.ts | 41 ++++++ src/features/profissionais/types/index.ts | 83 +++++++++++ src/types/api.ts | 24 ++++ 4 files changed, 314 insertions(+) create mode 100644 src/features/pacientes/api/index.ts create mode 100644 src/features/pacientes/types/index.ts create mode 100644 src/features/profissionais/types/index.ts create mode 100644 src/types/api.ts diff --git a/src/features/pacientes/api/index.ts b/src/features/pacientes/api/index.ts new file mode 100644 index 0000000..49b25c3 --- /dev/null +++ b/src/features/pacientes/api/index.ts @@ -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; +declare function withPrefer( + headers: Record, + prefer: string +): Record; +declare function parse(res: Response): Promise; + +function rangeHeaders(page?: number, limit?: number): Record { + 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 { + 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 { + 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(); + + // 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(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 { + const url = `${REST}/patients?id=eq.${id}`; + const res = await fetch(url, { method: "GET", headers: baseHeaders() }); + const arr = await parse(res); + if (!arr?.length) throw new Error("404: Paciente não encontrado"); + return arr[0]; +} + +export async function criarPaciente(input: PacienteInput): Promise { + 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(res); + return Array.isArray(arr) ? arr[0] : (arr as Paciente); +} + +export async function atualizarPaciente( + id: string | number, + input: PacienteInput +): Promise { + 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(res); + return Array.isArray(arr) ? arr[0] : (arr as Paciente); +} + +export async function excluirPaciente(id: string | number): Promise { + const url = `${REST}/patients?id=eq.${id}`; + const res = await fetch(url, { method: "DELETE", headers: baseHeaders() }); + await parse(res); +} +// ===== PACIENTES (Extra: verificação de CPF duplicado) ===== +export async function verificarCpfDuplicado(cpf: string): Promise { + 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; +} diff --git a/src/features/pacientes/types/index.ts b/src/features/pacientes/types/index.ts new file mode 100644 index 0000000..35122c0 --- /dev/null +++ b/src/features/pacientes/types/index.ts @@ -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; +}; diff --git a/src/features/profissionais/types/index.ts b/src/features/profissionais/types/index.ts new file mode 100644 index 0000000..29bcc00 --- /dev/null +++ b/src/features/profissionais/types/index.ts @@ -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; +}; diff --git a/src/types/api.ts b/src/types/api.ts new file mode 100644 index 0000000..9ad717d --- /dev/null +++ b/src/types/api.ts @@ -0,0 +1,24 @@ +// src/types/api.ts + +export type ApiOk = { + 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; +};