diff --git a/susconecta/app/paciente/page.tsx b/susconecta/app/paciente/page.tsx index 13e2201..bee91e7 100644 --- a/susconecta/app/paciente/page.tsx +++ b/susconecta/app/paciente/page.tsx @@ -17,8 +17,10 @@ import Link from 'next/link' import ProtectedRoute from '@/components/ProtectedRoute' import { useAuth } from '@/hooks/useAuth' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' -import { buscarPacientes, buscarPacientePorUserId, getUserInfo, listarMensagensPorPaciente } from '@/lib/api' -import { useReports } from '@/hooks/useReports' +import { buscarPacientes, buscarPacientePorUserId, getUserInfo, listarMensagensPorPaciente, listarAgendamentos, buscarMedicosPorIds } from '@/lib/api' +import { ENV_CONFIG } from '@/lib/env-config' +import { listarRelatoriosPorPaciente } from '@/lib/reports' +// reports are rendered statically for now // Simulação de internacionalização básica const strings = { dashboard: 'Dashboard', @@ -323,9 +325,116 @@ export default function PacientePage() { const activeToggleClass = "w-full transition duration-200 focus-visible:ring-2 focus-visible:ring-[#2563eb]/60 active:scale-[0.97] bg-[#2563eb] text-white hover:bg-[#2563eb] hover:text-white" const inactiveToggleClass = "w-full transition duration-200 bg-slate-50 text-[#2563eb] border border-[#2563eb]/30 hover:bg-slate-100 hover:text-[#2563eb] dark:bg-white/5 dark:text-white dark:hover:bg-white/10 dark:border-white/20" const hoverPrimaryIconClass = "rounded-xl bg-white text-[#1e293b] border border-black/10 shadow-[0_2px_8px_rgba(0,0,0,0.03)] transition duration-200 hover:bg-[#2563eb] hover:text-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#2563eb] dark:bg-slate-800 dark:text-slate-100 dark:border-white/10 dark:shadow-none dark:hover:bg-[#2563eb] dark:hover:text-white" - const today = new Date(); today.setHours(0, 0, 0, 0); - const selectedDate = new Date(currentDate); selectedDate.setHours(0, 0, 0, 0); - const isSelectedDateToday = selectedDate.getTime() === today.getTime() + const today = new Date(); today.setHours(0, 0, 0, 0); + const selectedDate = new Date(currentDate); selectedDate.setHours(0, 0, 0, 0); + const isSelectedDateToday = selectedDate.getTime() === today.getTime() + + + + // Appointments state (loaded when "Ver consultas agendadas" is opened) + const [appointments, setAppointments] = useState(null) + const [loadingAppointments, setLoadingAppointments] = useState(false) + const [appointmentsError, setAppointmentsError] = useState(null) + + useEffect(() => { + let mounted = true + if (!mostrarAgendadas) return + if (!patientId) { + setAppointmentsError('Paciente não identificado. Faça login novamente.') + return + } + + async function loadAppointments() { + try { + setLoadingAppointments(true) + setAppointmentsError(null) + setAppointments(null) + + // Try `eq.` first, then fallback to `in.(id)` which some views expect + const baseEncoded = encodeURIComponent(String(patientId)) + const queriesToTry = [ + `patient_id=eq.${baseEncoded}&order=scheduled_at.asc&limit=200`, + `patient_id=in.(${baseEncoded})&order=scheduled_at.asc&limit=200`, + ]; + + let rows: any[] = [] + for (const q of queriesToTry) { + try { + // Debug: also fetch raw response to inspect headers/response body in the browser + try { + const token = (typeof window !== 'undefined') ? (localStorage.getItem('auth_token') || localStorage.getItem('token') || sessionStorage.getItem('auth_token') || sessionStorage.getItem('token')) : null + const headers: Record = { + apikey: ENV_CONFIG.SUPABASE_ANON_KEY, + Accept: 'application/json', + } + if (token) headers.Authorization = `Bearer ${token}` + const rawUrl = `${ENV_CONFIG.SUPABASE_URL}/rest/v1/appointments?${q}` + console.debug('[Consultas][debug] GET', rawUrl, 'Headers(masked):', { ...headers, Authorization: headers.Authorization ? `${String(headers.Authorization).slice(0,6)}...${String(headers.Authorization).slice(-6)}` : undefined }) + const rawRes = await fetch(rawUrl, { method: 'GET', headers }) + const rawText = await rawRes.clone().text().catch(() => '') + console.debug('[Consultas][debug] raw response', { url: rawUrl, status: rawRes.status, bodyPreview: (typeof rawText === 'string' && rawText.length > 0) ? rawText.slice(0, 200) : rawText }) + } catch (dbgErr) { + console.debug('[Consultas][debug] não foi possível capturar raw response', dbgErr) + } + + const r = await listarAgendamentos(q) + if (r && Array.isArray(r) && r.length) { + rows = r + break + } + // if r is empty array, continue to next query format + } catch (e) { + // keep trying next format + console.debug('[Consultas] tentativa listarAgendamentos falhou para query', q, e) + } + } + + if (!mounted) return + if (!rows || rows.length === 0) { + // no appointments found for this patient using either filter + setAppointments([]) + return + } + + const doctorIds = Array.from(new Set(rows.map((r: any) => r.doctor_id).filter(Boolean))) + const doctorsMap: Record = {} + if (doctorIds.length) { + try { + const docs = await buscarMedicosPorIds(doctorIds).catch(() => []) + for (const d of docs || []) doctorsMap[d.id] = d + } catch (e) { + // ignore + } + } + + const mapped = (rows || []).map((a: any) => { + const sched = a.scheduled_at ? new Date(a.scheduled_at) : null + const doc = a.doctor_id ? doctorsMap[String(a.doctor_id)] : null + return { + id: a.id, + medico: doc?.full_name || a.doctor_id || '---', + especialidade: doc?.specialty || '', + local: a.location || a.place || '', + data: sched ? sched.toISOString().split('T')[0] : '', + hora: sched ? sched.toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' }) : '', + status: a.status ? String(a.status) : 'Pendente', + } + }) + + setAppointments(mapped) + } catch (err: any) { + console.warn('[Consultas] falha ao carregar agendamentos', err) + if (!mounted) return + setAppointmentsError(err?.message ?? 'Falha ao carregar agendamentos.') + setAppointments([]) + } finally { + if (mounted) setLoadingAppointments(false) + } + } + + loadAppointments() + return () => { mounted = false } + }, [mostrarAgendadas, patientId]) // Monta a URL de resultados com os filtros atuais const buildResultadosHref = () => { @@ -336,6 +445,10 @@ export default function PacientePage() { return `/resultados?${qs.toString()}` } + // derived lists for the "Ver consultas agendadas" dialog (computed after appointments state is declared) + const _dialogSource = (appointments !== null ? appointments : consultasFicticias) + const _todaysAppointments = (_dialogSource || []).filter((c: any) => c.data === todayStr) + return (
@@ -462,79 +575,90 @@ export default function PacientePage() { )}
- {consultasDoDia.length} consulta{consultasDoDia.length !== 1 ? 's' : ''} agendada{consultasDoDia.length !== 1 ? 's' : ''} + {`${_todaysAppointments.length} consulta${_todaysAppointments.length !== 1 ? 's' : ''} agendada${_todaysAppointments.length !== 1 ? 's' : ''}`}
- {consultasDoDia.length === 0 ? ( -
- -

Nenhuma consulta agendada para este dia

-

Use a busca para marcar uma nova consulta.

-
+ {loadingAppointments && mostrarAgendadas ? ( +
Carregando consultas...
+ ) : appointmentsError ? ( +
{appointmentsError}
) : ( - consultasDoDia.map(consulta => ( -
-
-
- -
-
- - {consulta.medico} + // prefer appointments (client-loaded) when present; fallback to fictitious list + (() => { + const todays = _todaysAppointments + if (!todays || todays.length === 0) { + return ( +
+ +

Nenhuma consulta agendada para este dia

+

Use a busca para marcar uma nova consulta.

+
+ ) + } + return todays.map((consulta: any) => ( +
+
+
+ +
+
+ + {consulta.medico} +
+

+ {consulta.especialidade} • {consulta.local} +

-

- {consulta.especialidade} • {consulta.local} -

-
-
- - {consulta.hora} -
+
+ + {consulta.hora} +
-
- - {consulta.status} - -
+
+ + {consulta.status} + +
-
- - {consulta.status !== 'Cancelada' && ( - - )} - {consulta.status !== 'Cancelada' && ( +
- )} + {consulta.status !== 'Cancelada' && ( + + )} + {consulta.status !== 'Cancelada' && ( + + )} +
-
- )) + )) + })() )}
@@ -549,41 +673,60 @@ export default function PacientePage() { ) } - // Reports (laudos) hook - const { reports, loadReportsByPatient, loading: reportsLoading } = useReports() + // Selected report state const [selectedReport, setSelectedReport] = useState(null) function ExamesLaudos() { + const [reports, setReports] = useState(null) + const [loadingReports, setLoadingReports] = useState(false) + const [reportsError, setReportsError] = useState(null) + useEffect(() => { + let mounted = true if (!patientId) return - // load laudos for this patient - loadReportsByPatient(patientId).catch(() => {}) + setLoadingReports(true) + setReportsError(null) + listarRelatoriosPorPaciente(String(patientId)) + .then(res => { + if (!mounted) return + setReports(Array.isArray(res) ? res : []) + }) + .catch(err => { + console.warn('[ExamesLaudos] erro ao carregar laudos', err) + if (!mounted) return + setReportsError('Falha ao carregar laudos.') + }) + .finally(() => { if (mounted) setLoadingReports(false) }) + + return () => { mounted = false } }, [patientId]) return (

