From 5a3ea1bb756fc08a34a09481dcc6c3fc149d910e Mon Sep 17 00:00:00 2001 From: m1guelmcf Date: Sun, 30 Nov 2025 14:43:25 -0300 Subject: [PATCH 1/2] 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}; + } +}; From ce45c7187a417e5d8e98103c29964576fb615d53 Mon Sep 17 00:00:00 2001 From: GagoDuBroca Date: Wed, 3 Dec 2025 20:54:00 -0300 Subject: [PATCH 2/2] =?UTF-8?q?Adi=C3=A7=C3=A3o=20de=20barra=20de=20pesqui?= =?UTF-8?q?sar=20pagina=20de=20consultas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/secretary/appointments/page.tsx | 177 +++++++++++++++------------- 1 file changed, 93 insertions(+), 84 deletions(-) diff --git a/app/secretary/appointments/page.tsx b/app/secretary/appointments/page.tsx index e2c4623..d3f995b 100644 --- a/app/secretary/appointments/page.tsx +++ b/app/secretary/appointments/page.tsx @@ -11,6 +11,7 @@ import { import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Dialog } from "@/components/ui/dialog"; +import { Input } from "@/components/ui/input"; // Importei o Input import { Calendar as CalendarShadcn } from "@/components/ui/calendar"; import { Separator } from "@/components/ui/separator"; import { @@ -24,6 +25,7 @@ import { List, RefreshCw, Loader2, + Search, // Importei o ícone de busca } from "lucide-react"; import { format, parseISO, isValid, isToday, isTomorrow } from "date-fns"; import { ptBR } from "date-fns/locale"; @@ -43,6 +45,9 @@ export default function SecretaryAppointments() { const [deleteModal, setDeleteModal] = useState(false); const [editModal, setEditModal] = useState(false); + // Estado da Busca + const [searchTerm, setSearchTerm] = useState(""); + // Estado para o formulário de edição const [editFormData, setEditFormData] = useState({ date: "", @@ -50,7 +55,7 @@ export default function SecretaryAppointments() { status: "", }); - // Estado de data selecionada para o layout novo + // Estado de data selecionada const [selectedDate, setSelectedDate] = useState(new Date()); const fetchData = async () => { @@ -91,17 +96,31 @@ export default function SecretaryAppointments() { fetchData(); }, []); - // --- Agrupamento por dia para o layout novo --- + // --- Filtragem e Agrupamento --- 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; + let filteredList = appointments; - return list.reduce((acc: Record, apt: any) => { + // 1. Filtro de Texto (Nome do Paciente ou Médico) + if (searchTerm) { + const lowerTerm = searchTerm.toLowerCase(); + filteredList = filteredList.filter( + (apt) => + apt.patient.full_name.toLowerCase().includes(lowerTerm) || + apt.doctor.full_name.toLowerCase().includes(lowerTerm) + ); + } + + // 2. Filtro de Data (se selecionada) + if (selectedDate) { + filteredList = filteredList.filter((apt) => { + if (!apt.scheduled_at) return false; + const iso = apt.scheduled_at.toString(); + return iso.startsWith(format(selectedDate, "yyyy-MM-dd")); + }); + } + + // 3. Agrupamento por dia + return filteredList.reduce((acc: Record, apt: any) => { if (!apt.scheduled_at) return acc; const dateObj = new Date(apt.scheduled_at); if (!isValid(dateObj)) return acc; @@ -110,7 +129,7 @@ export default function SecretaryAppointments() { acc[key].push(apt); return acc; }, {}); - }, [appointments, selectedDate]); + }, [appointments, selectedDate, searchTerm]); // Dias que têm consulta (para destacar no calendário) const bookedDays = useMemo( @@ -134,7 +153,7 @@ export default function SecretaryAppointments() { return format(date, "EEEE, dd 'de' MMMM", { locale: ptBR }); }; - // --- LÓGICA DE EDIÇÃO --- + // --- LÓGICA DE EDIÇÃO E DELEÇÃO --- const handleEdit = (appointment: any) => { setSelectedAppointment(appointment); const appointmentDate = new Date(appointment.scheduled_at); @@ -172,9 +191,7 @@ export default function SecretaryAppointments() { }; await appointmentsService.update(selectedAppointment.id, updatePayload); - await fetchData(); - setEditModal(false); toast.success("Consulta atualizada com sucesso!"); } catch (error) { @@ -183,7 +200,6 @@ export default function SecretaryAppointments() { } }; - // --- LÓGICA DE DELEÇÃO --- const handleDelete = (appointment: any) => { setSelectedAppointment(appointment); setDeleteModal(true); @@ -204,39 +220,11 @@ export default function SecretaryAppointments() { } }; - // Mantidos caso use nos modais - const timeSlots = [ - "08:00", - "08:30", - "09:00", - "09:30", - "10:00", - "10:30", - "11:00", - "11:30", - "14:00", - "14:30", - "15:00", - "15:30", - "16:00", - "16:30", - "17:00", - "17:30", - ]; - const appointmentStatuses = [ - "requested", - "confirmed", - "checked_in", - "completed", - "cancelled", - "no_show", - ]; - return (
{/* Cabeçalho principal */} -
+

Agenda Médica @@ -245,43 +233,61 @@ export default function SecretaryAppointments() { Consultas para os pacientes

-
- - - -
+ + +
- {/* Subtítulo e ações (mostrar todas / atualizar) */} -
-

+ {/* Barra de Filtros e Ações */} +
+

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

-
- - +
+ +
+ + +

@@ -326,9 +332,11 @@ export default function SecretaryAppointments() {

- {selectedDate - ? "Não há agendamentos para esta data." - : "Não há próximas consultas agendadas."} + {searchTerm + ? "Nenhum resultado para a busca." + : selectedDate + ? "Não há agendamentos para esta data." + : "Não há consultas agendadas."}

@@ -350,7 +358,7 @@ export default function SecretaryAppointments() { key={appointment.id} className="shadow-sm hover:shadow-md transition-shadow" > - + {/* Coluna 1: Paciente + hora */}
@@ -384,8 +392,8 @@ export default function SecretaryAppointments() {
{/* Coluna 3: Ações */} -
-
+
+
@@ -456,4 +465,4 @@ const getStatusBadge = (status: string) => { default: return {status}; } -}; +}; \ No newline at end of file