diff --git a/susconecta/app/(main-routes)/doutores/page.tsx b/susconecta/app/(main-routes)/doutores/page.tsx index feb1798..8fae66c 100644 --- a/susconecta/app/(main-routes)/doutores/page.tsx +++ b/susconecta/app/(main-routes)/doutores/page.tsx @@ -12,12 +12,12 @@ import { Badge } from "@/components/ui/badge"; import { DoctorRegistrationForm } from "@/components/forms/doctor-registration-form"; -import { listarMedicos, excluirMedico, Medico } from "@/lib/api"; +import { listarMedicos, excluirMedico, buscarMedicos, buscarMedicoPorId, Medico } from "@/lib/api"; function normalizeMedico(m: any): Medico { return { id: String(m.id ?? m.uuid ?? ""), - nome: m.nome ?? m.full_name ?? "", // 👈 Supabase usa full_name + full_name: m.full_name ?? m.nome ?? "", // 👈 Correção: usar full_name como padrão nome_social: m.nome_social ?? m.social_name ?? null, cpf: m.cpf ?? "", rg: m.rg ?? m.document_number ?? null, @@ -39,6 +39,20 @@ function normalizeMedico(m: any): Medico { dados_bancarios: m.dados_bancarios ?? null, agenda_horario: m.agenda_horario ?? null, valor_consulta: m.valor_consulta ?? null, + active: m.active ?? true, + cep: m.cep ?? "", + city: m.city ?? "", + complement: m.complement ?? null, + neighborhood: m.neighborhood ?? "", + number: m.number ?? "", + phone2: m.phone2 ?? null, + state: m.state ?? "", + street: m.street ?? "", + created_at: m.created_at ?? null, + created_by: m.created_by ?? null, + updated_at: m.updated_at ?? null, + updated_by: m.updated_by ?? null, + user_id: m.user_id ?? null, }; } @@ -50,33 +64,178 @@ export default function DoutoresPage() { const [showForm, setShowForm] = useState(false); const [editingId, setEditingId] = useState(null); const [viewingDoctor, setViewingDoctor] = useState(null); + const [searchResults, setSearchResults] = useState([]); + const [searchMode, setSearchMode] = useState(false); + const [searchTimeout, setSearchTimeout] = useState(null); async function load() { setLoading(true); try { const list = await listarMedicos({ limit: 50 }); -setDoctors((list ?? []).map(normalizeMedico)); + const normalized = (list ?? []).map(normalizeMedico); + console.log('🏥 Médicos carregados:', normalized); + setDoctors(normalized); } finally { setLoading(false); } } + // Função para detectar se é um UUID válido + function isValidUUID(str: string): boolean { + const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; + return uuidRegex.test(str); + } + + // Função para buscar médicos no servidor + async function handleBuscarServidor(termoBusca?: string) { + const termo = (termoBusca || search).trim(); + + if (!termo) { + setSearchMode(false); + setSearchResults([]); + return; + } + console.log('🔍 Buscando médico por:', termo); + + setLoading(true); + try { + // Se parece com UUID, tenta busca direta por ID + if (isValidUUID(termo)) { + console.log('📋 Detectado UUID, buscando por ID...'); + try { + const medico = await buscarMedicoPorId(termo); + const normalizado = normalizeMedico(medico); + console.log('✅ Médico encontrado por ID:', normalizado); + setSearchResults([normalizado]); + setSearchMode(true); + return; + } catch (error) { + console.log('❌ Não encontrado por ID, tentando busca geral...'); + } + } + + // Busca geral + const resultados = await buscarMedicos(termo); + const normalizados = resultados.map(normalizeMedico); + console.log('📋 Resultados da busca geral:', normalizados); + + setSearchResults(normalizados); + setSearchMode(true); + } catch (error) { + console.error('❌ Erro na busca:', error); + setSearchResults([]); + setSearchMode(true); + } finally { + setLoading(false); + } + } + + // Handler para mudança no campo de busca com busca automática + function handleSearchChange(e: React.ChangeEvent) { + const valor = e.target.value; + setSearch(valor); + + // Limpa o timeout anterior se existir + if (searchTimeout) { + clearTimeout(searchTimeout); + } + + // Se limpar a busca, volta ao modo normal + if (!valor.trim()) { + setSearchMode(false); + setSearchResults([]); + return; + } + + // Busca automática com debounce ajustável + // Para IDs (UUID) longos, faz busca no servidor + // Para busca parcial, usa apenas filtro local + const isLikeUUID = valor.includes('-') && valor.length > 10; + const shouldSearchServer = isLikeUUID || valor.length >= 3; + + if (shouldSearchServer) { + const debounceTime = isLikeUUID ? 300 : 500; + const newTimeout = setTimeout(() => { + handleBuscarServidor(valor); + }, debounceTime); + + setSearchTimeout(newTimeout); + } else { + // Para termos curtos, apenas usa filtro local + setSearchMode(false); + setSearchResults([]); + } + } + + // Handler para Enter no campo de busca + function handleSearchKeyDown(e: React.KeyboardEvent) { + if (e.key === 'Enter') { + e.preventDefault(); + handleBuscarServidor(); + } + } + + // Handler para o botão de busca + function handleClickBuscar() { + handleBuscarServidor(); + } + useEffect(() => { load(); }, []); - const filtered = useMemo(() => { + // Limpa o timeout quando o componente é desmontado + useEffect(() => { + return () => { + if (searchTimeout) { + clearTimeout(searchTimeout); + } + }; + }, [searchTimeout]); + + // Lista de médicos a exibir (busca ou filtro local) + const displayedDoctors = useMemo(() => { + console.log('🔍 Filtro - search:', search, 'searchMode:', searchMode, 'doctors:', doctors.length, 'searchResults:', searchResults.length); + + // Se não tem busca, mostra todos os médicos if (!search.trim()) return doctors; - const q = search.toLowerCase(); - return doctors.filter((d) => { - const byName = (d.nome || "").toLowerCase().includes(q); - const byCrm = (d.crm || "").toLowerCase().includes(q); + + const q = search.toLowerCase().trim(); + const qDigits = q.replace(/\D/g, ""); + + // Se estamos em modo de busca (servidor), filtra os resultados da busca + const sourceList = searchMode ? searchResults : doctors; + console.log('🔍 Usando sourceList:', searchMode ? 'searchResults' : 'doctors', '- tamanho:', sourceList.length); + + const filtered = sourceList.filter((d) => { + // Busca por nome + const byName = (d.full_name || "").toLowerCase().includes(q); + + // Busca por CRM (remove formatação se necessário) + const byCrm = qDigits.length >= 3 && (d.crm || "").replace(/\D/g, "").includes(qDigits); + + // Busca por ID (UUID completo ou parcial) + const byId = (d.id || "").toLowerCase().includes(q); + + // Busca por email + const byEmail = (d.email || "").toLowerCase().includes(q); + + // Busca por especialidade const byEspecialidade = (d.especialidade || "").toLowerCase().includes(q); - return byName || byCrm || byEspecialidade; + + const match = byName || byCrm || byId || byEmail || byEspecialidade; + if (match) { + console.log('✅ Match encontrado:', d.full_name, d.id, 'por:', { byName, byCrm, byId, byEmail, byEspecialidade }); + } + + return match; }); - }, [doctors, search]); + + console.log('🔍 Resultados filtrados:', filtered.length); + return filtered; + }, [doctors, search, searchMode, searchResults]); function handleAdd() { setEditingId(null); @@ -139,7 +298,7 @@ setDoctors((list ?? []).map(normalizeMedico)); setShowForm(false)} /> @@ -156,14 +315,36 @@ setDoctors((list ?? []).map(normalizeMedico));
-
- - setSearch(e.target.value)} - /> +
+
+ + +
+ + {searchMode && ( + + )}
+ )} +
+ + {/* Tabela de pacientes padrão */} +
+

