import React, { useState, useEffect, useCallback } from "react"; import { Calendar, Clock, User, MessageCircle, HelpCircle, LogOut, Home, Stethoscope, Video, MapPin, CheckCircle, XCircle, AlertCircle, FileText, Eye, X, } from "lucide-react"; import toast from "react-hot-toast"; import { format } from "date-fns"; import { ptBR } from "date-fns/locale"; import { useNavigate, useLocation } from "react-router-dom"; import { useAuth } from "../hooks/useAuth"; import { appointmentService, doctorService, reportService } from "../services"; import type { Report } from "../services/reports/types"; import AgendamentoConsulta from "../components/AgendamentoConsulta"; import { AvatarUpload } from "../components/ui/AvatarUpload"; import { avatarService } from "../services/avatars/avatarService"; interface Consulta { _id: string; pacienteId: string; medicoId: string; dataHora: string; status: "agendada" | "confirmada" | "realizada" | "cancelada" | "faltou"; tipoConsulta: string; motivoConsulta: string; observacoes?: string; resultados?: string; prescricoes?: string; proximaConsulta?: string; medicoNome?: string; especialidade?: string; valorConsulta?: number; } interface Medico { id: string; nome: string; especialidade: string; crm: string; foto?: string; email?: string; telefone?: string; valorConsulta?: number; } const AcompanhamentoPaciente: React.FC = () => { const { user, roles = [], logout } = useAuth(); const navigate = useNavigate(); const location = useLocation(); // Helper para formatar nome do médico com Dr. const formatDoctorName = (fullName: string): string => { const name = fullName.trim(); // Verifica se já começa com Dr. ou Dr (case insensitive) if (/^dr\.?\s/i.test(name)) { return name; } return `Dr. ${name}`; }; // State const [activeTab, setActiveTab] = useState("dashboard"); const [consultas, setConsultas] = useState([]); const [medicos, setMedicos] = useState([]); const [loadingMedicos, setLoadingMedicos] = useState(true); const [selectedMedicoId, setSelectedMedicoId] = useState(""); const [loading, setLoading] = useState(true); const [especialidadeFiltro, setEspecialidadeFiltro] = useState(""); const [laudos, setLaudos] = useState([]); const [loadingLaudos, setLoadingLaudos] = useState(false); const [paginaProximas, setPaginaProximas] = useState(1); const [paginaPassadas, setPaginaPassadas] = useState(1); const consultasPorPagina = 20; // Aumentado de 10 para 20 const [avatarUrl, setAvatarUrl] = useState(undefined); const [selectedLaudo, setSelectedLaudo] = useState(null); const [showLaudoModal, setShowLaudoModal] = useState(false); const [requestedByNames, setRequestedByNames] = useState< Record >({}); const pacienteId = user?.id || ""; const pacienteNome = user?.nome || "Paciente"; useEffect(() => { // Permite acesso se for paciente OU se roles inclui 'paciente' const isPaciente = user?.role === "paciente" || roles.includes("paciente"); if (!user || !isPaciente) navigate("/paciente"); }, [user, roles, navigate]); // Detecta se veio de navegação com estado para abrir aba específica useEffect(() => { if ( location.state && (location.state as { activeTab?: string }).activeTab ) { const state = location.state as { activeTab: string }; setActiveTab(state.activeTab); // Limpa o estado após usar window.history.replaceState({}, document.title); } }, [location]); // Carregar avatar ao montar componente useEffect(() => { if (user?.id) { // Tenta carregar avatar existente (testa png, jpg, webp) const extensions = ["png", "jpg", "webp"]; const testAvatar = async () => { for (const ext of extensions) { try { const url = avatarService.getPublicUrl({ userId: user.id, ext: ext as "jpg" | "png" | "webp", }); const response = await fetch(url, { method: "HEAD" }); if (response.ok) { setAvatarUrl(url); console.log(`[AcompanhamentoPaciente] Avatar encontrado: ${url}`); break; } } catch (error) { // Continua testando próxima extensão } } }; testAvatar(); } }, [user?.id]); const fetchConsultas = useCallback(async () => { if (!pacienteId) return; setLoading(true); setLoadingMedicos(true); try { // Buscar TODOS os agendamentos da API (sem limite) const appointments = await appointmentService.list({ patient_id: pacienteId, limit: 1000, // Aumenta limite para buscar todas order: "scheduled_at.desc", }); // Buscar médicos const medicosData = await doctorService.list(); const medicosFormatted: Medico[] = medicosData.map((d) => ({ id: d.id, nome: formatDoctorName(d.full_name), especialidade: d.specialty || "", crm: d.crm, email: d.email, telefone: d.phone_mobile || undefined, })); setMedicos(medicosFormatted); setLoadingMedicos(false); // Map appointments to old Consulta format const consultasAPI: Consulta[] = appointments.map((apt) => ({ _id: apt.id, pacienteId: apt.patient_id, medicoId: apt.doctor_id, dataHora: apt.scheduled_at || "", status: apt.status === "confirmed" ? "confirmada" : apt.status === "completed" ? "realizada" : apt.status === "cancelled" ? "cancelada" : apt.status === "no_show" ? "faltou" : "agendada", tipoConsulta: "presencial", motivoConsulta: apt.notes || "Consulta médica", observacoes: apt.notes || undefined, })); // Set consultas setConsultas(consultasAPI); } catch (error) { setLoadingMedicos(false); console.error("Erro ao carregar consultas:", error); toast.error("Erro ao carregar consultas"); setConsultas([]); } finally { setLoading(false); } }, [pacienteId]); useEffect(() => { fetchConsultas(); }, [fetchConsultas]); // Função para carregar nomes dos médicos solicitantes const loadRequestedByNames = useCallback(async (reports: Report[]) => { const uniqueIds = [ ...new Set( reports.map((r) => r.requested_by).filter((id): id is string => !!id) ), ]; if (uniqueIds.length === 0) return; try { const doctors = await doctorService.list(); const nameMap: Record = {}; uniqueIds.forEach((id) => { const doctor = doctors.find((d) => d.id === id); if (doctor && doctor.full_name) { nameMap[id] = formatDoctorName(doctor.full_name); } }); setRequestedByNames(nameMap); } catch (error) { console.error("Erro ao buscar nomes dos médicos:", error); } }, []); // Recarregar consultas quando mudar para a aba de consultas const fetchLaudos = useCallback(async () => { if (!pacienteId) return; setLoadingLaudos(true); try { const data = await reportService.list({ patient_id: pacienteId }); setLaudos(data); // Carregar nomes dos médicos await loadRequestedByNames(data); } catch (error) { console.error("Erro ao buscar laudos:", error); toast.error("Erro ao carregar laudos"); setLaudos([]); } finally { setLoadingLaudos(false); } }, [pacienteId, loadRequestedByNames]); useEffect(() => { if (activeTab === "appointments") { fetchConsultas(); } }, [activeTab, fetchConsultas]); useEffect(() => { if (activeTab === "reports") { fetchLaudos(); } }, [activeTab, fetchLaudos]); const getMedicoNome = (medicoId: string) => { const medico = medicos.find((m) => m._id === medicoId || m.id === medicoId); return medico?.nome || "Médico"; }; const getMedicoEspecialidade = (medicoId: string) => { const medico = medicos.find((m) => m._id === medicoId || m.id === medicoId); return medico?.especialidade || "Especialidade"; }; const handleRemarcar = () => { setActiveTab("book"); toast.success("Selecione um novo horário para remarcar sua consulta"); }; const handleCancelar = async (consultaId: string) => { if (!window.confirm("Tem certeza que deseja cancelar esta consulta?")) { return; } try { await appointmentService.update(consultaId, { status: "cancelled", }); toast.success("Consulta cancelada com sucesso"); fetchConsultas(); } catch (error) { console.error("Erro ao cancelar consulta:", error); toast.error("Erro ao cancelar consulta. Tente novamente."); } }; const todasConsultasProximas = consultas .filter((c) => c.status === "agendada" || c.status === "confirmada") .sort( (a, b) => new Date(a.dataHora).getTime() - new Date(b.dataHora).getTime() ); const todasConsultasPassadas = consultas .filter((c) => c.status === "realizada") .sort( (a, b) => new Date(b.dataHora).getTime() - new Date(a.dataHora).getTime() ); // Para o dashboard (apenas 3 consultas) const consultasProximasDashboard = todasConsultasProximas.slice(0, 3); const consultasPassadasDashboard = todasConsultasPassadas.slice(0, 3); // Para a página de consultas (com paginação) const totalPaginasProximas = Math.ceil( todasConsultasProximas.length / consultasPorPagina ); const totalPaginasPassadas = Math.ceil( todasConsultasPassadas.length / consultasPorPagina ); const consultasProximas = todasConsultasProximas.slice( (paginaProximas - 1) * consultasPorPagina, paginaProximas * consultasPorPagina ); const consultasPassadas = todasConsultasPassadas.slice( (paginaPassadas - 1) * consultasPorPagina, paginaPassadas * consultasPorPagina ); const getStatusColor = (status: string) => { switch (status) { case "confirmada": return "bg-green-100 text-green-800 border-green-200 dark:bg-green-900/30 dark:text-green-300 dark:border-green-800"; case "agendada": return "bg-blue-100 text-blue-800 border-blue-200 dark:bg-blue-900/30 dark:text-blue-300 dark:border-blue-800"; case "realizada": return "bg-gray-100 text-gray-800 border-gray-200 dark:bg-gray-900/30 dark:text-gray-300 dark:border-gray-800"; case "cancelada": case "faltou": return "bg-red-100 text-red-800 border-red-200 dark:bg-red-900/30 dark:text-red-300 dark:border-red-800"; default: return "bg-gray-100 text-gray-800 border-gray-200 dark:bg-gray-900/30 dark:text-gray-300 dark:border-gray-800"; } }; const getStatusLabel = (status: string) => { switch (status) { case "confirmada": return "Confirmada"; case "agendada": return "Agendada"; case "realizada": return "Concluída"; case "cancelada": return "Cancelada"; case "faltou": return "Não Compareceu"; default: return status; } }; const getStatusIcon = (status: string) => { switch (status) { case "confirmada": return ; case "agendada": return ; case "cancelada": case "faltou": return ; default: return ; } }; // Menu items const menuItems = [ { id: "dashboard", label: "Início", icon: Home }, { id: "appointments", label: "Minhas Consultas", icon: Calendar }, { id: "reports", label: "Meus Laudos", icon: FileText }, { id: "book", label: "Agendar Consulta", icon: Stethoscope }, { id: "messages", label: "Mensagens", icon: MessageCircle }, { id: "profile", label: "Meu Perfil", icon: User, isLink: true, path: "/perfil-paciente", }, { id: "help", label: "Ajuda", icon: HelpCircle }, ]; // Sidebar const renderSidebar = () => (
{/* Patient Profile */}
setAvatarUrl(url || undefined)} />

