import { useState, useEffect, useCallback } from "react"; import toast from "react-hot-toast"; import { ChevronLeft, ChevronRight, Plus, Edit, Trash2, Calendar as CalendarIcon, } from "lucide-react"; import { doctorService, appointmentService, availabilityService, type Doctor, type Appointment, type DoctorAvailability, type Weekday, } from "../../services"; // Helper para converter weekday (string em inglês) para texto legível em português const weekdayToText = (weekday: Weekday | undefined | null): string => { if (weekday === undefined || weekday === null) { return "Desconhecido"; } const weekdayMap: Record = { sunday: "Domingo", monday: "Segunda-feira", tuesday: "Terça-feira", wednesday: "Quarta-feira", thursday: "Quinta-feira", friday: "Sexta-feira", saturday: "Sábado", }; return weekdayMap[weekday] || "Desconhecido"; }; interface DayCell { date: Date; isCurrentMonth: boolean; appointments: Appointment[]; } // Helper para formatar nome do médico sem duplicar "Dr." const formatDoctorName = (fullName: string): string => { const name = fullName.trim(); // Verifica se já começa com Dr. ou Dr (case insensitive) if (/^dr\.?\s/i.test(name)) { return name; } return `Dr. ${name}`; }; export function SecretaryDoctorSchedule() { const [doctors, setDoctors] = useState([]); const [selectedDoctorId, setSelectedDoctorId] = useState(""); const [currentDate, setCurrentDate] = useState(new Date()); const [calendarDays, setCalendarDays] = useState([]); const [availabilities, setAvailabilities] = useState( [] ); const [loading, setLoading] = useState(false); // Modal states const [showAvailabilityDialog, setShowAvailabilityDialog] = useState(false); const [showExceptionDialog, setShowExceptionDialog] = useState(false); const [showEditDialog, setShowEditDialog] = useState(false); const [editingAvailability, setEditingAvailability] = useState(null); // Availability form const [selectedWeekdays, setSelectedWeekdays] = useState([]); const [startTime, setStartTime] = useState("08:00"); const [endTime, setEndTime] = useState("18:00"); const [duration, setDuration] = useState(30); // Edit form const [editStartTime, setEditStartTime] = useState("08:00"); const [editEndTime, setEditEndTime] = useState("18:00"); const [editDuration, setEditDuration] = useState(30); const [editActive, setEditActive] = useState(true); // Exception form const [exceptionType, setExceptionType] = useState("férias"); const [exceptionStartDate, setExceptionStartDate] = useState(""); const [exceptionEndDate, setExceptionEndDate] = useState(""); const [exceptionReason, setExceptionReason] = useState(""); useEffect(() => { loadDoctors(); }, []); // If a doctor id was requested by other components (via sessionStorage), select it useEffect(() => { const requested = sessionStorage.getItem("selectedDoctorForSchedule"); if (requested) { setSelectedDoctorId(requested); sessionStorage.removeItem("selectedDoctorForSchedule"); } }, [doctors]); useEffect(() => { console.log("[SecretaryDoctorSchedule] Estado availabilities atualizado:", { count: availabilities.length, data: availabilities, }); }, [availabilities]); const loadDoctorSchedule = useCallback(async () => { if (!selectedDoctorId) return; console.log("[SecretaryDoctorSchedule] Carregando agenda do médico:", selectedDoctorId); setLoading(true); try { // Load availabilities const availData = await availabilityService.list({ doctor_id: selectedDoctorId, }); console.log("[SecretaryDoctorSchedule] Disponibilidades recebidas:", { count: availData?.length || 0, data: availData, }); setAvailabilities(Array.isArray(availData) ? availData : []); // Load appointments for the month (will be used for calendar display) await appointmentService.list(); } catch (error) { console.error("[SecretaryDoctorSchedule] Erro ao carregar agenda:", error); toast.error("Erro ao carregar agenda do médico"); } finally { setLoading(false); } }, [selectedDoctorId]); useEffect(() => { loadDoctorSchedule(); }, [loadDoctorSchedule]); const generateCalendar = useCallback(() => { const year = currentDate.getFullYear(); const month = currentDate.getMonth(); const firstDay = new Date(year, month, 1); const startDate = new Date(firstDay); startDate.setDate(startDate.getDate() - firstDay.getDay()); const days: DayCell[] = []; const currentDatePointer = new Date(startDate); for (let i = 0; i < 42; i++) { days.push({ date: new Date(currentDatePointer), isCurrentMonth: currentDatePointer.getMonth() === month, appointments: [], }); currentDatePointer.setDate(currentDatePointer.getDate() + 1); } setCalendarDays(days); }, [currentDate]); useEffect(() => { generateCalendar(); }, [generateCalendar]); const loadDoctors = async () => { try { const data = await doctorService.list(); setDoctors(Array.isArray(data) ? data : []); if (data.length > 0) { setSelectedDoctorId(data[0].id); } } catch (error) { console.error("Erro ao carregar médicos:", error); toast.error("Erro ao carregar médicos"); } }; const previousMonth = () => { setCurrentDate( new Date(currentDate.getFullYear(), currentDate.getMonth() - 1) ); }; const nextMonth = () => { setCurrentDate( new Date(currentDate.getFullYear(), currentDate.getMonth() + 1) ); }; const goToToday = () => { setCurrentDate(new Date()); }; const formatMonthYear = (date: Date) => { return date.toLocaleDateString("pt-BR", { month: "long", year: "numeric" }); }; const handleAddAvailability = async () => { if (selectedWeekdays.length === 0) { toast.error("Selecione pelo menos um dia da semana"); return; } if (!selectedDoctorId) { toast.error("Selecione um médico"); return; } try { console.log("[SecretaryDoctorSchedule] Criando disponibilidades:", { doctor_id: selectedDoctorId, weekdays: selectedWeekdays, start_time: startTime, end_time: endTime, slot_minutes: duration, }); // Os dias da semana já estão no formato correto (sunday, monday, etc.) const promises = selectedWeekdays.map((weekdayStr) => { const payload = { doctor_id: selectedDoctorId, weekday: weekdayStr as Weekday, start_time: startTime, end_time: endTime, slot_minutes: duration, appointment_type: "presencial" as const, active: true, }; console.log("[SecretaryDoctorSchedule] Payload para criação:", payload); return availabilityService.create(payload); }); await Promise.all(promises); toast.success( `Disponibilidade${selectedWeekdays.length > 1 ? "s" : ""} adicionada${ selectedWeekdays.length > 1 ? "s" : "" } com sucesso` ); setShowAvailabilityDialog(false); setSelectedWeekdays([]); setStartTime("08:00"); setEndTime("18:00"); setDuration(30); loadDoctorSchedule(); } catch (error: any) { console.error("[SecretaryDoctorSchedule] Erro ao adicionar disponibilidade:", { error, message: error?.message, response: error?.response?.data, }); const errorMsg = error?.response?.data?.message || error?.response?.data?.hint || error?.message || "Erro ao adicionar disponibilidade"; toast.error(errorMsg); } }; const handleAddException = async () => { if (!exceptionStartDate || !exceptionEndDate) { toast.error("Preencha as datas de início e fim"); return; } try { // TODO: Implement exception creation toast.success("Exceção adicionada com sucesso"); setShowExceptionDialog(false); loadDoctorSchedule(); } catch (error) { console.error("Erro ao adicionar exceção:", error); toast.error("Erro ao adicionar exceção"); } }; const handleEditAvailability = (availability: DoctorAvailability) => { setEditingAvailability(availability); setEditStartTime(availability.start_time); setEditEndTime(availability.end_time); setEditDuration(availability.slot_minutes || 30); setEditActive(availability.active ?? true); setShowEditDialog(true); }; const handleSaveEdit = async () => { if (!editingAvailability?.id) return; console.log("[SecretaryDoctorSchedule] Salvando edição:", { id: editingAvailability.id, start_time: editStartTime, end_time: editEndTime, slot_minutes: editDuration, active: editActive, }); try { const updateData = { start_time: editStartTime, end_time: editEndTime, slot_minutes: editDuration, active: editActive, }; console.log("[SecretaryDoctorSchedule] Dados de atualização:", updateData); const result = await availabilityService.update(editingAvailability.id, updateData); console.log("[SecretaryDoctorSchedule] Resultado da atualização:", result); toast.success("Disponibilidade atualizada com sucesso"); setShowEditDialog(false); setEditingAvailability(null); loadDoctorSchedule(); } catch (error: any) { console.error("[SecretaryDoctorSchedule] Erro ao atualizar disponibilidade:", { error, message: error?.message, response: error?.response, data: error?.response?.data, }); const errorMessage = error?.response?.data?.message || error?.message || "Erro ao atualizar disponibilidade"; toast.error(errorMessage); } }; const handleDeleteAvailability = async (availability: DoctorAvailability) => { if (!availability.id) return; const confirmDelete = window.confirm( `Tem certeza que deseja deletar a disponibilidade de ${weekdayToText( availability.weekday )} (${availability.start_time} - ${ availability.end_time })?\n\n⚠️ Esta ação é permanente e não pode ser desfeita.` ); if (!confirmDelete) return; try { await availabilityService.delete(availability.id); toast.success("Disponibilidade deletada com sucesso"); loadDoctorSchedule(); } catch (error) { console.error("Erro ao deletar disponibilidade:", error); toast.error("Erro ao deletar disponibilidade"); } }; const weekdays = [ { value: "monday", label: "Segunda" }, { value: "tuesday", label: "Terça" }, { value: "wednesday", label: "Quarta" }, { value: "thursday", label: "Quinta" }, { value: "friday", label: "Sexta" }, { value: "saturday", label: "Sábado" }, { value: "sunday", label: "Domingo" }, ]; return (
{/* Header */}

