From 5a3ea1bb756fc08a34a09481dcc6c3fc149d910e Mon Sep 17 00:00:00 2001 From: m1guelmcf Date: Sun, 30 Nov 2025 14:43:25 -0300 Subject: [PATCH] novo layout da secretaria --- app/secretary/appointments/page.tsx | 383 +++++++++++++++++++--------- 1 file changed, 263 insertions(+), 120 deletions(-) diff --git a/app/secretary/appointments/page.tsx b/app/secretary/appointments/page.tsx index cde08cd..e2c4623 100644 --- a/app/secretary/appointments/page.tsx +++ b/app/secretary/appointments/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState, useEffect } from "react"; +import { useState, useEffect, useMemo } from "react"; import { Card, CardContent, @@ -11,15 +11,22 @@ import { import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Dialog } from "@/components/ui/dialog"; +import { Calendar as CalendarShadcn } from "@/components/ui/calendar"; +import { Separator } from "@/components/ui/separator"; import { - Calendar, + Calendar as CalendarIcon, Clock, MapPin, Phone, User, Trash2, Pencil, + List, + RefreshCw, + Loader2, } from "lucide-react"; +import { format, parseISO, isValid, isToday, isTomorrow } from "date-fns"; +import { ptBR } from "date-fns/locale"; import { toast } from "sonner"; import Link from "next/link"; import { appointmentsService } from "@/services/appointmentsApi.mjs"; @@ -43,15 +50,15 @@ export default function SecretaryAppointments() { status: "", }); + // Estado de data selecionada para o layout novo + const [selectedDate, setSelectedDate] = useState(new Date()); + const fetchData = async () => { setIsLoading(true); try { - // 1. DEFINIR O PARÂMETRO DE ORDENAÇÃO - // 'scheduled_at.desc' ordena pela data do agendamento, em ordem descendente (mais recentes primeiro). - const queryParams = "order=scheduled_at.desc"; + const queryParams = "order=scheduled_at.asc"; const [appointmentList, patientList, doctorList] = await Promise.all([ - // 2. USAR A FUNÇÃO DE BUSCA COM O PARÂMETRO DE ORDENAÇÃO appointmentsService.search_appointment(queryParams), patientsService.list(), doctorsService.list(), @@ -82,7 +89,50 @@ export default function SecretaryAppointments() { useEffect(() => { fetchData(); - }, []); // Array vazio garante que a busca ocorra apenas uma vez, no carregamento da página. + }, []); + + // --- Agrupamento por dia para o layout novo --- + const groupedAppointments = useMemo(() => { + const list = selectedDate + ? appointments.filter((apt) => { + if (!apt.scheduled_at) return false; + const iso = apt.scheduled_at.toString(); + return iso.startsWith(format(selectedDate, "yyyy-MM-dd")); + }) + : appointments; + + return list.reduce((acc: Record, apt: any) => { + if (!apt.scheduled_at) return acc; + const dateObj = new Date(apt.scheduled_at); + if (!isValid(dateObj)) return acc; + const key = format(dateObj, "yyyy-MM-dd"); + if (!acc[key]) acc[key] = []; + acc[key].push(apt); + return acc; + }, {}); + }, [appointments, selectedDate]); + + // Dias que têm consulta (para destacar no calendário) + const bookedDays = useMemo( + () => + appointments + .map((apt) => + apt.scheduled_at ? new Date(apt.scheduled_at) : null + ) + .filter((d): d is Date => d !== null && isValid(d)), + [appointments] + ); + + const formatDisplayDate = (dateString: string) => { + const date = parseISO(dateString); + if (isToday(date)) { + return `Hoje, ${format(date, "dd 'de' MMMM", { locale: ptBR })}`; + } + if (isTomorrow(date)) { + return `Amanhã, ${format(date, "dd 'de' MMMM", { locale: ptBR })}`; + } + return format(date, "EEEE, dd 'de' MMMM", { locale: ptBR }); + }; // --- LÓGICA DE EDIÇÃO --- const handleEdit = (appointment: any) => { @@ -123,9 +173,7 @@ export default function SecretaryAppointments() { await appointmentsService.update(selectedAppointment.id, updatePayload); - // 3. RECARREGAR OS DADOS APÓS A EDIÇÃO - // Isso garante que a lista permaneça ordenada corretamente se a data for alterada. - fetchData(); + await fetchData(); setEditModal(false); toast.success("Consulta atualizada com sucesso!"); @@ -156,6 +204,7 @@ export default function SecretaryAppointments() { } }; + // Mantidos caso use nos modais const timeSlots = [ "08:00", "08:30", @@ -186,131 +235,225 @@ export default function SecretaryAppointments() { return (
+ {/* Cabeçalho principal */}
-

