From eb3055c069bbab36a66448b1aa764f0fc1f815d7 Mon Sep 17 00:00:00 2001 From: Potenz Date: Wed, 24 Sep 2025 18:20:01 +0530 Subject: [PATCH] feat(Pattient data extracts button added) --- apps/Frontend/src/pages/patients-page.tsx | 331 ++++++++++++++++++++-- 1 file changed, 305 insertions(+), 26 deletions(-) diff --git a/apps/Frontend/src/pages/patients-page.tsx b/apps/Frontend/src/pages/patients-page.tsx index f982e19..308587a 100644 --- a/apps/Frontend/src/pages/patients-page.tsx +++ b/apps/Frontend/src/pages/patients-page.tsx @@ -1,4 +1,4 @@ -import { useState, useRef } from "react"; +import { useState, useRef, useCallback } from "react"; import { useMutation } from "@tanstack/react-query"; import { PatientTable } from "@/components/patients/patient-table"; import { AddPatientModal } from "@/components/patients/add-patient-modal"; @@ -19,6 +19,8 @@ import useExtractPdfData from "@/hooks/use-extractPdfData"; import { useLocation } from "wouter"; import { InsertPatient, Patient } from "@repo/db/types"; import { QK_PATIENTS_BASE } from "@/components/patients/patient-table"; +import { parse } from "date-fns"; +import { formatLocalDate } from "@/utils/dateUtils"; // Type for the ref to access modal methods type AddPatientModalRef = { @@ -92,6 +94,69 @@ export default function PatientsPage() { } }; + // helper: ensure patient exists (returns patient object) + const ensurePatientExists = async (data: { + name: string; + memberId: string; + dob: string; + }) => { + try { + // 1) try to find by insurance id + const findRes = await apiRequest( + "GET", + `/api/patients/by-insurance-id?insuranceId=${encodeURIComponent( + data.memberId + )}` + ); + if (findRes.ok) { + const found = await findRes.json(); + if (found && found.id) return found; + } else { + // If API returns a non-ok with body, try to parse a possible 404-with-JSON + try { + const body = await findRes.json(); + if (body && body.id) return body; + } catch { + // ignore + } + } + + // 2) not found -> create patient + const [firstName, ...rest] = (data.name || "").trim().split(" "); + const lastName = rest.join(" ") || ""; + const parsedDob = parse(data.dob, "M/d/yyyy", new Date()); // robust for "4/17/1964", "12/1/1975", etc. + + // convert dob to whatever format your API expects. Here we keep as received. + const newPatient: InsertPatient = { + firstName: firstName || "", + lastName: lastName || "", + dateOfBirth: formatLocalDate(parsedDob), + gender: "", + phone: "", + userId: user?.id ?? 1, + status: "active", + insuranceId: data.memberId || "", + }; + + const createRes = await apiRequest("POST", "/api/patients/", newPatient); + if (!createRes.ok) { + // surface error + let body: any = null; + try { + body = await createRes.json(); + } catch {} + throw new Error( + body?.message || + `Failed to create patient (status ${createRes.status})` + ); + } + const created = await createRes.json(); + return created; + } catch (err) { + throw err; + } + }; + const isLoading = addPatientMutation.isPending; // File upload handling @@ -108,38 +173,218 @@ export default function PatientsPage() { setIsUploading(false); }; - // data extraction - const handleExtract = () => { - setIsExtracting(true); + /** + * Central helper: + * - extracts PDF (awaits the mutate via a Promise wrapper), + * - shows success toast, + * - ensures patient exists (find by insuranceId, create if missing), + * - shows toasts for errors, + * - returns Patient on success or null on error. + */ + const extractAndEnsurePatient = + useCallback(async (): Promise => { + if (!uploadedFile) { + toast({ + title: "Error", + description: "Please upload a PDF", + variant: "destructive", + }); + return null; + } - if (!uploadedFile) { - return toast({ - title: "Error", - description: "Please upload a PDF", - variant: "destructive", - }); - } - extractPdf(uploadedFile, { - onSuccess: (data) => { - setIsExtracting(false); + setIsExtracting(true); + try { + // wrap the extractPdf mutate in a promise so we can await it + const data: { name: string; memberId: string; dob: string } = + await new Promise((resolve, reject) => { + try { + extractPdf(uploadedFile, { + onSuccess: (d) => resolve(d as any), + onError: (err: any) => reject(err), + }); + } catch (err) { + reject(err); + } + }); + // 2) basic validation of extracted data — require memberId + name (adjust if needed) + if (!data?.memberId) { + toast({ + title: "Extraction result invalid", + description: + "No memberId found in PDF — cannot find/create patient.", + variant: "destructive", + }); + return null; + } + if (!data?.name) { + toast({ + title: "Extraction result invalid", + description: + "No patient name found in PDF — cannot create patient.", + variant: "destructive", + }); + return null; + } + if (!data.dob) { + toast({ + title: "Extraction result invalid", + description: "No DOB extracted — cannot create patient", + variant: "default", + }); + } + + // success toast for extraction toast({ title: "Success Pdf Data Extracted", description: `Name: ${data.name}, Member ID: ${data.memberId}, DOB: ${data.dob}`, variant: "default", }); - const params = new URLSearchParams({ - name: data.name, - memberId: data.memberId, - dob: data.dob, - }); + // 1) try to find patient by insurance/memberId + let findRes: Response | null = null; + try { + findRes = await apiRequest( + "GET", + `/api/patients/by-insurance-id?insuranceId=${encodeURIComponent( + data.memberId + )}` + ); + } catch (err: any) { + // Network / fetch error — do NOT attempt create; surface error and abort. + toast({ + title: "Network error", + description: + err?.message || + "Unable to verify patient by insurance ID due to a network error. Try again.", + variant: "destructive", + }); + return null; + } - navigate( - `/claims?name=${encodeURIComponent(data.name)}&memberId=${data.memberId}&dob=${data.dob}` - ); - }, - }); + // 1a) handle fetch response + if (findRes.ok) { + try { + const found = await findRes.json(); + if (found && found.id) { + return found as Patient; + } + // If the API returned 200 but empty body / null, we'll treat as "not found" and go to create. + } catch (err: any) { + toast({ + title: "Error parsing response", + description: + err?.message || + "Unable to parse server response when checking existing patient.", + variant: "destructive", + }); + return null; + } + } else { + // findRes is not ok + if (findRes.status === 404) { + // Not found -> proceed to create + } else { + // Other non-OK -> parse body if possible and abort (don't create) + let body: any = null; + try { + body = await findRes.json(); + } catch {} + toast({ + title: "Error checking patient", + description: + body?.message || + `Failed to check patient (status ${findRes.status}). Aborting.`, + variant: "destructive", + }); + return null; + } + } + + // 2) not found: create patient + try { + const [firstName, ...rest] = (data.name || "").trim().split(" "); + const lastName = rest.join(" ") || ""; + const parsedDob = parse(data.dob, "M/d/yyyy", new Date()); // robust for "4/17/1964", "12/1/1975", etc. + + const newPatient: InsertPatient = { + firstName: firstName || "", + lastName: lastName || "", + dateOfBirth: formatLocalDate(parsedDob), + gender: "", + phone: "", + userId: user?.id ?? 1, + status: "active", + insuranceId: data.memberId || "", + }; + + const createRes = await apiRequest( + "POST", + "/api/patients/", + newPatient + ); + if (!createRes.ok) { + let body: any = null; + try { + body = await createRes.json(); + } catch {} + const msg = + body?.message || + `Failed to create patient (status ${createRes.status})`; + toast({ + title: "Error creating patient", + description: msg, + variant: "destructive", + }); + return null; + } + const created = await createRes.json(); + + // success toast already shown by addPatientMutation.onSuccess elsewhere — but we can also show a brief success here + queryClient.invalidateQueries({ queryKey: QK_PATIENTS_BASE }); + + toast({ + title: "Patient created", + description: `${created.firstName || ""} ${created.lastName || ""} created.`, + variant: "default", + }); + return created as Patient; + } catch (err: any) { + toast({ + title: "Error creating patient", + description: err?.message || "Failed to create patient.", + variant: "destructive", + }); + return null; + } + } catch (err: any) { + // extraction error + toast({ + title: "Extraction failed", + description: err?.message ?? "Failed to extract data from PDF", + variant: "destructive", + }); + return null; + } finally { + setIsExtracting(false); + } + }, [uploadedFile, extractPdf]); + + // handlers are now minimal and don't repeat error/toast logic + const handleExtractAndClaim = async () => { + const patient = await extractAndEnsurePatient(); + if (!patient) return; // error already shown in helper + navigate(`/claims?newPatient=${patient.id}`); + }; + + const handleExtractAndAppointment = async () => { + const patient = await extractAndEnsurePatient(); + if (!patient) return; + navigate(`/appointments?newPatient=${patient.id}`); + }; + + const handleExtractAndSave = async () => { + await extractAndEnsurePatient(); }; return ( @@ -183,11 +428,45 @@ export default function PatientsPage() { acceptedFileTypes="application/pdf" /> -
+
+ +