From f2a9dc7b7070320e4cf19aaafee05d66ac57ba3b Mon Sep 17 00:00:00 2001 From: Pedro Araujo da Silveira Date: Sun, 2 Nov 2025 23:37:02 -0300 Subject: [PATCH] =?UTF-8?q?fix:=20excess=C3=A3o=20medica=20painel=20secret?= =?UTF-8?q?aria?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../secretaria/SecretaryDoctorSchedule.tsx | 315 ++++++++++++++++-- 1 file changed, 294 insertions(+), 21 deletions(-) diff --git a/src/components/secretaria/SecretaryDoctorSchedule.tsx b/src/components/secretaria/SecretaryDoctorSchedule.tsx index c0055132e..ff14f510b 100644 --- a/src/components/secretaria/SecretaryDoctorSchedule.tsx +++ b/src/components/secretaria/SecretaryDoctorSchedule.tsx @@ -15,6 +15,7 @@ import { type Doctor, type Appointment, type DoctorAvailability, + type DoctorException, type Weekday, } from "../../services"; @@ -41,6 +42,7 @@ interface DayCell { date: Date; isCurrentMonth: boolean; appointments: Appointment[]; + exceptions: DoctorException[]; } // Helper para formatar nome do médico sem duplicar "Dr." @@ -61,6 +63,8 @@ export function SecretaryDoctorSchedule() { const [availabilities, setAvailabilities] = useState( [] ); + const [appointments, setAppointments] = useState([]); + const [exceptions, setExceptions] = useState([]); const [loading, setLoading] = useState(false); // Modal states @@ -87,6 +91,9 @@ export function SecretaryDoctorSchedule() { const [exceptionStartDate, setExceptionStartDate] = useState(""); const [exceptionEndDate, setExceptionEndDate] = useState(""); const [exceptionReason, setExceptionReason] = useState(""); + const [exceptionIsFullDay, setExceptionIsFullDay] = useState(true); + const [exceptionStartTime, setExceptionStartTime] = useState("08:00"); + const [exceptionEndTime, setExceptionEndTime] = useState("18:00"); useEffect(() => { loadDoctors(); @@ -127,8 +134,25 @@ export function SecretaryDoctorSchedule() { setAvailabilities(Array.isArray(availData) ? availData : []); - // Load appointments for the month (will be used for calendar display) - await appointmentService.list(); + // Load appointments for the doctor + const appointmentsData = await appointmentService.list({ + doctor_id: selectedDoctorId, + }); + console.log("[SecretaryDoctorSchedule] Consultas recebidas:", { + count: appointmentsData?.length || 0, + data: appointmentsData, + }); + setAppointments(Array.isArray(appointmentsData) ? appointmentsData : []); + + // Load exceptions for the doctor + const exceptionsData = await availabilityService.listExceptions({ + doctor_id: selectedDoctorId, + }); + console.log("[SecretaryDoctorSchedule] Exceções recebidas:", { + count: exceptionsData?.length || 0, + data: exceptionsData, + }); + setExceptions(Array.isArray(exceptionsData) ? exceptionsData : []); } catch (error) { console.error("[SecretaryDoctorSchedule] Erro ao carregar agenda:", error); toast.error("Erro ao carregar agenda do médico"); @@ -154,16 +178,32 @@ export function SecretaryDoctorSchedule() { const currentDatePointer = new Date(startDate); for (let i = 0; i < 42; i++) { + const dayDate = new Date(currentDatePointer); + const dayDateStr = dayDate.toISOString().split('T')[0]; + + // Filter appointments for this day + const dayAppointments = appointments.filter(apt => { + if (!apt.scheduled_at) return false; + const aptDate = new Date(apt.scheduled_at).toISOString().split('T')[0]; + return aptDate === dayDateStr; + }); + + // Filter exceptions for this day + const dayExceptions = exceptions.filter(exc => { + return exc.date === dayDateStr; + }); + days.push({ - date: new Date(currentDatePointer), + date: dayDate, isCurrentMonth: currentDatePointer.getMonth() === month, - appointments: [], + appointments: dayAppointments, + exceptions: dayExceptions, }); currentDatePointer.setDate(currentDatePointer.getDate() + 1); } setCalendarDays(days); - }, [currentDate]); + }, [currentDate, appointments, exceptions]); useEffect(() => { generateCalendar(); @@ -274,14 +314,58 @@ export function SecretaryDoctorSchedule() { return; } + if (!selectedDoctorId) { + toast.error("Selecione um médico"); + return; + } + + if (!exceptionIsFullDay && (!exceptionStartTime || !exceptionEndTime)) { + toast.error("Preencha os horários de início e fim"); + return; + } + try { - // TODO: Implement exception creation - toast.success("Exceção adicionada com sucesso"); + const start = new Date(exceptionStartDate); + const end = new Date(exceptionEndDate); + + // Criar exceções para cada dia no intervalo + const promises = []; + for (let date = new Date(start); date <= end; date.setDate(date.getDate() + 1)) { + const dateStr = date.toISOString().split('T')[0]; + promises.push( + availabilityService.createException({ + doctor_id: selectedDoctorId, + date: dateStr, + kind: "bloqueio", + start_time: exceptionIsFullDay ? null : exceptionStartTime, + end_time: exceptionIsFullDay ? null : exceptionEndTime, + reason: exceptionReason || exceptionType, + created_by: selectedDoctorId, // Idealmente deveria ser o ID da secretária + }) + ); + } + + await Promise.all(promises); + + const days = Math.ceil((end.getTime() - start.getTime()) / (1000 * 60 * 60 * 24)) + 1; + toast.success(`Exceção adicionada para ${days} dia${days > 1 ? 's' : ''} com sucesso`); + setShowExceptionDialog(false); + setExceptionType("férias"); + setExceptionStartDate(""); + setExceptionEndDate(""); + setExceptionReason(""); + setExceptionIsFullDay(true); + setExceptionStartTime("08:00"); + setExceptionEndTime("18:00"); + loadDoctorSchedule(); - } catch (error) { + } catch (error: any) { console.error("Erro ao adicionar exceção:", error); - toast.error("Erro ao adicionar exceção"); + const errorMsg = error?.response?.data?.message || + error?.message || + "Erro ao adicionar exceção"; + toast.error(errorMsg); } }; @@ -403,7 +487,7 @@ export function SecretaryDoctorSchedule() { {/* Calendar */}
-
+

