checkpoint claimpage

This commit is contained in:
2025-07-28 14:40:01 +05:30
parent af1b0f8d70
commit d9ce19df80
3 changed files with 167 additions and 236 deletions

View File

@@ -108,7 +108,6 @@ interface ClaimFormData {
interface ClaimFormProps {
patientId: number;
extractedData?: Partial<Patient>;
onSubmit: (data: ClaimFormData) => Promise<Claim>;
onHandleAppointmentSubmit: (
appointmentData: InsertAppointment | UpdateAppointment
@@ -123,7 +122,6 @@ type Staff = z.infer<typeof StaffUncheckedCreateInputObjectSchema>;
export function ClaimForm({
patientId,
extractedData,
onHandleAppointmentSubmit,
onHandleUpdatePatient,
onHandleForMHSelenium,
@@ -133,10 +131,7 @@ export function ClaimForm({
const { toast } = useToast();
const { user } = useAuth();
// Patient state - initialize from extractedData (if given ) or null (new patient)
const [patient, setPatient] = useState<Patient | null>(
extractedData ? ({ ...extractedData } as Patient) : null
);
const [patient, setPatient] = useState<Patient | null>(null);
// Query patient based on given patient id
const {
@@ -185,14 +180,6 @@ export function ClaimForm({
const [serviceDate, setServiceDate] = useState<string>(
formatLocalDate(new Date())
);
useEffect(() => {
if (extractedData?.serviceDate) {
const parsed = parseLocalDate(extractedData.serviceDate);
const isoFormatted = formatLocalDate(parsed);
setServiceDateValue(parsed);
setServiceDate(isoFormatted);
}
}, [extractedData]);
// Update service date when calendar date changes
const onServiceDateChange = (date: Date | undefined) => {
@@ -219,7 +206,7 @@ export function ClaimForm({
});
}, [serviceDate]);
// Determine patient date of birth format
// Determine patient date of birth format - required as date extracted from pdfs has different format.
const formatDOB = (dob: string | undefined) => {
if (!dob) return "";
if (/^\d{2}\/\d{2}\/\d{4}$/.test(dob)) return dob; // already MM/DD/YYYY

View File

@@ -31,6 +31,7 @@ import { CalendarIcon } from "lucide-react";
import { format } from "date-fns";
import { Button } from "../ui/button";
import { cn } from "@/lib/utils";
import { formatLocalDate, parseLocalDate } from "@/utils/dateUtils";
const PatientSchema = (
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
@@ -94,7 +95,7 @@ export const PatientForm = forwardRef<PatientFormRef, PatientFormProps>(
return {
...sanitizedPatient,
dateOfBirth: patient.dateOfBirth
? new Date(patient.dateOfBirth).toISOString().split("T")[0]
? formatLocalDate(new Date(patient.dateOfBirth))
: "",
};
}
@@ -146,7 +147,7 @@ export const PatientForm = forwardRef<PatientFormRef, PatientFormProps>(
const resetValues: Partial<Patient> = {
...sanitizedPatient,
dateOfBirth: patient.dateOfBirth
? new Date(patient.dateOfBirth).toISOString().split("T")[0]
? formatLocalDate(new Date(patient.dateOfBirth))
: "",
};
form.reset(resetValues);
@@ -218,7 +219,7 @@ export const PatientForm = forwardRef<PatientFormRef, PatientFormProps>(
)}
>
{field.value ? (
format(field.value, "PPP")
format(parseLocalDate(field.value), "PPP")
) : (
<span>Pick a date</span>
)}
@@ -229,10 +230,14 @@ export const PatientForm = forwardRef<PatientFormRef, PatientFormProps>(
<PopoverContent className="w-auto p-4">
<Calendar
mode="single"
selected={field.value}
selected={
field.value
? parseLocalDate(field.value)
: undefined
}
onSelect={(date) => {
if (date) {
const localDate = format(date, "yyyy-MM-dd");
const localDate = formatLocalDate(date); // keep yyyy-MM-dd
field.onChange(localDate);
}
}}

View File

@@ -91,13 +91,11 @@ export default function ClaimsPage() {
const { status, message, show } = useAppSelector(
(state) => state.seleniumClaimSubmitTask
);
const { toast } = useToast();
const { user } = useAuth();
const [claimFormData, setClaimFormData] = useState<any>({
patientId: null,
serviceDate: "",
});
const toggleMobileMenu = () => {
setIsMobileMenuOpen(!isMobileMenuOpen);
};
// Fetch patients
const { data: patients = [], isLoading: isLoadingPatients } = useQuery<
@@ -204,51 +202,95 @@ export default function ClaimsPage() {
},
});
// Update appointment mutation
const updateAppointmentMutation = useMutation({
mutationFn: async ({
id,
appointment,
}: {
id: number;
appointment: UpdateAppointment;
}) => {
const res = await apiRequest(
"PUT",
`/api/appointments/${id}`,
appointment
);
return await res.json();
// create claim mutation
const createClaimMutation = useMutation({
mutationFn: async (claimData: any) => {
const res = await apiRequest("POST", "/api/claims/", claimData);
return res.json();
},
onSuccess: () => {
toast({
title: "Success",
description: "Appointment updated successfully.",
title: "Claim created successfully",
variant: "default",
});
queryClient.invalidateQueries({ queryKey: ["/api/appointments/all"] });
queryClient.invalidateQueries({ queryKey: ["/api/patients/"] });
},
onError: (error: Error) => {
onError: (error: any) => {
toast({
title: "Error",
description: `Failed to update appointment: ${error.message}`,
title: "Error submitting claim",
description: error.message,
variant: "destructive",
});
},
});
// workflow starts from there - this params are set by pdf extraction/patient page.
// the fields are either send by pdfExtraction or by selecting patients.
const [location] = useLocation();
const { name, memberId, dob } = useMemo(() => {
const search = window.location.search;
const params = new URLSearchParams(search);
return {
name: params.get("name") || "",
memberId: params.get("memberId") || "",
dob: params.get("dob") || "",
};
}, [location]);
const handleNewClaim = (patientId: number) => {
setSelectedPatientId(patientId);
setIsClaimFormOpen(true);
};
useEffect(() => {
if (memberId && dob) {
// if matching patient found then simply send its id to claim form,
// if not then create new patient then send its id to claim form.
const fetchMatchingPatient = async () => {
try {
const matchingPatient = await getPatientByInsuranceId(memberId);
if (matchingPatient) {
handleNewClaim(matchingPatient.id);
} else {
const [firstName, ...rest] = name.trim().split(" ");
const lastName = rest.join(" ") || "";
const parsedDob = parse(dob, "M/d/yyyy", new Date()); // robust for "4/17/1964", "12/1/1975", etc.
const newPatient: InsertPatient = {
firstName,
lastName,
dateOfBirth: formatLocalDate(parsedDob),
gender: "",
phone: "",
userId: user?.id ?? 1,
status: "active",
insuranceId: memberId,
};
addPatientMutation.mutate(newPatient, {
onSuccess: (created) => {
handleNewClaim(created.id);
},
});
}
} catch (err) {
console.error("Error checking/creating patient:", err);
}
};
fetchMatchingPatient();
}
}, [memberId, dob]);
// 1. upsert appointment.
const handleAppointmentSubmit = async (
appointmentData: InsertAppointment | UpdateAppointment
): Promise<number> => {
return new Promise<number>((resolve, reject) => {
console.log("Constructed appointmentData:", appointmentData);
console.log(appointmentData.date);
createAppointmentMutation.mutate(
{
date: appointmentData.date,
startTime: appointmentData.startTime || "09:00",
endTime: appointmentData.endTime || "09:30",
startTime: "09:00",
endTime: "09:30",
staffId: appointmentData.staffId,
patientId: appointmentData.patientId,
userId: user?.id,
@@ -272,192 +314,15 @@ export default function ClaimsPage() {
});
};
const createClaimMutation = useMutation({
mutationFn: async (claimData: any) => {
const res = await apiRequest("POST", "/api/claims/", claimData);
return res.json();
},
onSuccess: () => {
toast({
title: "Claim created successfully",
variant: "default",
});
},
onError: (error: any) => {
toast({
title: "Error submitting claim",
description: error.message,
variant: "destructive",
});
},
});
function handleClaimSubmit(claimData: any): Promise<Claim> {
return createClaimMutation.mutateAsync(claimData).then((data) => {
return data;
});
}
const [location] = useLocation(); // gets path, e.g. "/claims"
const { name, memberId, dob } = useMemo(() => {
const search = window.location.search; // this gets the real query string
const params = new URLSearchParams(search);
return {
name: params.get("name") || "",
memberId: params.get("memberId") || "",
dob: params.get("dob") || "",
};
}, [location]);
const toggleMobileMenu = () => {
setIsMobileMenuOpen(!isMobileMenuOpen);
};
const handleNewClaim = (patientId: number) => {
setSelectedPatientId(patientId);
setIsClaimFormOpen(true);
const patient = patients.find((p) => p.id === patientId);
if (!patient) return;
prefillClaimForm(patient);
};
const closeClaim = () => {
setSelectedPatientId(null);
setIsClaimFormOpen(false);
setClaimFormData({
patientId: null,
serviceDate: "",
});
// Remove query parameters without reload
const url = new URL(window.location.href);
url.searchParams.delete("memberId");
url.searchParams.delete("dob");
url.searchParams.delete("name");
// Use history.replaceState to update the URL without reloading
window.history.replaceState({}, document.title, url.toString());
};
const prefillClaimForm = (patient: Patient) => {
const patientAppointments = appointments
.filter((a) => a.patientId === patient.id)
.sort(
(a, b) =>
parseLocalDate(b.date).getTime() - parseLocalDate(a.date).getTime()
);
const lastAppointment = patientAppointments[0]; // most recent
const dateToUse = lastAppointment
? parseLocalDate(lastAppointment.date)
: parseLocalDate(new Date());
setClaimFormData((prev: any) => ({
...prev,
patientId: patient.id,
serviceDate: formatLocalDate(dateToUse),
}));
};
useEffect(() => {
if (memberId && dob) {
const matchingPatient = patients.find(
(p) =>
p.insuranceId?.toLowerCase().trim() === memberId.toLowerCase().trim()
);
if (matchingPatient) {
setSelectedPatientId(matchingPatient.id);
prefillClaimForm(matchingPatient);
setIsClaimFormOpen(true);
} else {
const [firstName, ...rest] = name.trim().split(" ");
const lastName = rest.join(" ") || "";
const parsedDob = parse(dob, "M/d/yyyy", new Date()); // robust for "4/17/1964", "12/1/1975", etc.
const newPatient: InsertPatient = {
firstName,
lastName,
dateOfBirth: formatLocalDate(parsedDob),
gender: "",
phone: "",
userId: user?.id ?? 1,
status: "active",
insuranceId: memberId,
};
addPatientMutation.mutate(newPatient, {
onSuccess: (created) => {
setSelectedPatientId(created.id);
prefillClaimForm(created);
setIsClaimFormOpen(true);
},
});
}
}
}, [memberId, dob]);
const getDisplayProvider = (provider: string) => {
const insuranceMap: Record<string, string> = {
delta: "Delta Dental",
metlife: "MetLife",
cigna: "Cigna",
aetna: "Aetna",
};
return insuranceMap[provider?.toLowerCase()] || provider;
};
// Get unique patients with appointments
const patientsWithAppointments = patients.reduce(
(acc, patient) => {
const patientAppointments = appointments
.filter((appt) => appt.patientId === patient.id)
.sort(
(a, b) =>
parseLocalDate(b.date).getTime() - parseLocalDate(a.date).getTime()
); // Sort descending by date
if (patientAppointments.length > 0) {
const latestAppointment = patientAppointments[0];
acc.push({
patientId: patient.id,
patientName: `${patient.firstName} ${patient.lastName}`,
appointmentId: latestAppointment!.id,
insuranceProvider: patient.insuranceProvider || "N/A",
insuranceId: patient.insuranceId || "N/A",
lastAppointment: formatLocalDate(
parseLocalDate(latestAppointment!.date)
),
});
}
return acc;
},
[] as Array<{
patientId: number;
patientName: string;
appointmentId: number;
insuranceProvider: string;
insuranceId: string;
lastAppointment: string;
}>
);
// Update Patient ( for insuranceId and Insurance Provider)
// 2. Update Patient ( for insuranceId and Insurance Provider)
const handleUpdatePatient = (patient: UpdatePatient & { id?: number }) => {
if (patient && user) {
if (patient) {
const { id, ...sanitizedPatient } = patient;
updatePatientMutation.mutate({
id: Number(patient.id),
patient: sanitizedPatient,
});
} else {
console.error("No current patient or user found for update");
toast({
title: "Error",
description: "Cannot update patient: No patient or user found",
@@ -466,7 +331,14 @@ export default function ClaimsPage() {
}
};
// handle selenium sybmiting Mass Health claim
// 3. create claim.
const handleClaimSubmit = (claimData: any): Promise<Claim> => {
return createClaimMutation.mutateAsync(claimData).then((data) => {
return data;
});
};
// 4. handle selenium sybmiting Mass Health claim
const handleMHClaimSubmitSelenium = async (data: any) => {
const formData = new FormData();
formData.append("data", JSON.stringify(data));
@@ -509,7 +381,10 @@ export default function ClaimsPage() {
variant: "default",
});
const result2 = await handleMHSeleniumPdfDownload(result1);
const result2 = await handleMHSeleniumPdfDownload(
result1,
selectedPatientId
);
return result2;
} catch (error: any) {
dispatch(
@@ -526,8 +401,11 @@ export default function ClaimsPage() {
}
};
// selenium pdf download handler
const handleMHSeleniumPdfDownload = async (data: any) => {
// 5. selenium pdf download handler
const handleMHSeleniumPdfDownload = async (
data: any,
selectedPatientId: number | null
) => {
try {
if (!selectedPatientId) {
throw new Error("Missing patientId");
@@ -575,6 +453,68 @@ export default function ClaimsPage() {
}
};
// 6. close claim
const closeClaim = () => {
setSelectedPatientId(null);
setIsClaimFormOpen(false);
// Remove query parameters without reload
const url = new URL(window.location.href);
url.searchParams.delete("memberId");
url.searchParams.delete("dob");
url.searchParams.delete("name");
// Use history.replaceState to update the URL without reloading
window.history.replaceState({}, document.title, url.toString());
};
// helper func for frontend
const getDisplayProvider = (provider: string) => {
const insuranceMap: Record<string, string> = {
delta: "Delta Dental",
metlife: "MetLife",
cigna: "Cigna",
aetna: "Aetna",
};
return insuranceMap[provider?.toLowerCase()] || provider;
};
// Get unique patients with appointments - might not needed now, can shift this to the recent patients table
const patientsWithAppointments = patients.reduce(
(acc, patient) => {
const patientAppointments = appointments
.filter((appt) => appt.patientId === patient.id)
.sort(
(a, b) =>
parseLocalDate(b.date).getTime() - parseLocalDate(a.date).getTime()
); // Sort descending by date
if (patientAppointments.length > 0) {
const latestAppointment = patientAppointments[0];
acc.push({
patientId: patient.id,
patientName: `${patient.firstName} ${patient.lastName}`,
appointmentId: latestAppointment!.id,
insuranceProvider: patient.insuranceProvider || "N/A",
insuranceId: patient.insuranceId || "N/A",
lastAppointment: formatLocalDate(
parseLocalDate(latestAppointment!.date)
),
});
}
return acc;
},
[] as Array<{
patientId: number;
patientName: string;
appointmentId: number;
insuranceProvider: string;
insuranceId: string;
lastAppointment: string;
}>
);
return (
<div className="flex h-screen overflow-hidden bg-gray-100">
<Sidebar
@@ -720,7 +660,6 @@ export default function ClaimsPage() {
<ClaimForm
patientId={selectedPatientId}
onClose={closeClaim}
extractedData={claimFormData}
onSubmit={handleClaimSubmit}
onHandleAppointmentSubmit={handleAppointmentSubmit}
onHandleUpdatePatient={handleUpdatePatient}