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}
-
-
-
-
-
-
-
- Abrir menu
-
-
-
- handleView(doctor)}>
-
- Ver
-
- handleEdit(String(doctor.id))}>
-
- Editar
-
- handleDelete(String(doctor.id))} className="text-destructive">
-
- Excluir
-
-
-
-
-
- ))
- ) : (
-
-
- Nenhum médico encontrado
-
-
- )}
-
-
-
-
- {viewingDoctor && (
-
- )}
+
-
- 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}
+
+
+
+
+
+
+
+ Abrir menu
+
+
+
+ handleView(doctor)}>
+
+ Ver
+
+ handleEdit(String(doctor.id))}>
+
+ Editar
+
+ handleDelete(String(doctor.id))} className="text-destructive">
+
+ Excluir
+
+
+
+
+
+ ))
+ ) : (
+
+
+ Nenhum médico encontrado
+
+
+ )}
+
+
+
+
+ {viewingDoctor && (
+
+ )}
+
+
+ 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;