"use client"; import { useEffect, useMemo, useState } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog"; import { Label } from "@/components/ui/label"; import { MoreHorizontal, Plus, Search, Edit, Trash2, ArrowLeft, Eye, Users } from "lucide-react"; import { Badge } from "@/components/ui/badge"; import { DoctorRegistrationForm } from "@/components/forms/doctor-registration-form"; import AvailabilityForm from '@/components/forms/availability-form' import ExceptionForm from '@/components/forms/exception-form' import { listarDisponibilidades, DoctorAvailability, deletarDisponibilidade, listarExcecoes, DoctorException, deletarExcecao } from '@/lib/api' import { listarMedicos, excluirMedico, buscarMedicos, buscarMedicoPorId, buscarPacientesPorIds, Medico } from "@/lib/api"; import { listAssignmentsForUser } from '@/lib/assignment'; function normalizeMedico(m: any): Medico { return { id: String(m.id ?? m.uuid ?? ""), 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, sexo: m.sexo ?? m.sex ?? null, data_nascimento: m.data_nascimento ?? m.birth_date ?? null, telefone: m.telefone ?? m.phone_mobile ?? "", celular: m.celular ?? m.phone2 ?? null, contato_emergencia: m.contato_emergencia ?? null, email: m.email ?? "", crm: m.crm ?? "", estado_crm: m.estado_crm ?? m.crm_state ?? null, rqe: m.rqe ?? null, formacao_academica: m.formacao_academica ?? [], curriculo_url: m.curriculo_url ?? null, especialidade: m.especialidade ?? m.specialty ?? "", observacoes: m.observacoes ?? m.notes ?? null, foto_url: m.foto_url ?? null, tipo_vinculo: m.tipo_vinculo ?? null, 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, }; } function translateWeekday(w?: string) { if (!w) return ''; const key = w.toString().toLowerCase().normalize('NFD').replace(/\p{Diacritic}/gu, '').replace(/[^a-z0-9]/g, ''); const map: Record = { 'segunda': 'Segunda', 'terca': 'Terça', 'quarta': 'Quarta', 'quinta': 'Quinta', 'sexta': 'Sexta', 'sabado': 'Sábado', 'domingo': 'Domingo', 'monday': 'Segunda', 'tuesday': 'Terça', 'wednesday': 'Quarta', 'thursday': 'Quinta', 'friday': 'Sexta', 'saturday': 'Sábado', 'sunday': 'Domingo', }; return map[key] ?? w; } export default function DoutoresPage() { const [doctors, setDoctors] = useState([]); const [loading, setLoading] = useState(false); const [search, setSearch] = useState(""); const [showForm, setShowForm] = useState(false); const [editingId, setEditingId] = useState(null); const [viewingDoctor, setViewingDoctor] = useState(null); const [assignedDialogOpen, setAssignedDialogOpen] = useState(false); const [assignedPatients, setAssignedPatients] = useState([]); const [assignedLoading, setAssignedLoading] = useState(false); const [assignedDoctor, setAssignedDoctor] = useState(null); const [availabilityOpenFor, setAvailabilityOpenFor] = useState(null); const [availabilityViewingFor, setAvailabilityViewingFor] = useState(null); const [availabilities, setAvailabilities] = useState([]); const [availLoading, setAvailLoading] = useState(false); const [editingAvailability, setEditingAvailability] = useState(null); const [exceptions, setExceptions] = useState([]); const [exceptionsLoading, setExceptionsLoading] = useState(false); const [exceptionViewingFor, setExceptionViewingFor] = useState(null); const [exceptionOpenFor, setExceptionOpenFor] = 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 }); 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() { void handleBuscarServidor(); } useEffect(() => { load(); }, []); // 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().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); 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; }); console.log('🔍 Resultados filtrados:', filtered.length); return filtered; }, [doctors, search, searchMode, searchResults]); function handleAdd() { setEditingId(null); setShowForm(true); } function handleEdit(id: string) { setEditingId(id); setShowForm(true); } function handleView(doctor: Medico) { setViewingDoctor(doctor); } async function handleViewAssignedPatients(doctor: Medico) { setAssignedDoctor(doctor); setAssignedLoading(true); setAssignedPatients([]); try { const assigns = await listAssignmentsForUser(String(doctor.user_id ?? doctor.id)); const patientIds = Array.isArray(assigns) ? assigns.map((a:any) => String(a.patient_id)).filter(Boolean) : []; if (patientIds.length) { const patients = await buscarPacientesPorIds(patientIds); setAssignedPatients(patients || []); } else { setAssignedPatients([]); } } catch (e) { console.warn('[DoutoresPage] erro ao carregar pacientes atribuídos:', e); setAssignedPatients([]); } finally { setAssignedLoading(false); setAssignedDialogOpen(true); } } async function reloadAvailabilities(doctorId?: string) { if (!doctorId) return; setAvailLoading(true); try { const list = await listarDisponibilidades({ doctorId, active: true }); setAvailabilities(list || []); } catch (e) { console.warn('Erro ao recarregar disponibilidades:', e); } finally { setAvailLoading(false); } } async function handleDelete(id: string) { if (!confirm("Excluir este médico?")) return; await excluirMedico(id); await load(); } function handleSaved(savedDoctor?: Medico) { setShowForm(false); if (savedDoctor) { const normalized = normalizeMedico(savedDoctor); setDoctors((prev) => { const i = prev.findIndex((d) => String(d.id) === String(normalized.id)); if (i < 0) { // Novo médico → adiciona no topo return [normalized, ...prev]; } else { // Médico editado → substitui na lista const clone = [...prev]; clone[i] = normalized; return clone; } }); } else { // fallback → recarrega tudo load(); } } if (showForm) { return (

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

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

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 {/* Ver pacientes atribuídos ao médico */} handleViewAssignedPatients(doctor)}> Ver pacientes atribuídos setAvailabilityOpenFor(doctor)}> Criar disponibilidade setExceptionOpenFor(doctor)}> Criar exceção { setAvailLoading(true); try { const list = await listarDisponibilidades({ doctorId: doctor.id, active: true }); setAvailabilities(list || []); setAvailabilityViewingFor(doctor); } catch (e) { console.warn('Erro ao listar disponibilidades:', e); } finally { setAvailLoading(false); } }}> Ver disponibilidades { setExceptionsLoading(true); try { const list = await listarExcecoes({ doctorId: doctor.id }); setExceptions(list || []); setExceptionViewingFor(doctor); } catch (e) { console.warn('Erro ao listar exceções:', e); } finally { setExceptionsLoading(false); } }}> Ver exceções 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}
)} {/* Availability modal */} {availabilityOpenFor && ( { if (!open) setAvailabilityOpenFor(null); }} doctorId={availabilityOpenFor?.id} onSaved={(saved) => { console.log('Disponibilidade salva', saved); setAvailabilityOpenFor(null); /* optionally reload list */ reloadAvailabilities(availabilityOpenFor?.id); }} /> )} {exceptionOpenFor && ( { if (!open) setExceptionOpenFor(null); }} doctorId={exceptionOpenFor?.id} onSaved={(saved) => { console.log('Exceção criada', saved); setExceptionOpenFor(null); /* reload availabilities in case a full-day block affects listing */ reloadAvailabilities(exceptionOpenFor?.id); }} /> )} {/* Edit availability modal */} {editingAvailability && ( { if (!open) setEditingAvailability(null); }} doctorId={editingAvailability?.doctor_id ?? availabilityViewingFor?.id} availability={editingAvailability} mode="edit" onSaved={(saved) => { console.log('Disponibilidade atualizada', saved); setEditingAvailability(null); reloadAvailabilities(editingAvailability?.doctor_id ?? availabilityViewingFor?.id); }} /> )} {/* Ver disponibilidades dialog */} {availabilityViewingFor && ( { if (!open) { setAvailabilityViewingFor(null); setAvailabilities([]); } }}> Disponibilidades - {availabilityViewingFor.full_name} Lista de disponibilidades públicas do médico selecionado.
{availLoading ? (
Carregando disponibilidades…
) : availabilities && availabilities.length ? (
{availabilities.map((a) => (
{translateWeekday(a.weekday)} • {a.start_time} — {a.end_time}
Duração: {a.slot_minutes} min • Tipo: {a.appointment_type || '—'} • {a.active ? 'Ativa' : 'Inativa'}
))}
) : (
Nenhuma disponibilidade encontrada.
)}
)} {/* Ver exceções dialog */} {exceptionViewingFor && ( { if (!open) { setExceptionViewingFor(null); setExceptions([]); } }}> Exceções - {exceptionViewingFor.full_name} Lista de exceções (bloqueios/liberações) do médico selecionado.
{exceptionsLoading ? (
Carregando exceções…
) : exceptions && exceptions.length ? (
{exceptions.map((ex) => (
{ex.date} {ex.start_time ? `• ${ex.start_time}` : ''} {ex.end_time ? `— ${ex.end_time}` : ''}
Tipo: {ex.kind} • Motivo: {ex.reason || '—'}
))}
) : (
Nenhuma exceção encontrada.
)}
)}
Mostrando {displayedDoctors.length} {searchMode ? 'resultado(s) da busca' : `de ${doctors.length}`}
{/* Dialog para pacientes atribuídos */} { if (!open) { setAssignedDialogOpen(false); setAssignedPatients([]); setAssignedDoctor(null); } }}> Pacientes atribuídos{assignedDoctor ? ` - ${assignedDoctor.full_name}` : ''} Lista de pacientes atribuídos a este médico.
{assignedLoading ? (
Carregando pacientes...
) : assignedPatients && assignedPatients.length ? (
{assignedPatients.map((p:any) => (
{p.full_name ?? p.nome ?? p.name ?? '(sem nome)'}
ID: {p.id} {p.cpf ? `• CPF: ${p.cpf}` : ''}
))}
) : (
Nenhum paciente atribuído encontrado.
)}
); }