all ui done
This commit is contained in:
@@ -26,11 +26,6 @@ export function Sidebar({ isMobileOpen, setIsMobileOpen }: SidebarProps) {
|
||||
path: "/patients",
|
||||
icon: <Users className="h-5 w-5" />,
|
||||
},
|
||||
{
|
||||
name: "Reports",
|
||||
path: "/reports",
|
||||
icon: <FileText className="h-5 w-5" />,
|
||||
},
|
||||
{
|
||||
name: "Settings",
|
||||
path: "/settings",
|
||||
|
||||
@@ -9,6 +9,9 @@ import {
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { useLocation } from "wouter";
|
||||
|
||||
|
||||
|
||||
interface TopAppBarProps {
|
||||
toggleMobileMenu: () => void;
|
||||
@@ -16,6 +19,7 @@ interface TopAppBarProps {
|
||||
|
||||
export function TopAppBar({ toggleMobileMenu }: TopAppBarProps) {
|
||||
const { user, logoutMutation } = useAuth();
|
||||
const [location, setLocation] = useLocation();
|
||||
|
||||
const handleLogout = () => {
|
||||
logoutMutation.mutate();
|
||||
@@ -68,7 +72,8 @@ export function TopAppBar({ toggleMobileMenu }: TopAppBarProps) {
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem>{user?.username}</DropdownMenuItem>
|
||||
<DropdownMenuItem>My Profile</DropdownMenuItem>
|
||||
<DropdownMenuItem>Account Settings</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => setLocation("/settings")}>
|
||||
Account Settings</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem onClick={handleLogout}>
|
||||
Log out
|
||||
|
||||
@@ -109,9 +109,12 @@ export const AddPatientModal = forwardRef<
|
||||
};
|
||||
|
||||
const handleSaveAndSchedule = () => {
|
||||
setSaveAndSchedule(true);
|
||||
document.querySelector("form")?.requestSubmit();
|
||||
};
|
||||
setSaveAndSchedule(true);
|
||||
if (patientFormRef.current) {
|
||||
patientFormRef.current.submit();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
@@ -137,6 +140,7 @@ export const AddPatientModal = forwardRef<
|
||||
</DialogHeader>
|
||||
|
||||
<PatientForm
|
||||
ref={patientFormRef}
|
||||
patient={patient}
|
||||
extractedInfo={extractedInfo}
|
||||
onSubmit={handleFormSubmit}
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { useEffect, useMemo } from "react";
|
||||
import { forwardRef, useImperativeHandle } from "react";
|
||||
|
||||
const PatientSchema = (
|
||||
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
||||
@@ -64,333 +65,340 @@ export type PatientFormRef = {
|
||||
submit: () => void;
|
||||
};
|
||||
|
||||
export function PatientForm({
|
||||
patient,
|
||||
extractedInfo,
|
||||
onSubmit,
|
||||
}: PatientFormProps) {
|
||||
const { user } = useAuth();
|
||||
const isEditing = !!patient;
|
||||
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 schema = useMemo(
|
||||
() =>
|
||||
isEditing
|
||||
? updatePatientSchema
|
||||
: insertPatientSchema.extend({ userId: z.number().optional() }),
|
||||
[isEditing]
|
||||
);
|
||||
|
||||
const computedDefaultValues = useMemo(() => {
|
||||
if (isEditing && patient) {
|
||||
const { id, userId, createdAt, ...sanitizedPatient } = patient;
|
||||
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 {
|
||||
...sanitizedPatient,
|
||||
dateOfBirth: patient.dateOfBirth
|
||||
? new Date(patient.dateOfBirth).toISOString().split("T")[0]
|
||||
: "",
|
||||
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]);
|
||||
|
||||
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,
|
||||
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
|
||||
? new Date(patient.dateOfBirth).toISOString().split("T")[0]
|
||||
: "",
|
||||
};
|
||||
form.reset(resetValues);
|
||||
}
|
||||
}, [patient, computedDefaultValues, form]);
|
||||
|
||||
const handleSubmit2 = (data: InsertPatient | UpdatePatient) => {
|
||||
onSubmit(data);
|
||||
};
|
||||
}, [isEditing, patient, extractedInfo, user?.id]);
|
||||
|
||||
const form = useForm<InsertPatient | UpdatePatient>({
|
||||
resolver: zodResolver(schema),
|
||||
defaultValues: computedDefaultValues,
|
||||
});
|
||||
|
||||
// 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
|
||||
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>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="dateOfBirth"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Date of Birth *</FormLabel>
|
||||
<FormControl>
|
||||
<Input type="date" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="gender"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Gender *</FormLabel>
|
||||
<Select
|
||||
onValueChange={field.onChange}
|
||||
defaultValue={field.value as string}
|
||||
>
|
||||
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>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select gender" />
|
||||
</SelectTrigger>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
<SelectItem value="male">Male</SelectItem>
|
||||
<SelectItem value="female">Female</SelectItem>
|
||||
<SelectItem value="other">Other</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* 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="insuranceProvider"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Insurance Provider</FormLabel>
|
||||
<Select
|
||||
onValueChange={field.onChange}
|
||||
defaultValue={(field.value as string) || ""}
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="lastName"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Last Name *</FormLabel>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select provider" />
|
||||
</SelectTrigger>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
<SelectItem value="placeholder">
|
||||
Select provider
|
||||
</SelectItem>
|
||||
<SelectItem value="delta">Delta Dental</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>
|
||||
)}
|
||||
/>
|
||||
<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="dateOfBirth"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Date of Birth *</FormLabel>
|
||||
<FormControl>
|
||||
<Input type="date" {...field} />
|
||||
</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>
|
||||
)}
|
||||
/>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
{/* Hidden submit button for form validation */}
|
||||
<button type="submit" className="hidden" aria-hidden="true"></button>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
{/* 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="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="delta">Delta Dental</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>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -56,17 +56,19 @@ export function PatientTable({ patients, onEdit, onView }: PatientTableProps) {
|
||||
};
|
||||
|
||||
const getAvatarColor = (id: number) => {
|
||||
const colors = [
|
||||
"bg-blue-500",
|
||||
"bg-teal-500",
|
||||
"bg-amber-500",
|
||||
"bg-rose-500",
|
||||
"bg-indigo-500",
|
||||
"bg-green-500",
|
||||
"bg-purple-500",
|
||||
];
|
||||
return colors[id % colors.length];
|
||||
};
|
||||
const colorClasses = [
|
||||
"bg-blue-500",
|
||||
"bg-teal-500",
|
||||
"bg-amber-500",
|
||||
"bg-rose-500",
|
||||
"bg-indigo-500",
|
||||
"bg-green-500",
|
||||
"bg-purple-500",
|
||||
];
|
||||
|
||||
// This returns a literal string from above — not a generated string
|
||||
return colorClasses[id % colorClasses.length];
|
||||
};
|
||||
|
||||
const formatDate = (dateString: string | Date) => {
|
||||
const date = new Date(dateString);
|
||||
@@ -108,6 +110,7 @@ export function PatientTable({ patients, onEdit, onView }: PatientTableProps) {
|
||||
{getInitials(patient.firstName, patient.lastName)}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
|
||||
<div className="ml-4">
|
||||
<div className="text-sm font-medium text-gray-900">
|
||||
{patient.firstName} {patient.lastName}
|
||||
|
||||
@@ -39,7 +39,7 @@ const AvatarFallback = React.forwardRef<
|
||||
<AvatarPrimitive.Fallback
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex h-full w-full items-center justify-center rounded-full bg-muted",
|
||||
"flex h-full w-full items-center justify-center rounded-full",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -1,34 +1,37 @@
|
||||
import { LucideIcon } from "lucide-react";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
|
||||
type ColorKey = "primary" | "secondary" | "success" | "warning" | "blue" | "teal" | "green" | "orange" | "rose" | "violet";
|
||||
|
||||
interface StatCardProps {
|
||||
title: string;
|
||||
value: number | string;
|
||||
icon: LucideIcon;
|
||||
color: string;
|
||||
color: ColorKey;
|
||||
}
|
||||
|
||||
const colorMap: Record<ColorKey, { bg: string; text: string }> = {
|
||||
primary: { bg: "bg-primary bg-opacity-10", text: "text-primary" },
|
||||
secondary: { bg: "bg-teal-500 bg-opacity-10", text: "text-teal-500" },
|
||||
success: { bg: "bg-green-500 bg-opacity-10", text: "text-green-500" },
|
||||
warning: { bg: "bg-orange-500 bg-opacity-10", text: "text-orange-500" },
|
||||
blue: { bg: "bg-blue-100", text: "text-blue-600" },
|
||||
teal: { bg: "bg-teal-100", text: "text-teal-600" },
|
||||
green: { bg: "bg-green-100", text: "text-green-600" },
|
||||
orange: { bg: "bg-orange-100", text: "text-orange-600" },
|
||||
rose: { bg: "bg-rose-100", text: "text-rose-600" },
|
||||
violet: { bg: "bg-violet-100", text: "text-violet-600" },
|
||||
};
|
||||
|
||||
export function StatCard({ title, value, icon: Icon, color }: StatCardProps) {
|
||||
const getBackgroundColorClass = (color: string) => {
|
||||
switch(color) {
|
||||
case 'primary':
|
||||
return 'bg-primary bg-opacity-10 text-primary';
|
||||
case 'secondary':
|
||||
return 'bg-teal-500 bg-opacity-10 text-teal-500';
|
||||
case 'success':
|
||||
return 'bg-green-500 bg-opacity-10 text-green-500';
|
||||
case 'warning':
|
||||
return 'bg-orange-500 bg-opacity-10 text-orange-500';
|
||||
default:
|
||||
return 'bg-primary bg-opacity-10 text-primary';
|
||||
}
|
||||
};
|
||||
const { bg, text } = colorMap[color] ?? colorMap.primary;
|
||||
|
||||
return (
|
||||
<Card className="shadow-sm hover:shadow transition-shadow duration-200">
|
||||
<CardContent className="p-4 flex items-center space-x-4">
|
||||
<div className={`rounded-full ${getBackgroundColorClass(color)} p-3`}>
|
||||
<Icon className="h-5 w-5" />
|
||||
<div className={`rounded-full p-3 ${bg} ${text}`}>
|
||||
<Icon className="h-5 w-5" stroke="currentColor" />
|
||||
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-600">{title}</p>
|
||||
|
||||
@@ -127,14 +127,20 @@ export default function AppointmentsPage() {
|
||||
enabled: !!user,
|
||||
});
|
||||
|
||||
const colorMap: Record<string, string> = {
|
||||
"Dr. Kai Gao": "bg-blue-600",
|
||||
"Dr. Jane Smith": "bg-emerald-600",
|
||||
};
|
||||
const colors = [
|
||||
"bg-blue-600",
|
||||
"bg-emerald-600",
|
||||
"bg-purple-600",
|
||||
"bg-pink-600",
|
||||
"bg-yellow-500",
|
||||
"bg-red-600",
|
||||
];
|
||||
|
||||
const staffMembers = staffMembersRaw.map((staff) => ({
|
||||
// Assign colors cycling through the list
|
||||
const staffMembers = staffMembersRaw.map((staff, index) => ({
|
||||
...staff,
|
||||
color: colorMap[staff.name] || "bg-gray-400",
|
||||
|
||||
color: colors[index % colors.length] || "bg-gray-400",
|
||||
}));
|
||||
|
||||
// Generate time slots from 8:00 AM to 6:00 PM in 30-minute increments
|
||||
|
||||
@@ -326,7 +326,7 @@ export default function Dashboard() {
|
||||
title="Total Patients"
|
||||
value={patients.length}
|
||||
icon={Users}
|
||||
color="primary"
|
||||
color="blue"
|
||||
/>
|
||||
<StatCard
|
||||
title="Today's Appointments"
|
||||
@@ -380,7 +380,7 @@ export default function Dashboard() {
|
||||
className="p-4 flex items-center justify-between"
|
||||
>
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="h-10 w-10 rounded-full bg-primary bg-opacity-10 text-primary flex items-center justify-center">
|
||||
<div className="h-10 w-10 rounded-full bg-opacity-10 text-primary flex items-center justify-center">
|
||||
<Clock className="h-5 w-5" />
|
||||
</div>
|
||||
<div>
|
||||
|
||||
@@ -219,109 +219,6 @@ export default function PatientsPage() {
|
||||
});
|
||||
};
|
||||
|
||||
// Process file and extract patient information
|
||||
const handleExtractInfo = async () => {
|
||||
if (!uploadedFile) {
|
||||
toast({
|
||||
title: "No file selected",
|
||||
description: "Please select a file first.",
|
||||
variant: "destructive",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setIsExtracting(true);
|
||||
|
||||
try {
|
||||
// Read the file as base64
|
||||
const reader = new FileReader();
|
||||
|
||||
// Set up a Promise to handle file reading
|
||||
const fileReadPromise = new Promise<string>((resolve, reject) => {
|
||||
reader.onload = (event) => {
|
||||
if (event.target && typeof event.target.result === "string") {
|
||||
resolve(event.target.result);
|
||||
} else {
|
||||
reject(new Error("Failed to read file as base64"));
|
||||
}
|
||||
};
|
||||
|
||||
reader.onerror = () => {
|
||||
reject(new Error("Error reading file"));
|
||||
};
|
||||
|
||||
// Read the file as a data URL (base64)
|
||||
reader.readAsDataURL(uploadedFile);
|
||||
});
|
||||
|
||||
// Get the base64 data
|
||||
const base64Data = await fileReadPromise;
|
||||
|
||||
// Send file to server as base64
|
||||
const response = await fetch("/api/upload-file", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
pdfData: base64Data,
|
||||
filename: uploadedFile.name,
|
||||
}),
|
||||
credentials: "include",
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`Server returned ${response.status}: ${response.statusText}`
|
||||
);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
// Only keep firstName, lastName, dateOfBirth, and insuranceId from the extracted info
|
||||
const simplifiedInfo = {
|
||||
firstName: data.extractedInfo.firstName,
|
||||
lastName: data.extractedInfo.lastName,
|
||||
dateOfBirth: data.extractedInfo.dateOfBirth,
|
||||
insuranceId: data.extractedInfo.insuranceId,
|
||||
};
|
||||
|
||||
setExtractedInfo(simplifiedInfo);
|
||||
|
||||
// Show success message
|
||||
toast({
|
||||
title: "Information Extracted",
|
||||
description:
|
||||
"Basic patient information (name, DOB, ID) has been extracted successfully.",
|
||||
variant: "default",
|
||||
});
|
||||
|
||||
// Open patient form pre-filled with extracted data
|
||||
setCurrentPatient(undefined);
|
||||
|
||||
// Pre-fill the form by opening the modal with the extracted information
|
||||
setTimeout(() => {
|
||||
setIsAddPatientOpen(true);
|
||||
}, 500);
|
||||
} else {
|
||||
throw new Error(data.message || "Failed to extract information");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error extracting information:", error);
|
||||
toast({
|
||||
title: "Error",
|
||||
description:
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Failed to extract information from file",
|
||||
variant: "destructive",
|
||||
});
|
||||
} finally {
|
||||
setIsExtracting(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Filter patients based on search criteria
|
||||
const filteredPatients = useMemo(() => {
|
||||
if (!searchCriteria || !searchCriteria.searchTerm) {
|
||||
@@ -424,7 +321,6 @@ export default function PatientsPage() {
|
||||
<div className="md:col-span-1 flex items-end">
|
||||
<Button
|
||||
className="w-full h-12 gap-2"
|
||||
onClick={handleExtractInfo}
|
||||
disabled={!uploadedFile || isExtracting}
|
||||
>
|
||||
{isExtracting ? (
|
||||
|
||||
Reference in New Issue
Block a user