From b791186640a7513350f187cf3725cf75ab5cbcd6 Mon Sep 17 00:00:00 2001 From: pedrosiimoes Date: Mon, 13 Oct 2025 23:56:53 -0300 Subject: [PATCH] agendamento dos pacientes --- app/patient/appointments/page.tsx | 575 ++++++++++++++++-------------- app/patient/schedule/page.tsx | 252 +++++++++---- 2 files changed, 493 insertions(+), 334 deletions(-) diff --git a/app/patient/appointments/page.tsx b/app/patient/appointments/page.tsx index 1152076..d2d02a8 100644 --- a/app/patient/appointments/page.tsx +++ b/app/patient/appointments/page.tsx @@ -1,288 +1,343 @@ "use client"; -import { useState } from "react"; -import Link from "next/link"; -import { toast } from "sonner"; -import { useAppointments, Appointment } from "../../context/AppointmentsContext"; - -// Componentes de UI e Ícones +import { useState, useEffect } from "react"; import PatientLayout from "@/components/patient-layout"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; -import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogClose } from "@/components/ui/dialog"; +import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; -import { Calendar, Clock, MapPin, Phone, CalendarDays, X, Trash2 } from "lucide-react"; +import { Calendar, Clock, MapPin, Phone, User, X, CalendarDays } from "lucide-react"; +import { toast } from "sonner"; -export default function PatientAppointmentsPage() { - const { appointments, updateAppointment, deleteAppointment } = useAppointments(); +import { appointmentsService } from "@/services/appointmentsApi.mjs"; +import { patientsService } from "@/services/patientsApi.mjs"; +import { doctorsService } from "@/services/doctorsApi.mjs"; - // Estados para controlar os modais e os dados do formulário - const [isRescheduleModalOpen, setRescheduleModalOpen] = useState(false); - const [isCancelModalOpen, setCancelModalOpen] = useState(false); - const [selectedAppointment, setSelectedAppointment] = useState(null); - - const [rescheduleData, setRescheduleData] = useState({ date: "", time: "", reason: "" }); - const [cancelReason, setCancelReason] = useState(""); +const APPOINTMENTS_STORAGE_KEY = "clinic-appointments"; - // --- MANIPULADORES DE EVENTOS --- +// Simulação do paciente logado +const LOGGED_PATIENT_ID = "P001"; - const handleRescheduleClick = (appointment: Appointment) => { - setSelectedAppointment(appointment); - // Preenche o formulário com os dados atuais da consulta - setRescheduleData({ date: appointment.date, time: appointment.time, reason: appointment.observations || "" }); - setRescheduleModalOpen(true); - }; +export default function PatientAppointments() { + const [appointments, setAppointments] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const [selectedAppointment, setSelectedAppointment] = useState(null); - const handleCancelClick = (appointment: Appointment) => { - setSelectedAppointment(appointment); - setCancelReason(""); // Limpa o motivo ao abrir - setCancelModalOpen(true); - }; - - const confirmReschedule = () => { - if (!rescheduleData.date || !rescheduleData.time) { - toast.error("Por favor, selecione uma nova data e horário"); - return; - } - if (selectedAppointment) { - updateAppointment(selectedAppointment.id, { - date: rescheduleData.date, - time: rescheduleData.time, - observations: rescheduleData.reason, // Atualiza as observações com o motivo - }); - toast.success("Consulta reagendada com sucesso!"); - setRescheduleModalOpen(false); - } - }; + // Modais + const [rescheduleModal, setRescheduleModal] = useState(false); + const [cancelModal, setCancelModal] = useState(false); - const confirmCancel = () => { - if (cancelReason.trim().length < 10) { - toast.error("Por favor, forneça um motivo com pelo menos 10 caracteres."); - return; - } - if (selectedAppointment) { - // Apenas atualiza o status e adiciona o motivo do cancelamento nas observações - updateAppointment(selectedAppointment.id, { - status: "Cancelada", - observations: `Motivo do cancelamento: ${cancelReason}` - }); - toast.success("Consulta cancelada com sucesso!"); - setCancelModalOpen(false); - } - }; + // Formulário de reagendamento/cancelamento + const [rescheduleData, setRescheduleData] = useState({ date: "", time: "", reason: "" }); + const [cancelReason, setCancelReason] = useState(""); - const handleDeleteClick = (appointmentId: string) => { - if (window.confirm("Tem certeza que deseja excluir permanentemente esta consulta? Esta ação não pode ser desfeita.")) { - deleteAppointment(appointmentId); - toast.success("Consulta excluída do histórico."); - } - }; + 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", + ]; - // --- LÓGICA AUXILIAR --- - - const getStatusBadge = (status: Appointment['status']) => { - switch (status) { - case "Agendada": return Agendada; - case "Realizada": return Realizada; - case "Cancelada": return Cancelada; - } - }; + const fetchData = async () => { + setIsLoading(true); + try { + const [appointmentList, patientList, doctorList] = await Promise.all([ + appointmentsService.list(), + patientsService.list(), + doctorsService.list(), + ]); - const timeSlots = ["08:00", "08:30", "09:00", "09:30", "10:00", "10:30", "14:00", "14:30", "15:00", "15:30"]; - const today = new Date(); - today.setHours(0, 0, 0, 0); // Zera o horário para comparar apenas o dia + const doctorMap = new Map(doctorList.map((d: any) => [d.id, d])); + const patientMap = new Map(patientList.map((p: any) => [p.id, p])); - // ETAPA 1: ORDENAÇÃO DAS CONSULTAS - // Cria uma cópia do array e o ordena - const sortedAppointments = [...appointments].sort((a, b) => { - const statusWeight = { 'Agendada': 1, 'Realizada': 2, 'Cancelada': 3 }; - - // Primeiro, ordena por status (Agendada vem primeiro) - if (statusWeight[a.status] !== statusWeight[b.status]) { - return statusWeight[a.status] - statusWeight[b.status]; - } + // Filtra apenas as consultas do paciente logado + const patientAppointments = appointmentList + .filter((apt: any) => apt.patient_id === LOGGED_PATIENT_ID) + .map((apt: any) => ({ + ...apt, + doctor: doctorMap.get(apt.doctor_id) || { full_name: "Médico não encontrado", specialty: "N/A" }, + patient: patientMap.get(apt.patient_id) || { full_name: "Paciente não encontrado" }, + })); - // Se o status for o mesmo, ordena por data (mais recente/futura no topo) - return new Date(b.date).getTime() - new Date(a.date).getTime(); - }); + setAppointments(patientAppointments); + } catch (error) { + console.error("Erro ao carregar consultas:", error); + toast.error("Não foi possível carregar suas consultas."); + } finally { + setIsLoading(false); + } + }; - return ( - -
-
+ useEffect(() => { + fetchData(); + }, []); + + 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; + default: + return {status}; + } + }; + + const handleReschedule = (appointment: any) => { + setSelectedAppointment(appointment); + setRescheduleData({ date: "", time: "", reason: "" }); + setRescheduleModal(true); + }; + + const handleCancel = (appointment: any) => { + setSelectedAppointment(appointment); + setCancelReason(""); + setCancelModal(true); + }; + + const confirmReschedule = async () => { + if (!rescheduleData.date || !rescheduleData.time) { + toast.error("Por favor, selecione uma nova data e horário."); + return; + } + try { + const newScheduledAt = new Date(`${rescheduleData.date}T${rescheduleData.time}:00Z`).toISOString(); + + await appointmentsService.update(selectedAppointment.id, { + scheduled_at: newScheduledAt, + status: "requested", + }); + + setAppointments((prev) => + prev.map((apt) => + apt.id === selectedAppointment.id ? { ...apt, scheduled_at: newScheduledAt, status: "requested" } : apt + ) + ); + + setRescheduleModal(false); + toast.success("Consulta reagendada com sucesso!"); + } catch (error) { + console.error("Erro ao reagendar consulta:", error); + toast.error("Não foi possível reagendar a consulta."); + } + }; + + const confirmCancel = async () => { + if (!cancelReason.trim() || cancelReason.trim().length < 10) { + toast.error("Por favor, informe um motivo de cancelamento (mínimo 10 caracteres)."); + return; + } + try { + await appointmentsService.update(selectedAppointment.id, { + status: "cancelled", + cancel_reason: cancelReason, + }); + + setAppointments((prev) => + prev.map((apt) => + apt.id === selectedAppointment.id ? { ...apt, status: "cancelled" } : apt + ) + ); + + setCancelModal(false); + toast.success("Consulta cancelada com sucesso!"); + } catch (error) { + console.error("Erro ao cancelar consulta:", error); + toast.error("Não foi possível cancelar a consulta."); + } + }; + + return ( + +
+
+
+

Minhas Consultas

+

Veja, reagende ou cancele suas consultas

+
+
+ +
+ {isLoading ? ( +

Carregando suas consultas...

+ ) : appointments.length > 0 ? ( + appointments.map((appointment) => ( + + +
-

Minhas Consultas

-

Histórico e consultas agendadas

+ {appointment.doctor.full_name} + {appointment.doctor.specialty}
- - - -
+ {getStatusBadge(appointment.status)} +
+ + +
+
+
+ + {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"} +
+
+
-
- {/* Utiliza o array ORDENADO para a renderização */} - {sortedAppointments.map((appointment) => { - const appointmentDate = new Date(appointment.date); - let displayStatus = appointment.status; + {appointment.status !== "cancelled" && ( +
+ + +
+ )} + + + )) + ) : ( +

Você ainda não possui consultas agendadas.

+ )} +
+
- if (appointment.status === 'Agendada' && appointmentDate < today) { - displayStatus = 'Realizada'; - } - - return ( - - -
-
- {appointment.doctorName} - {appointment.specialty} -
- {getStatusBadge(displayStatus)} -
-
- -
-
-
- - {new Date(appointment.date).toLocaleDateString("pt-BR", { timeZone: 'UTC' })} -
-
- - {appointment.time} -
-
-
-
- - {appointment.location || 'Local não informado'} -
-
- - {appointment.phone || 'Telefone não informado'} -
-
-
- - {/* Container ÚNICO para todas as ações */} -
- {(displayStatus === "Agendada") && ( - <> - - - - )} - - {(displayStatus === "Realizada" || displayStatus === "Cancelada") && ( - - )} -
-
-
- ); - })} -
+ {/* MODAL DE REAGENDAMENTO */} + + + + Reagendar Consulta + + Escolha uma nova data e horário para sua consulta com{" "} + {selectedAppointment?.doctor?.full_name}. + + +
+
+ + setRescheduleData((prev) => ({ ...prev, date: e.target.value }))} + min={new Date().toISOString().split("T")[0]} + />
- - {/* ETAPA 2: CONSTRUÇÃO DOS MODAIS */} +
+ + +
+
+ +