import { useEffect, useRef, useState } from "react"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { format } from "date-fns"; import { apiRequest } from "@/lib/queryClient"; import { Button } from "@/components/ui/button"; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Clock } from "lucide-react"; import { useQuery } from "@tanstack/react-query"; import { useAuth } from "@/hooks/use-auth"; import { useDebounce } from "use-debounce"; import { Appointment, InsertAppointment, insertAppointmentSchema, Patient, Staff, UpdateAppointment, } from "@repo/db/types"; import { DateInputField } from "@/components/ui/dateInputField"; interface AppointmentFormProps { appointment?: Appointment; patients: Patient[]; onSubmit: (data: InsertAppointment | UpdateAppointment) => void; onDelete?: (id: number) => void; onOpenChange?: (open: boolean) => void; isLoading?: boolean; } export function AppointmentForm({ appointment, patients, onSubmit, onDelete, onOpenChange, isLoading = false, }: AppointmentFormProps) { const { user } = useAuth(); const inputRef = useRef(null); useEffect(() => { const timeout = setTimeout(() => { inputRef.current?.focus(); }, 50); // small delay ensures content is mounted return () => clearTimeout(timeout); }, []); const { data: staffMembersRaw = [] as Staff[], isLoading: isLoadingStaff } = useQuery({ queryKey: ["/api/staffs/"], queryFn: async () => { const res = await apiRequest("GET", "/api/staffs/"); return res.json(); }, enabled: !!user, }); const colorMap: Record = { "Dr. Kai Gao": "bg-blue-600", "Dr. Jane Smith": "bg-emerald-600", }; const staffMembers = staffMembersRaw.map((staff) => ({ ...staff, color: colorMap[staff.name] || "bg-gray-400", })); function parseLocalDate(dateString: string): Date { const parts = dateString.split("-"); if (parts.length !== 3) { return new Date(); } const year = parseInt(parts[0] ?? "", 10); const month = parseInt(parts[1] ?? "", 10); const day = parseInt(parts[2] ?? "", 10); if (isNaN(year) || isNaN(month) || isNaN(day)) { return new Date(); } // Create date at UTC midnight instead of local midnight return new Date(year, month - 1, day); } // Get the stored data from session storage const storedDataString = sessionStorage.getItem("newAppointmentData"); let parsedStoredData = null; // Try to parse it if it exists if (storedDataString) { try { parsedStoredData = JSON.parse(storedDataString); } catch (error) { console.error("Error parsing stored appointment data:", error); } } // Format the date and times for the form const defaultValues: Partial = appointment ? { userId: user?.id, patientId: appointment.patientId, title: appointment.title, date: typeof appointment.date === "string" ? parseLocalDate(appointment.date) : appointment.date, startTime: appointment.startTime || "09:00", // Default "09:00" endTime: appointment.endTime || "09:30", // Default "09:30" type: appointment.type, notes: appointment.notes || "", status: appointment.status || "scheduled", staffId: typeof appointment.staffId === "number" ? appointment.staffId : undefined, } : parsedStoredData ? { userId: user?.id, patientId: Number(parsedStoredData.patientId), date: parsedStoredData.date ? typeof parsedStoredData.date === "string" ? parseLocalDate(parsedStoredData.date) : new Date(parsedStoredData.date) // in case it’s a stringified date or timestamp : new Date(), title: parsedStoredData.title || "", startTime: parsedStoredData.startTime, endTime: parsedStoredData.endTime, type: parsedStoredData.type || "checkup", status: parsedStoredData.status || "scheduled", notes: parsedStoredData.notes || "", staffId: typeof parsedStoredData.staff === "number" ? parsedStoredData.staff : (staffMembers?.[0]?.id ?? undefined), } : { userId: user?.id ?? 0, date: new Date(), title: "", startTime: "09:00", endTime: "09:30", type: "checkup", status: "scheduled", staffId: staffMembers?.[0]?.id ?? undefined, }; const form = useForm({ resolver: zodResolver(insertAppointmentSchema), defaultValues, }); const [searchTerm, setSearchTerm] = useState(""); const [debouncedSearchTerm] = useDebounce(searchTerm, 200); // 1 seconds const [filteredPatients, setFilteredPatients] = useState(patients); useEffect(() => { if (!debouncedSearchTerm.trim()) { setFilteredPatients(patients); } else { const term = debouncedSearchTerm.toLowerCase(); setFilteredPatients( patients.filter((p) => `${p.firstName} ${p.lastName} ${p.phone} ${p.dateOfBirth}` .toLowerCase() .includes(term) ) ); } }, [debouncedSearchTerm, patients]); // Force form field values to update and clean up storage useEffect(() => { if (parsedStoredData) { // Update form field values directly if (parsedStoredData.startTime) { form.setValue("startTime", parsedStoredData.startTime); } if (parsedStoredData.endTime) { form.setValue("endTime", parsedStoredData.endTime); } if (parsedStoredData.staff) { form.setValue("staffId", parsedStoredData.staff); } if (parsedStoredData.date) { const parsedDate = typeof parsedStoredData.date === "string" ? parseLocalDate(parsedStoredData.date) : new Date(parsedStoredData.date); form.setValue("date", parsedDate); } // Clean up session storage sessionStorage.removeItem("newAppointmentData"); } }, [form]); const handleSubmit = (data: InsertAppointment) => { // Make sure patientId is a number const patientId = typeof data.patientId === "string" ? parseInt(data.patientId, 10) : data.patientId; // Get patient name for the title const patient = patients.find((p) => p.id === patientId); const patientName = patient ? `${patient.firstName} ${patient.lastName}` : "Patient"; // Auto-create title if it's empty let title = data.title; if (!title || title.trim() === "") { // Format: "April 19" - just the date title = format(data.date, "MMMM d"); } let notes = data.notes || ""; const selectedStaff = staffMembers.find((staff) => staff.id?.toString() === data.staffId) || staffMembers[0]; if (!selectedStaff) { console.error("No staff selected and no available staff in the list"); return; // Handle this case as well } // If there's no staff information in the notes, add it if (!notes.includes("Appointment with")) { notes = notes ? `${notes}\nAppointment with ${selectedStaff?.name}` : `Appointment with ${selectedStaff?.name}`; } const formattedDate = data.date.toLocaleDateString("en-CA"); onSubmit({ ...data, userId: Number(user?.id), title, notes, patientId, date: formattedDate, startTime: data.startTime, endTime: data.endTime, }); }; return (
{ handleSubmit(data); }, (errors) => { console.error("Validation failed:", errors); } )} className="space-y-6" > ( Patient setSearchTerm(e.target.value)} onKeyDown={(e) => { const navKeys = ["ArrowDown", "ArrowUp", "Enter"]; if (!navKeys.includes(e.key)) { e.stopPropagation(); // Only stop keys that affect select state } }} />
{filteredPatients.length > 0 ? ( filteredPatients.map((patient) => (
{patient.firstName} {patient.lastName} DOB:{" "} {new Date( patient.dateOfBirth ).toLocaleDateString()}{" "} • {patient.phone}
)) ) : (
No patients found
)}
)} /> ( Appointment Title{" "} (optional) )} />
( Start Time
)} /> ( End Time
)} />
( Appointment Type )} /> ( Status )} /> ( Doctor/Hygienist )} /> ( Notes