From 805aa66f6f50ae6988081ce259097ad0ba1186d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucas=20Deir=C3=B3=20Rodrigues?= Date: Fri, 7 Nov 2025 02:14:53 -0300 Subject: [PATCH 01/16] consultas paciente e listagem das consultas para paciente --- app/patient/appointments/page.tsx | 452 ++++++++--------------- app/patient/schedule/page.tsx | 587 +++++++++++++++++------------- components/doctor-layout.tsx | 7 +- services/availabilityApi.mjs | 10 + 4 files changed, 496 insertions(+), 560 deletions(-) diff --git a/app/patient/appointments/page.tsx b/app/patient/appointments/page.tsx index 78f2d8b..575fb02 100644 --- a/app/patient/appointments/page.tsx +++ b/app/patient/appointments/page.tsx @@ -2,323 +2,185 @@ import { useState, useEffect } from "react"; import PatientLayout from "@/components/patient-layout"; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; -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, User, X, CalendarDays } from "lucide-react"; +import { Calendar, Clock, CalendarDays, X } from "lucide-react"; import { toast } from "sonner"; import { appointmentsService } from "@/services/appointmentsApi.mjs"; -import { patientsService } from "@/services/patientsApi.mjs"; -import { doctorsService } from "@/services/doctorsApi.mjs"; import { usersService } from "@/services/usersApi.mjs"; -const APPOINTMENTS_STORAGE_KEY = "clinic-appointments"; - -interface UserPermissions { - isAdmin: boolean; - isManager: boolean; - isDoctor: boolean; - isSecretary: boolean; - isAdminOrManager: boolean; +// Tipagem correta para o usuário +interface UserProfile { + id: string; + full_name: string; + email: string; + phone?: string; + avatar_url?: string; } -interface UserData { - user: { - id: string; - email: string; - email_confirmed_at: string | null; - created_at: string | null; - last_sign_in_at: string | null; - }; - profile: { - id: string; - full_name: string; - email: string; - phone: string; - avatar_url: string | null; - disabled: boolean; - created_at: string | null; - updated_at: string | null; - }; - roles: string[]; - permissions: UserPermissions; +interface User { + user: { + id: string; + email: string; + }; + profile: UserProfile; + roles: string[]; + permissions?: any; } -export default function PatientAppointments() { - const [appointments, setAppointments] = useState([]); - const [isLoading, setIsLoading] = useState(true); - const [selectedAppointment, setSelectedAppointment] = useState(null); - const [userData, setUserData] = useState(); +interface Appointment { + id: string; + doctor_id: string; + scheduled_at: string; + status: string; + doctorName?: string; +} - // Modais - const [rescheduleModal, setRescheduleModal] = useState(false); - const [cancelModal, setCancelModal] = useState(false); +export default function PatientAppointmentsPage() { + const [appointments, setAppointments] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const [userData, setUserData] = useState(null); - // Formulário de reagendamento/cancelamento - const [rescheduleData, setRescheduleData] = useState({ date: "", time: "", reason: "" }); - const [cancelReason, setCancelReason] = useState(""); + // --- Busca o usuário logado --- + const fetchUser = async () => { + try { + const user: User = await usersService.getMe(); + if (!user.roles.includes("patient") && !user.roles.includes("user")) { + toast.error("Apenas pacientes podem visualizar suas consultas."); + setIsLoading(false); + return null; + } + setUserData(user); + return user; + } catch (err) { + console.error("Erro ao buscar usuário logado:", err); + toast.error("Não foi possível identificar o usuário logado."); + setIsLoading(false); + return null; + } + }; - 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"]; + // --- Busca consultas do paciente --- + const fetchAppointments = async (patientId: string) => { + setIsLoading(true); + try { + const queryParams = `patient_id=eq.${patientId}&order=scheduled_at.desc`; + const appointmentsList: Appointment[] = await appointmentsService.search_appointment(queryParams); - const fetchData = async () => { - setIsLoading(true); - try { - const queryParams = "order=scheduled_at.desc"; - const appointmentList = await appointmentsService.search_appointment(queryParams); - const patientList = await patientsService.list(); - const doctorList = await doctorsService.list(); + // Buscar nome do médico para cada consulta + const appointmentsWithDoctor = await Promise.all( + appointmentsList.map(async (apt) => { + let doctorName = apt.doctor_id; + if (apt.doctor_id) { + try { + const doctorInfo = await usersService.full_data(apt.doctor_id); + doctorName = doctorInfo?.profile?.full_name || apt.doctor_id; + } catch (err) { + console.error("Erro ao buscar nome do médico:", err); + } + } + return { ...apt, doctorName }; + }) + ); - const user = await usersService.getMe(); - setUserData(user); + setAppointments(appointmentsWithDoctor); + } catch (err) { + console.error("Erro ao carregar consultas:", err); + toast.error("Não foi possível carregar suas consultas."); + } finally { + setIsLoading(false); + } + }; - const doctorMap = new Map(doctorList.map((d: any) => [d.id, d])); - const patientMap = new Map(patientList.map((p: any) => [p.id, p])); + useEffect(() => { + (async () => { + const user = await fetchUser(); + if (user?.user.id) { + await fetchAppointments(user.user.id); + } + })(); + }, []); - console.log(appointmentList); + 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}; + } + }; - // Filtra apenas as consultas do paciente logado - const patientAppointments = appointmentList - .filter((apt: any) => apt.patient_id === userData?.user.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" }, - })); + const handleReschedule = (apt: Appointment) => { + toast.info(`Funcionalidade de reagendamento da consulta ${apt.id} ainda não implementada`); + }; - setAppointments(patientAppointments); - } catch (error) { - console.error("Erro ao carregar consultas:", error); - toast.error("Não foi possível carregar suas consultas."); - } finally { - setIsLoading(false); - } - }; + const handleCancel = (apt: Appointment) => { + toast.info(`Funcionalidade de cancelamento da consulta ${apt.id} ainda não implementada`); + }; - useEffect(() => { - fetchData(); - }, []); + return ( + +
+

Minhas Consultas

+

Veja, reagende ou cancele suas consultas

- 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 consultas...

+ ) : appointments.length === 0 ? ( +

Você ainda não possui consultas agendadas.

+ ) : ( + appointments.map((apt) => ( + + +
+ {apt.doctorName} + Especialidade: N/A +
+ {getStatusBadge(apt.status)} +
+ +
+
+ + {new Date(apt.scheduled_at).toLocaleDateString("pt-BR")}
-
- -
- {isLoading ? ( -

Carregando suas consultas...

- ) : appointments.length > 0 ? ( - appointments.map((appointment) => ( - - -
-
- {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"} -
-
-
- - {appointment.status !== "cancelled" && ( -
- - -
- )} -
-
- )) - ) : ( -

Você ainda não possui consultas agendadas.

+
+ + {new Date(apt.scheduled_at).toLocaleTimeString("pt-BR", { + hour: "2-digit", + minute: "2-digit", + })} +
+
+
+ {apt.status !== "cancelled" && ( + <> + + + )} -
-
- - {/* 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]} /> -
-
- - -
-
- -