From d9ce19df8021b26c9693acfa8ca7e19242e25fa3 Mon Sep 17 00:00:00 2001 From: Potenz Date: Mon, 28 Jul 2025 14:40:01 +0530 Subject: [PATCH] checkpoint claimpage --- .../src/components/claims/claim-form.tsx | 17 +- .../src/components/patients/patient-form.tsx | 15 +- apps/Frontend/src/pages/claims-page.tsx | 371 ++++++++---------- 3 files changed, 167 insertions(+), 236 deletions(-) diff --git a/apps/Frontend/src/components/claims/claim-form.tsx b/apps/Frontend/src/components/claims/claim-form.tsx index d585086..9c92020 100644 --- a/apps/Frontend/src/components/claims/claim-form.tsx +++ b/apps/Frontend/src/components/claims/claim-form.tsx @@ -108,7 +108,6 @@ interface ClaimFormData { interface ClaimFormProps { patientId: number; - extractedData?: Partial; onSubmit: (data: ClaimFormData) => Promise; onHandleAppointmentSubmit: ( appointmentData: InsertAppointment | UpdateAppointment @@ -123,7 +122,6 @@ type Staff = z.infer; 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( - extractedData ? ({ ...extractedData } as Patient) : null - ); + const [patient, setPatient] = useState(null); // Query patient based on given patient id const { @@ -185,14 +180,6 @@ export function ClaimForm({ const [serviceDate, setServiceDate] = useState( 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 diff --git a/apps/Frontend/src/components/patients/patient-form.tsx b/apps/Frontend/src/components/patients/patient-form.tsx index 7b6964c..e660b6c 100644 --- a/apps/Frontend/src/components/patients/patient-form.tsx +++ b/apps/Frontend/src/components/patients/patient-form.tsx @@ -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 @@ -94,7 +95,7 @@ export const PatientForm = forwardRef( 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( const resetValues: Partial = { ...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( )} > {field.value ? ( - format(field.value, "PPP") + format(parseLocalDate(field.value), "PPP") ) : ( Pick a date )} @@ -229,10 +230,14 @@ export const PatientForm = forwardRef( { if (date) { - const localDate = format(date, "yyyy-MM-dd"); + const localDate = formatLocalDate(date); // keep yyyy-MM-dd field.onChange(localDate); } }} diff --git a/apps/Frontend/src/pages/claims-page.tsx b/apps/Frontend/src/pages/claims-page.tsx index 1bb6d19..1b6fad0 100644 --- a/apps/Frontend/src/pages/claims-page.tsx +++ b/apps/Frontend/src/pages/claims-page.tsx @@ -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({ - 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 => { return new Promise((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 { - 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 = { - 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 => { + 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 = { + 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 (