{formatMonthYear(currentDate)}

@@ -429,6 +513,30 @@ export function SecretaryDoctorSchedule() {
+ {/* Legenda */} +
+
+
+ Solicitada +
+
+
+ Confirmada +
+
+
+ Concluída +
+
+
+ Bloqueio +
+
+
+ Disponibilidade Extra +
+
+
{["Dom", "Seg", "Ter", "Qua", "Qui", "Sex", "Sáb"].map((day) => (
(
-
+
{day.date.getDate()}
- {day.appointments.map((apt, i) => ( -
- {apt.patient_id} -
- ))} + + {/* Exceções (bloqueios e disponibilidades extras) */} + {day.exceptions.map((exc, i) => { + const timeRange = exc.start_time && exc.end_time + ? `${exc.start_time} - ${exc.end_time}` + : "Dia inteiro"; + const tooltipText = exc.reason + ? `${timeRange} - ${exc.reason}` + : timeRange; + + return ( +
+ {exc.kind === "bloqueio" ? "🚫" : "➕"} {timeRange} +
+ ); + })} + + {/* Consultas agendadas */} + {day.appointments.map((apt, i) => { + const time = apt.scheduled_at + ? new Date(apt.scheduled_at).toLocaleTimeString('pt-BR', { + hour: '2-digit', + minute: '2-digit', + }) + : ''; + return ( +
+ 📅 {time} +
+ ); + })}
))}
@@ -532,6 +685,81 @@ export function SecretaryDoctorSchedule() { )}
+ {/* Exceções (Bloqueios e Disponibilidades Extras) */} + {exceptions.length > 0 && ( +
+

+ Exceções Cadastradas +

+
+ {exceptions + .sort((a, b) => a.date.localeCompare(b.date)) + .map((exc) => ( +
+
+

+ {new Date(exc.date).toLocaleDateString('pt-BR', { + weekday: 'long', + year: 'numeric', + month: 'long', + day: 'numeric', + })} +

+

+ {exc.start_time && exc.end_time + ? `${exc.start_time} - ${exc.end_time}` + : "Dia inteiro"} +

+ {exc.reason && ( +

{exc.reason}

+ )} +
+
+ + {exc.kind === "bloqueio" ? "Bloqueio" : "Disponibilidade Extra"} + + +
+
+ ))} +
+
+ )} + {/* Availability Dialog */} {showAvailabilityDialog && (
@@ -692,11 +920,56 @@ export function SecretaryDoctorSchedule() { className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-orange-500 focus:border-transparent" />
+ + {/* Opção de dia inteiro ou horário específico */} +
+ + + {!exceptionIsFullDay && ( +
+
+ + setExceptionStartTime(e.target.value)} + className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-orange-500 focus:border-transparent" + /> +
+
+ + setExceptionEndTime(e.target.value)} + className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-orange-500 focus:border-transparent" + /> +
+
+ )} +