Laudos

- {reportsLoading ? ( -
Carregando laudos...
- ) : (!reports || reports.length === 0) ? ( -
Nenhum laudo salvo.
- ) : ( -
- {reports.map((r: any) => ( -
+
+ {loadingReports ? ( +
{strings.carregando}
+ ) : reportsError ? ( +
{reportsError}
+ ) : (!reports || reports.length === 0) ? ( +
Nenhum laudo encontrado para este paciente.
+ ) : ( + reports.map((r) => ( +
-
{r.title || r.report_type || r.exame || r.name || 'Laudo'}
-
Data: {new Date(r.report_date || r.data || r.created_at || Date.now()).toLocaleDateString('pt-BR')}
+
{r.title || r.name || r.report_name || 'Laudo'}
+
Data: {new Date(r.report_date || r.created_at || Date.now()).toLocaleDateString('pt-BR')}
- - + +
- ))} -
- )} + )) + )} +
!open && setSelectedReport(null)}> @@ -592,9 +735,9 @@ export default function PacientePage() { {selectedReport && ( <> -
{selectedReport.title || selectedReport.report_type || selectedReport.exame || 'Laudo'}
-
Data: {new Date(selectedReport.report_date || selectedReport.data || selectedReport.created_at || Date.now()).toLocaleDateString('pt-BR')}
-
{selectedReport.content || selectedReport.laudo || selectedReport.body || JSON.stringify(selectedReport, null, 2)}
+
{selectedReport.title || selectedReport.name || 'Laudo'}
+
Data: {new Date(selectedReport.report_date || selectedReport.created_at || Date.now()).toLocaleDateString('pt-BR')}
+
{selectedReport.content || selectedReport.body || JSON.stringify(selectedReport, null, 2)}
)}
@@ -662,7 +805,7 @@ export default function PacientePage() { } function Perfil() { - const hasAddress = Boolean(profileData.endereco || profileData.cidade || profileData.cep || profileData.biografia) + const hasAddress = Boolean(profileData.endereco || profileData.cidade || profileData.cep) return (
@@ -732,14 +875,7 @@ export default function PacientePage() {

{profileData.cep}

)}
-
- - {isEditingProfile ? ( -