major functionalities are fixed
This commit is contained in:
@@ -1,4 +1,10 @@
|
||||
import { useState, forwardRef, useImperativeHandle } from "react";
|
||||
import {
|
||||
useState,
|
||||
forwardRef,
|
||||
useImperativeHandle,
|
||||
useEffect,
|
||||
useRef,
|
||||
} from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
@@ -8,38 +14,44 @@ import {
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
} from "@/components/ui/dialog";
|
||||
import { PatientForm } from "./patient-form";
|
||||
import { PatientForm, PatientFormRef } from "./patient-form";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import { X, Calendar } from "lucide-react";
|
||||
import { useLocation } from "wouter";
|
||||
// import { InsertPatient, Patient, UpdatePatient } from "@repo/db/shared/schemas";
|
||||
import { PatientUncheckedCreateInputObjectSchema } from "@repo/db/shared/schemas";
|
||||
import {z} from "zod";
|
||||
import { z } from "zod";
|
||||
|
||||
const PatientSchema = (PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).omit({
|
||||
const PatientSchema = (
|
||||
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
||||
).omit({
|
||||
appointments: true,
|
||||
});
|
||||
type Patient = z.infer<typeof PatientSchema>;
|
||||
|
||||
const insertPatientSchema = (PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).omit({
|
||||
const insertPatientSchema = (
|
||||
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
||||
).omit({
|
||||
id: true,
|
||||
createdAt: true,
|
||||
});
|
||||
type InsertPatient = z.infer<typeof insertPatientSchema>;
|
||||
|
||||
const updatePatientSchema = (PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).omit({
|
||||
id: true,
|
||||
createdAt: true,
|
||||
userId: true,
|
||||
}).partial();
|
||||
const updatePatientSchema = (
|
||||
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
||||
)
|
||||
.omit({
|
||||
id: true,
|
||||
createdAt: true,
|
||||
userId: true,
|
||||
})
|
||||
.partial();
|
||||
|
||||
type UpdatePatient = z.infer<typeof updatePatientSchema>;
|
||||
|
||||
|
||||
interface AddPatientModalProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
onSubmit: (data: InsertPatient | UpdatePatient) => void;
|
||||
onSubmit: (data: InsertPatient | (UpdatePatient & { id?: number })) => void;
|
||||
isLoading: boolean;
|
||||
patient?: Patient;
|
||||
extractedInfo?: {
|
||||
@@ -56,35 +68,49 @@ export type AddPatientModalRef = {
|
||||
navigateToSchedule: (patientId: number) => void;
|
||||
};
|
||||
|
||||
export const AddPatientModal = forwardRef<AddPatientModalRef, AddPatientModalProps>(function AddPatientModal(props, ref) {
|
||||
const { open, onOpenChange, onSubmit, isLoading, patient, extractedInfo } = props;
|
||||
export const AddPatientModal = forwardRef<
|
||||
AddPatientModalRef,
|
||||
AddPatientModalProps
|
||||
>(function AddPatientModal(props, ref) {
|
||||
const { open, onOpenChange, onSubmit, isLoading, patient, extractedInfo } =
|
||||
props;
|
||||
const { toast } = useToast();
|
||||
const [formData, setFormData] = useState<InsertPatient | UpdatePatient | null>(null);
|
||||
const [formData, setFormData] = useState<
|
||||
InsertPatient | UpdatePatient | null
|
||||
>(null);
|
||||
const isEditing = !!patient;
|
||||
const [, navigate] = useLocation();
|
||||
const [saveAndSchedule, setSaveAndSchedule] = useState(false);
|
||||
const patientFormRef = useRef<PatientFormRef>(null); // Ref for PatientForm
|
||||
|
||||
// Set up the imperativeHandle to expose functionality to the parent component
|
||||
useEffect(() => {
|
||||
if (isEditing && patient) {
|
||||
const { id, userId, createdAt, ...sanitized } = patient;
|
||||
setFormData(sanitized); // Update the form data with the patient data for editing
|
||||
} else {
|
||||
setFormData(null); // Reset form data when not editing
|
||||
}
|
||||
}, [isEditing, patient]);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
shouldSchedule: saveAndSchedule,
|
||||
navigateToSchedule: (patientId: number) => {
|
||||
navigate(`/appointments?newPatient=${patientId}`);
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
const handleFormSubmit = (data: InsertPatient | UpdatePatient) => {
|
||||
setFormData(data);
|
||||
onSubmit(data);
|
||||
if (patient && patient.id) {
|
||||
onSubmit({ ...data, id: patient.id });
|
||||
} else {
|
||||
onSubmit(data);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const handleSaveAndSchedule = () => {
|
||||
setSaveAndSchedule(true);
|
||||
if (formData) {
|
||||
onSubmit(formData);
|
||||
} else {
|
||||
// Trigger form validation by clicking the hidden submit button
|
||||
document.querySelector('form')?.requestSubmit();
|
||||
}
|
||||
document.querySelector("form")?.requestSubmit();
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -117,41 +143,41 @@ export const AddPatientModal = forwardRef<AddPatientModalRef, AddPatientModalPro
|
||||
/>
|
||||
|
||||
<DialogFooter className="mt-6">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => onOpenChange(false)}
|
||||
>
|
||||
<Button variant="outline" onClick={() => onOpenChange(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
|
||||
|
||||
{!isEditing && (
|
||||
<Button
|
||||
variant="outline"
|
||||
className="gap-1"
|
||||
onClick={handleSaveAndSchedule}
|
||||
onClick={() => {
|
||||
handleSaveAndSchedule();
|
||||
}}
|
||||
disabled={isLoading}
|
||||
>
|
||||
<Calendar className="h-4 w-4" />
|
||||
Save & Schedule
|
||||
</Button>
|
||||
)}
|
||||
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
form="patient-form"
|
||||
onClick={() => {
|
||||
if (formData) {
|
||||
onSubmit(formData);
|
||||
} else {
|
||||
// Trigger form validation by clicking the hidden submit button
|
||||
document.querySelector('form')?.requestSubmit();
|
||||
if (patientFormRef.current) {
|
||||
patientFormRef.current.submit();
|
||||
}
|
||||
}}
|
||||
disabled={isLoading}
|
||||
>
|
||||
{isLoading
|
||||
? isEditing ? "Updating..." : "Saving..."
|
||||
: isEditing ? "Update Patient" : "Save Patient"
|
||||
}
|
||||
{isLoading
|
||||
? patient
|
||||
? "Updating..."
|
||||
: "Saving..."
|
||||
: patient
|
||||
? "Update Patient"
|
||||
: "Save Patient"}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
||||
@@ -2,7 +2,6 @@ import { useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { z } from "zod";
|
||||
import { PatientUncheckedCreateInputObjectSchema } from "@repo/db/shared/schemas";
|
||||
// import { insertPatientSchema, InsertPatient, Patient, updatePatientSchema, UpdatePatient } from "@repo/db/shared/schemas";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import {
|
||||
Form,
|
||||
@@ -13,7 +12,6 @@ import {
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
@@ -21,27 +19,36 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { useEffect, useMemo } from "react";
|
||||
|
||||
const PatientSchema = (PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).omit({
|
||||
const PatientSchema = (
|
||||
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
||||
).omit({
|
||||
appointments: true,
|
||||
});
|
||||
type Patient = z.infer<typeof PatientSchema>;
|
||||
|
||||
const insertPatientSchema = (PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).omit({
|
||||
id: true,
|
||||
createdAt: true,
|
||||
});
|
||||
type InsertPatient = z.infer<typeof insertPatientSchema>;
|
||||
|
||||
const updatePatientSchema = (PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).omit({
|
||||
const insertPatientSchema = (
|
||||
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
||||
).omit({
|
||||
id: true,
|
||||
createdAt: true,
|
||||
userId: true,
|
||||
}).partial();
|
||||
});
|
||||
type InsertPatient = z.infer<typeof insertPatientSchema>;
|
||||
|
||||
const updatePatientSchema = (
|
||||
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
||||
)
|
||||
.omit({
|
||||
id: true,
|
||||
createdAt: true,
|
||||
userId: true,
|
||||
})
|
||||
.partial();
|
||||
|
||||
type UpdatePatient = z.infer<typeof updatePatientSchema>;
|
||||
|
||||
|
||||
interface PatientFormProps {
|
||||
patient?: Patient;
|
||||
extractedInfo?: {
|
||||
@@ -53,50 +60,103 @@ interface PatientFormProps {
|
||||
onSubmit: (data: InsertPatient | UpdatePatient) => void;
|
||||
}
|
||||
|
||||
export function PatientForm({ patient, extractedInfo, onSubmit }: PatientFormProps) {
|
||||
export type PatientFormRef = {
|
||||
submit: () => void;
|
||||
};
|
||||
|
||||
export function PatientForm({
|
||||
patient,
|
||||
extractedInfo,
|
||||
onSubmit,
|
||||
}: PatientFormProps) {
|
||||
const { user } = useAuth();
|
||||
const isEditing = !!patient;
|
||||
|
||||
const schema = isEditing ? updatePatientSchema : insertPatientSchema.extend({
|
||||
userId: z.number().optional(),
|
||||
});
|
||||
|
||||
// Merge extracted info into default values if available
|
||||
const defaultValues = {
|
||||
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,
|
||||
};
|
||||
|
||||
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
|
||||
? new Date(patient.dateOfBirth).toISOString().split("T")[0]
|
||||
: "",
|
||||
};
|
||||
}
|
||||
|
||||
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: patient || defaultValues,
|
||||
defaultValues: computedDefaultValues,
|
||||
});
|
||||
|
||||
const handleSubmit = (data: InsertPatient | UpdatePatient) => {
|
||||
// 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
|
||||
? new Date(patient.dateOfBirth).toISOString().split("T")[0]
|
||||
: "",
|
||||
};
|
||||
form.reset(resetValues);
|
||||
}
|
||||
}, [patient, computedDefaultValues, form]);
|
||||
|
||||
const handleSubmit2 = (data: InsertPatient | UpdatePatient) => {
|
||||
onSubmit(data);
|
||||
};
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(handleSubmit)} className="space-y-6">
|
||||
<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>
|
||||
<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}
|
||||
@@ -111,7 +171,7 @@ export function PatientForm({ patient, extractedInfo, onSubmit }: PatientFormPro
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="lastName"
|
||||
@@ -125,7 +185,7 @@ export function PatientForm({ patient, extractedInfo, onSubmit }: PatientFormPro
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="dateOfBirth"
|
||||
@@ -139,15 +199,15 @@ export function PatientForm({ patient, extractedInfo, onSubmit }: PatientFormPro
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="gender"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Gender *</FormLabel>
|
||||
<Select
|
||||
onValueChange={field.onChange}
|
||||
<Select
|
||||
onValueChange={field.onChange}
|
||||
defaultValue={field.value as string}
|
||||
>
|
||||
<FormControl>
|
||||
@@ -167,10 +227,12 @@ export function PatientForm({ patient, extractedInfo, onSubmit }: PatientFormPro
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{/* Contact Information */}
|
||||
<div>
|
||||
<h4 className="text-md font-medium text-gray-700 mb-3">Contact Information</h4>
|
||||
<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}
|
||||
@@ -185,7 +247,7 @@ export function PatientForm({ patient, extractedInfo, onSubmit }: PatientFormPro
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="email"
|
||||
@@ -193,13 +255,13 @@ export function PatientForm({ patient, extractedInfo, onSubmit }: PatientFormPro
|
||||
<FormItem>
|
||||
<FormLabel>Email</FormLabel>
|
||||
<FormControl>
|
||||
<Input type="email" {...field} value={field.value || ''} />
|
||||
<Input type="email" {...field} value={field.value || ""} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="address"
|
||||
@@ -207,13 +269,13 @@ export function PatientForm({ patient, extractedInfo, onSubmit }: PatientFormPro
|
||||
<FormItem className="md:col-span-2">
|
||||
<FormLabel>Address</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} value={field.value || ''} />
|
||||
<Input {...field} value={field.value || ""} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="city"
|
||||
@@ -221,13 +283,13 @@ export function PatientForm({ patient, extractedInfo, onSubmit }: PatientFormPro
|
||||
<FormItem>
|
||||
<FormLabel>City</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} value={field.value || ''} />
|
||||
<Input {...field} value={field.value || ""} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="zipCode"
|
||||
@@ -235,7 +297,7 @@ export function PatientForm({ patient, extractedInfo, onSubmit }: PatientFormPro
|
||||
<FormItem>
|
||||
<FormLabel>ZIP Code</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} value={field.value || ''} />
|
||||
<Input {...field} value={field.value || ""} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -243,10 +305,12 @@ export function PatientForm({ patient, extractedInfo, onSubmit }: PatientFormPro
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{/* Insurance Information */}
|
||||
<div>
|
||||
<h4 className="text-md font-medium text-gray-700 mb-3">Insurance Information</h4>
|
||||
<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}
|
||||
@@ -254,9 +318,9 @@ export function PatientForm({ patient, extractedInfo, onSubmit }: PatientFormPro
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Insurance Provider</FormLabel>
|
||||
<Select
|
||||
onValueChange={field.onChange}
|
||||
defaultValue={field.value as string || ''}
|
||||
<Select
|
||||
onValueChange={field.onChange}
|
||||
defaultValue={(field.value as string) || ""}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
@@ -264,7 +328,9 @@ export function PatientForm({ patient, extractedInfo, onSubmit }: PatientFormPro
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
<SelectItem value="placeholder">Select provider</SelectItem>
|
||||
<SelectItem value="placeholder">
|
||||
Select provider
|
||||
</SelectItem>
|
||||
<SelectItem value="delta">Delta Dental</SelectItem>
|
||||
<SelectItem value="metlife">MetLife</SelectItem>
|
||||
<SelectItem value="cigna">Cigna</SelectItem>
|
||||
@@ -277,7 +343,7 @@ export function PatientForm({ patient, extractedInfo, onSubmit }: PatientFormPro
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="insuranceId"
|
||||
@@ -285,13 +351,13 @@ export function PatientForm({ patient, extractedInfo, onSubmit }: PatientFormPro
|
||||
<FormItem>
|
||||
<FormLabel>Insurance ID</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} value={field.value || ''} />
|
||||
<Input {...field} value={field.value || ""} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="groupNumber"
|
||||
@@ -299,13 +365,13 @@ export function PatientForm({ patient, extractedInfo, onSubmit }: PatientFormPro
|
||||
<FormItem>
|
||||
<FormLabel>Group Number</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} value={field.value || ''} />
|
||||
<Input {...field} value={field.value || ""} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="policyHolder"
|
||||
@@ -313,7 +379,7 @@ export function PatientForm({ patient, extractedInfo, onSubmit }: PatientFormPro
|
||||
<FormItem>
|
||||
<FormLabel>Policy Holder (if not self)</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} value={field.value || ''} />
|
||||
<Input {...field} value={field.value || ""} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -321,7 +387,7 @@ export function PatientForm({ patient, extractedInfo, onSubmit }: PatientFormPro
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{/* Hidden submit button for form validation */}
|
||||
<button type="submit" className="hidden" aria-hidden="true"></button>
|
||||
</form>
|
||||
|
||||
Reference in New Issue
Block a user