{pacienteNome}

Paciente

{/* Navigation */} {/* Logout */}
); // Stat Card const renderStatCard = ( title: string, value: string | number, icon: React.ElementType, description?: string ) => { const Icon = icon; return (

{title}

{value}
{description && (

{description}

)}
); }; // Appointment Card const renderAppointmentCard = ( consulta: Consulta, isPast: boolean = false ) => { // Usar dados da consulta local se disponível, senão buscar pelo ID do médico const medicoNome = consulta.medicoNome || getMedicoNome(consulta.medicoId); const especialidade = consulta.especialidade || getMedicoEspecialidade(consulta.medicoId); return (
{medicoNome .split(" ") .map((n) => n[0]) .join("") .toUpperCase() .slice(0, 2)}

{medicoNome}

{especialidade}

{getStatusIcon(consulta.status)} {getStatusLabel(consulta.status)}
{format(new Date(consulta.dataHora), "dd/MM/yyyy", { locale: ptBR, })}
{format(new Date(consulta.dataHora), "HH:mm", { locale: ptBR })}
{consulta.tipoConsulta === "online" || consulta.tipoConsulta === "telemedicina" ? ( <>

Motivo: {consulta.motivoConsulta}

{!isPast && consulta.status !== "cancelada" && (
{consulta.status === "confirmada" && (consulta.tipoConsulta === "online" || consulta.tipoConsulta === "telemedicina") && ( )}
)}
); }; // Dashboard Content const renderDashboard = () => { const proximaConsulta = consultasProximas[0]; return (

