'use client'; import { useEffect, useId, useState } from 'react'; import { useTranslations } from 'next-intl'; import { Button } from '@/components/ui/shared/Button'; import { DialogCloseButton } from '@/components/ui/shared/DialogCloseButton'; import { ResponsiveDialogOverlay, ResponsiveDialogPanel } from '@/components/ui/shared/ResponsiveDialog'; import { Dropdown } from '@/components/ui/shared/Dropdown'; import { TimeStepInput } from '@/components/ui/shared/TimeStepInput'; import type { AppointmentPurpose, AppointmentRecord } from '@/types/appointment'; import type { TreatmentCatalogEntry } from '@/types/treatment-catalog'; import { DROPDOWN_OPTION_BG, treatmentTypeColor, } from '@/components/shared/treatmentTypeDisplay'; import type { Patient } from '@/types/patient'; import { PatientSearchCombobox } from '@/components/ui/patient/PatientSearchCombobox'; import { usePatientSearchQuery } from '@/lib/hooks/usePatientSearchQuery'; import { combineLocalDateAndTime, compareLocalDayStart, formatTimeForInput, isSameLocalCalendarDay, } from '@/components/appointments/appointmentTime'; const DEFAULT_DURATION_MINUTES = 30; interface AppointmentBookingModalProps { open: boolean; scheduleDate: Date; /** Pre-selected patient from the page sidebar (optional). */ initialPatient?: Patient; onPatientChange?: (patient: Patient | undefined) => void; canAddPatient?: boolean; onAddPatient?: () => void; providerUserId: string | null; providerName: string; initialStartMinute: number; onClose: () => void; onSubmit: (payload: { patientId: string; providerUserId: string; startAt: string; endAt: string; purpose: AppointmentPurpose; }) => Promise; treatmentCatalog: TreatmentCatalogEntry[]; editingAppointment?: AppointmentRecord | null; loading?: boolean; canDelete?: boolean; onDelete?: () => void | Promise; deleting?: boolean; } function patientFromRecord( patient: AppointmentRecord['patient'], ): Pick { return { id: patient.id, firstName: patient.firstName, lastName: patient.lastName, mobile: patient.mobile, }; } export function AppointmentBookingModal({ open, scheduleDate, initialPatient, onPatientChange, canAddPatient = false, onAddPatient, providerUserId, providerName, initialStartMinute, onClose, onSubmit, treatmentCatalog, editingAppointment = null, loading = false, canDelete = false, onDelete, deleting = false, }: AppointmentBookingModalProps) { const t = useTranslations('appointments'); const tCommon = useTranslations('common'); const startInputId = useId(); const endInputId = useId(); const defaultPurpose = treatmentCatalog[0]?.code ?? ''; const [startTime, setStartTime] = useState('09:00'); const [endTime, setEndTime] = useState('09:30'); const [purpose, setPurpose] = useState(defaultPurpose); const [selectedPatient, setSelectedPatient] = useState< Pick | null >(null); const [error, setError] = useState(''); const { search, setSearch, patients, loading: loadingPatients } = usePatientSearchQuery(open); const patientLocked = Boolean(editingAppointment?.hasTreatment); const purposeIndex = treatmentCatalog.findIndex((e) => e.code === purpose); const purposeTextColor = treatmentTypeColor(purpose, purposeIndex < 0 ? 0 : purposeIndex); useEffect(() => { if (!open) { return; } setSearch(''); if (editingAppointment) { const start = new Date(editingAppointment.startAt); const end = new Date(editingAppointment.endAt); setStartTime(formatTimeForInput(start)); setEndTime(formatTimeForInput(end)); setPurpose(editingAppointment.purpose || defaultPurpose); setSelectedPatient(patientFromRecord(editingAppointment.patient)); } else { const start = new Date( scheduleDate.getFullYear(), scheduleDate.getMonth(), scheduleDate.getDate(), Math.floor(initialStartMinute / 60), initialStartMinute % 60, 0, 0, ); const endMinute = Math.min( initialStartMinute + DEFAULT_DURATION_MINUTES, 24 * 60 - 1, ); const end = new Date( scheduleDate.getFullYear(), scheduleDate.getMonth(), scheduleDate.getDate(), Math.floor(endMinute / 60), endMinute % 60, 0, 0, ); setStartTime(formatTimeForInput(start)); setEndTime(formatTimeForInput(end)); setPurpose(defaultPurpose); setSelectedPatient( initialPatient ? { id: initialPatient.id, firstName: initialPatient.firstName, lastName: initialPatient.lastName, mobile: initialPatient.mobile, } : null, ); } setError(''); }, [ open, scheduleDate, initialStartMinute, editingAppointment?.id, defaultPurpose, initialPatient?.id, setSearch, ]); if (!open || !providerUserId) { return null; } function selectPatient(patient: Patient) { if (patientLocked) { return; } const next = { id: patient.id, firstName: patient.firstName, lastName: patient.lastName, mobile: patient.mobile, }; setSelectedPatient(next); onPatientChange?.(patient); if (error === t('errorSelectPatient')) { setError(''); } } async function handleSubmit() { setError(''); if (!providerUserId) { return; } if (!selectedPatient?.id) { setError(t('errorSelectPatient')); return; } const startAt = combineLocalDateAndTime(scheduleDate, startTime); const endAt = combineLocalDateAndTime(scheduleDate, endTime); if (endAt <= startAt) { setError(t('errorEndAfterStart')); return; } const now = new Date(); if ( !editingAppointment && isSameLocalCalendarDay(scheduleDate, now) && startAt.getTime() < now.getTime() ) { setError(t('errorPastSchedule')); return; } if (!editingAppointment && compareLocalDayStart(scheduleDate, now) < 0) { setError(t('errorPastViewOnly')); return; } await onSubmit({ patientId: selectedPatient.id, providerUserId, startAt: startAt.toISOString(), endAt: endAt.toISOString(), purpose, }); } const selectedForDisplay = selectedPatient ? ({ ...selectedPatient, isActive: true, createdAt: '', updatedAt: '', } satisfies Patient) : null; return (

{editingAppointment ? t('editTitle') : t('newTitle')}

{t('providerLabel')}{' '} {providerName}

setPurpose(e.target.value)} style={{ color: purposeTextColor }} > {treatmentCatalog.map((entry, index) => ( ))} {error &&

{error}

} {editingAppointment?.hasTreatment ? (

{t('deleteBlockedHint')}

) : null}
{editingAppointment && canDelete && onDelete ? ( ) : null}
); }