"use client"; import { useState, useEffect, useRef } from "react"; import { buscarPacientePorId, listarMedicos, buscarPacientesPorMedico, getAvailableSlots, buscarPacientes, listarPacientes, listarDisponibilidades, listarExcecoes } from "@/lib/api"; import { toast } from '@/hooks/use-toast'; import { AlertDialog, AlertDialogContent, AlertDialogHeader, AlertDialogTitle, AlertDialogDescription, AlertDialogFooter, AlertDialogAction, AlertDialogCancel, } from '@/components/ui/alert-dialog'; import { listAssignmentsForPatient } from "@/lib/assignment"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from "@/components/ui/select"; import { Calendar, Search, ChevronDown, X } from "lucide-react"; interface FormData { patientName?: string; cpf?: string; rg?: string; birthDate?: string; phoneCode?: string; phoneNumber?: string; email?: string; convenio?: string; matricula?: string; validade?: string; documentos?: string; professionalName?: string; patientId?: string | null; doctorId?: string | null; unit?: string; appointmentDate?: string; startTime?: string; endTime?: string; requestingProfessional?: string; appointmentType?: string; notes?: string; // API-editable appointment fields status?: string; duration_minutes?: number; chief_complaint?: string; patient_notes?: string; insurance_provider?: string; checked_in_at?: string; // ISO datetime completed_at?: string; // ISO datetime cancelled_at?: string; // ISO datetime cancellation_reason?: string; } interface CalendarRegistrationFormProperties { formData: FormData; onFormChange: (data: FormData) => void; createMode?: boolean; // when true, enable fields needed to create a new appointment } const formatValidityDate = (value: string) => { const cleaned = value.replaceAll(/\D/g, ""); if (cleaned.length > 4) { return `${cleaned.slice(0, 2)}/${cleaned.slice(2, 4)}/${cleaned.slice(4, 8)}`; } if (cleaned.length > 2) { return `${cleaned.slice(0, 2)}/${cleaned.slice(2, 4)}`; } return cleaned; }; export function CalendarRegistrationForm({ formData, onFormChange, createMode = false }: CalendarRegistrationFormProperties) { const [isAdditionalInfoOpen, setIsAdditionalInfoOpen] = useState(false); const [patientDetails, setPatientDetails] = useState(null); const [loadingPatient, setLoadingPatient] = useState(false); const [doctorOptions, setDoctorOptions] = useState([]); const [filteredDoctorOptions, setFilteredDoctorOptions] = useState(null); const [patientOptions, setPatientOptions] = useState([]); const [patientSearch, setPatientSearch] = useState(''); const searchTimerRef = useRef(null); const [loadingDoctors, setLoadingDoctors] = useState(false); const [loadingPatients, setLoadingPatients] = useState(false); const [loadingAssignedDoctors, setLoadingAssignedDoctors] = useState(false); const [loadingPatientsForDoctor, setLoadingPatientsForDoctor] = useState(false); const [availableSlots, setAvailableSlots] = useState>([]); const [availabilityWindows, setAvailabilityWindows] = useState>([]); const [loadingSlots, setLoadingSlots] = useState(false); const [lockedDurationFromSlot, setLockedDurationFromSlot] = useState(false); const [exceptionDialogOpen, setExceptionDialogOpen] = useState(false); const [exceptionDialogMessage, setExceptionDialogMessage] = useState(null); // Helpers to convert between ISO (server) and input[type=datetime-local] value const isoToDatetimeLocal = (iso?: string | null) => { if (!iso) return ""; try { let s = String(iso).trim(); // normalize common variants: space between date/time -> T s = s.replace(" ", "T"); // If no timezone info (no Z or +/-), try treating as UTC by appending Z if (!/[zZ]$/.test(s) && !/[+-]\d{2}:?\d{2}$/.test(s)) { // try parse first; if invalid, append Z const tryParse = Date.parse(s); if (isNaN(tryParse)) { s = s + "Z"; } } const d = new Date(s); if (isNaN(d.getTime())) return ""; const yyyy = d.getFullYear(); const mm = String(d.getMonth() + 1).padStart(2, "0"); const dd = String(d.getDate()).padStart(2, "0"); const hh = String(d.getHours()).padStart(2, "0"); const min = String(d.getMinutes()).padStart(2, "0"); return `${yyyy}-${mm}-${dd}T${hh}:${min}`; } catch (e) { return ""; } }; const datetimeLocalToIso = (value: string) => { if (!value) return null; // value expected: YYYY-MM-DDTHH:MM or with seconds try { // If the browser gives a value without seconds, Date constructor will treat as local when we split const [datePart, timePart] = value.split("T"); if (!datePart || !timePart) return null; const [y, m, d] = datePart.split("-").map((s) => parseInt(s, 10)); const timeParts = timePart.split(":"); const hh = parseInt(timeParts[0], 10); const min = parseInt(timeParts[1] || "0", 10); const sec = parseInt(timeParts[2] || "0", 10); if ([y, m, d, hh, min, sec].some((n) => Number.isNaN(n))) return null; const dt = new Date(y, m - 1, d, hh, min, sec, 0); return dt.toISOString(); } catch (e) { return null; } }; // Automatically fetch patient details when the form receives a patientId useEffect(() => { const maybeId = (formData as any).patientId || (formData as any).patient_id || null; if (!maybeId) { setPatientDetails(null); return; } let mounted = true; setLoadingPatient(true); buscarPacientePorId(maybeId) .then((p) => { if (!mounted) return; setPatientDetails(p); }) .catch((e) => { if (!mounted) return; setPatientDetails({ error: String(e) }); }) .finally(() => { if (!mounted) return; setLoadingPatient(false); }); return () => { mounted = false; }; }, [(formData as any).patientId, (formData as any).patient_id]); // Load doctor suggestions (simple listing) when the component mounts useEffect(() => { let mounted = true; (async () => { setLoadingDoctors(true); try { // listarMedicos returns a paginated list of doctors; request a reasonable limit const docs = await listarMedicos({ limit: 200 }); if (!mounted) return; setDoctorOptions(docs || []); } catch (e) { console.warn('[CalendarRegistrationForm] falha ao carregar médicos', e); } finally { if (!mounted) return; setLoadingDoctors(false); } })(); return () => { mounted = false; }; }, []); // Preload patients so the patient { const val = value || null; const selected = (patientOptions || []).find((p) => p.id === val) || null; onFormChange({ ...formData, patientId: val, patientName: selected ? (selected.full_name || selected.id) : '' }); }} > {loadingPatients || loadingPatientsForDoctor ? ( Carregando pacientes... ) : ( (patientOptions || []).map((p) => ( {(p.full_name || p.nome || p.id) + (p.cpf ? ` - CPF: ${p.cpf}` : '')} )) )} {((formData as any).patientId || (formData as any).patient_id) && ( )} ) : ( )}
{loadingPatient ? (
Carregando dados do paciente...
) : patientDetails ? ( patientDetails.error ? (
Erro ao carregar paciente: {String(patientDetails.error)}
) : (
CPF: {patientDetails.cpf || '-'}
Telefone: {patientDetails.phone_mobile || patientDetails.telefone || '-'}
E-mail: {patientDetails.email || '-'}
Data de nascimento: {patientDetails.birth_date || '-'}
) ) : (
Paciente não vinculado
)}
Para editar os dados do paciente, acesse a ficha do paciente.

Informações do atendimento

{createMode ? (
{((formData as any).doctorId || (formData as any).doctor_id) && ( )}
) : ( )}
{createMode ? ( ) : ( )}
{/* When creating a new appointment from a predefined slot, the end time is derived from the slot's start + duration and therefore cannot be edited. We disable/readOnly the input in create mode when either a slot is selected (startTime corresponds to an availableSlots entry) or the duration was locked from a slot (lockedDurationFromSlot). */} { try { const date = (formData as any).appointmentDate || ''; const time = (formData as any).startTime || ''; if (!date || !time) return false; // Check if startTime matches one of the availableSlots (meaning slot-driven) return (availableSlots || []).some((s) => { try { const d = new Date(s.datetime); const hh = String(d.getHours()).padStart(2, '0'); const mm = String(d.getMinutes()).padStart(2, '0'); const dateOnly = d.toISOString().split('T')[0]; return dateOnly === date && `${hh}:${mm}` === time; } catch (e) { return false; } }); } catch (e) { return false; } })()))} disabled={createMode && (lockedDurationFromSlot || Boolean(((): boolean => { try { const date = (formData as any).appointmentDate || ''; const time = (formData as any).startTime || ''; if (!date || !time) return false; return (availableSlots || []).some((s) => { try { const d = new Date(s.datetime); const hh = String(d.getHours()).padStart(2, '0'); const mm = String(d.getMinutes()).padStart(2, '0'); const dateOnly = d.toISOString().split('T')[0]; return dateOnly === date && `${hh}:${mm}` === time; } catch (e) { return false; } }); } catch (e) { return false; } })()))} />
{/* Profissional solicitante removed per user request */}
{/* Available slots area (createMode only) */} {createMode && (
{loadingSlots ? (
Carregando horários...
) : availableSlots && availableSlots.length ? ( availableSlots.map((s) => { const dt = new Date(s.datetime); const hh = String(dt.getHours()).padStart(2, '0'); const mm = String(dt.getMinutes()).padStart(2, '0'); const label = `${hh}:${mm}`; return ( ); }) ) : (
Nenhum horário disponível para o médico nesta data.
)}
)}
{/* Non-searchable select with allowed values only */}