// UI/UX: adiciona refs e ícones para melhorar acessibilidade e feedback visual import React, { useState, useEffect, useMemo, useRef } from "react"; import { Calendar as CalendarIcon, Clock, Loader2, Stethoscope, X, } from "lucide-react"; import toast from "react-hot-toast"; import { appointmentService, doctorService, patientService, } from "../../services/index"; import type { Patient } from "../../services/patients/types"; import type { Doctor } from "../../services/doctors/types"; import AvailableSlotsPicker from "./AvailableSlotsPicker"; interface Props { isOpen: boolean; onClose: () => void; patientId?: string; // opcional: quando não informado, seleciona paciente no modal patientName?: string; // opcional onSuccess?: () => void; } const ScheduleAppointmentModal: React.FC = ({ isOpen, onClose, patientId, patientName, onSuccess, }) => { const [doctors, setDoctors] = useState([]); const [loadingDoctors, setLoadingDoctors] = useState(false); const [patients, setPatients] = useState([]); const [loadingPatients, setLoadingPatients] = useState(false); const [selectedDoctorId, setSelectedDoctorId] = useState(""); const [selectedDate, setSelectedDate] = useState(""); const [selectedTime, setSelectedTime] = useState(""); const [appointmentType, setAppointmentType] = useState< "presencial" | "telemedicina" >("presencial"); const [reason, setReason] = useState(""); const [loading, setLoading] = useState(false); const [selectedPatientId, setSelectedPatientId] = useState(""); const [selectedPatientName, setSelectedPatientName] = useState(""); // A11y & UX: refs para foco inicial e fechamento via overlay/ESC const overlayRef = useRef(null); const dialogRef = useRef(null); const firstFieldRef = useRef(null); const closeBtnRef = useRef(null); // A11y: IDs para aria-labelledby/aria-describedby const titleId = useMemo( () => `schedule-modal-title-${patientId ?? "novo"}`, [patientId] ); const descId = useMemo( () => `schedule-modal-desc-${patientId ?? "novo"}`, [patientId] ); useEffect(() => { if (isOpen) { loadDoctors(); if (!patientId) { loadPatients(); } else { // Garantir estados internos alinhados com props setSelectedPatientId(patientId); setSelectedPatientName(patientName || ""); } // UX: foco no primeiro campo quando abrir setTimeout(() => firstFieldRef.current?.focus(), 0); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [isOpen]); async function loadDoctors() { setLoadingDoctors(true); try { const doctors = await doctorService.list(); setDoctors(doctors); } catch (error) { console.error("Erro ao carregar médicos:", error); toast.error("Erro ao carregar médicos"); } finally { setLoadingDoctors(false); } } async function loadPatients() { setLoadingPatients(true); try { const patients = await patientService.list(); setPatients(patients); } catch (error) { console.error("Erro ao carregar pacientes:", error); toast.error("Erro ao carregar pacientes"); } finally { setLoadingPatients(false); } } async function handleSubmit(e: React.FormEvent) { e.preventDefault(); const finalPatientId = patientId || selectedPatientId; if ( !selectedDoctorId || !selectedDate || !selectedTime || !finalPatientId ) { toast.error("Preencha médico, data e horário"); return; } setLoading(true); const datetime = `${selectedDate}T${selectedTime}:00`; try { await appointmentService.create({ patient_id: finalPatientId, doctor_id: selectedDoctorId, scheduled_at: datetime, appointment_type: appointmentType, chief_complaint: reason || undefined, }); toast.success("Agendamento criado com sucesso!"); onSuccess?.(); handleClose(); } catch (error) { console.error("Erro ao criar agendamento:", error); toast.error("Erro ao criar agendamento"); } finally { setLoading(false); } } function handleClose() { setSelectedDoctorId(""); setSelectedDate(""); setSelectedTime(""); setAppointmentType("presencial"); setReason(""); setSelectedPatientId(""); setSelectedPatientName(""); onClose(); } if (!isOpen) return null; const selectedDoctor = doctors.find((d) => d.id === selectedDoctorId); const patientPreselected = !!patientId; const effectivePatientName = patientPreselected ? patientName : selectedPatientName || (patients.find((p) => p.id === selectedPatientId)?.full_name ?? ""); // UX: handlers para ESC e clique fora function onKeyDown(e: React.KeyboardEvent) { if (e.key === "Escape") { e.stopPropagation(); handleClose(); } } function onOverlayClick(e: React.MouseEvent) { if (e.target === overlayRef.current) handleClose(); } return (
e.stopPropagation()} >

Selecione o médico, a data, o tipo de consulta e um horário disponível para criar um novo agendamento.

{/* Paciente (apenas quando não veio por props) */} {!patientPreselected && (
{loadingPatients ? ( // Skeleton para carregamento de pacientes
) : ( )}
)} {/* Médico */}
{loadingDoctors ? (
) : ( )} {selectedDoctor && (
CRM: {selectedDoctor.crm}
)}
{/* Data */}
{ setSelectedDate(e.target.value); setSelectedTime(""); // Limpa o horário ao mudar a data }} min={new Date().toISOString().split("T")[0]} className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-600 focus:border-blue-600/40 transition-colors" required />

Selecione uma data para ver os horários disponíveis.

{/* Tipo de Consulta */}

O tipo de consulta pode alterar a disponibilidade de horários.

{/* Horários Disponíveis */} {selectedDoctorId && selectedDate && (
setSelectedTime(time)} /> {selectedTime && (
Horário selecionado:{" "} {selectedTime}
)}
)} {/* Motivo */}