Bem-vindo, {pacienteNome.split(" ")[0]}!

Gerencie suas consultas e cuide da sua saúde

{/* Stats */}
{renderStatCard( "Próxima Consulta", proximaConsulta ? format(new Date(proximaConsulta.dataHora), "dd MMM", { locale: ptBR, }) : "Nenhuma", Calendar, proximaConsulta ? `${getMedicoEspecialidade(proximaConsulta.medicoId)} - ${format( new Date(proximaConsulta.dataHora), "HH:mm" )}` : "Agende uma consulta" )} {renderStatCard( "Consultas Agendadas", consultasProximas.length, Clock, "Este mês" )} {renderStatCard( "Médicos Favoritos", new Set(consultas.map((c) => c.medicoId)).size, Stethoscope, "Salvos" )}
{/* Próximas Consultas e Ações Rápidas */}

Próximas Consultas

{loading ? (
) : consultasProximasDashboard.length === 0 ? (

Nenhuma consulta agendada

) : ( <>
{consultasProximasDashboard.map((c) => (

{getMedicoNome(c.medicoId)}

{getMedicoEspecialidade(c.medicoId)}

{format( new Date(c.dataHora), "dd/MM/yyyy - HH:mm", { locale: ptBR, } )}

))}
{todasConsultasProximas.length > 3 && ( )} )}

Ações Rápidas

{/* Dicas de Saúde */}

Dicas de Saúde

💧 Hidratação

Beba pelo menos 2 litros de água por dia para manter seu corpo hidratado

🏃 Exercícios

30 minutos de atividade física diária ajudam a prevenir doenças

); }; // Appointments Content const renderAppointments = () => (

