"use client"; import { CalendarDays, Search } from "lucide-react"; import { type FormEvent, type ReactNode, useEffect, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { TODAY } from "@/components/appointments/appointments-view"; import { Button } from "@/components/ui/button"; import { Calendar } from "@/components/ui/calendar"; import { Dialog, DialogClose, DialogDescription, DialogFooter, DialogHeader, DialogPanel, DialogPopup, DialogTitle, } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { Popover, PopoverPopup, PopoverTrigger } from "@/components/ui/popover"; import { listPatients, type Patient } from "@/lib/patients"; import { notify } from "@/lib/toast"; export type NewAppointment = { fileNumber: string; name: string; initials: string; date: string; // ISO YYYY-MM-DD time: string; type: string; provider: string; }; // Local-date ISO key (avoids UTC drift from toISOString). const keyOf = (d: Date) => `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String( d.getDate(), ).padStart(2, "0")}`; const TYPES = [ "Follow-up", "New patient", "Consultation", "Lab review", "Vaccination", ]; const controlClass = "h-9 w-full rounded-3xl border border-transparent bg-input/50 px-3 text-sm text-foreground outline-none transition-[color,box-shadow] focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/30"; function Field({ label, children }: { label: string; children: ReactNode }) { return ( {label} {children} ); } // Compact "New appointment" dialog. The patient is chosen via a quick search by // name or file number; the rest is the slot. The new entry is handed back to the // page via onAdd, which persists it through the appointments API. export function AddAppointmentDialog({ open, onOpenChange, onAdd, }: { open: boolean; onOpenChange: (open: boolean) => void; onAdd: (appt: NewAppointment) => void; }) { const { t } = useTranslation(); const [patients, setPatients] = useState([]); const [query, setQuery] = useState(""); const [selected, setSelected] = useState(null); const [date, setDate] = useState(() => new Date(`${TODAY}T00:00:00`)); const [dateOpen, setDateOpen] = useState(false); const [time, setTime] = useState("09:00"); const [type, setType] = useState(TYPES[0]); const [provider, setProvider] = useState(""); // Load patients lazily when the dialog opens (for the quick search). useEffect(() => { if (!open) return; let active = true; listPatients() .then((data) => { if (active) setPatients(data); }) .catch(() => { /* search just stays empty */ }); return () => { active = false; }; }, [open]); const matches = useMemo(() => { const q = query.trim().toLowerCase(); if (!q) return []; return patients .filter( (p) => p.name.toLowerCase().includes(q) || p.fileNumber.includes(q), ) .slice(0, 6); }, [patients, query]); const reset = () => { setQuery(""); setSelected(null); setDate(new Date(`${TODAY}T00:00:00`)); setDateOpen(false); setTime("09:00"); setType(TYPES[0]); setProvider(""); }; const submit = (event: FormEvent) => { event.preventDefault(); if (!selected) { notify.error( t("appointments.dialog.pickPatientTitle"), t("appointments.dialog.pickPatientBody"), ); return; } onAdd({ fileNumber: selected.fileNumber, name: selected.name, initials: selected.initials, date: keyOf(date), time, type, provider: provider.trim() || selected.pcp, }); notify.success( t("appointments.dialog.addedTitle"), `${selected.name} ยท ${time}`, ); reset(); onOpenChange(false); }; return ( { onOpenChange(o); if (!o) reset(); }} open={open} > {t("appointments.dialog.title")} {t("appointments.dialog.description")} {selected ? ( {selected.name} {t("appointments.dialog.fileNumber", { number: selected.fileNumber, })} { setSelected(null); setQuery(""); }} size="sm" type="button" variant="ghost" > {t("appointments.dialog.change")} ) : ( setQuery(event.target.value)} placeholder={t("appointments.dialog.searchPlaceholder")} value={query} /> {query.trim() && ( {matches.length === 0 ? ( {t("appointments.dialog.noPatients")} ) : ( matches.map((p) => ( { setSelected(p); setQuery(""); }} type="button" > {p.name} #{p.fileNumber} )) )} )} )} {t("appointments.dialog.date")} {date.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric", })} } /> { if (d) { setDate(d); setDateOpen(false); } }} selected={date} /> setTime(event.target.value)} type="time" value={time} /> setType(event.target.value)} value={type} > {TYPES.map((option) => ( {option} ))} setProvider(event.target.value)} placeholder={t("appointments.dialog.providerPlaceholder")} value={provider} /> }> {t("appointments.dialog.cancel")} {t("appointments.dialog.submit")} ); }
{t("appointments.dialog.noPatients")}