Pacientes Recentes

+ + + + Paciente + CPF + Idade + Status do laudo + Ações + + + + {pacientes.map((paciente) => ( + + {paciente.nome} + {paciente.cpf} + {paciente.idade} + {paciente.statusLaudo} + +
+
+ +
+ Ver informações do paciente +
+
+
+
+
+
+ ))} +
+
+
+ + ); + }; const renderProntuarioSection = () => ( diff --git a/susconecta/components/dashboard/header.tsx b/susconecta/components/dashboard/header.tsx index 9bf263a..9942a60 100644 --- a/susconecta/components/dashboard/header.tsx +++ b/susconecta/components/dashboard/header.tsx @@ -1,6 +1,6 @@ "use client" -import { Bell, Search, ChevronDown } from "lucide-react" +import { Bell, ChevronDown } from "lucide-react" import { useAuth } from "@/hooks/useAuth" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" @@ -40,11 +40,6 @@ export function PagesHeader({ title = "", subtitle = "" }: { title?: string, sub
-
- - -
- diff --git a/susconecta/components/forms/doctor-registration-form.tsx b/susconecta/components/forms/doctor-registration-form.tsx index caeef2e..b50ef60 100644 --- a/susconecta/components/forms/doctor-registration-form.tsx +++ b/susconecta/components/forms/doctor-registration-form.tsx @@ -1,6 +1,7 @@ "use client"; import { useEffect, useMemo, useState } from "react"; +import { buscarPacientePorId } from "@/lib/api"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; @@ -21,8 +22,10 @@ import { listarAnexosMedico, adicionarAnexoMedico, removerAnexoMedico, - MedicoInput, + MedicoInput, // 👈 importado do lib/api + Medico, // 👈 adicionado import do tipo Medico } from "@/lib/api"; +; import { buscarCepAPI } from "@/lib/api"; @@ -39,39 +42,16 @@ type DadosBancarios = { tipo_conta: string; }; -export type Medico = { - id: string; - nome?: string; - 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; -}; + + + type Mode = "create" | "edit"; export interface DoctorRegistrationFormProps { open?: boolean; onOpenChange?: (open: boolean) => void; - doctorId?: number | null; + doctorId?: string | number | null; inline?: boolean; mode?: Mode; onSaved?: (medico: Medico) => void; @@ -80,7 +60,7 @@ export interface DoctorRegistrationFormProps { type FormData = { photo: File | null; - nome: string; + full_name: string; // Substitua 'nome' por 'full_name' nome_social: string; crm: string; estado_crm: string; @@ -107,14 +87,13 @@ type FormData = { anexos: File[]; tipo_vinculo: string; dados_bancarios: DadosBancarios; - agenda_horario: string; valor_consulta: string; }; const initial: FormData = { photo: null, - nome: "", + full_name: "", nome_social: "", crm: "", estado_crm: "", @@ -128,7 +107,7 @@ const initial: FormData = { data_nascimento: "", email: "", telefone: "", - celular: "", + celular: "", // Aqui, 'celular' pode ser 'phone_mobile' contato_emergencia: "", cep: "", logradouro: "", @@ -152,6 +131,7 @@ const initial: FormData = { + export function DoctorRegistrationForm({ open = true, onOpenChange, @@ -175,46 +155,78 @@ export function DoctorRegistrationForm({ let alive = true; async function load() { if (mode === "edit" && doctorId) { - const medico = await buscarMedicoPorId(doctorId); - if (!alive) return; - setForm({ - photo: null, - nome: medico.nome ?? "", - nome_social: medico.nome_social ?? "", - crm: medico.crm ?? "", - estado_crm: medico.estado_crm ?? "", - rqe: medico.rqe ?? "", - formacao_academica: medico.formacao_academica ?? [], - curriculo: null, - especialidade: medico.especialidade ?? "", - cpf: medico.cpf ?? "", - rg: medico.rg ?? "", - sexo: medico.sexo ?? "", - data_nascimento: medico.data_nascimento ?? "", - email: medico.email ?? "", - telefone: medico.telefone ?? "", - celular: medico.celular ?? "", - contato_emergencia: medico.contato_emergencia ?? "", - cep: "", - logradouro: "", - numero: "", - complemento: "", - bairro: "", - cidade: "", - estado: "", - observacoes: medico.observacoes ?? "", - anexos: [], - tipo_vinculo: medico.tipo_vinculo ?? "", - dados_bancarios: medico.dados_bancarios ?? { banco: "", agencia: "", conta: "", tipo_conta: "" }, - agenda_horario: medico.agenda_horario ?? "", - valor_consulta: medico.valor_consulta ? String(medico.valor_consulta) : "", - }); - - try { - const list = await listarAnexosMedico(doctorId); - setServerAnexos(list ?? []); - } catch {} + console.log("[DoctorForm] Carregando médico ID:", doctorId); + const medico = await buscarMedicoPorId(String(doctorId)); + console.log("[DoctorForm] Dados recebidos do API:", medico); + console.log("[DoctorForm] Campos principais:", { + full_name: medico.full_name, + crm: medico.crm, + especialidade: medico.especialidade, + specialty: (medico as any).specialty, + cpf: medico.cpf, + email: medico.email + }); + console.log("[DoctorForm] Verificando especialidade:", { + 'medico.especialidade': medico.especialidade, + 'medico.specialty': (medico as any).specialty, + 'typeof especialidade': typeof medico.especialidade, + 'especialidade length': medico.especialidade?.length + }); + if (!alive) return; + + // Busca a especialidade em diferentes campos possíveis + const especialidade = medico.especialidade || + (medico as any).specialty || + (medico as any).speciality || + ""; + console.log('🎯 Especialidade encontrada:', especialidade); + + const formData = { + photo: null, + full_name: String(medico.full_name || ""), + nome_social: String(medico.nome_social || ""), + crm: String(medico.crm || ""), + estado_crm: String(medico.estado_crm || ""), + rqe: String(medico.rqe || ""), + formacao_academica: Array.isArray(medico.formacao_academica) ? medico.formacao_academica : [], + curriculo: null, + especialidade: String(especialidade), + cpf: String(medico.cpf || ""), + rg: String(medico.rg || ""), + sexo: String(medico.sexo || ""), + data_nascimento: String(medico.data_nascimento || ""), + email: String(medico.email || ""), + telefone: String(medico.telefone || ""), + celular: String(medico.celular || ""), + contato_emergencia: String(medico.contato_emergencia || ""), + cep: String(medico.cep || ""), + logradouro: String(medico.street || ""), + numero: String(medico.number || ""), + complemento: String(medico.complement || ""), + bairro: String(medico.neighborhood || ""), + cidade: String(medico.city || ""), + estado: String(medico.state || ""), + observacoes: String(medico.observacoes || ""), + anexos: [], + tipo_vinculo: String(medico.tipo_vinculo || ""), + dados_bancarios: medico.dados_bancarios || { banco: "", agencia: "", conta: "", tipo_conta: "" }, + agenda_horario: String(medico.agenda_horario || ""), + valor_consulta: medico.valor_consulta ? String(medico.valor_consulta) : "", + }; + + console.log("[DoctorForm] Dados do formulário preparados:", formData); + setForm(formData); + + try { + const list = await listarAnexosMedico(String(doctorId)); + setServerAnexos(list ?? []); + } catch (err) { + console.error("[DoctorForm] Erro ao carregar anexos:", err); + } + } catch (err) { + console.error("[DoctorForm] Erro ao carregar médico:", err); + } } } load(); @@ -222,10 +234,11 @@ export function DoctorRegistrationForm({ }, [mode, doctorId]); - function setField(k: T, v: FormData[T]) { - setForm((s) => ({ ...s, [k]: v })); - if (errors[k as string]) setErrors((e) => ({ ...e, [k]: "" })); - } +function setField(k: T, v: FormData[T]) { + setForm((s) => ({ ...s, [k]: v })); + if (errors[k as string]) setErrors((e) => ({ ...e, [k]: "" })); +} + function addFormacao() { @@ -299,76 +312,92 @@ export function DoctorRegistrationForm({ } - function validateLocal(): boolean { - const e: Record = {}; - if (!form.nome.trim()) e.nome = "Nome é obrigatório"; - if (!form.cpf.trim()) e.cpf = "CPF é obrigatório"; - if (!form.crm.trim()) e.crm = "CRM é obrigatório"; - if (!form.especialidade.trim()) e.especialidade = "Especialidade é obrigatória"; - setErrors(e); - return Object.keys(e).length === 0; - } + function validateLocal(): boolean { + const e: Record = {}; - async function handleSubmit(ev: React.FormEvent) { + if (!form.full_name.trim()) e.full_name = "Nome é obrigatório"; + if (!form.cpf.trim()) e.cpf = "CPF é obrigatório"; + if (!form.crm.trim()) e.crm = "CRM é obrigatório"; + if (!form.especialidade.trim()) e.especialidade = "Especialidade é obrigatória"; + if (!form.cep.trim()) e.cep = "CEP é obrigatório"; // Verifique se o CEP está preenchido + if (!form.bairro.trim()) e.bairro = "Bairro é obrigatório"; // Verifique se o bairro está preenchido + if (!form.cidade.trim()) e.cidade = "Cidade é obrigatória"; // Verifique se a cidade está preenchida + + setErrors(e); + return Object.keys(e).length === 0; +} + + + +async function handleSubmit(ev: React.FormEvent) { ev.preventDefault(); - if (!validateLocal()) return; + console.log("Submitting the form..."); // Verifique se a função está sendo chamada + + if (!validateLocal()) { + console.log("Validation failed"); + return; // Se a validação falhar, saia da função. + } setSubmitting(true); setErrors((e) => ({ ...e, submit: "" })); - try { - const payload: MedicoInput = { - nome: form.nome, - nome_social: form.nome_social || null, - cpf: form.cpf || null, - rg: form.rg || null, - sexo: form.sexo || null, - data_nascimento: form.data_nascimento || null, - telefone: form.telefone || null, - celular: form.celular || null, - contato_emergencia: form.contato_emergencia || null, - email: form.email || null, - crm: form.crm, - estado_crm: form.estado_crm || null, - rqe: form.rqe || null, - formacao_academica: form.formacao_academica ?? [], - curriculo_url: null, - especialidade: form.especialidade, - observacoes: form.observacoes || null, - tipo_vinculo: form.tipo_vinculo || null, - dados_bancarios: form.dados_bancarios ?? null, - agenda_horario: form.agenda_horario || null, - valor_consulta: form.valor_consulta || null, - }; +const payload: MedicoInput = { + user_id: null, + crm: form.crm || "", + crm_uf: form.estado_crm || "", + specialty: form.especialidade || "", + full_name: form.full_name || "", + cpf: form.cpf || "", + email: form.email || "", + phone_mobile: form.celular || "", + phone2: form.telefone || null, + cep: form.cep || "", + street: form.logradouro || "", + number: form.numero || "", + complement: form.complemento || undefined, + neighborhood: form.bairro || undefined, + city: form.cidade || "", + state: form.estado || "", + birth_date: form.data_nascimento || null, + rg: form.rg || null, + active: true, + created_by: null, + updated_by: null, +}; +// Validação dos campos obrigatórios +const requiredFields = ['crm', 'crm_uf', 'specialty', 'full_name', 'cpf', 'email', 'phone_mobile', 'cep', 'street', 'number', 'city', 'state']; +const missingFields = requiredFields.filter(field => !payload[field as keyof MedicoInput]); + +if (missingFields.length > 0) { + console.warn('⚠️ Campos obrigatórios vazios:', missingFields); +} + + + + console.log("📤 Payload being sent:", payload); + console.log("🔧 Mode:", mode, "DoctorId:", doctorId); + + try { + if (mode === "edit" && !doctorId) { + throw new Error("ID do médico não fornecido para edição"); + } + const saved = mode === "create" ? await criarMedico(payload) - : await atualizarMedico(doctorId as number, payload); + : await atualizarMedico(String(doctorId), payload); - const medicoId = saved.id; - - if (form.photo) { - try { - await uploadFotoMedico(medicoId, form.photo); - } catch (e) { - console.warn("Falha ao enviar foto:", e); - } - } - - if (form.anexos?.length) { - for (const f of form.anexos) { - try { - await adicionarAnexoMedico(medicoId, f); - } catch (e) { - console.warn("Falha ao enviar anexo:", f.name, e); - } - } - } + console.log("✅ Médico salvo com sucesso:", saved); onSaved?.(saved); - if (inline) onClose?.(); - else onOpenChange?.(false); + setSubmitting(false); } catch (err: any) { + console.error("❌ Erro ao salvar médico:", err); + console.error("❌ Detalhes do erro:", { + message: err?.message, + status: err?.status, + stack: err?.stack + }); setErrors((e) => ({ ...e, submit: err?.message || "Erro ao salvar médico" })); } finally { setSubmitting(false); @@ -376,6 +405,10 @@ export function DoctorRegistrationForm({ } + + + + function handlePhoto(e: React.ChangeEvent) { const f = e.target.files?.[0]; if (!f) return; @@ -449,8 +482,10 @@ export function DoctorRegistrationForm({
- setField("nome", e.target.value)} className={errors.nome ? "border-destructive" : ""} /> - {errors.nome &&

{errors.nome}

} + setField("full_name", e.target.value)} /> + + + {errors.full_name &&

{errors.full_name}

}
@@ -471,16 +506,21 @@ export function DoctorRegistrationForm({
-
- - setField("especialidade", e.target.value)} className={errors.especialidade ? "border-destructive" : ""} /> - {errors.especialidade &&

{errors.especialidade}

} -
-
- - setField("rqe", e.target.value)} /> -
-
+
+ + setField("especialidade", e.target.value)} // Envia o valor correto + className={errors.especialidade ? "border-destructive" : ""} + /> + {errors.especialidade &&

{errors.especialidade}

} +
+
+ + setField("rqe", e.target.value)} /> +
+
+
@@ -629,14 +669,25 @@ export function DoctorRegistrationForm({ setField("email", e.target.value)} />
-
- - setField("telefone", formatPhone(e.target.value))} - placeholder="(XX) XXXXX-XXXX" - /> -
+
+
+ + setField("telefone", formatPhone(e.target.value))} + placeholder="(XX) XXXXX-XXXX" + /> +
+
+ + setField("celular", formatPhone(e.target.value))} + placeholder="(XX) XXXXX-XXXX" + /> +
+
+
@@ -703,11 +754,14 @@ export function DoctorRegistrationForm({
-