- Consultas Agendadas +

+ Agenda Médica

-

Gerencie as consultas dos pacientes

+

+ Consultas para os pacientes +

+
+
+ + +
- - -
-
- {isLoading ? ( -

Carregando consultas...

- ) : appointments.length > 0 ? ( - appointments.map((appointment) => ( - + {/* Subtítulo e ações (mostrar todas / atualizar) */} +
+

+ {selectedDate + ? `Agenda de ${format(selectedDate, "dd/MM/yyyy")}` + : "Próximas Consultas"} +

+
+ + +
+
+ + {/* Grid com calendário + lista */} +
+ {/* Coluna esquerda: calendário */} +
+ + + + + Filtrar por Data + + + Selecione um dia para ver os detalhes. + + + + + + +
+ + {/* Coluna direita: lista de consultas */} +
+ {isLoading ? ( +
+ +
+ ) : Object.keys(groupedAppointments).length === 0 ? ( + -
-
- - {appointment.doctor.full_name} - - - {appointment.doctor.specialty} - -
- {getStatusBadge(appointment.status)} -
+ Nenhuma consulta encontrada
-
-
-
- - {appointment.patient.full_name} -
-
- - {new Date(appointment.scheduled_at).toLocaleDateString( - "pt-BR", - { timeZone: "UTC" } - )} -
-
- - {new Date(appointment.scheduled_at).toLocaleTimeString( - "pt-BR", - { - hour: "2-digit", - minute: "2-digit", - timeZone: "UTC", - } - )} -
-
-
-
- - {appointment.doctor.location || "Local a definir"} -
-
- - {appointment.doctor.phone || "N/A"} -
+

+ {selectedDate + ? "Não há agendamentos para esta data." + : "Não há próximas consultas agendadas."} +

+ + + ) : ( + Object.entries(groupedAppointments).map( + ([date, appointmentsForDay]) => ( +
+

+ {formatDisplayDate(date)} +

+
+ {appointmentsForDay.map((appointment: any) => { + const scheduledAtDate = new Date( + appointment.scheduled_at + ); + + return ( + + + {/* Coluna 1: Paciente + hora */} +
+
+ + {appointment.patient.full_name} +
+
+ + {isValid(scheduledAtDate) + ? format(scheduledAtDate, "HH:mm") + : "--:--"} +
+
+ + {/* Coluna 2: Médico / local / telefone */} +
+
+ + {appointment.doctor.full_name} +
+
+ + {appointment.doctor.location || + "Local a definir"} +
+
+ + {appointment.doctor.phone || "N/A"} +
+
{getStatusBadge(appointment.status)}
+
+ + {/* Coluna 3: Ações */} +
+
+ + +
+
+
+
+ ); + })}
+
+ ) + ) + )} +
+
-
- - -
-
-
- )) - ) : ( -

Nenhuma consulta encontrada.

- )} -
-
+ {/* MODAL DE EDIÇÃO */} + + {/* ... (código do modal de edição permanece) ... */} + - {/* MODAL DE EDIÇÃO */} - - {/* ... (código do modal de edição) ... */} - - - {/* Modal de Deleção */} - - {/* ... (código do modal de deleção) ... */} - + {/* Modal de Deleção */} + + {/* ... (código do modal de deleção permanece) ... */} + +
); } const getStatusBadge = (status: string) => { - switch (status) { - case "requested": - return ( - Solicitada - ); - case "confirmed": - return Confirmada; - case "checked_in": - return ( - Check-in - ); - case "completed": - return Realizada; - case "cancelled": - return Cancelada; - case "no_show": - return ( - Não Compareceu - ); - default: - return {status}; - } - }; + switch (status) { + case "requested": + return ( + Solicitada + ); + case "confirmed": + return Confirmada; + case "checked_in": + return ( + Check-in + ); + case "completed": + return Realizada; + case "cancelled": + return ( + Cancelada + ); + case "no_show": + return ( + Não Compareceu + ); + default: + return {status}; + } +};