claim page , auto fill field working
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
SelectItem,
|
SelectItem,
|
||||||
@@ -14,103 +14,145 @@ import { Label } from "@/components/ui/label";
|
|||||||
import { X, Calendar as CalendarIcon } from "lucide-react";
|
import { X, Calendar as CalendarIcon } from "lucide-react";
|
||||||
import { useToast } from "@/hooks/use-toast";
|
import { useToast } from "@/hooks/use-toast";
|
||||||
import { Calendar } from "@/components/ui/calendar";
|
import { Calendar } from "@/components/ui/calendar";
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from "@/components/ui/popover";
|
||||||
import { PatientUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas";
|
import { PatientUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas";
|
||||||
import {z} from "zod";
|
import { z } from "zod";
|
||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import { apiRequest } from "@/lib/queryClient";
|
||||||
|
|
||||||
const PatientSchema = (PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).omit({
|
const PatientSchema = (
|
||||||
|
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
||||||
|
).omit({
|
||||||
appointments: true,
|
appointments: true,
|
||||||
});
|
});
|
||||||
type Patient = z.infer<typeof PatientSchema>;
|
type Patient = z.infer<typeof PatientSchema>;
|
||||||
|
|
||||||
interface ClaimFormProps {
|
interface ServiceLine {
|
||||||
patientId: number;
|
procedureCode: string;
|
||||||
appointmentId: number;
|
toothNumber: string;
|
||||||
patientName: string;
|
surface: string;
|
||||||
onClose: () => void;
|
quad: string;
|
||||||
patientData?: Patient;
|
authNo: string;
|
||||||
|
billedAmount: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ClaimForm({
|
interface ClaimFormProps {
|
||||||
patientId,
|
patientId?: number;
|
||||||
appointmentId,
|
extractedData?: Partial<Patient>;
|
||||||
patientName,
|
onSubmit: (claimData: any) => void;
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ClaimForm({
|
||||||
|
patientId,
|
||||||
|
extractedData,
|
||||||
|
onSubmit,
|
||||||
onClose,
|
onClose,
|
||||||
patientData
|
|
||||||
}: ClaimFormProps) {
|
}: ClaimFormProps) {
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const [patient, setPatient] = useState<Patient | null>(null);
|
|
||||||
const [loading, setLoading] = useState(false);
|
// Query patient if patientId provided
|
||||||
const [serviceDateValue, setServiceDateValue] = useState<Date>(new Date());
|
const { data: fetchedPatient, isLoading, error } = useQuery<Patient>({
|
||||||
const [serviceDate, setServiceDate] = useState<string>(format(new Date(), 'MM/dd/yy'));
|
queryKey: ["/api/patients/", patientId],
|
||||||
const [clinicalNotes, setClinicalNotes] = useState<string>('');
|
queryFn: async () => {
|
||||||
|
const res = await apiRequest("GET", `/api/patients/${patientId}`);
|
||||||
// Fetch patient data if not provided
|
if (!res.ok) throw new Error("Failed to fetch patient");
|
||||||
|
return res.json();
|
||||||
|
},
|
||||||
|
enabled: !!patientId,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Patient state - initialize from extractedData or null (new patient)
|
||||||
|
const [patient, setPatient] = useState<Patient | null>(
|
||||||
|
extractedData ? ({ ...extractedData } as Patient) : null
|
||||||
|
);
|
||||||
|
|
||||||
|
// Sync fetched patient when available
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (patientData) {
|
if (fetchedPatient) {
|
||||||
setPatient(patientData);
|
setPatient(fetchedPatient);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
}, [fetchedPatient]);
|
||||||
const fetchPatient = async () => {
|
|
||||||
try {
|
|
||||||
setLoading(true);
|
// Service date state
|
||||||
const response = await fetch(`/api/patients/${patientId}`);
|
const [serviceDateValue, setServiceDateValue] = useState<Date>(new Date());
|
||||||
if (!response.ok) {
|
const [serviceDate, setServiceDate] = useState<string>(format(new Date(), "MM/dd/yy"));
|
||||||
throw new Error("Failed to fetch patient data");
|
|
||||||
}
|
// Clinical notes state
|
||||||
const data = await response.json();
|
const [clinicalNotes, setClinicalNotes] = useState<string>("");
|
||||||
setPatient(data);
|
|
||||||
} catch (error) {
|
// Doctor selection state
|
||||||
console.error("Error fetching patient:", error);
|
const [doctor, setDoctor] = useState("doctor1");
|
||||||
toast({
|
|
||||||
title: "Error",
|
// Service lines state with one empty default line
|
||||||
description: "Failed to load patient information",
|
const [serviceLines, setServiceLines] = useState<ServiceLine[]>([
|
||||||
variant: "destructive",
|
{
|
||||||
});
|
procedureCode: "",
|
||||||
} finally {
|
toothNumber: "",
|
||||||
setLoading(false);
|
surface: "",
|
||||||
|
quad: "",
|
||||||
|
authNo: "",
|
||||||
|
billedAmount: "",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Update a field in serviceLines at index
|
||||||
|
const updateServiceLine = (
|
||||||
|
index: number,
|
||||||
|
field: keyof ServiceLine,
|
||||||
|
value: string
|
||||||
|
) => {
|
||||||
|
setServiceLines((prev) => {
|
||||||
|
const updated = [...prev];
|
||||||
|
if (updated[index]) {
|
||||||
|
updated[index][field] = value;
|
||||||
}
|
}
|
||||||
};
|
return updated;
|
||||||
|
});
|
||||||
if (patientId) {
|
};
|
||||||
fetchPatient();
|
|
||||||
}
|
|
||||||
}, [patientId, patientData, toast]);
|
|
||||||
|
|
||||||
|
// Handle patient field changes (to make inputs controlled and editable)
|
||||||
|
const updatePatientField = (field: keyof Patient, value: any) => {
|
||||||
|
setPatient((prev) => (prev ? { ...prev, [field]: value } : null));
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
// Update service date when calendar date changes
|
// Update service date when calendar date changes
|
||||||
const onServiceDateChange = (date: Date | undefined) => {
|
const onServiceDateChange = (date: Date | undefined) => {
|
||||||
if (date) {
|
if (date) {
|
||||||
setServiceDateValue(date);
|
setServiceDateValue(date);
|
||||||
setServiceDate(format(date, 'MM/dd/yy'));
|
setServiceDate(format(date, "MM/dd/yy"));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Determine patient date of birth format
|
// Determine patient date of birth format
|
||||||
const formatDOB = (dob: string | undefined) => {
|
const formatDOB = (dob: string | undefined) => {
|
||||||
if (!dob) return '';
|
if (!dob) return "";
|
||||||
|
|
||||||
// If already in MM/DD/YYYY format, return as is
|
if (/^\d{2}\/\d{2}\/\d{4}$/.test(dob)) return dob; // already MM/DD/YYYY
|
||||||
if (/^\d{2}\/\d{2}\/\d{4}$/.test(dob)) {
|
|
||||||
return dob;
|
if (/^\d{4}-\d{2}-\d{2}/.test(dob)) {
|
||||||
}
|
const datePart = dob?.split("T")[0]; // safe optional chaining
|
||||||
|
if (!datePart) return "";
|
||||||
// If in YYYY-MM-DD format, convert to MM/DD/YYYY
|
const [year, month, day] = datePart.split("-");
|
||||||
if (/^\d{4}-\d{2}-\d{2}$/.test(dob)) {
|
return `${month}/${day}/${year}`;
|
||||||
const [year, month, day] = dob.split('-');
|
}
|
||||||
return `${month}/${day}/${year}`;
|
|
||||||
}
|
return dob;
|
||||||
|
};
|
||||||
return dob;
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4 overflow-y-auto">
|
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4 overflow-y-auto">
|
||||||
<Card className="w-full max-w-5xl max-h-[90vh] overflow-y-auto bg-white">
|
<Card className="w-full max-w-5xl max-h-[90vh] overflow-y-auto bg-white">
|
||||||
<CardHeader className="flex flex-row items-center justify-between pb-2 border-b">
|
<CardHeader className="flex flex-row items-center justify-between pb-2 border-b">
|
||||||
<CardTitle className="text-xl font-bold">Insurance Claim Form</CardTitle>
|
<CardTitle className="text-xl font-bold">
|
||||||
|
Insurance Claim Form
|
||||||
|
</CardTitle>
|
||||||
<Button variant="ghost" size="icon" onClick={onClose}>
|
<Button variant="ghost" size="icon" onClick={onClose}>
|
||||||
<X className="h-4 w-4" />
|
<X className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -121,52 +163,56 @@ export function ClaimForm({
|
|||||||
<div className="grid grid-cols-4 gap-4">
|
<div className="grid grid-cols-4 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<Label htmlFor="memberId">Member ID</Label>
|
<Label htmlFor="memberId">Member ID</Label>
|
||||||
<Input
|
<Input
|
||||||
id="memberId"
|
id="memberId"
|
||||||
value={patient?.insuranceId || ''}
|
value={patient?.insuranceId || ""}
|
||||||
disabled={loading}
|
onChange={(e) => updatePatientField("insuranceId", e.target.value)}
|
||||||
|
disabled={isLoading}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Label htmlFor="dateOfBirth">Date Of Birth</Label>
|
<Label htmlFor="dateOfBirth">Date Of Birth</Label>
|
||||||
<Input
|
<Input
|
||||||
id="dateOfBirth"
|
id="dateOfBirth"
|
||||||
value={formatDOB(patient?.dateOfBirth)}
|
value={formatDOB(patient?.dateOfBirth)}
|
||||||
disabled={loading}
|
onChange={(e) => updatePatientField("dateOfBirth", e.target.value)}
|
||||||
|
disabled={isLoading}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Label htmlFor="firstName">First Name</Label>
|
<Label htmlFor="firstName">First Name</Label>
|
||||||
<Input
|
<Input
|
||||||
id="firstName"
|
id="firstName"
|
||||||
value={patient?.firstName || ''}
|
value={patient?.firstName || ""}
|
||||||
disabled={loading}
|
onChange={(e) => updatePatientField("firstName", e.target.value)}
|
||||||
|
disabled={isLoading}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Label htmlFor="lastName">Last Name</Label>
|
<Label htmlFor="lastName">Last Name</Label>
|
||||||
<Input
|
<Input
|
||||||
id="lastName"
|
id="lastName"
|
||||||
value={patient?.lastName || ''}
|
value={patient?.lastName || ""}
|
||||||
disabled={loading}
|
onChange={(e) => updatePatientField("lastName", e.target.value)}
|
||||||
|
disabled={isLoading}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{/* Clinical Notes Entry */}
|
{/* Clinical Notes Entry */}
|
||||||
<div className="mb-4 flex items-center gap-2">
|
<div className="mb-4 flex items-center gap-2">
|
||||||
<Label htmlFor="clinicalNotes" className="whitespace-nowrap">Clinical Notes:</Label>
|
<Label htmlFor="clinicalNotes" className="whitespace-nowrap">
|
||||||
<Input
|
Clinical Notes:
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
id="clinicalNotes"
|
id="clinicalNotes"
|
||||||
className="flex-grow"
|
className="flex-grow"
|
||||||
placeholder="Paste clinical notes here"
|
placeholder="Paste clinical notes here"
|
||||||
value={clinicalNotes}
|
value={clinicalNotes}
|
||||||
onChange={(e) => setClinicalNotes(e.target.value)}
|
onChange={(e) => setClinicalNotes(e.target.value)}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (clinicalNotes.trim()) {
|
if (clinicalNotes.trim()) {
|
||||||
@@ -180,7 +226,7 @@ export function ClaimForm({
|
|||||||
toast({
|
toast({
|
||||||
title: "Empty Input",
|
title: "Empty Input",
|
||||||
description: "Please enter clinical notes to extract",
|
description: "Please enter clinical notes to extract",
|
||||||
variant: "destructive"
|
variant: "destructive",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
@@ -191,7 +237,9 @@ export function ClaimForm({
|
|||||||
|
|
||||||
{/* Service Lines */}
|
{/* Service Lines */}
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-xl font-semibold mb-2 text-center">Service Lines</h3>
|
<h3 className="text-xl font-semibold mb-2 text-center">
|
||||||
|
Service Lines
|
||||||
|
</h3>
|
||||||
<div className="flex justify-end items-center mb-2">
|
<div className="flex justify-end items-center mb-2">
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Label className="flex items-center">Service Date</Label>
|
<Label className="flex items-center">Service Date</Label>
|
||||||
@@ -214,8 +262,10 @@ export function ClaimForm({
|
|||||||
/>
|
/>
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
<Label className="flex items-center ml-2">Treating Doctor</Label>
|
<Label className="flex items-center ml-2">
|
||||||
<Select defaultValue="doctor1">
|
Treating Doctor
|
||||||
|
</Label>
|
||||||
|
<Select value={doctor} onValueChange={setDoctor}>
|
||||||
<SelectTrigger className="w-36">
|
<SelectTrigger className="w-36">
|
||||||
<SelectValue placeholder="Select Doctor" />
|
<SelectValue placeholder="Select Doctor" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
@@ -227,50 +277,45 @@ export function ClaimForm({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-6 gap-4 mb-2">
|
<div className="grid grid-cols-6 gap-4 mb-2 font-medium text-sm text-gray-700">
|
||||||
<div>
|
<span>Procedure Code</span>
|
||||||
<Label htmlFor="procedureCode1">Procedure Code</Label>
|
<span>Tooth Number</span>
|
||||||
<Input id="procedureCode1" placeholder="e.g. D0120" />
|
<span>Surface</span>
|
||||||
</div>
|
<span>Quadrant</span>
|
||||||
<div>
|
<span>Auth No.</span>
|
||||||
<Label htmlFor="toothNumber1">Tooth Number</Label>
|
<span>Billed Amount</span>
|
||||||
<Input id="toothNumber1" placeholder="e.g. 14" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Label htmlFor="surface1">Surface</Label>
|
|
||||||
<Input id="surface1" placeholder="e.g. MOD" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Label htmlFor="quad1">Quad</Label>
|
|
||||||
<Select>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue placeholder="Select" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="UR">Upper Right</SelectItem>
|
|
||||||
<SelectItem value="UL">Upper Left</SelectItem>
|
|
||||||
<SelectItem value="LR">Lower Right</SelectItem>
|
|
||||||
<SelectItem value="LL">Lower Left</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Label htmlFor="authNo1">Auth No.</Label>
|
|
||||||
<Input id="authNo1" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Label htmlFor="billedAmount1">Billed Amount</Label>
|
|
||||||
<Input id="billedAmount1" placeholder="$0.00" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Add more service lines - simplified for clarity */}
|
{/* Dynamic Rows */}
|
||||||
{[2, 3, 4, 5].map(i => (
|
{serviceLines.map((line, i) => (
|
||||||
<div key={i} className="grid grid-cols-6 gap-4 mb-2">
|
<div key={i} className="grid grid-cols-6 gap-4 mb-2">
|
||||||
<Input placeholder="Procedure Code" />
|
<Input
|
||||||
<Input placeholder="Tooth Number" />
|
placeholder="e.g. D0120"
|
||||||
<Input placeholder="Surface" />
|
value={line.procedureCode}
|
||||||
<Select>
|
onChange={(e) =>
|
||||||
|
updateServiceLine(i, "procedureCode", e.target.value)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
placeholder="e.g. 14"
|
||||||
|
value={line.toothNumber}
|
||||||
|
onChange={(e) =>
|
||||||
|
updateServiceLine(i, "toothNumber", e.target.value)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
placeholder="e.g. MOD"
|
||||||
|
value={line.surface}
|
||||||
|
onChange={(e) =>
|
||||||
|
updateServiceLine(i, "surface", e.target.value)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Select
|
||||||
|
value={line.quad}
|
||||||
|
onValueChange={(value) =>
|
||||||
|
updateServiceLine(i, "quad", value)
|
||||||
|
}
|
||||||
|
>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue placeholder="Select" />
|
<SelectValue placeholder="Select" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
@@ -281,10 +326,40 @@ export function ClaimForm({
|
|||||||
<SelectItem value="LL">Lower Left</SelectItem>
|
<SelectItem value="LL">Lower Left</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
<Input placeholder="Auth No." />
|
<Input
|
||||||
<Input placeholder="$0.00" />
|
placeholder="e.g. 123456"
|
||||||
|
value={line.authNo}
|
||||||
|
onChange={(e) =>
|
||||||
|
updateServiceLine(i, "authNo", e.target.value)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
placeholder="$0.00"
|
||||||
|
value={line.billedAmount}
|
||||||
|
onChange={(e) =>
|
||||||
|
updateServiceLine(i, "billedAmount", e.target.value)
|
||||||
|
}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={() =>
|
||||||
|
setServiceLines([
|
||||||
|
...serviceLines,
|
||||||
|
{
|
||||||
|
procedureCode: "",
|
||||||
|
toothNumber: "",
|
||||||
|
surface: "",
|
||||||
|
quad: "",
|
||||||
|
authNo: "",
|
||||||
|
billedAmount: "",
|
||||||
|
},
|
||||||
|
])
|
||||||
|
}
|
||||||
|
>
|
||||||
|
+ Add Service Line
|
||||||
|
</Button>
|
||||||
|
|
||||||
<div className="flex gap-2 mt-4">
|
<div className="flex gap-2 mt-4">
|
||||||
<Button variant="outline">Child Prophy Codes</Button>
|
<Button variant="outline">Child Prophy Codes</Button>
|
||||||
@@ -295,7 +370,10 @@ export function ClaimForm({
|
|||||||
|
|
||||||
{/* File Upload Section */}
|
{/* File Upload Section */}
|
||||||
<div className="mt-4 bg-gray-100 p-3 rounded-md">
|
<div className="mt-4 bg-gray-100 p-3 rounded-md">
|
||||||
<p className="text-sm text-gray-500 mb-2">Please note that file types with 4 or more character extensions are not allowed, such as .DOCX, .PPTX, or .XLSX</p>
|
<p className="text-sm text-gray-500 mb-2">
|
||||||
|
Please note that file types with 4 or more character
|
||||||
|
extensions are not allowed, such as .DOCX, .PPTX, or .XLSX
|
||||||
|
</p>
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Label>Select Field:</Label>
|
<Label>Select Field:</Label>
|
||||||
@@ -304,7 +382,9 @@ export function ClaimForm({
|
|||||||
<SelectValue />
|
<SelectValue />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="supportData">Support Data for Claim</SelectItem>
|
<SelectItem value="supportData">
|
||||||
|
Support Data for Claim
|
||||||
|
</SelectItem>
|
||||||
<SelectItem value="xrays">X-Ray Images</SelectItem>
|
<SelectItem value="xrays">X-Ray Images</SelectItem>
|
||||||
<SelectItem value="photos">Clinical Photos</SelectItem>
|
<SelectItem value="photos">Clinical Photos</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
@@ -317,18 +397,24 @@ export function ClaimForm({
|
|||||||
|
|
||||||
{/* Insurance Carriers */}
|
{/* Insurance Carriers */}
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-xl font-semibold mb-4 text-center">Insurance Carriers</h3>
|
<h3 className="text-xl font-semibold mb-4 text-center">
|
||||||
|
Insurance Carriers
|
||||||
|
</h3>
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<Button className="w-32" variant="outline">Delta MA</Button>
|
<Button className="w-32" variant="outline">
|
||||||
<Button className="w-32" variant="outline">MH</Button>
|
Delta MA
|
||||||
<Button className="w-32" variant="outline">Others</Button>
|
</Button>
|
||||||
|
<Button className="w-32" variant="outline">
|
||||||
|
MH
|
||||||
|
</Button>
|
||||||
|
<Button className="w-32" variant="outline">
|
||||||
|
Others
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,20 @@
|
|||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||||
import { TopAppBar } from "@/components/layout/top-app-bar";
|
import { TopAppBar } from "@/components/layout/top-app-bar";
|
||||||
import { Sidebar } from "@/components/layout/sidebar";
|
import { Sidebar } from "@/components/layout/sidebar";
|
||||||
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
|
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
|
||||||
import { ClaimForm } from "@/components/claims/claim-form";
|
import { ClaimForm } from "@/components/claims/claim-form";
|
||||||
import { useToast } from "@/hooks/use-toast";
|
import { useToast } from "@/hooks/use-toast";
|
||||||
import { useAuth } from "@/hooks/use-auth";
|
import { useAuth } from "@/hooks/use-auth";
|
||||||
import { PatientUncheckedCreateInputObjectSchema, AppointmentUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas";
|
import {
|
||||||
import { Plus, FileCheck, CheckCircle, Clock, AlertCircle } from "lucide-react";
|
PatientUncheckedCreateInputObjectSchema,
|
||||||
|
AppointmentUncheckedCreateInputObjectSchema,
|
||||||
|
} from "@repo/db/usedSchemas";
|
||||||
|
import { FileCheck, CheckCircle, Clock, AlertCircle } from "lucide-react";
|
||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
import {z} from "zod";
|
import { z } from "zod";
|
||||||
import { apiRequest } from "@/lib/queryClient";
|
import { apiRequest, queryClient } from "@/lib/queryClient";
|
||||||
|
import { useLocation } from "wouter";
|
||||||
|
|
||||||
//creating types out of schema auto generated.
|
//creating types out of schema auto generated.
|
||||||
type Appointment = z.infer<typeof AppointmentUncheckedCreateInputObjectSchema>;
|
type Appointment = z.infer<typeof AppointmentUncheckedCreateInputObjectSchema>;
|
||||||
@@ -60,40 +64,127 @@ const updatePatientSchema = (
|
|||||||
|
|
||||||
type UpdatePatient = z.infer<typeof updatePatientSchema>;
|
type UpdatePatient = z.infer<typeof updatePatientSchema>;
|
||||||
|
|
||||||
|
function getQueryParams() {
|
||||||
|
const search = window.location.search;
|
||||||
|
const params = new URLSearchParams(search);
|
||||||
|
return {
|
||||||
|
name: params.get("name") || "",
|
||||||
|
memberId: params.get("memberId") || "",
|
||||||
|
dob: params.get("dob") || "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export default function ClaimsPage() {
|
export default function ClaimsPage() {
|
||||||
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
||||||
const [isClaimFormOpen, setIsClaimFormOpen] = useState(false);
|
const [isClaimFormOpen, setIsClaimFormOpen] = useState(false);
|
||||||
const [selectedPatient, setSelectedPatient] = useState<number | null>(null);
|
const [selectedPatient, setSelectedPatient] = useState<number | null>(null);
|
||||||
const [selectedAppointment, setSelectedAppointment] = useState<number | null>(null);
|
const [selectedAppointment, setSelectedAppointment] = useState<number | null>(
|
||||||
|
null
|
||||||
|
);
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
|
const [claimFormData, setClaimFormData] = useState<any>({
|
||||||
|
patientId: null,
|
||||||
|
carrier: "",
|
||||||
|
doctorName: "",
|
||||||
|
serviceDate: "",
|
||||||
|
clinicalNotes: "",
|
||||||
|
serviceLines: [],
|
||||||
|
});
|
||||||
|
|
||||||
// Fetch patients
|
// Fetch patients
|
||||||
const { data: patients = [], isLoading: isLoadingPatients } = useQuery<
|
const { data: patients = [], isLoading: isLoadingPatients } = useQuery<
|
||||||
Patient[]
|
Patient[]
|
||||||
>({
|
>({
|
||||||
queryKey: ["/api/patients/"],
|
queryKey: ["/api/patients/"],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const res = await apiRequest("GET", "/api/patients/");
|
const res = await apiRequest("GET", "/api/patients/");
|
||||||
return res.json();
|
return res.json();
|
||||||
},
|
},
|
||||||
enabled: !!user,
|
enabled: !!user,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Fetch appointments
|
// Fetch appointments
|
||||||
const {
|
const {
|
||||||
data: appointments = [] as Appointment[],
|
data: appointments = [] as Appointment[],
|
||||||
isLoading: isLoadingAppointments,
|
isLoading: isLoadingAppointments,
|
||||||
} = useQuery<Appointment[]>({
|
} = useQuery<Appointment[]>({
|
||||||
queryKey: ["/api/appointments/all"],
|
queryKey: ["/api/appointments/all"],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const res = await apiRequest("GET", "/api/appointments/all");
|
const res = await apiRequest("GET", "/api/appointments/all");
|
||||||
return res.json();
|
return res.json();
|
||||||
},
|
},
|
||||||
enabled: !!user,
|
enabled: !!user,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Add patient mutation
|
||||||
|
const addPatientMutation = useMutation({
|
||||||
|
mutationFn: async (patient: InsertPatient) => {
|
||||||
|
const res = await apiRequest("POST", "/api/patients/", patient);
|
||||||
|
return res.json();
|
||||||
|
},
|
||||||
|
onSuccess: (newPatient) => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["/api/patients/"] });
|
||||||
|
toast({
|
||||||
|
title: "Success",
|
||||||
|
description: "Patient added successfully!",
|
||||||
|
variant: "default",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
toast({
|
||||||
|
title: "Error",
|
||||||
|
description: `Failed to add patient: ${error.message}`,
|
||||||
|
variant: "destructive",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create appointment mutation
|
||||||
|
const createAppointmentMutation = useMutation({
|
||||||
|
mutationFn: async (appointment: InsertAppointment) => {
|
||||||
|
const res = await apiRequest("POST", "/api/appointments/", appointment);
|
||||||
|
return await res.json();
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
toast({
|
||||||
|
title: "Success",
|
||||||
|
description: "Appointment created successfully.",
|
||||||
|
});
|
||||||
|
// Invalidate both appointments and patients queries
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["/api/appointments/all"] });
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["/api/patients/"] });
|
||||||
|
},
|
||||||
|
onError: (error: Error) => {
|
||||||
|
toast({
|
||||||
|
title: "Error",
|
||||||
|
description: `Failed to create appointment: ${error.message}`,
|
||||||
|
variant: "destructive",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const createClaimMutation = useMutation({
|
||||||
|
mutationFn: async (claimData: any) => {
|
||||||
|
const res = await apiRequest("POST", "/api/claims/", claimData);
|
||||||
|
return res.json();
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
toast({
|
||||||
|
title: "Claim submitted successfully",
|
||||||
|
variant: "default",
|
||||||
|
});
|
||||||
|
closeClaim();
|
||||||
|
// optionally refetch claims or appointments if needed
|
||||||
|
},
|
||||||
|
onError: (error: any) => {
|
||||||
|
toast({
|
||||||
|
title: "Error submitting claim",
|
||||||
|
description: error.message,
|
||||||
|
variant: "destructive",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const toggleMobileMenu = () => {
|
const toggleMobileMenu = () => {
|
||||||
setIsMobileMenuOpen(!isMobileMenuOpen);
|
setIsMobileMenuOpen(!isMobileMenuOpen);
|
||||||
@@ -111,100 +202,182 @@ export default function ClaimsPage() {
|
|||||||
setSelectedAppointment(null);
|
setSelectedAppointment(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get unique patients with appointments
|
const { name, memberId, dob } = getQueryParams();
|
||||||
const patientsWithAppointments = appointments.reduce((acc, appointment) => {
|
const prefillClaimForm = (patient: Patient) => {
|
||||||
if (!acc.some(item => item.patientId === appointment.patientId)) {
|
setClaimFormData((prev: any) => ({
|
||||||
const patient = patients.find(p => p.id === appointment.patientId);
|
...prev,
|
||||||
if (patient) {
|
patientId: patient.id,
|
||||||
acc.push({
|
carrier: patient.insuranceProvider || "",
|
||||||
patientId: patient.id,
|
doctorName: user?.username || "",
|
||||||
patientName: `${patient.firstName} ${patient.lastName}`,
|
serviceDate: new Date().toISOString().slice(0, 10),
|
||||||
appointmentId: Number(appointment.id),
|
clinicalNotes: "",
|
||||||
insuranceProvider: patient.insuranceProvider || 'N/A',
|
serviceLines: [],
|
||||||
insuranceId: patient.insuranceId || 'N/A',
|
}));
|
||||||
lastAppointment: String(appointment.date)
|
};
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (memberId && dob) {
|
||||||
|
const matchingPatient = patients.find(
|
||||||
|
(p) =>
|
||||||
|
p.insuranceId?.toLowerCase().trim() === memberId.toLowerCase().trim()
|
||||||
|
);
|
||||||
|
|
||||||
|
if (matchingPatient) {
|
||||||
|
setSelectedPatient(matchingPatient.id);
|
||||||
|
prefillClaimForm(matchingPatient);
|
||||||
|
setIsClaimFormOpen(true);
|
||||||
|
} else {
|
||||||
|
const [firstName, ...rest] = name.trim().split(" ");
|
||||||
|
const lastName = rest.join(" ") || "";
|
||||||
|
|
||||||
|
const newPatient: InsertPatient = {
|
||||||
|
firstName,
|
||||||
|
lastName,
|
||||||
|
dateOfBirth: new Date(dob),
|
||||||
|
gender: "unknown",
|
||||||
|
phone: "000-000-0000",
|
||||||
|
userId: user?.id ?? 1,
|
||||||
|
insuranceId: memberId,
|
||||||
|
};
|
||||||
|
|
||||||
|
addPatientMutation.mutate(newPatient, {
|
||||||
|
onSuccess: (created) => {
|
||||||
|
setSelectedPatient(created.id);
|
||||||
|
prefillClaimForm(created);
|
||||||
|
setIsClaimFormOpen(true);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return acc;
|
}, [memberId, dob, patients]);
|
||||||
}, [] as Array<{
|
|
||||||
patientId: number;
|
function handleClaimSubmit(claimData: any) {
|
||||||
patientName: string;
|
createClaimMutation.mutate(claimData);
|
||||||
appointmentId: number;
|
}
|
||||||
insuranceProvider: string;
|
|
||||||
insuranceId: string;
|
// Get unique patients with appointments
|
||||||
lastAppointment: string;
|
const patientsWithAppointments = appointments.reduce(
|
||||||
}>);
|
(acc, appointment) => {
|
||||||
|
if (!acc.some((item) => item.patientId === appointment.patientId)) {
|
||||||
|
const patient = patients.find((p) => p.id === appointment.patientId);
|
||||||
|
if (patient) {
|
||||||
|
acc.push({
|
||||||
|
patientId: patient.id,
|
||||||
|
patientName: `${patient.firstName} ${patient.lastName}`,
|
||||||
|
appointmentId: Number(appointment.id),
|
||||||
|
insuranceProvider: patient.insuranceProvider || "N/A",
|
||||||
|
insuranceId: patient.insuranceId || "N/A",
|
||||||
|
lastAppointment: String(appointment.date),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
[] as Array<{
|
||||||
|
patientId: number;
|
||||||
|
patientName: string;
|
||||||
|
appointmentId: number;
|
||||||
|
insuranceProvider: string;
|
||||||
|
insuranceId: string;
|
||||||
|
lastAppointment: string;
|
||||||
|
}>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-screen overflow-hidden bg-gray-100">
|
<div className="flex h-screen overflow-hidden bg-gray-100">
|
||||||
<Sidebar isMobileOpen={isMobileMenuOpen} setIsMobileOpen={setIsMobileMenuOpen} />
|
<Sidebar
|
||||||
|
isMobileOpen={isMobileMenuOpen}
|
||||||
|
setIsMobileOpen={setIsMobileMenuOpen}
|
||||||
|
/>
|
||||||
|
|
||||||
<div className="flex-1 flex flex-col overflow-hidden">
|
<div className="flex-1 flex flex-col overflow-hidden">
|
||||||
<TopAppBar toggleMobileMenu={toggleMobileMenu} />
|
<TopAppBar toggleMobileMenu={toggleMobileMenu} />
|
||||||
|
|
||||||
<main className="flex-1 overflow-y-auto p-4">
|
<main className="flex-1 overflow-y-auto p-4">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
<h1 className="text-2xl font-semibold text-gray-800">Insurance Claims</h1>
|
<h1 className="text-2xl font-semibold text-gray-800">
|
||||||
<p className="text-gray-600">Manage and submit insurance claims for patients</p>
|
Insurance Claims
|
||||||
|
</h1>
|
||||||
|
<p className="text-gray-600">
|
||||||
|
Manage and submit insurance claims for patients
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* New Claims Section */}
|
{/* New Claims Section */}
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div className="flex items-center justify-between mb-4">
|
||||||
<div
|
<div
|
||||||
className="flex items-center cursor-pointer group"
|
className="flex items-center cursor-pointer group"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (patientsWithAppointments.length > 0) {
|
if (patientsWithAppointments.length > 0) {
|
||||||
const firstPatient = patientsWithAppointments[0];
|
const firstPatient = patientsWithAppointments[0];
|
||||||
handleNewClaim(Number(firstPatient?.patientId), Number(firstPatient?.appointmentId));
|
handleNewClaim(
|
||||||
|
Number(firstPatient?.patientId),
|
||||||
|
Number(firstPatient?.appointmentId)
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
toast({
|
toast({
|
||||||
title: "No patients available",
|
title: "No patients available",
|
||||||
description: "There are no patients with appointments to create a claim",
|
description:
|
||||||
|
"There are no patients with appointments to create a claim",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<h2 className="text-xl font-medium text-gray-800 group-hover:text-primary">New Claims</h2>
|
<h2 className="text-xl font-medium text-gray-800 group-hover:text-primary">
|
||||||
|
New Claims
|
||||||
|
</h2>
|
||||||
<div className="ml-2 text-primary">
|
<div className="ml-2 text-primary">
|
||||||
<FileCheck className="h-5 w-5" />
|
<FileCheck className="h-5 w-5" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader className="pb-2">
|
<CardHeader className="pb-2">
|
||||||
<CardTitle>Recent Patients for Claims</CardTitle>
|
<CardTitle>Recent Patients for Claims</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
{isLoadingPatients || isLoadingAppointments ? (
|
{isLoadingPatients || isLoadingAppointments ? (
|
||||||
<div className="text-center py-4">Loading patients data...</div>
|
<div className="text-center py-4">
|
||||||
|
Loading patients data...
|
||||||
|
</div>
|
||||||
) : patientsWithAppointments.length > 0 ? (
|
) : patientsWithAppointments.length > 0 ? (
|
||||||
<div className="divide-y">
|
<div className="divide-y">
|
||||||
{patientsWithAppointments.map((item) => (
|
{patientsWithAppointments.map((item) => (
|
||||||
<div
|
<div
|
||||||
key={item.patientId}
|
key={item.patientId}
|
||||||
className="py-4 flex items-center justify-between cursor-pointer hover:bg-gray-50"
|
className="py-4 flex items-center justify-between cursor-pointer hover:bg-gray-50"
|
||||||
onClick={() => handleNewClaim(item.patientId, item.appointmentId)}
|
onClick={() =>
|
||||||
|
handleNewClaim(item.patientId, item.appointmentId)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<h3 className="font-medium">{item.patientName}</h3>
|
<h3 className="font-medium">{item.patientName}</h3>
|
||||||
<div className="text-sm text-gray-500">
|
<div className="text-sm text-gray-500">
|
||||||
<span>Insurance: {item.insuranceProvider === 'delta'
|
<span>
|
||||||
? 'Delta Dental'
|
Insurance:{" "}
|
||||||
: item.insuranceProvider === 'metlife'
|
{item.insuranceProvider === "delta"
|
||||||
? 'MetLife'
|
? "Delta Dental"
|
||||||
: item.insuranceProvider === 'cigna'
|
: item.insuranceProvider === "metlife"
|
||||||
? 'Cigna'
|
? "MetLife"
|
||||||
: item.insuranceProvider === 'aetna'
|
: item.insuranceProvider === "cigna"
|
||||||
? 'Aetna'
|
? "Cigna"
|
||||||
: item.insuranceProvider}</span>
|
: item.insuranceProvider === "aetna"
|
||||||
|
? "Aetna"
|
||||||
|
: item.insuranceProvider}
|
||||||
|
</span>
|
||||||
<span className="mx-2">•</span>
|
<span className="mx-2">•</span>
|
||||||
<span>ID: {item.insuranceId}</span>
|
<span>ID: {item.insuranceId}</span>
|
||||||
<span className="mx-2">•</span>
|
<span className="mx-2">•</span>
|
||||||
<span>Last Visit: {new Date(item.lastAppointment).toLocaleDateString()}</span>
|
<span>
|
||||||
|
Last Visit:{" "}
|
||||||
|
{new Date(
|
||||||
|
item.lastAppointment
|
||||||
|
).toLocaleDateString()}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-primary">
|
<div className="text-primary">
|
||||||
@@ -216,100 +389,30 @@ export default function ClaimsPage() {
|
|||||||
) : (
|
) : (
|
||||||
<div className="text-center py-8">
|
<div className="text-center py-8">
|
||||||
<FileCheck className="h-12 w-12 mx-auto text-gray-400 mb-3" />
|
<FileCheck className="h-12 w-12 mx-auto text-gray-400 mb-3" />
|
||||||
<h3 className="text-lg font-medium">No eligible patients for claims</h3>
|
<h3 className="text-lg font-medium">
|
||||||
|
No eligible patients for claims
|
||||||
|
</h3>
|
||||||
<p className="text-gray-500 mt-1">
|
<p className="text-gray-500 mt-1">
|
||||||
Patients with appointments will appear here for insurance claim processing
|
Patients with appointments will appear here for insurance
|
||||||
|
claim processing
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Old Claims Section */}
|
|
||||||
<div>
|
|
||||||
<div className="flex items-center justify-between mb-4">
|
|
||||||
<h2 className="text-xl font-medium text-gray-800">Old Claims</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Card>
|
|
||||||
<CardHeader className="pb-2">
|
|
||||||
<CardTitle>Submitted Claims History</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
{/* Sample Old Claims */}
|
|
||||||
<div className="divide-y">
|
|
||||||
{patientsWithAppointments.slice(0, 3).map((item, index) => (
|
|
||||||
<div
|
|
||||||
key={`old-claim-${index}`}
|
|
||||||
className="py-4 flex items-center justify-between cursor-pointer hover:bg-gray-50"
|
|
||||||
onClick={() => toast({
|
|
||||||
title: "Claim Details",
|
|
||||||
description: `Viewing details for claim #${2000 + index}`
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<h3 className="font-medium">{item.patientName}</h3>
|
|
||||||
<div className="text-sm text-gray-500">
|
|
||||||
<span>Claim #: {2000 + index}</span>
|
|
||||||
<span className="mx-2">•</span>
|
|
||||||
<span>Submitted: {format(new Date(new Date().setDate(new Date().getDate() - (index * 15))), 'MMM dd, yyyy')}</span>
|
|
||||||
<span className="mx-2">•</span>
|
|
||||||
<span>Amount: ${(Math.floor(Math.random() * 500) + 100).toFixed(2)}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<span className={`px-2 py-1 text-xs font-medium rounded-full ${
|
|
||||||
index === 0 ? 'bg-yellow-100 text-yellow-800' :
|
|
||||||
index === 1 ? 'bg-green-100 text-green-800' :
|
|
||||||
'bg-blue-100 text-blue-800'
|
|
||||||
}`}>
|
|
||||||
{index === 0 ? (
|
|
||||||
<span className="flex items-center">
|
|
||||||
<Clock className="h-3 w-3 mr-1" />
|
|
||||||
Pending
|
|
||||||
</span>
|
|
||||||
) : index === 1 ? (
|
|
||||||
<span className="flex items-center">
|
|
||||||
<CheckCircle className="h-3 w-3 mr-1" />
|
|
||||||
Approved
|
|
||||||
</span>
|
|
||||||
) : (
|
|
||||||
<span className="flex items-center">
|
|
||||||
<AlertCircle className="h-3 w-3 mr-1" />
|
|
||||||
Review
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
|
|
||||||
{patientsWithAppointments.length === 0 && (
|
|
||||||
<div className="text-center py-8">
|
|
||||||
<Clock className="h-12 w-12 mx-auto text-gray-400 mb-3" />
|
|
||||||
<h3 className="text-lg font-medium">No claim history</h3>
|
|
||||||
<p className="text-gray-500 mt-1">
|
|
||||||
Submitted insurance claims will appear here
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Claim Form Modal */}
|
{/* Claim Form Modal */}
|
||||||
{isClaimFormOpen && selectedPatient !== null && selectedAppointment !== null && (
|
{isClaimFormOpen && selectedPatient !== null && (
|
||||||
<ClaimForm
|
<ClaimForm
|
||||||
patientId={selectedPatient}
|
patientId={selectedPatient}
|
||||||
appointmentId={selectedAppointment}
|
|
||||||
patientName="" // Will be loaded by the component
|
|
||||||
onClose={closeClaim}
|
onClose={closeClaim}
|
||||||
|
extractedData={claimFormData}
|
||||||
|
onSubmit={handleClaimSubmit}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useState, useRef } from "react";
|
import { useState, useRef } from "react";
|
||||||
import { useQuery, useMutation } from "@tanstack/react-query";
|
import { useQuery, useMutation } from "@tanstack/react-query";
|
||||||
import { format, parse, parseISO } from "date-fns";
|
import { format, parse, isValid, parseISO } from "date-fns";
|
||||||
import { TopAppBar } from "@/components/layout/top-app-bar";
|
import { TopAppBar } from "@/components/layout/top-app-bar";
|
||||||
import { Sidebar } from "@/components/layout/sidebar";
|
import { Sidebar } from "@/components/layout/sidebar";
|
||||||
import { StatCard } from "@/components/ui/stat-card";
|
import { StatCard } from "@/components/ui/stat-card";
|
||||||
@@ -14,8 +14,8 @@ import { useAuth } from "@/hooks/use-auth";
|
|||||||
import { apiRequest, queryClient } from "@/lib/queryClient";
|
import { apiRequest, queryClient } from "@/lib/queryClient";
|
||||||
import { AppointmentsByDay } from "@/components/analytics/appointments-by-day";
|
import { AppointmentsByDay } from "@/components/analytics/appointments-by-day";
|
||||||
import { NewPatients } from "@/components/analytics/new-patients";
|
import { NewPatients } from "@/components/analytics/new-patients";
|
||||||
import { AppointmentUncheckedCreateInputObjectSchema } from '@repo/db/usedSchemas';
|
import { AppointmentUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas";
|
||||||
import { PatientUncheckedCreateInputObjectSchema } from '@repo/db/usedSchemas';
|
import { PatientUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Users,
|
Users,
|
||||||
@@ -630,15 +630,26 @@ export default function Dashboard() {
|
|||||||
</h4>
|
</h4>
|
||||||
<div className="mt-2 space-y-2">
|
<div className="mt-2 space-y-2">
|
||||||
<p>
|
<p>
|
||||||
<span className="text-gray-500">Date of Birth:</span>{" "}
|
{currentPatient.dateOfBirth ? (
|
||||||
<span className="text-gray-500">Date of Birth:</span>{" "}
|
(() => {
|
||||||
{format(
|
const dobDate = parseISO(currentPatient.dateOfBirth);
|
||||||
parse(
|
return isValid(dobDate) ? (
|
||||||
currentPatient.dateOfBirth,
|
<span>
|
||||||
"yyyy-MM-dd",
|
<span className="text-gray-500">
|
||||||
new Date()
|
Date of Birth:
|
||||||
),
|
</span>{" "}
|
||||||
"PPP"
|
{format(dobDate, "PPP")}
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<span className="text-gray-500">
|
||||||
|
Date of Birth: N/A
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
})()
|
||||||
|
) : (
|
||||||
|
<span className="text-gray-500">
|
||||||
|
Date of Birth: N/A
|
||||||
|
</span>
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import {
|
|||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
import useExtractPdfData from "@/hooks/use-extractPdfData";
|
import useExtractPdfData from "@/hooks/use-extractPdfData";
|
||||||
|
import { useLocation } from "wouter";
|
||||||
|
|
||||||
const PatientSchema = (
|
const PatientSchema = (
|
||||||
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
||||||
@@ -86,9 +87,13 @@ export default function PatientsPage() {
|
|||||||
const [uploadedFile, setUploadedFile] = useState<File | null>(null);
|
const [uploadedFile, setUploadedFile] = useState<File | null>(null);
|
||||||
const [isUploading, setIsUploading] = useState(false);
|
const [isUploading, setIsUploading] = useState(false);
|
||||||
const [isExtracting, setIsExtracting] = useState(false);
|
const [isExtracting, setIsExtracting] = useState(false);
|
||||||
const [formData, setFormData] = useState({ PatientName: "", PatientMemberId: "", PatientDob:"" });
|
const [formData, setFormData] = useState({
|
||||||
const { mutate: extractPdf} = useExtractPdfData();
|
PatientName: "",
|
||||||
|
PatientMemberId: "",
|
||||||
|
PatientDob: "",
|
||||||
|
});
|
||||||
|
const { mutate: extractPdf } = useExtractPdfData();
|
||||||
|
const [location, navigate] = useLocation();
|
||||||
|
|
||||||
// Fetch patients
|
// Fetch patients
|
||||||
const {
|
const {
|
||||||
@@ -244,7 +249,10 @@ export default function PatientsPage() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const isLoading = isLoadingPatients || addPatientMutation.isPending || updatePatientMutation.isPending;
|
const isLoading =
|
||||||
|
isLoadingPatients ||
|
||||||
|
addPatientMutation.isPending ||
|
||||||
|
updatePatientMutation.isPending;
|
||||||
|
|
||||||
// Search handling
|
// Search handling
|
||||||
const handleSearch = (criteria: SearchCriteria) => {
|
const handleSearch = (criteria: SearchCriteria) => {
|
||||||
@@ -291,10 +299,9 @@ export default function PatientsPage() {
|
|||||||
});
|
});
|
||||||
}, [patients, searchCriteria]);
|
}, [patients, searchCriteria]);
|
||||||
|
|
||||||
|
|
||||||
// File upload handling
|
// File upload handling
|
||||||
const handleFileUpload = (file: File) => {
|
const handleFileUpload = (file: File) => {
|
||||||
setIsUploading(true);
|
setIsUploading(true);
|
||||||
setUploadedFile(file);
|
setUploadedFile(file);
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
@@ -307,13 +314,13 @@ export default function PatientsPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// data extraction
|
// data extraction
|
||||||
const handleExtract = () =>{
|
const handleExtract = () => {
|
||||||
setIsExtracting(true);
|
setIsExtracting(true);
|
||||||
|
|
||||||
if (!uploadedFile){
|
if (!uploadedFile) {
|
||||||
return toast({
|
return toast({
|
||||||
title: "Error",
|
title: "Error",
|
||||||
description:"Please upload a PDF",
|
description: "Please upload a PDF",
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -322,17 +329,24 @@ export default function PatientsPage() {
|
|||||||
setIsExtracting(false);
|
setIsExtracting(false);
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: "Success Pdf Data Extracted",
|
title: "Success Pdf Data Extracted",
|
||||||
description: `Name: ${data.name}, Member ID: ${data.memberId}, DOB: ${data.dob}`,
|
description: `Name: ${data.name}, Member ID: ${data.memberId}, DOB: ${data.dob}`,
|
||||||
variant: "default",
|
variant: "default",
|
||||||
});
|
});
|
||||||
|
|
||||||
setFormData({ PatientName: data.name || "", PatientMemberId: data.memberId || "", PatientDob: data.dob || ""});
|
const params = new URLSearchParams({
|
||||||
|
name: data.name,
|
||||||
|
memberId: data.memberId,
|
||||||
|
dob: data.dob,
|
||||||
|
});
|
||||||
|
|
||||||
|
navigate(
|
||||||
|
`/claims?name=${encodeURIComponent(data.name)}&memberId=${data.memberId}&dob=${data.dob}`
|
||||||
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-screen overflow-hidden bg-gray-100">
|
<div className="flex h-screen overflow-hidden bg-gray-100">
|
||||||
<Sidebar
|
<Sidebar
|
||||||
@@ -462,165 +476,177 @@ export default function PatientsPage() {
|
|||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* View Patient Modal */}
|
{/* View Patient Modal */}
|
||||||
<Dialog open={isViewPatientOpen} onOpenChange={setIsViewPatientOpen}>
|
<Dialog
|
||||||
<DialogContent className="sm:max-w-[600px]">
|
open={isViewPatientOpen}
|
||||||
<DialogHeader>
|
onOpenChange={setIsViewPatientOpen}
|
||||||
<DialogTitle>Patient Details</DialogTitle>
|
>
|
||||||
<DialogDescription>
|
<DialogContent className="sm:max-w-[600px]">
|
||||||
Complete information about the patient.
|
<DialogHeader>
|
||||||
</DialogDescription>
|
<DialogTitle>Patient Details</DialogTitle>
|
||||||
</DialogHeader>
|
<DialogDescription>
|
||||||
|
Complete information about the patient.
|
||||||
{currentPatient && (
|
</DialogDescription>
|
||||||
<div className="space-y-4">
|
</DialogHeader>
|
||||||
<div className="flex items-center space-x-4">
|
|
||||||
<div className="h-16 w-16 rounded-full bg-primary text-white flex items-center justify-center text-xl font-medium">
|
{currentPatient && (
|
||||||
{currentPatient.firstName.charAt(0)}
|
<div className="space-y-4">
|
||||||
{currentPatient.lastName.charAt(0)}
|
<div className="flex items-center space-x-4">
|
||||||
</div>
|
<div className="h-16 w-16 rounded-full bg-primary text-white flex items-center justify-center text-xl font-medium">
|
||||||
<div>
|
{currentPatient.firstName.charAt(0)}
|
||||||
<h3 className="text-xl font-semibold">
|
{currentPatient.lastName.charAt(0)}
|
||||||
{currentPatient.firstName} {currentPatient.lastName}
|
</div>
|
||||||
</h3>
|
<div>
|
||||||
<p className="text-gray-500">
|
<h3 className="text-xl font-semibold">
|
||||||
Patient ID: {currentPatient.id.toString().padStart(4, "0")}
|
{currentPatient.firstName} {currentPatient.lastName}
|
||||||
</p>
|
</h3>
|
||||||
</div>
|
<p className="text-gray-500">
|
||||||
</div>
|
Patient ID:{" "}
|
||||||
|
{currentPatient.id.toString().padStart(4, "0")}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 pt-4">
|
</p>
|
||||||
<div>
|
</div>
|
||||||
<h4 className="font-medium text-gray-900">
|
</div>
|
||||||
Personal Information
|
|
||||||
</h4>
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 pt-4">
|
||||||
<div className="mt-2 space-y-2">
|
<div>
|
||||||
<p>
|
<h4 className="font-medium text-gray-900">
|
||||||
<span className="text-gray-500">Date of Birth:</span>{" "}
|
Personal Information
|
||||||
{new Date(
|
</h4>
|
||||||
currentPatient.dateOfBirth
|
<div className="mt-2 space-y-2">
|
||||||
).toLocaleDateString()}
|
<p>
|
||||||
</p>
|
<span className="text-gray-500">
|
||||||
<p>
|
Date of Birth:
|
||||||
<span className="text-gray-500">Gender:</span>{" "}
|
</span>{" "}
|
||||||
{currentPatient.gender.charAt(0).toUpperCase() +
|
{new Date(
|
||||||
currentPatient.gender.slice(1)}
|
currentPatient.dateOfBirth
|
||||||
</p>
|
).toLocaleDateString()}
|
||||||
<p>
|
</p>
|
||||||
<span className="text-gray-500">Status:</span>{" "}
|
<p>
|
||||||
<span
|
<span className="text-gray-500">Gender:</span>{" "}
|
||||||
className={`${
|
{currentPatient.gender.charAt(0).toUpperCase() +
|
||||||
currentPatient.status === "active"
|
currentPatient.gender.slice(1)}
|
||||||
? "text-green-600"
|
</p>
|
||||||
: "text-amber-600"
|
<p>
|
||||||
} font-medium`}
|
<span className="text-gray-500">Status:</span>{" "}
|
||||||
>
|
<span
|
||||||
{currentPatient.status.charAt(0).toUpperCase() +
|
className={`${
|
||||||
currentPatient.status.slice(1)}
|
currentPatient.status === "active"
|
||||||
</span>
|
? "text-green-600"
|
||||||
</p>
|
: "text-amber-600"
|
||||||
</div>
|
} font-medium`}
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h4 className="font-medium text-gray-900">
|
|
||||||
Contact Information
|
|
||||||
</h4>
|
|
||||||
<div className="mt-2 space-y-2">
|
|
||||||
<p>
|
|
||||||
<span className="text-gray-500">Phone:</span>{" "}
|
|
||||||
{currentPatient.phone}
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<span className="text-gray-500">Email:</span>{" "}
|
|
||||||
{currentPatient.email || "N/A"}
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<span className="text-gray-500">Address:</span>{" "}
|
|
||||||
{currentPatient.address ? (
|
|
||||||
<>
|
|
||||||
{currentPatient.address}
|
|
||||||
{currentPatient.city && `, ${currentPatient.city}`}
|
|
||||||
{currentPatient.zipCode &&
|
|
||||||
` ${currentPatient.zipCode}`}
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
"N/A"
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h4 className="font-medium text-gray-900">Insurance</h4>
|
|
||||||
<div className="mt-2 space-y-2">
|
|
||||||
<p>
|
|
||||||
<span className="text-gray-500">Provider:</span>{" "}
|
|
||||||
{currentPatient.insuranceProvider
|
|
||||||
? currentPatient.insuranceProvider === "delta"
|
|
||||||
? "Delta Dental"
|
|
||||||
: currentPatient.insuranceProvider === "metlife"
|
|
||||||
? "MetLife"
|
|
||||||
: currentPatient.insuranceProvider === "cigna"
|
|
||||||
? "Cigna"
|
|
||||||
: currentPatient.insuranceProvider === "aetna"
|
|
||||||
? "Aetna"
|
|
||||||
: currentPatient.insuranceProvider
|
|
||||||
: "N/A"}
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<span className="text-gray-500">ID:</span>{" "}
|
|
||||||
{currentPatient.insuranceId || "N/A"}
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<span className="text-gray-500">Group Number:</span>{" "}
|
|
||||||
{currentPatient.groupNumber || "N/A"}
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<span className="text-gray-500">Policy Holder:</span>{" "}
|
|
||||||
{currentPatient.policyHolder || "Self"}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h4 className="font-medium text-gray-900">
|
|
||||||
Medical Information
|
|
||||||
</h4>
|
|
||||||
<div className="mt-2 space-y-2">
|
|
||||||
<p>
|
|
||||||
<span className="text-gray-500">Allergies:</span>{" "}
|
|
||||||
{currentPatient.allergies || "None reported"}
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<span className="text-gray-500">Medical Conditions:</span>{" "}
|
|
||||||
{currentPatient.medicalConditions || "None reported"}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex justify-end space-x-2 pt-4">
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
onClick={() => setIsViewPatientOpen(false)}
|
|
||||||
>
|
>
|
||||||
Close
|
{currentPatient.status.charAt(0).toUpperCase() +
|
||||||
</Button>
|
currentPatient.status.slice(1)}
|
||||||
<Button
|
</span>
|
||||||
onClick={() => {
|
</p>
|
||||||
setIsViewPatientOpen(false);
|
|
||||||
handleEditPatient(currentPatient);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Edit Patient
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
<div>
|
||||||
|
<h4 className="font-medium text-gray-900">
|
||||||
|
Contact Information
|
||||||
|
</h4>
|
||||||
|
<div className="mt-2 space-y-2">
|
||||||
|
<p>
|
||||||
|
<span className="text-gray-500">Phone:</span>{" "}
|
||||||
|
{currentPatient.phone}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span className="text-gray-500">Email:</span>{" "}
|
||||||
|
{currentPatient.email || "N/A"}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span className="text-gray-500">Address:</span>{" "}
|
||||||
|
{currentPatient.address ? (
|
||||||
|
<>
|
||||||
|
{currentPatient.address}
|
||||||
|
{currentPatient.city &&
|
||||||
|
`, ${currentPatient.city}`}
|
||||||
|
{currentPatient.zipCode &&
|
||||||
|
` ${currentPatient.zipCode}`}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
"N/A"
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h4 className="font-medium text-gray-900">Insurance</h4>
|
||||||
|
<div className="mt-2 space-y-2">
|
||||||
|
<p>
|
||||||
|
<span className="text-gray-500">Provider:</span>{" "}
|
||||||
|
{currentPatient.insuranceProvider
|
||||||
|
? currentPatient.insuranceProvider === "delta"
|
||||||
|
? "Delta Dental"
|
||||||
|
: currentPatient.insuranceProvider === "metlife"
|
||||||
|
? "MetLife"
|
||||||
|
: currentPatient.insuranceProvider === "cigna"
|
||||||
|
? "Cigna"
|
||||||
|
: currentPatient.insuranceProvider ===
|
||||||
|
"aetna"
|
||||||
|
? "Aetna"
|
||||||
|
: currentPatient.insuranceProvider
|
||||||
|
: "N/A"}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span className="text-gray-500">ID:</span>{" "}
|
||||||
|
{currentPatient.insuranceId || "N/A"}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span className="text-gray-500">Group Number:</span>{" "}
|
||||||
|
{currentPatient.groupNumber || "N/A"}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span className="text-gray-500">
|
||||||
|
Policy Holder:
|
||||||
|
</span>{" "}
|
||||||
|
{currentPatient.policyHolder || "Self"}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h4 className="font-medium text-gray-900">
|
||||||
|
Medical Information
|
||||||
|
</h4>
|
||||||
|
<div className="mt-2 space-y-2">
|
||||||
|
<p>
|
||||||
|
<span className="text-gray-500">Allergies:</span>{" "}
|
||||||
|
{currentPatient.allergies || "None reported"}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span className="text-gray-500">
|
||||||
|
Medical Conditions:
|
||||||
|
</span>{" "}
|
||||||
|
{currentPatient.medicalConditions ||
|
||||||
|
"None reported"}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-end space-x-2 pt-4">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => setIsViewPatientOpen(false)}
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setIsViewPatientOpen(false);
|
||||||
|
handleEditPatient(currentPatient);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Edit Patient
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
{/* Add/Edit Patient Modal */}
|
{/* Add/Edit Patient Modal */}
|
||||||
<AddPatientModal
|
<AddPatientModal
|
||||||
ref={addPatientModalRef}
|
ref={addPatientModalRef}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ model User {
|
|||||||
password String
|
password String
|
||||||
patients Patient[]
|
patients Patient[]
|
||||||
appointments Appointment[]
|
appointments Appointment[]
|
||||||
|
claims Claim[]
|
||||||
}
|
}
|
||||||
|
|
||||||
model Patient {
|
model Patient {
|
||||||
@@ -49,6 +50,7 @@ model Patient {
|
|||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
user User @relation(fields: [userId], references: [id])
|
user User @relation(fields: [userId], references: [id])
|
||||||
appointments Appointment[]
|
appointments Appointment[]
|
||||||
|
claims Claim[]
|
||||||
}
|
}
|
||||||
|
|
||||||
model Appointment {
|
model Appointment {
|
||||||
@@ -68,14 +70,44 @@ model Appointment {
|
|||||||
patient Patient @relation(fields: [patientId], references: [id])
|
patient Patient @relation(fields: [patientId], references: [id])
|
||||||
user User @relation(fields: [userId], references: [id])
|
user User @relation(fields: [userId], references: [id])
|
||||||
staff Staff? @relation(fields: [staffId], references: [id])
|
staff Staff? @relation(fields: [staffId], references: [id])
|
||||||
|
claims Claim[]
|
||||||
}
|
}
|
||||||
|
|
||||||
model Staff {
|
model Staff {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
name String
|
name String
|
||||||
email String?
|
email String?
|
||||||
role String // e.g., "Dentist", "Hygienist", "Assistant"
|
role String // e.g., "Dentist", "Hygienist", "Assistant"
|
||||||
phone String?
|
phone String?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
appointments Appointment[]
|
appointments Appointment[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model Claim {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
patientId Int
|
||||||
|
appointmentId Int
|
||||||
|
clinicalNotes String
|
||||||
|
serviceDate DateTime
|
||||||
|
doctorName String
|
||||||
|
carrier String // e.g., "Delta MA"
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
patient Patient @relation(fields: [patientId], references: [id])
|
||||||
|
appointment Appointment @relation(fields: [appointmentId], references: [id])
|
||||||
|
serviceLines ServiceLine[]
|
||||||
|
User User? @relation(fields: [userId], references: [id])
|
||||||
|
userId Int?
|
||||||
|
}
|
||||||
|
|
||||||
|
model ServiceLine {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
claimId Int
|
||||||
|
procedureCode String
|
||||||
|
toothNumber String?
|
||||||
|
surface String?
|
||||||
|
quadrant String?
|
||||||
|
authNumber String?
|
||||||
|
billedAmount Float
|
||||||
|
claim Claim @relation(fields: [claimId], references: [id])
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user