"use client"; import { useState, useEffect, useRef } from "react"; import { buscarPacientePorId, listarMedicos, buscarPacientesPorMedico, getAvailableSlots, buscarPacientes, listarPacientes, listarDisponibilidades } from "@/lib/api"; 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 } 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 [loadingSlots, setLoadingSlots] = useState(false); const [lockedDurationFromSlot, setLockedDurationFromSlot] = useState(false); // 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}` : '')} )) )} ) : ( )}
{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 ? ( ) : ( )}
{createMode ? ( ) : ( )}
{/* 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.
)}
)}