Agenda Médica

Gerencie disponibilidades e exceções

{/* Doctor Selector */}
{/* Calendar */}

{formatMonthYear(currentDate)}

{["Dom", "Seg", "Ter", "Qua", "Qui", "Sex", "Sáb"].map((day) => (
{day}
))} {calendarDays.map((day, index) => (
{day.date.getDate()}
{day.appointments.map((apt, i) => (
{apt.patient_id}
))}
))}
{/* Action Buttons */}
{/* Current Availability */}

Disponibilidade Atual

{loading ? (

Carregando...

) : availabilities.length === 0 ? (

Nenhuma disponibilidade configurada

) : (
{availabilities.map((avail) => (

{weekdayToText(avail.weekday)}

{avail.start_time} - {avail.end_time}

{avail.active ? "Ativo" : "Inativo"}
))}
)}
{/* Availability Dialog */} {showAvailabilityDialog && (

Adicionar Disponibilidade

{weekdays.map((day) => ( ))}
setStartTime(e.target.value)} className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent" />
setEndTime(e.target.value)} className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent" />
setDuration(parseInt(e.target.value))} className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent" />
)} {/* Exception Dialog */} {showExceptionDialog && (

Adicionar Exceção

setExceptionStartDate(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" />
setExceptionEndDate(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" />
setExceptionReason(e.target.value)} placeholder="Ex: Férias anuais, Conferência médica..." className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-orange-500 focus:border-transparent" />
)} {/* Edit Dialog */} {showEditDialog && editingAvailability && (

Editar Disponibilidade

{weekdayToText(editingAvailability.weekday)}

setEditStartTime(e.target.value)} className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent" />
setEditEndTime(e.target.value)} className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent" />
setEditActive(e.target.checked)} className="w-4 h-4 text-green-600 border-gray-300 rounded focus:ring-green-500" />
)}
); }