Minhas Consultas

Visualize e gerencie todas as suas consultas

{/* Próximas */}

Próximas Consultas

{loading ? (
) : consultasProximas.length === 0 ? (

Nenhuma consulta agendada

) : ( <>
{consultasProximas.map((c) => renderAppointmentCard(c))}
{/* Paginação Próximas Consultas */} {totalPaginasProximas > 1 && (
Mostrando {(paginaProximas - 1) * consultasPorPagina + 1} a{" "} {Math.min( paginaProximas * consultasPorPagina, todasConsultasProximas.length )}{" "} de {todasConsultasProximas.length} consultas
Página {paginaProximas} de {totalPaginasProximas}
)} )}
{/* Passadas */}

Histórico

{consultasPassadas.length === 0 ? (

Nenhuma consulta realizada

) : ( <>
{consultasPassadas.map((c) => renderAppointmentCard(c, true))}
{/* Paginação Consultas Passadas */} {totalPaginasPassadas > 1 && (
Mostrando {(paginaPassadas - 1) * consultasPorPagina + 1} a{" "} {Math.min( paginaPassadas * consultasPorPagina, todasConsultasPassadas.length )}{" "} de {todasConsultasPassadas.length} consultas
Página {paginaPassadas} de {totalPaginasPassadas}
)} )}
); // Book Appointment Content const renderBookAppointment = () => (
); // Messages Content const renderMessages = () => (

Mensagens

Converse com seus médicos

Sistema de mensagens em desenvolvimento

); // Help Content const renderHelp = () => (

Central de Ajuda

Como podemos ajudar você?

Central de ajuda em desenvolvimento

); // Profile Content const renderProfile = () => (

Meu Perfil

Gerencie suas informações pessoais

Edição de perfil em desenvolvimento

); const renderReports = () => (

Meus Laudos Médicos

{loadingLaudos ? (

Carregando laudos...

) : laudos.length === 0 ? (

Você ainda não possui laudos médicos.

) : (
{laudos.map((laudo) => ( ))}
Número Exame Médico Solicitante Status Data Ações
{laudo.order_number} {laudo.exam || "-"} {laudo.requested_by ? (requestedByNames[laudo.requested_by] || laudo.requested_by) : "-"} {laudo.status === "completed" ? "Concluído" : laudo.status === "pending" ? "Pendente" : laudo.status === "cancelled" ? "Cancelado" : "Rascunho"} {new Date(laudo.created_at).toLocaleDateString("pt-BR")}
)}
); const renderContent = () => { switch (activeTab) { case "dashboard": return renderDashboard(); case "appointments": return renderAppointments(); case "reports": return renderReports(); case "book": return renderBookAppointment(); case "messages": return renderMessages(); case "help": return renderHelp(); case "profile": return renderProfile(); default: return null; } }; if (!user) { return (

Acesso Negado

Você precisa estar logado para acessar esta página.

); } return (
{renderSidebar()} {/* Mobile Header */}
setAvatarUrl(url || undefined)} />

{pacienteNome}

Paciente

{/* Mobile Nav */}
{menuItems.map((item) => { const Icon = item.icon; const isActive = activeTab === item.id; return ( ); })}
{renderContent()}
{/* Modal de Visualização do Laudo */} {showLaudoModal && selectedLaudo && (
{/* Header do Modal */}

Detalhes do Laudo

{/* Conteúdo do Modal */}
{/* Informações Principais */}

{selectedLaudo.order_number}

{selectedLaudo.status === "completed" ? "Concluído" : selectedLaudo.status === "pending" ? "Pendente" : selectedLaudo.status === "cancelled" ? "Cancelado" : "Rascunho"}

{new Date(selectedLaudo.created_at).toLocaleDateString( "pt-BR", { day: "2-digit", month: "long", year: "numeric", } )}

{selectedLaudo.due_at && (

{new Date(selectedLaudo.due_at).toLocaleDateString( "pt-BR", { day: "2-digit", month: "long", year: "numeric", } )}

)}
{/* Exame */} {selectedLaudo.exam && (

{selectedLaudo.exam}

)} {/* Diagnóstico */} {selectedLaudo.diagnosis && (

{selectedLaudo.diagnosis}

)} {/* CID */} {selectedLaudo.cid_code && (

{selectedLaudo.cid_code}

)} {/* Conclusão */} {selectedLaudo.conclusion && (

{selectedLaudo.conclusion}

)} {/* Solicitado por */} {selectedLaudo.requested_by && (

{requestedByNames[selectedLaudo.requested_by] || selectedLaudo.requested_by}

)} {/* Conteúdo HTML (se houver) */} {selectedLaudo.content_html && (
)}
{/* Footer do Modal */}
)}
); }; export default AcompanhamentoPaciente;