import { useEffect } 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 { Calendar } from "@/components/ui/calendar"; import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; import { CalendarIcon, Clock } from "lucide-react"; import { cn } from "@/lib/utils"; import { useQuery } from "@tanstack/react-query"; import { useAuth } from "@/hooks/use-auth"; import { AppointmentUncheckedCreateInputObjectSchema, PatientUncheckedCreateInputObjectSchema, StaffUncheckedCreateInputObjectSchema, } from "@repo/db/shared/schemas"; import { z } from "zod"; type Appointment = z.infer; type Staff = z.infer; const insertAppointmentSchema = ( AppointmentUncheckedCreateInputObjectSchema as unknown as z.ZodObject ).omit({ id: true, createdAt: true, userId: true, }); type InsertAppointment = z.infer; const updateAppointmentSchema = ( AppointmentUncheckedCreateInputObjectSchema as unknown as z.ZodObject ) .omit({ id: true, createdAt: true, userId: true, }) .partial(); type UpdateAppointment = z.infer; const PatientSchema = ( PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject ).omit({ appointments: true, }); type Patient = z.infer; interface AppointmentFormProps { appointment?: Appointment; patients: Patient[]; onSubmit: (data: InsertAppointment | UpdateAppointment) => void; isLoading?: boolean; } export function AppointmentForm({ appointment, patients, onSubmit, isLoading = false, }: AppointmentFormProps) { const { user } = useAuth(); 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", })); // 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 ? { patientId: appointment.patientId, title: appointment.title, date: new 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 ? { patientId: Number(parsedStoredData.patientId), date: new Date(parsedStoredData.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), } : { 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, }); // 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) { form.setValue("date", new Date(parsedStoredData.date)); } // 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}`; } // 👇 Use current date if none provided const appointmentDate = data.date ? new Date(data.date) : new Date(); if (isNaN(appointmentDate.getTime())) { console.error("Invalid date:", data.date); return; } onSubmit({ ...data, title, notes, patientId, date: format(appointmentDate, "yyyy-MM-dd"), startTime: data.startTime, endTime: data.endTime, }); }; return (
{ handleSubmit(data); }, (errors) => { console.error("Validation failed:", errors); } )} className="space-y-6" > ( Patient )} /> ( Appointment Title{" "} (optional) )} /> ( Date date < new Date(new Date().setHours(0, 0, 0, 0)) } initialFocus /> )} />
( Start Time
)} /> ( End Time
)} />
( Appointment Type )} /> ( Status )} /> ( Doctor/Hygienist )} /> ( Notes