From 1071b8093026ead66286015172c3d8f5db0e63ba Mon Sep 17 00:00:00 2001 From: Potenz Date: Thu, 17 Jul 2025 19:21:16 +0530 Subject: [PATCH] document page updated --- apps/Backend/src/routes/claims.ts | 29 +- apps/Backend/src/routes/documents.ts | 33 + .../src/routes/insuranceEligibility.ts | 50 +- apps/Backend/src/storage/index.ts | 26 +- apps/Frontend/src/pages/claims-page.tsx | 5 - .../src/pages/documents-page-basic.tsx | 474 ++++++++++++++ apps/Frontend/src/pages/documents-page.tsx | 608 ++++++----------- .../src/pages/insurance-eligibility-page.tsx | 81 +-- apps/SeleniumService/agent.py | 1 - .../MassDHP_Member_Eligibility_1497211726.pdf | 615 ------------------ dele.txt | 5 - 11 files changed, 838 insertions(+), 1089 deletions(-) create mode 100644 apps/Frontend/src/pages/documents-page-basic.tsx delete mode 100644 apps/SeleniumService/seleniumDownloads/MassDHP_Member_Eligibility_1497211726.pdf delete mode 100644 dele.txt diff --git a/apps/Backend/src/routes/claims.ts b/apps/Backend/src/routes/claims.ts index bad11e5..2f116fd 100644 --- a/apps/Backend/src/routes/claims.ts +++ b/apps/Backend/src/routes/claims.ts @@ -132,7 +132,7 @@ router.post( return sendError(res, "Unauthorized: user info missing", 401); } - const { patientId, claimId, pdf_url } = req.body; + const { patientId, pdf_url } = req.body; if (!pdf_url) { return sendError(res, "Missing pdf_url"); @@ -141,26 +141,35 @@ router.post( if (!patientId) { return sendError(res, "Missing Patient Id"); } - if (!claimId) { - return sendError(res, "Missing Claim Id"); - } const parsedPatientId = parseInt(patientId); - const parsedClaimId = parseInt(claimId); const filename = path.basename(new URL(pdf_url).pathname); const pdfResponse = await axios.get(pdf_url, { responseType: "arraybuffer", }); - // saving at postgres db - await storage.createClaimPdf( + const groupTitle = `Insurance Claim`; + const groupCategory = "CLAIM"; + + // ✅ Find or create PDF group for this claim + let group = await storage.findPdfGroupByPatientTitleAndCategory( parsedPatientId, - parsedClaimId, - filename, - pdfResponse.data + groupTitle, + groupCategory ); + if (!group) { + group = await storage.createPdfGroup( + parsedPatientId, + groupTitle, + groupCategory + ); + } + + // ✅ Save PDF file into that group + await storage.createPdfFile(group.id!, filename, pdfResponse.data); + return res.json({ success: true, pdfPath: `/temp/${filename}`, diff --git a/apps/Backend/src/routes/documents.ts b/apps/Backend/src/routes/documents.ts index f72d4c1..4db8cef 100644 --- a/apps/Backend/src/routes/documents.ts +++ b/apps/Backend/src/routes/documents.ts @@ -33,6 +33,24 @@ router.post( } ); +router.get( + "/pdf-groups/patient/:patientId", + async (req: Request, res: Response): Promise => { + try { + const { patientId } = req.params; + if (!patientId) { + return res.status(400).json({ error: "Missing patient ID" }); + } + + const groups = await storage.getPdfGroupsByPatientId(parseInt(patientId)); + res.json(groups); + } catch (err) { + console.error("Error fetching groups by patient ID:", err); + res.status(500).json({ error: "Internal server error" }); + } + } +); + router.get( "/pdf-groups/:id", async (req: Request, res: Response): Promise => { @@ -126,6 +144,21 @@ router.post( } ); +router.get("/pdf-files/group/:groupId", async (req: Request, res: Response):Promise => { + try { + const idParam = req.params.groupId; + if (!idParam) { + return res.status(400).json({ error: "Missing Groupt ID" }); + } + const groupId = parseInt(idParam); + const files = await storage.getPdfFilesByGroupId(groupId); // implement this + res.json(files); + } catch (err) { + res.status(500).json({ error: "Internal server error" }); + } +}); + + router.get( "/pdf-files/:id", async (req: Request, res: Response): Promise => { diff --git a/apps/Backend/src/routes/insuranceEligibility.ts b/apps/Backend/src/routes/insuranceEligibility.ts index 40f94af..9168026 100644 --- a/apps/Backend/src/routes/insuranceEligibility.ts +++ b/apps/Backend/src/routes/insuranceEligibility.ts @@ -2,6 +2,8 @@ import { Router } from "express"; import { Request, Response } from "express"; import { storage } from "../storage"; import { forwardToSeleniumInsuranceEligibilityAgent } from "../services/seleniumInsuranceEligibilityClient"; +import fs from "fs/promises"; +import path from "path"; const router = Router(); @@ -48,15 +50,55 @@ router.post("/check", async (req: Request, res: Response): Promise => { const newStatus = result.eligibility === "Y" ? "active" : "inactive"; await storage.updatePatient(patient.id, { status: newStatus }); result.patientUpdateStatus = `Patient status updated to ${newStatus}`; + + // ✅ Step 2: Handle PDF Upload + if (result.pdf_path && result.pdf_path.endsWith(".pdf")) { + const pdfBuffer = await fs.readFile(result.pdf_path); + + const groupTitle = "Eligibility PDFs"; + const groupCategory = "ELIGIBILITY"; + + let group = await storage.findPdfGroupByPatientTitleAndCategory( + patient.id, + groupTitle, + groupCategory + ); + + // Step 2b: Create group if it doesn’t exist + if (!group) { + group = await storage.createPdfGroup( + patient.id, + groupTitle, + groupCategory + ); + } + + if (!group?.id) { + throw new Error("PDF group creation failed: missing group ID"); + } + await storage.createPdfFile( + group.id, + path.basename(result.pdf_path), + pdfBuffer + ); + + await fs.unlink(result.pdf_path); + + result.pdfUploadStatus = `PDF saved to group: ${group.title}`; + } else { + result.pdfUploadStatus = + "No valid PDF path provided by Selenium, Couldn't upload pdf to server."; + } } else { - console.warn( - `No patient found with insuranceId: ${insuranceEligibilityData.memberId}` - ); result.patientUpdateStatus = "Patient not found or missing ID; no update performed"; } - res.json(result); + res.json({ + patientUpdateStatus: result.patientUpdateStatus, + pdfUploadStatus: result.pdfUploadStatus, +}); + } catch (err: any) { console.error(err); return res.status(500).json({ diff --git a/apps/Backend/src/storage/index.ts b/apps/Backend/src/storage/index.ts index 6b9d9ed..b31b91e 100644 --- a/apps/Backend/src/storage/index.ts +++ b/apps/Backend/src/storage/index.ts @@ -266,11 +266,15 @@ export interface IStorage { // Group management createPdfGroup( - patientId: number, - title: string, - category: PdfCategory -): Promise; - + patientId: number, + title: string, + category: PdfCategory + ): Promise; + findPdfGroupByPatientTitleAndCategory( + patientId: number, + title: string, + category: PdfCategory + ): Promise; getAllPdfGroups(): Promise; getPdfGroupById(id: number): Promise; getPdfGroupsByPatientId(patientId: number): Promise; @@ -694,6 +698,18 @@ export const storage: IStorage = { }); }, + async findPdfGroupByPatientTitleAndCategory(patientId, title, category) { + return ( + (await db.pdfGroup.findFirst({ + where: { + patientId, + title, + category, + }, + })) ?? undefined + ); + }, + async getPdfGroupById(id) { return (await db.pdfGroup.findUnique({ where: { id } })) ?? undefined; }, diff --git a/apps/Frontend/src/pages/claims-page.tsx b/apps/Frontend/src/pages/claims-page.tsx index 318bef7..a330dc3 100644 --- a/apps/Frontend/src/pages/claims-page.tsx +++ b/apps/Frontend/src/pages/claims-page.tsx @@ -17,7 +17,6 @@ import { z } from "zod"; import { apiRequest, queryClient } from "@/lib/queryClient"; import { useLocation } from "wouter"; import RecentClaims from "@/components/claims/recent-claims"; - import { useAppDispatch, useAppSelector } from "@/redux/hooks"; import { setTaskStatus, @@ -556,9 +555,6 @@ export default function ClaimsPage() { // selenium pdf download handler const handleSeleniumPdfDownload = async (data: any) => { try { - if (!data.claimId) { - throw new Error("Missing claimId in handleSeleniumPdfDownload"); - } if (!selectedPatient) { throw new Error("Missing patientId"); } @@ -572,7 +568,6 @@ export default function ClaimsPage() { const res = await apiRequest("POST", "/api/claims/selenium/fetchpdf", { patientId: selectedPatient, - claimId: data.claimId, pdf_url: data.pdf_url, }); const result = await res.json(); diff --git a/apps/Frontend/src/pages/documents-page-basic.tsx b/apps/Frontend/src/pages/documents-page-basic.tsx new file mode 100644 index 0000000..5a81933 --- /dev/null +++ b/apps/Frontend/src/pages/documents-page-basic.tsx @@ -0,0 +1,474 @@ +import { useEffect, useState } from "react"; +import { useMutation, useQuery } from "@tanstack/react-query"; +import { TopAppBar } from "@/components/layout/top-app-bar"; +import { Sidebar } from "@/components/layout/sidebar"; +import { Input } from "@/components/ui/input"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent } from "@/components/ui/card"; +import { + Search, + Eye, + ChevronLeft, + ChevronRight, + Settings, + Trash, + Download, +} from "lucide-react"; +import { useAuth } from "@/hooks/use-auth"; +import { cn } from "@/lib/utils"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { ClaimPdfUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas"; +import { z } from "zod"; +import "@react-pdf-viewer/core/lib/styles/index.css"; +import "@react-pdf-viewer/default-layout/lib/styles/index.css"; +import { Viewer, Worker } from "@react-pdf-viewer/core"; +import { defaultLayoutPlugin } from "@react-pdf-viewer/default-layout"; +import { apiRequest, queryClient } from "@/lib/queryClient"; +import { toast } from "@/hooks/use-toast"; +import { DeleteConfirmationDialog } from "@/components/ui/deleteDialog"; + +const ClaimPdfSchema = + ClaimPdfUncheckedCreateInputObjectSchema as unknown as z.ZodObject; +type ClaimPdf = z.infer; + +export default function DocumentsPage() { + const { user } = useAuth(); + const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); + const [searchTerm, setSearchTerm] = useState(""); + const [searchField, setSearchField] = useState("all"); + const [currentPage, setCurrentPage] = useState(1); + const itemsPerPage = 5; + const [selectedPdfId, setSelectedPdfId] = useState(null); + const defaultLayoutPluginInstance = defaultLayoutPlugin(); + const [isDeletePdfOpen, setIsDeletePdfOpen] = useState(false); + const [currentPdf, setCurrentPdf] = useState(null); + + const { data: pdfs = [], isLoading } = useQuery({ + queryKey: ["/api/documents/claim-pdf/recent"], + enabled: !!user, + queryFn: async () => { + const res = await apiRequest("GET", "/api/documents/claim-pdf/recent"); + return res.json(); + }, + }); + + const deletePdfMutation = useMutation({ + mutationFn: async (id: number) => { + await apiRequest("DELETE", `/api/documents/claim-pdf/${id}`); + }, + onSuccess: () => { + setIsDeletePdfOpen(false); + setCurrentPdf(null); + queryClient.invalidateQueries({ + queryKey: ["/api/documents/claim-pdf/recent"], + }); + + toast({ + title: "Success", + description: "PDF deleted successfully!", + variant: "default", + }); + }, + onError: (error: any) => { + console.error("Error deleting PDF:", error); + toast({ + title: "Error", + description: `Failed to delete PDF: ${error.message || error}`, + variant: "destructive", + }); + }, + }); + + const formatDate = (dateString: string) => { + const date = new Date(dateString); + return date.toLocaleDateString("en-US", { + month: "short", + day: "numeric", + year: "numeric", + }); + }; + + const getPatientInitials = (first: string, last: string) => + `${first[0]}${last[0]}`.toUpperCase(); + + const [fileBlobUrl, setFileBlobUrl] = useState(null); + + useEffect(() => { + if (!selectedPdfId) return; + let url: string | null = null; + + const fetchPdf = async () => { + try { + const res = await apiRequest( + "GET", + `/api/documents/claim-pdf/${selectedPdfId}` + ); + + const arrayBuffer = await res.arrayBuffer(); + const blob = new Blob([arrayBuffer], { type: "application/pdf" }); + const objectUrl = URL.createObjectURL(blob); + setFileBlobUrl(objectUrl); + url = objectUrl; + } catch (err) { + console.error("Failed to load PDF", err); + } + }; + + fetchPdf(); + + return () => { + if (url) { + URL.revokeObjectURL(url); + } + }; + }, [selectedPdfId]); + + const toggleMobileMenu = () => setIsMobileMenuOpen((prev) => !prev); + + const viewPdf = (pdfId: number) => { + setSelectedPdfId(pdfId); + }; + + const downloadPdf = async (pdfId: number, filename: string) => { + try { + const res = await apiRequest("GET", `/api/documents/claim-pdf/${pdfId}`); + const arrayBuffer = await res.arrayBuffer(); + const blob = new Blob([arrayBuffer], { type: "application/pdf" }); + const url = URL.createObjectURL(blob); + + const a = document.createElement("a"); + a.href = url; + a.download = filename; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + } catch (err) { + console.error("Failed to download PDF:", err); + } + }; + + const handleDeletePdf = (pdf: ClaimPdf) => { + setCurrentPdf(pdf); + setIsDeletePdfOpen(true); + }; + + const handleConfirmDeletePdf = () => { + if (currentPdf) { + deletePdfMutation.mutate(currentPdf.id); + } else { + toast({ + title: "Error", + description: "No PDF selected for deletion.", + variant: "destructive", + }); + } + }; + + const filteredPdfs = pdfs.filter((pdf) => { + const patient = pdf.patient; + const searchLower = searchTerm.toLowerCase(); + const fullName = `${patient.firstName} ${patient.lastName}`.toLowerCase(); + const patientId = `PID-${patient.id.toString().padStart(4, "0")}`; + + switch (searchField) { + case "name": + return fullName.includes(searchLower); + case "id": + return patientId.toLowerCase().includes(searchLower); + case "phone": + return patient.phone?.toLowerCase().includes(searchLower) || false; + case "all": + default: + return ( + fullName.includes(searchLower) || + patientId.includes(searchLower) || + patient.phone?.toLowerCase().includes(searchLower) || + patient.email?.toLowerCase().includes(searchLower) || + false + ); + } + }); + + const totalPages = Math.ceil(filteredPdfs.length / itemsPerPage); + const startIndex = (currentPage - 1) * itemsPerPage; + const currentPdfs = filteredPdfs.slice(startIndex, startIndex + itemsPerPage); + + return ( +
+ + +
+ + +
+
+
+

+ Documents +

+

+ View and manage recent uploaded claim PDFs +

+
+ + + +
+
+ + setSearchTerm(e.target.value)} + className="pl-10" + /> +
+
+ + +
+
+
+
+ + + + {isLoading ? ( +
Loading data...
+ ) : currentPdfs.length === 0 ? ( +
+ {searchTerm + ? "No results matching your search." + : "No recent claim PDFs available."} +
+ ) : ( + <> +
+
Patient
+
DOB / Gender
+
Contact
+
Insurance
+
Status
+
Actions
+
+ + {currentPdfs.map((pdf) => { + const patient = pdf.patient; + return ( +
+
+
+ {getPatientInitials( + patient.firstName, + patient.lastName + )} +
+
+
+ {patient.firstName} {patient.lastName} +
+
+ PID-{patient.id.toString().padStart(4, "0")} +
+
+
+ +
+
+ {formatDate(patient.dateOfBirth)} +
+
+ {patient.gender} +
+
+ +
+
+ {patient.phone || "Not provided"} +
+
+ {patient.email || "No email"} +
+
+ +
+
+ {patient.insuranceProvider + ? `${patient.insuranceProvider.charAt(0).toUpperCase()}${patient.insuranceProvider.slice(1)}` + : "Not specified"} +
+
+ ID: {patient.insuranceId || "N/A"} +
+
+ +
+ + {patient.status === "active" + ? "Active" + : "Inactive"} + +
+ +
+
+ + + + +
+
+
+ ); + })} + + setIsDeletePdfOpen(false)} + entityName={`PDF #${currentPdf?.id}`} + /> + + {/* PDF Viewer */} + {selectedPdfId && fileBlobUrl && ( +
+
+

+ Viewing PDF #{selectedPdfId} +

+ +
+
+ + + +
+
+ )} + + {/* Pagination */} + {totalPages > 1 && ( +
+
+ Showing {startIndex + 1} to{" "} + {Math.min( + startIndex + itemsPerPage, + filteredPdfs.length + )}{" "} + of {filteredPdfs.length} results +
+
+ + {Array.from( + { length: totalPages }, + (_, i) => i + 1 + ).map((page) => ( + + ))} + +
+
+ )} + + )} +
+
+
+
+
+
+ ); +} diff --git a/apps/Frontend/src/pages/documents-page.tsx b/apps/Frontend/src/pages/documents-page.tsx index 5a81933..b2d8efd 100644 --- a/apps/Frontend/src/pages/documents-page.tsx +++ b/apps/Frontend/src/pages/documents-page.tsx @@ -1,164 +1,103 @@ import { useEffect, useState } from "react"; -import { useMutation, useQuery } from "@tanstack/react-query"; -import { TopAppBar } from "@/components/layout/top-app-bar"; -import { Sidebar } from "@/components/layout/sidebar"; -import { Input } from "@/components/ui/input"; +import { useQuery, useMutation } from "@tanstack/react-query"; +import { + Card, + CardHeader, + CardTitle, + CardDescription, + CardContent, +} from "@/components/ui/card"; import { Button } from "@/components/ui/button"; -import { Card, CardContent } from "@/components/ui/card"; -import { - Search, - Eye, - ChevronLeft, - ChevronRight, - Settings, - Trash, - Download, -} from "lucide-react"; -import { useAuth } from "@/hooks/use-auth"; -import { cn } from "@/lib/utils"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; -import { ClaimPdfUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas"; -import { z } from "zod"; -import "@react-pdf-viewer/core/lib/styles/index.css"; -import "@react-pdf-viewer/default-layout/lib/styles/index.css"; -import { Viewer, Worker } from "@react-pdf-viewer/core"; -import { defaultLayoutPlugin } from "@react-pdf-viewer/default-layout"; -import { apiRequest, queryClient } from "@/lib/queryClient"; import { toast } from "@/hooks/use-toast"; +import { apiRequest, queryClient } from "@/lib/queryClient"; +import { Eye, Trash, Download, FolderOpen } from "lucide-react"; +import { + PatientUncheckedCreateInputObjectSchema, + PdfFileUncheckedCreateInputObjectSchema, +} from "@repo/db/usedSchemas"; import { DeleteConfirmationDialog } from "@/components/ui/deleteDialog"; +import { PatientTable } from "@/components/patients/patient-table"; +import { z } from "zod"; +import { Sidebar } from "@/components/layout/sidebar"; +import { TopAppBar } from "@/components/layout/top-app-bar"; -const ClaimPdfSchema = - ClaimPdfUncheckedCreateInputObjectSchema as unknown as z.ZodObject; -type ClaimPdf = z.infer; +const PatientSchema = ( + PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject +).omit({ + appointments: true, +}); +type Patient = z.infer; + +const PdfFileSchema = + PdfFileUncheckedCreateInputObjectSchema as unknown as z.ZodObject; +type PdfFile = z.infer; export default function DocumentsPage() { - const { user } = useAuth(); const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); - const [searchTerm, setSearchTerm] = useState(""); - const [searchField, setSearchField] = useState("all"); - const [currentPage, setCurrentPage] = useState(1); - const itemsPerPage = 5; + const [selectedPatient, setSelectedPatient] = useState(null); + const [selectedGroupId, setSelectedGroupId] = useState(null); const [selectedPdfId, setSelectedPdfId] = useState(null); - const defaultLayoutPluginInstance = defaultLayoutPlugin(); + const [fileBlobUrl, setFileBlobUrl] = useState(null); const [isDeletePdfOpen, setIsDeletePdfOpen] = useState(false); - const [currentPdf, setCurrentPdf] = useState(null); + const [currentPdf, setCurrentPdf] = useState(null); - const { data: pdfs = [], isLoading } = useQuery({ - queryKey: ["/api/documents/claim-pdf/recent"], - enabled: !!user, + const toggleMobileMenu = () => setIsMobileMenuOpen((prev) => !prev); + + useEffect(() => { + setSelectedGroupId(null); + }, [selectedPatient]); + + const handleSelectGroup = (groupId: number) => { + setSelectedGroupId((prev) => (prev === groupId ? null : groupId)); + }; + + const { data: groups = [] } = useQuery({ + queryKey: ["groups", selectedPatient?.id], + enabled: !!selectedPatient, queryFn: async () => { - const res = await apiRequest("GET", "/api/documents/claim-pdf/recent"); + const res = await apiRequest( + "GET", + `/api/documents/pdf-groups/patient/${selectedPatient?.id}` + ); + return res.json(); + }, + }); + + const { data: groupPdfs = [] } = useQuery({ + queryKey: ["groupPdfs", selectedGroupId], + enabled: !!selectedGroupId, + queryFn: async () => { + const res = await apiRequest( + "GET", + `/api/documents/pdf-files/group/${selectedGroupId}` + ); return res.json(); }, }); const deletePdfMutation = useMutation({ mutationFn: async (id: number) => { - await apiRequest("DELETE", `/api/documents/claim-pdf/${id}`); + await apiRequest("DELETE", `/api/documents/pdf-files/${id}`); }, onSuccess: () => { setIsDeletePdfOpen(false); setCurrentPdf(null); - queryClient.invalidateQueries({ - queryKey: ["/api/documents/claim-pdf/recent"], - }); - - toast({ - title: "Success", - description: "PDF deleted successfully!", - variant: "default", - }); + if (selectedGroupId != null) { + queryClient.invalidateQueries({ + queryKey: ["groupPdfs", selectedGroupId], + }); + } + toast({ title: "Success", description: "PDF deleted successfully!" }); }, onError: (error: any) => { - console.error("Error deleting PDF:", error); toast({ title: "Error", - description: `Failed to delete PDF: ${error.message || error}`, + description: error.message || "Failed to delete PDF", variant: "destructive", }); }, }); - const formatDate = (dateString: string) => { - const date = new Date(dateString); - return date.toLocaleDateString("en-US", { - month: "short", - day: "numeric", - year: "numeric", - }); - }; - - const getPatientInitials = (first: string, last: string) => - `${first[0]}${last[0]}`.toUpperCase(); - - const [fileBlobUrl, setFileBlobUrl] = useState(null); - - useEffect(() => { - if (!selectedPdfId) return; - let url: string | null = null; - - const fetchPdf = async () => { - try { - const res = await apiRequest( - "GET", - `/api/documents/claim-pdf/${selectedPdfId}` - ); - - const arrayBuffer = await res.arrayBuffer(); - const blob = new Blob([arrayBuffer], { type: "application/pdf" }); - const objectUrl = URL.createObjectURL(blob); - setFileBlobUrl(objectUrl); - url = objectUrl; - } catch (err) { - console.error("Failed to load PDF", err); - } - }; - - fetchPdf(); - - return () => { - if (url) { - URL.revokeObjectURL(url); - } - }; - }, [selectedPdfId]); - - const toggleMobileMenu = () => setIsMobileMenuOpen((prev) => !prev); - - const viewPdf = (pdfId: number) => { - setSelectedPdfId(pdfId); - }; - - const downloadPdf = async (pdfId: number, filename: string) => { - try { - const res = await apiRequest("GET", `/api/documents/claim-pdf/${pdfId}`); - const arrayBuffer = await res.arrayBuffer(); - const blob = new Blob([arrayBuffer], { type: "application/pdf" }); - const url = URL.createObjectURL(blob); - - const a = document.createElement("a"); - a.href = url; - a.download = filename; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - URL.revokeObjectURL(url); - } catch (err) { - console.error("Failed to download PDF:", err); - } - }; - - const handleDeletePdf = (pdf: ClaimPdf) => { - setCurrentPdf(pdf); - setIsDeletePdfOpen(true); - }; - const handleConfirmDeletePdf = () => { if (currentPdf) { deletePdfMutation.mutate(currentPdf.id); @@ -171,37 +110,31 @@ export default function DocumentsPage() { } }; - const filteredPdfs = pdfs.filter((pdf) => { - const patient = pdf.patient; - const searchLower = searchTerm.toLowerCase(); - const fullName = `${patient.firstName} ${patient.lastName}`.toLowerCase(); - const patientId = `PID-${patient.id.toString().padStart(4, "0")}`; + const handleViewPdf = async (pdfId: number) => { + const res = await apiRequest("GET", `/api/documents/pdf-files/${pdfId}`); + const arrayBuffer = await res.arrayBuffer(); + const blob = new Blob([arrayBuffer], { type: "application/pdf" }); + const url = URL.createObjectURL(blob); + setFileBlobUrl(url); + setSelectedPdfId(pdfId); + }; - switch (searchField) { - case "name": - return fullName.includes(searchLower); - case "id": - return patientId.toLowerCase().includes(searchLower); - case "phone": - return patient.phone?.toLowerCase().includes(searchLower) || false; - case "all": - default: - return ( - fullName.includes(searchLower) || - patientId.includes(searchLower) || - patient.phone?.toLowerCase().includes(searchLower) || - patient.email?.toLowerCase().includes(searchLower) || - false - ); - } - }); - - const totalPages = Math.ceil(filteredPdfs.length / itemsPerPage); - const startIndex = (currentPage - 1) * itemsPerPage; - const currentPdfs = filteredPdfs.slice(startIndex, startIndex + itemsPerPage); + const handleDownloadPdf = async (pdfId: number, filename: string) => { + const res = await apiRequest("GET", `/api/documents/pdf-files/${pdfId}`); + const arrayBuffer = await res.arrayBuffer(); + const blob = new Blob([arrayBuffer], { type: "application/pdf" }); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = filename; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + }; return ( -
+
-
+

Documents @@ -221,251 +154,142 @@ export default function DocumentsPage() {

- - -
-
- - setSearchTerm(e.target.value)} - className="pl-10" - /> -
-
- - -
-
-
-
+ {selectedPatient && ( + + + + Document Groups for {selectedPatient.firstName}{" "} + {selectedPatient.lastName} + + Select a group to view PDFs + + + {groups.length === 0 ? ( +

+ No groups found for this patient. +

+ ) : ( + groups.map((group: any) => ( + + )) + )} +
+
+ )} - - - {isLoading ? ( -
Loading data...
- ) : currentPdfs.length === 0 ? ( -
- {searchTerm - ? "No results matching your search." - : "No recent claim PDFs available."} -
- ) : ( - <> -
-
Patient
-
DOB / Gender
-
Contact
-
Insurance
-
Status
-
Actions
-
- - {currentPdfs.map((pdf) => { - const patient = pdf.patient; - return ( -
-
-
- {getPatientInitials( - patient.firstName, - patient.lastName - )} -
-
-
- {patient.firstName} {patient.lastName} -
-
- PID-{patient.id.toString().padStart(4, "0")} -
-
-
- -
-
- {formatDate(patient.dateOfBirth)} -
-
- {patient.gender} -
-
- -
-
- {patient.phone || "Not provided"} -
-
- {patient.email || "No email"} -
-
- -
-
- {patient.insuranceProvider - ? `${patient.insuranceProvider.charAt(0).toUpperCase()}${patient.insuranceProvider.slice(1)}` - : "Not specified"} -
-
- ID: {patient.insuranceId || "N/A"} -
-
- -
- - {patient.status === "active" - ? "Active" - : "Inactive"} - -
- -
-
- - - - -
-
-
- ); - })} - - setIsDeletePdfOpen(false)} - entityName={`PDF #${currentPdf?.id}`} - /> - - {/* PDF Viewer */} - {selectedPdfId && fileBlobUrl && ( -
-
-

- Viewing PDF #{selectedPdfId} -

+ {selectedGroupId && ( + + + PDFs in Group #{selectedGroupId} + + + {groupPdfs.length === 0 ? ( +

+ No PDFs found in this group. +

+ ) : ( + groupPdfs.map((pdf: any) => ( +
+ {pdf.filename} +
+ +
-
- - - -
- )} + )) + )} +
+
+ )} - {/* Pagination */} - {totalPages > 1 && ( -
-
- Showing {startIndex + 1} to{" "} - {Math.min( - startIndex + itemsPerPage, - filteredPdfs.length - )}{" "} - of {filteredPdfs.length} results -
-
- - {Array.from( - { length: totalPages }, - (_, i) => i + 1 - ).map((page) => ( - - ))} - -
-
- )} - - )} + + + Patient Records + + Select a patient to view document groups + + + + + + setIsDeletePdfOpen(false)} + entityName={`PDF #${currentPdf?.id}`} + /> + + {fileBlobUrl && ( + + + Viewing PDF #{selectedPdfId} + + + +