Files
DentalManagementE/apps/Frontend/src/components/patients/patient-form.tsx

407 lines
13 KiB
TypeScript

import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { useAuth } from "@/hooks/use-auth";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { useEffect, useMemo } from "react";
import { forwardRef, useImperativeHandle } from "react";
import { formatLocalDate } from "@/utils/dateUtils";
import {
InsertPatient,
insertPatientSchema,
Patient,
UpdatePatient,
updatePatientSchema,
} from "@repo/db/types";
import { z } from "zod";
import { DateInputField } from "@/components/ui/dateInputField";
interface PatientFormProps {
patient?: Patient;
extractedInfo?: {
firstName: string;
lastName: string;
dateOfBirth: string;
insuranceId: string;
};
onSubmit: (data: InsertPatient | UpdatePatient) => void;
}
export type PatientFormRef = {
submit: () => void;
};
export const PatientForm = forwardRef<PatientFormRef, PatientFormProps>(
({ patient, extractedInfo, onSubmit }, ref) => {
const { user } = useAuth();
const isEditing = !!patient;
const schema = useMemo(
() =>
isEditing
? updatePatientSchema
: insertPatientSchema.extend({ userId: z.number().optional() }),
[isEditing]
);
const computedDefaultValues = useMemo(() => {
if (isEditing && patient) {
const { id, userId, createdAt, ...sanitizedPatient } = patient;
return {
...sanitizedPatient,
dateOfBirth: patient.dateOfBirth
? formatLocalDate(new Date(patient.dateOfBirth))
: "",
};
}
return {
firstName: extractedInfo?.firstName || "",
lastName: extractedInfo?.lastName || "",
dateOfBirth: extractedInfo?.dateOfBirth || "",
gender: "",
phone: "",
email: "",
address: "",
city: "",
zipCode: "",
insuranceProvider: "",
insuranceId: extractedInfo?.insuranceId || "",
groupNumber: "",
policyHolder: "",
allergies: "",
medicalConditions: "",
status: "active",
userId: user?.id,
};
}, [isEditing, patient, extractedInfo, user?.id]);
const form = useForm<InsertPatient | UpdatePatient>({
resolver: zodResolver(schema),
defaultValues: computedDefaultValues,
});
useImperativeHandle(ref, () => ({
submit() {
(
document.getElementById("patient-form") as HTMLFormElement | null
)?.requestSubmit();
},
}));
// Debug form errors
useEffect(() => {
const errors = form.formState.errors;
if (Object.keys(errors).length > 0) {
console.log("❌ Form validation errors:", errors);
}
}, [form.formState.errors]);
useEffect(() => {
if (patient) {
const { id, userId, createdAt, ...sanitizedPatient } = patient;
const resetValues: Partial<Patient> = {
...sanitizedPatient,
dateOfBirth: patient.dateOfBirth
? formatLocalDate(new Date(patient.dateOfBirth))
: "",
};
form.reset(resetValues);
}
}, [patient, computedDefaultValues, form]);
const handleSubmit2 = (data: InsertPatient | UpdatePatient) => {
onSubmit(data);
};
return (
<Form {...form}>
<form
id="patient-form"
key={patient?.id || "new"}
onSubmit={form.handleSubmit((data) => {
handleSubmit2(data);
})}
className="space-y-6"
>
{/* Personal Information */}
<div>
<h4 className="text-md font-medium text-gray-700 mb-3">
Personal Information
</h4>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<FormField
control={form.control}
name="firstName"
render={({ field }) => (
<FormItem>
<FormLabel>First Name *</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="lastName"
render={({ field }) => (
<FormItem>
<FormLabel>Last Name *</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<DateInputField
control={form.control}
name="dateOfBirth"
label="Date of Birth *"
disableFuture
/>
<FormField
control={form.control}
name="gender"
render={({ field }) => (
<FormItem>
<FormLabel>Gender *</FormLabel>
<Select
onValueChange={field.onChange}
defaultValue={field.value as string}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select gender" />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="male">Male</SelectItem>
<SelectItem value="female">Female</SelectItem>
<SelectItem value="other">Other</SelectItem>
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
</div>
</div>
{/* Contact Information */}
<div>
<h4 className="text-md font-medium text-gray-700 mb-3">
Contact Information
</h4>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<FormField
control={form.control}
name="phone"
render={({ field }) => (
<FormItem>
<FormLabel>Phone Number *</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input
type="email"
{...field}
value={field.value || ""}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="address"
render={({ field }) => (
<FormItem className="md:col-span-2">
<FormLabel>Address</FormLabel>
<FormControl>
<Input {...field} value={field.value || ""} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="city"
render={({ field }) => (
<FormItem>
<FormLabel>City</FormLabel>
<FormControl>
<Input {...field} value={field.value || ""} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="zipCode"
render={({ field }) => (
<FormItem>
<FormLabel>ZIP Code</FormLabel>
<FormControl>
<Input {...field} value={field.value || ""} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
</div>
{/* Insurance Information */}
<div>
<h4 className="text-md font-medium text-gray-700 mb-3">
Insurance Information
</h4>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<FormField
control={form.control}
name="status"
render={({ field }) => (
<FormItem>
<FormLabel>Status *</FormLabel>
<Select
onValueChange={field.onChange}
value={field.value}
defaultValue="active"
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select status" />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="active">Active</SelectItem>
<SelectItem value="inactive">Inactive</SelectItem>
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="insuranceProvider"
render={({ field }) => (
<FormItem>
<FormLabel>Insurance Provider</FormLabel>
<Select
onValueChange={field.onChange}
defaultValue={(field.value as string) || ""}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select provider" />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="placeholder">
Select provider
</SelectItem>
<SelectItem value="Mass Health">Mass Health</SelectItem>
<SelectItem value="Delta MA">Delta MA</SelectItem>
<SelectItem value="Metlife">MetLife</SelectItem>
<SelectItem value="Cigna">Cigna</SelectItem>
<SelectItem value="Aetna">Aetna</SelectItem>
<SelectItem value="Other">Other</SelectItem>
<SelectItem value="none">None</SelectItem>
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="insuranceId"
render={({ field }) => (
<FormItem>
<FormLabel>Insurance ID</FormLabel>
<FormControl>
<Input {...field} value={field.value || ""} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="groupNumber"
render={({ field }) => (
<FormItem>
<FormLabel>Group Number</FormLabel>
<FormControl>
<Input {...field} value={field.value || ""} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="policyHolder"
render={({ field }) => (
<FormItem>
<FormLabel>Policy Holder (if not self)</FormLabel>
<FormControl>
<Input {...field} value={field.value || ""} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
</div>
{/* Hidden submit button for form validation */}
<button type="submit" className="hidden" aria-hidden="true"></button>
</form>
</Form>
);
}
);