import React, { useState, useEffect } from "react"; import { Link } from "react-router-dom"; import { getAccessToken } from "../../utils/auth.js"; import { getFullName, getUserId } from "../../utils/userInfo"; import "../../assets/css/index.css"; import { getUserRole } from "../../utils/userInfo"; const AvatarForm = "/img/AvatarForm.jpg"; const banner = "/img/banner.png"; export default function PatientDashboard() { const [appointments, setAppointments] = useState([]); const [reports, setReports] = useState([]); const [nextConsultations, setNextConsultations] = useState([]); const [recentExams, setRecentExams] = useState([]); const [loading, setLoading] = useState(true); const [currentTime, setCurrentTime] = useState(new Date()); const role = getUserRole(); const tokenUsuario = getAccessToken(); const userId = getUserId(); const patientName = getFullName() || "Paciente"; const supabaseUrl = import.meta.env.VITE_SUPABASE_URL; const supabaseAK = import.meta.env.VITE_SUPABASE_ANON_KEY; const API_KEY = supabaseAK; const requestOptions = { method: "GET", headers: { apikey: API_KEY, Authorization: `Bearer ${tokenUsuario}`, }, redirect: "follow", }; useEffect(() => { const loadPatientData = async () => { try { setLoading(true); console.log("🔄 Carregando dados do paciente...", { userId, tokenUsuario: !!tokenUsuario }); // Buscar todas as consultas primeiro (sem filtrar por patient_id se não existir na tabela) const appointmentsResponse = await fetch( `${supabaseUrl}/rest/v1/appointments`, requestOptions ); const reportsResponse = await fetch( `${supabaseUrl}/rest/v1/reports`, requestOptions ); const doctorsResponse = await fetch( `${supabaseUrl}/rest/v1/doctors?select=id,full_name`, requestOptions ); console.log("📡 Status das respostas:", { appointments: appointmentsResponse.status, reports: reportsResponse.status, doctors: doctorsResponse.status }); const [appointmentsData, reportsData, doctorsData] = await Promise.all([ appointmentsResponse.json(), reportsResponse.json(), doctorsResponse.json() ]); console.log("📊 Dados recebidos:", { appointments: appointmentsData, reports: reportsData, doctors: doctorsData }); const appointmentsArr = Array.isArray(appointmentsData) ? appointmentsData : []; const reportsArr = Array.isArray(reportsData) ? reportsData : []; const doctorsArr = Array.isArray(doctorsData) ? doctorsData : []; // Filtrar consultas por patient_id (se o campo existir) const patientAppointments = appointmentsArr.filter(apt => apt.patient_id === userId || apt.patient_id === parseInt(userId) || // Se não tiver patient_id, mostrar algumas para demonstração !apt.patient_id ); // Filtrar relatórios por patient_id (se o campo existir) const patientReports = reportsArr.filter(rep => rep.patient_id === userId || rep.patient_id === parseInt(userId) || // Se não tiver patient_id, mostrar alguns para demonstração !rep.patient_id ); // Enriquecer consultas com nomes dos médicos const enrichedAppointments = patientAppointments.map(appointment => { const doctor = doctorsArr.find(doc => doc.id === appointment.doctor_id); return { ...appointment, doctor_name: doctor ? doctor.full_name : 'Médico não informado' }; }); console.log("✅ Dados processados:", { enrichedAppointments, patientReports, totalDoctors: doctorsArr.length }); setAppointments(enrichedAppointments); setReports(patientReports); // Processar dados console.log("🔥 TESTE: Chamando processNextConsultations com:", enrichedAppointments.length, "consultas"); // FORÇAR para teste if (enrichedAppointments.length === 0) { forceShowConsultations(); } else { // Filtrar consultas não canceladas const nonCancelledConsultations = enrichedAppointments.filter(apt => apt.status !== 'cancelled' && apt.status !== 'cancelada' && apt.status !== 'canceled' ); console.log("📋 Consultas não canceladas:", nonCancelledConsultations.length, "de", enrichedAppointments.length); if (nonCancelledConsultations.length > 0) { // Ordenar por proximidade da data atual (mais próximas primeiro) const today = new Date(); today.setHours(0, 0, 0, 0); const sortedByProximity = nonCancelledConsultations .map(apt => { const dateField = apt.scheduled_at || apt.date; const timeField = apt.time; if (dateField) { let consultationDateTime; if (dateField.includes('T')) { // Data já inclui horário consultationDateTime = new Date(dateField); } else { // Combinar data com horário consultationDateTime = new Date(dateField); if (timeField) { const [hours, minutes] = timeField.split(':'); consultationDateTime.setHours(parseInt(hours), parseInt(minutes), 0, 0); } else { consultationDateTime.setHours(12, 0, 0, 0); // Default meio-dia se não houver horário } } const now = new Date(); const diffInMinutes = Math.abs((consultationDateTime - now) / (1000 * 60)); return { ...apt, proximityScore: diffInMinutes, consultationDateTime }; } return { ...apt, proximityScore: 999999 }; // Consultas sem data vão para o final }) .sort((a, b) => a.proximityScore - b.proximityScore) .slice(0, 2); console.log("✅ Mostrando 2 consultas mais próximas da data atual:", sortedByProximity); setNextConsultations(sortedByProximity); } else { console.log("⚠️ Todas as consultas estão canceladas - usando dados de teste"); forceShowConsultations(); } } processRecentExams(patientReports); } catch (error) { console.error("❌ Erro ao carregar dados do paciente:", error); } finally { setLoading(false); } }; if (tokenUsuario) { loadPatientData(); } }, [userId, tokenUsuario]); // Processar próximas consultas const processNextConsultations = (appointments) => { console.log("🔄 Processando consultas:", appointments); console.log("📊 Total de consultas recebidas:", appointments.length); // Análise detalhada de cada consulta appointments.forEach((apt, index) => { console.log(`📋 Consulta ${index + 1}:`, { id: apt.id, scheduled_at: apt.scheduled_at, date: apt.date, time: apt.time, doctor_name: apt.doctor_name, status: apt.status }); }); // Data de hoje em formato string para comparação const today = new Date(); const todayString = today.toISOString().split('T')[0]; // YYYY-MM-DD console.log("� Data de hoje (string):", todayString); // Filtrar consultas futuras (incluindo hoje) const futureConsultations = appointments.filter(apt => { // Usar scheduled_at como data principal const dateField = apt.scheduled_at || apt.date; if (!dateField) { console.log("⚠️ Consulta sem data:", apt.id); return false; } // Normalizar a data da consulta let consultationDate = dateField; // Se a data contém horário, pegar apenas a parte da data if (consultationDate.includes('T')) { consultationDate = consultationDate.split('T')[0]; } const isFuture = consultationDate >= todayString; console.log(`📅 Consulta ${apt.id}: ${consultationDate} >= ${todayString} = ${isFuture}`); return isFuture; }); console.log("🔮 Consultas futuras encontradas:", futureConsultations.length); console.log("📋 Lista de consultas futuras:", futureConsultations); // Mostrar as 2 consultas mais próximas do horário atual (futuras ou passadas) const consultationsWithProximity = appointments .map(apt => { const dateField = apt.scheduled_at || apt.date; const timeField = apt.time; if (dateField) { let consultationDateTime; if (dateField.includes('T')) { // Data já inclui horário consultationDateTime = new Date(dateField); } else { // Combinar data com horário consultationDateTime = new Date(dateField); if (timeField) { const [hours, minutes] = timeField.split(':'); consultationDateTime.setHours(parseInt(hours), parseInt(minutes), 0, 0); } else { consultationDateTime.setHours(12, 0, 0, 0); // Default meio-dia se não houver horário } } const now = new Date(); const diffInMinutes = Math.abs((consultationDateTime - now) / (1000 * 60)); return { ...apt, proximityScore: diffInMinutes, consultationDateTime }; } return { ...apt, proximityScore: 999999 }; // Consultas sem data vão para o final }) .sort((a, b) => a.proximityScore - b.proximityScore) .slice(0, 2); console.log("✅ 2 consultas mais próximas da data atual:", consultationsWithProximity); setNextConsultations(consultationsWithProximity); }; // FUNÇÃO DE TESTE - FORÇAR EXIBIÇÃO // Processar exames recentes const processRecentExams = (reports) => { console.log("🔬 Processando exames:", reports); // Ordenar por data de criação (mais recentes primeiro) const recent = reports .filter(report => report.created_at) // Apenas com data válida .sort((a, b) => new Date(b.created_at) - new Date(a.created_at)) .slice(0, 5); console.log("✅ Exames recentes processados:", recent); setRecentExams(recent); }; // Atualizar relógio useEffect(() => { const timer = setInterval(() => { setCurrentTime(new Date()); }, 1000); return () => clearInterval(timer); }, []); // Funções auxiliares para status das consultas const getStatusColor = (status) => { switch (status) { case 'confirmed': case 'confirmada': return 'bg-success'; case 'pending': case 'pendente': return 'bg-warning'; case 'cancelled': case 'cancelada': return 'bg-danger'; case 'completed': case 'finalizada': return 'bg-info'; default: return 'bg-primary'; } }; const getStatusBorderColor = (status) => { switch (status) { case 'confirmed': case 'confirmada': return '#28a745'; case 'pending': case 'pendente': return '#ffc107'; case 'cancelled': case 'cancelada': return '#dc3545'; case 'completed': case 'finalizada': return '#17a2b8'; default: return '#007bff'; } }; const getStatusIcon = (status) => { switch (status) { case 'confirmed': case 'confirmada': return 'fa-check'; case 'pending': case 'pendente': return 'fa-clock-o'; case 'cancelled': case 'cancelada': return 'fa-times'; case 'completed': case 'finalizada': return 'fa-check-circle'; default: return 'fa-calendar'; } }; // Funções auxiliares para status dos exames const getExamBorderColor = (status) => { switch (status) { case 'completed': case 'finalizado': return '#28a745'; case 'draft': case 'rascunho': return '#ffc107'; case 'pending': case 'pendente': return '#17a2b8'; default: return '#6c757d'; } }; const getExamIconColor = (status) => { switch (status) { case 'completed': case 'finalizado': return 'bg-success'; case 'draft': case 'rascunho': return 'bg-warning'; case 'pending': case 'pendente': return 'bg-info'; default: return 'bg-secondary'; } }; const getExamIcon = (status) => { switch (status) { case 'completed': case 'finalizado': return 'fa-check'; case 'draft': case 'rascunho': return 'fa-clock-o'; case 'pending': case 'pendente': return 'fa-file-text'; default: return 'fa-file-o'; } }; const getExamBadgeClass = (status) => { switch (status) { case 'completed': case 'finalizado': return 'bg-success'; case 'draft': case 'rascunho': return 'bg-warning'; case 'pending': case 'pendente': return 'bg-info'; default: return 'bg-secondary'; } }; const getExamStatusText = (status) => { switch (status) { case 'completed': case 'finalizado': return 'Concluído'; case 'draft': case 'rascunho': return 'Em análise'; case 'pending': case 'pendente': return 'Disponível'; default: return 'Processando'; } }; // Estatísticas calculadas baseadas nos dados reais da API + demonstração (apenas consultas não canceladas) const nonCancelledAppointments = appointments.filter(apt => apt.status !== 'cancelled' && apt.status !== 'cancelada' && apt.status !== 'canceled' ); const totalConsultas = nonCancelledAppointments.length > 0 ? nonCancelledAppointments.length : 5; const consultasRealizadas = nonCancelledAppointments.length > 0 ? nonCancelledAppointments.filter(apt => apt.status === 'completed' || apt.status === 'finalizada').length : 3; const proximasConsultas = nextConsultations.length; const examesRealizados = reports.length > 0 ? reports.length : 3; const [previewUrl, setPreviewUrl] = useState(AvatarForm); useEffect(() => { const loadAvatar = async () => { if (!userId) return; const myHeaders = new Headers(); myHeaders.append("apikey", supabaseAK); myHeaders.append("Authorization", `Bearer ${tokenUsuario}`); const requestOptions = { headers: myHeaders, method: 'GET', redirect: 'follow' }; try { const response = await fetch(`${supabaseUrl}/storage/v1/object/avatars/${userId}/avatar.png`, requestOptions); if (response.ok) { const blob = await response.blob(); const imageUrl = URL.createObjectURL(blob); setPreviewUrl(imageUrl); return; // Avatar encontrado } } catch (error) { } // Se chegou até aqui, não encontrou avatar - mantém o padrão }; loadAvatar(); }, [userId]); if (loading) { return (
Carregando...

Carregando dashboard...

); } return (
{/* Header com informações do paciente */}

👋 Olá, {patientName}!

Acompanhe suas consultas, resultados e tudo o que precisa em um só lugar. Cuide-se, e deixe o resto com a gente 💙

🕒 {currentTime.toLocaleString('pt-BR')}
Avatar
{/* Cards de estatísticas */}

{totalConsultas}

Total Consultas

{proximasConsultas}

Próximas Consultas

{examesRealizados}

Laudos

{consultasRealizadas}

Consultas Realizadas
{/* Ações rápidas */}
Agendar Consulta
Minhas Consultas
Meus Laudos
{/* Próximas Consultas */}

📅 Próximas Consultas

Ver todas
{nextConsultations.length > 0 ? (
{nextConsultations.map((consultation, index) => (
{consultation.doctor_name || 'Médico não informado'}
{(() => { const dateToShow = consultation.scheduled_at || consultation.date; if (dateToShow) { // Extrair data e hora sem conversão de fuso horário let dateStr = ''; let timeStr = ''; if (dateToShow.includes('T')) { // Formato ISO: 2025-11-15T14:30:00 const [datePart, timePart] = dateToShow.split('T'); const [year, month, day] = datePart.split('-'); dateStr = `${day}/${month}/${year}`; if (timePart) { const [hour, minute] = timePart.split(':'); timeStr = `${hour}:${minute}`; } } else { // Formato simples: 2025-11-15 const [year, month, day] = dateToShow.split('-'); dateStr = `${day}/${month}/${year}`; } // Usar horário do campo time se existir, senão usar o extraído const finalTime = consultation.time || timeStr || 'Horário a confirmar'; return `${dateStr} às ${finalTime}`; } return 'Data a confirmar'; })()}
{consultation.status === 'requested' ? ( <> Solicitado ) : consultation.status === 'confirmed' ? ( <> Confirmado ) : consultation.status === 'completed' ? ( <> Concluído ) : consultation.status === 'cancelled' ? ( <> Cancelado ) : ( <> {consultation.status} )}
))}
) : (

Nenhuma consulta agendada

Agendar primeira consulta
)} {/* Botão para ver mais */}
Agendar nova consulta
{/* Exames Recentes */}

🔬 Laudos Recentes

Ver todos
{recentExams.length > 0 ? (
{recentExams.map((exam) => (
{exam.exam || 'Exame'}
{new Date(exam.created_at).toLocaleDateString('pt-BR')} - {exam.requested_by || 'Médico não informado'}
{exam.status === 'draft' ? ( <> Rascunho ) : exam.status === 'completed' ? ( <> Concluído ) : ( exam.status )}
))}
) : (

Nenhum laudo realizado ainda

)}
Ver todos os laudos
{/* Informações de saúde */}

💡 Dicas de Saúde

Exercite-se Regularmente

30 minutos de atividade física por dia fazem a diferença

Alimentação Saudável

Consuma frutas e vegetais todos os dias

Durma Bem

7-8 horas de sono são essenciais para sua saúde

); } // CSS customizado para o PatientDashboard const style = document.createElement('style'); style.textContent = ` .timeline { position: relative; } .timeline-item { position: relative; } .timeline-marker { width: 12px; height: 12px; border-radius: 50%; margin-top: 6px; flex-shrink: 0; } .timeline-content { background: #f8f9fa; padding: 15px; border-radius: 10px; border-left: 3px solid #007bff; width: 100%; } .exam-icon { width: 40px; height: 40px; display: flex; align-items: center; justify-content: center; flex-shrink: 0; } .health-tip { transition: transform 0.2s ease, box-shadow 0.2s ease; } .health-tip:hover { transform: translateY(-5px); box-shadow: 0 8px 25px rgba(0,0,0,0.15); } .dash-widget { transition: transform 0.2s ease; } .dash-widget:hover { transform: translateY(-3px); } .user-info-banner { position: relative; overflow: hidden; } .user-info-banner::before { content: ''; position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: url('data:image/svg+xml,'); pointer-events: none; } `; if (!document.head.querySelector('[data-patient-dashboard-styles]')) { style.setAttribute('data-patient-dashboard-styles', 'true'); document.head.appendChild(style); }