diff --git a/susconecta/app/(main-routes)/doutores/page.tsx b/susconecta/app/(main-routes)/doutores/page.tsx index 96e0557..792b416 100644 --- a/susconecta/app/(main-routes)/doutores/page.tsx +++ b/susconecta/app/(main-routes)/doutores/page.tsx @@ -10,9 +10,9 @@ import { Label } from "@/components/ui/label"; import { MoreHorizontal, Plus, Search, Edit, Trash2, ArrowLeft, Eye } from "lucide-react"; import { Badge } from "@/components/ui/badge"; import { DoctorRegistrationForm } from "@/components/forms/doctor-registration-form"; +import ProtectedRoute from "@/components/ProtectedRoute"; // <-- IMPORTADO - -import { listarMedicos, excluirMedico, buscarMedicos, buscarMedicoPorId, Medico } from "@/lib/api"; +import { listarMedicos, excluirMedico, Medico } from "@/lib/api"; function normalizeMedico(m: any): Medico { return { @@ -296,176 +296,153 @@ export default function DoutoresPage() {

{editingId ? "Editar Médico" : "Novo Médico"}

- setShowForm(false)} - /> - - ); - } - - return ( -
-
-
-

Médicos

-

Gerencie os médicos da sua clínica

+ setShowForm(false)} + />
- -
-
-
- - + ) : ( +
+
+
+

Médicos

+

Gerencie os médicos da sua clínica

- - {searchMode && ( - - )} -
- -
-
- -
- - - - Nome - Especialidade - CRM - Contato - Ações - - - - {loading ? ( - - - Carregando… - - - ) : displayedDoctors.length > 0 ? ( - displayedDoctors.map((doctor) => ( - - {doctor.full_name} - - {doctor.especialidade} - - {doctor.crm} - -
- {doctor.email} - {doctor.telefone} -
-
- - - - - - - handleView(doctor)}> - - Ver - - handleEdit(String(doctor.id))}> - - Editar - - handleDelete(String(doctor.id))} className="text-destructive"> - - Excluir - - - - -
- )) - ) : ( - - - Nenhum médico encontrado - - - )} -
-
-
- - {viewingDoctor && ( - setViewingDoctor(null)}> - - - Detalhes do Médico - - Informações detalhadas de {viewingDoctor?.full_name}. - - -
-
- - {viewingDoctor?.full_name} -
-
- - - {viewingDoctor?.especialidade} - -
-
- - {viewingDoctor?.crm} -
-
- - {viewingDoctor?.email} -
-
- - {viewingDoctor?.telefone} -
- - - -
-
- )} +
-
- Mostrando {displayedDoctors.length} {searchMode ? 'resultado(s) da busca' : `de ${doctors.length}`} -
-
+
+ + + + Nome + Especialidade + CRM + Contato + Ações + + + + {loading ? ( + + + Carregando… + + + ) : filtered.length > 0 ? ( + filtered.map((doctor) => ( + + {doctor.nome} + + {doctor.especialidade} + + {doctor.crm} + +
+ {doctor.email} + {doctor.telefone} +
+
+ + + + + + + handleView(doctor)}> + + Ver + + handleEdit(String(doctor.id))}> + + Editar + + handleDelete(String(doctor.id))} className="text-destructive"> + + Excluir + + + + +
+ )) + ) : ( + + + Nenhum médico encontrado + + + )} +
+
+
+ + {viewingDoctor && ( + setViewingDoctor(null)}> + + + Detalhes do Médico + + Informações detalhadas de {viewingDoctor?.nome}. + + +
+
+ + {viewingDoctor?.nome} +
+
+ + + {viewingDoctor?.especialidade} + +
+
+ + {viewingDoctor?.crm} +
+
+ + {viewingDoctor?.email} +
+
+ + {viewingDoctor?.telefone} +
+
+ + + +
+
+ )} + +
+ Mostrando {filtered.length} de {doctors.length} +
+
+ )} + ); } diff --git a/susconecta/lib/api.ts b/susconecta/lib/api.ts index c602803..46a58fe 100644 --- a/susconecta/lib/api.ts +++ b/susconecta/lib/api.ts @@ -18,142 +18,6 @@ export type ApiOk = { }; }; -// ===== TIPOS COMUNS ===== -export type Endereco = { - cep?: string; - logradouro?: string; - numero?: string; - complemento?: string; - bairro?: string; - cidade?: string; - estado?: string; -}; - -// ===== 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; -}; - - -// ===== 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; -}; - - - // ===== CONFIG ===== export const API_BASE = process.env.NEXT_PUBLIC_API_BASE ?? "https://yuanqfswhberkoevtmfr.supabase.co"; @@ -215,349 +79,6 @@ export function rangeHeaders(page?: number, limit?: number): Record { - const qs = new URLSearchParams(); - if (params?.q) qs.set("q", params.q); - - const url = `${REST}/patients${qs.toString() ? `?${qs.toString()}` : ""}`; - const res = await fetch(url, { - method: "GET", - headers: { - ...baseHeaders(), - ...rangeHeaders(params?.page, params?.limit), - }, - }); - return await parse(res); -} - - -// 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; -} - - -// ===== MÉDICOS (CRUD) ===== -export async function listarMedicos(params?: { - page?: number; - limit?: number; - q?: string; -}): Promise { - const qs = new URLSearchParams(); - if (params?.q) qs.set("q", params.q); - - const url = `${REST}/doctors${qs.toString() ? `?${qs.toString()}` : ""}`; - const res = await fetch(url, { - method: "GET", - headers: { - ...baseHeaders(), - ...rangeHeaders(params?.page, params?.limit), - }, - }); - return await parse(res); -} - -// Nova função para busca avançada de médicos -export async function buscarMedicos(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 CRM (com e sem formatação) - if (digitsOnly.length >= 3) { - queries.push(`crm=ilike.*${digitsOnly}*`); - } - - // Busca por nome (usando ilike para busca case-insensitive) - if (searchTerm.length >= 2) { - queries.push(`full_name=ilike.*${searchTerm}*`); - queries.push(`nome_social=ilike.*${searchTerm}*`); - } - - // Busca por email se contém @ - if (searchTerm.includes('@')) { - queries.push(`email=ilike.*${searchTerm}*`); - } - - // Busca por especialidade - if (searchTerm.length >= 2) { - queries.push(`specialty=ilike.*${searchTerm}*`); - } - - const results: Medico[] = []; - const seenIds = new Set(); - - // Executa as buscas e combina resultados únicos - for (const query of queries) { - try { - const url = `${REST}/doctors?${query}&limit=10`; - const res = await fetch(url, { method: "GET", headers: baseHeaders() }); - const arr = await parse(res); - - if (arr?.length > 0) { - for (const medico of arr) { - if (!seenIds.has(medico.id)) { - seenIds.add(medico.id); - results.push(medico); - } - } - } - } catch (error) { - console.warn(`Erro na busca com query: ${query}`, error); - } - } - - return results.slice(0, 20); // Limita a 20 resultados -} - -export async function buscarMedicoPorId(id: string | number): Promise { - // Primeiro tenta buscar no Supabase (dados reais) - try { - const url = `${REST}/doctors?id=eq.${id}`; - const res = await fetch(url, { method: "GET", headers: baseHeaders() }); - const arr = await parse(res); - if (arr && arr.length > 0) { - console.log('✅ Médico encontrado no Supabase:', arr[0]); - console.log('🔍 Campo especialidade no médico:', { - especialidade: arr[0].especialidade, - specialty: (arr[0] as any).specialty, - hasEspecialidade: !!arr[0].especialidade, - hasSpecialty: !!((arr[0] as any).specialty) - }); - return arr[0]; - } - } catch (error) { - console.warn('⚠️ Erro ao buscar no Supabase, tentando mock API:', error); - } - - // Se não encontrar no Supabase, tenta o mock API - try { - const url = `https://mock.apidog.com/m1/1053378-0-default/rest/v1/doctors/${id}`; - const res = await fetch(url, { - method: "GET", - headers: { - "Accept": "application/json" - } - }); - - if (!res.ok) { - if (res.status === 404) { - throw new Error("404: Médico não encontrado"); - } - throw new Error(`Erro ao buscar médico: ${res.status} ${res.statusText}`); - } - - const medico = await res.json(); - console.log('✅ Médico encontrado no Mock API:', medico); - return medico as Medico; - } catch (error) { - console.error('❌ Erro ao buscar médico em ambas as APIs:', error); - throw new Error("404: Médico não encontrado"); - } -} - -// Dentro de lib/api.ts -export async function criarMedico(input: MedicoInput): Promise { - console.log("Enviando os dados para a API:", input); // Log para depuração - - const url = `${REST}/doctors`; // Endpoint de médicos - const res = await fetch(url, { - method: "POST", - headers: withPrefer({ ...baseHeaders(), "Content-Type": "application/json" }, "return=representation"), - body: JSON.stringify(input), // Enviando os dados padronizados - }); - - const arr = await parse(res); // Resposta da API - return Array.isArray(arr) ? arr[0] : (arr as Medico); // Retorno do médico -} - - - - -export async function atualizarMedico(id: string | number, input: MedicoInput): Promise { - console.log(`🔄 Tentando atualizar médico ID: ${id}`); - console.log(`📤 Payload original:`, input); - - // Criar um payload limpo apenas com campos básicos que sabemos que existem - const cleanPayload = { - full_name: input.full_name, - crm: input.crm, - specialty: input.specialty, - email: input.email, - phone_mobile: input.phone_mobile, - cpf: input.cpf, - cep: input.cep, - street: input.street, - number: input.number, - city: input.city, - state: input.state, - active: input.active ?? true - }; - - console.log(`📤 Payload limpo:`, cleanPayload); - - // Atualizar apenas no Supabase (dados reais) - try { - const url = `${REST}/doctors?id=eq.${id}`; - console.log(`🌐 URL de atualização: ${url}`); - - const res = await fetch(url, { - method: "PATCH", - headers: withPrefer({ ...baseHeaders(), "Content-Type": "application/json" }, "return=representation"), - body: JSON.stringify(cleanPayload), - }); - - console.log(`📡 Resposta do servidor: ${res.status} ${res.statusText}`); - - if (res.ok) { - const arr = await parse(res); - const result = Array.isArray(arr) ? arr[0] : (arr as Medico); - console.log('✅ Médico atualizado no Supabase:', result); - return result; - } else { - // Vamos tentar ver o erro detalhado - const errorText = await res.text(); - console.error(`❌ Erro detalhado do Supabase:`, { - status: res.status, - statusText: res.statusText, - response: errorText, - headers: Object.fromEntries(res.headers.entries()) - }); - throw new Error(`Supabase error: ${res.status} ${res.statusText} - ${errorText}`); - } - } catch (error) { - console.error('❌ Erro ao atualizar médico:', error); - throw error; - } -} - -export async function excluirMedico(id: string | number): Promise { - const url = `${REST}/doctors?id=eq.${id}`; - const res = await fetch(url, { method: "DELETE", headers: baseHeaders() }); - await parse(res); -} - // ===== CEP (usado nos formulários) ===== export async function buscarCepAPI(cep: string): Promise<{ logradouro?: string;