From 536c2fe3acb2d5c38e35e3969a6e9907f14717bd Mon Sep 17 00:00:00 2001 From: Potenz Date: Sun, 21 Sep 2025 01:45:53 +0530 Subject: [PATCH] feat(pdf-preview-modal) - updated files1 --- apps/Backend/src/routes/insuranceStatus.ts | 25 ++- .../insurance-status/pdf-preview-modal.tsx | 183 ++++++++++++++++++ .../src/pages/insurance-status-page.tsx | 40 ++++ 3 files changed, 246 insertions(+), 2 deletions(-) create mode 100644 apps/Frontend/src/components/insurance-status/pdf-preview-modal.tsx diff --git a/apps/Backend/src/routes/insuranceStatus.ts b/apps/Backend/src/routes/insuranceStatus.ts index 4e9bdbb..e736dfc 100644 --- a/apps/Backend/src/routes/insuranceStatus.ts +++ b/apps/Backend/src/routes/insuranceStatus.ts @@ -46,6 +46,8 @@ router.post( const result = await forwardToSeleniumInsuranceEligibilityAgent(enrichedData); + let createdPdfFileId: number | null = null; + // ✅ Step 1: Check result and update patient status const patient = await storage.getPatientByInsuranceId( insuranceEligibilityData.memberId @@ -80,12 +82,18 @@ router.post( if (!group?.id) { throw new Error("PDF group creation failed: missing group ID"); } - await storage.createPdfFile( + + const created = await storage.createPdfFile( group.id, path.basename(result.pdf_path), pdfBuffer ); + // created could be { id, filename } or just id, adapt to your storage API. + if (created && typeof created === "object" && "id" in created) { + createdPdfFileId = Number(created.id); + } + await fs.unlink(result.pdf_path); result.pdfUploadStatus = `PDF saved to group: ${group.title}`; @@ -101,6 +109,7 @@ router.post( res.json({ patientUpdateStatus: result.patientUpdateStatus, pdfUploadStatus: result.pdfUploadStatus, + pdfFileId: createdPdfFileId, }); } catch (err: any) { console.error(err); @@ -175,6 +184,8 @@ router.post( const result = await forwardToSeleniumInsuranceClaimStatusAgent(enrichedData); + let createdPdfFileId: number | null = null; + // ✅ Step 1: Check result const patient = await storage.getPatientByInsuranceId( insuranceClaimStatusData.memberId @@ -239,7 +250,15 @@ router.post( // Use the basename for storage const basename = path.basename(generatedPdfPath); - await storage.createPdfFile(group.id, basename, pdfBuffer); + const created = await storage.createPdfFile( + group.id, + basename, + pdfBuffer + ); + + if (created && typeof created === "object" && "id" in created) { + createdPdfFileId = Number(created.id); + } // Clean up temp files: try { @@ -268,7 +287,9 @@ router.post( res.json({ pdfUploadStatus: result.pdfUploadStatus, + pdfFileId: createdPdfFileId, }); + return; } catch (err: any) { console.error(err); return res.status(500).json({ diff --git a/apps/Frontend/src/components/insurance-status/pdf-preview-modal.tsx b/apps/Frontend/src/components/insurance-status/pdf-preview-modal.tsx new file mode 100644 index 0000000..347deda --- /dev/null +++ b/apps/Frontend/src/components/insurance-status/pdf-preview-modal.tsx @@ -0,0 +1,183 @@ +import React, { useEffect, useState } from "react"; +import { Button } from "@/components/ui/button"; +import { apiRequest } from "@/lib/queryClient"; + +interface Props { + open: boolean; + onClose: () => void; + pdfId?: number | null; + fallbackFilename?: string | null; // optional fallback +} + +/** + * Parse filename from Content-Disposition header. + * Returns string | null. + */ +function parseFilenameFromContentDisposition(header: string | null): string | null { + if (!header) return null; + + // filename* (RFC 5987): filename*=UTF-8''encoded + const filenameStarMatch = header.match(/filename\*\s*=\s*([^;]+)/i); + if (filenameStarMatch && filenameStarMatch[1]) { + let raw = filenameStarMatch[1].trim(); + raw = raw.replace(/^"(.*)"$/, "$1"); // remove quotes + const parts = raw.split("''"); + if (parts.length === 2 && parts[1]) { + try { + return decodeURIComponent(parts[1]); + } catch { + return parts[1]; + } + } + try { + return decodeURIComponent(raw); + } catch { + return raw; + } + } + + // filename="..." or filename=... + const filenameMatchQuoted = header.match(/filename\s*=\s*"([^"]+)"/i); + if (filenameMatchQuoted && filenameMatchQuoted[1]) { + return filenameMatchQuoted[1].trim(); + } + const filenameMatch = header.match(/filename\s*=\s*([^;]+)/i); + if (filenameMatch && filenameMatch[1]) { + return filenameMatch[1].trim().replace(/^"(.*)"$/, "$1"); + } + + return null; +} + +export function PdfPreviewModal({ + open, + onClose, + pdfId, + fallbackFilename = null, +}: Props) { + const [fileBlobUrl, setFileBlobUrl] = useState(null); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [resolvedFilename, setResolvedFilename] = useState(null); + + useEffect(() => { + if (!open) return; + + let objectUrl: string | null = null; + const controller = new AbortController(); + let aborted = false; + + const fetchPdf = async () => { + if (!pdfId) { + setError("No PDF id provided."); + return; + } + + setLoading(true); + setError(null); + setResolvedFilename(null); + + try { + // Use the same apiRequest signature as DocumentsPage. + // We don't pass the AbortSignal into apiRequest to avoid signature mismatch. + const res = await apiRequest("GET", `/api/documents/pdf-files/${pdfId}`); + + if (!res) { + throw new Error("No response from server"); + } + + if (!res.ok) { + // try to get error text for better message + const txt = await res.text().catch(() => ""); + throw new Error(txt || `Failed to fetch PDF: ${res.status}`); + } + + // read headers safely + const contentDispHeader = + res.headers?.get?.("content-disposition") ?? + res.headers?.get?.("Content-Disposition") ?? + null; + + const parsedFilename = parseFilenameFromContentDisposition(contentDispHeader); + // final name (string) + const finalName = parsedFilename ?? fallbackFilename ?? `file_${pdfId}.pdf`; + setResolvedFilename(finalName); + + const arrayBuffer = await res.arrayBuffer(); + if (aborted) return; + + const blob = new Blob([arrayBuffer], { type: "application/pdf" }); + objectUrl = URL.createObjectURL(blob); + setFileBlobUrl(objectUrl); + } catch (err: any) { + if (err && (err.name === "AbortError" || err.message === "The user aborted a request.")) { + // ignore abort error + return; + } + console.error("PdfPreviewModal fetch error:", err); + setError(err?.message ?? "Failed to fetch PDF"); + } finally { + setLoading(false); + } + }; + + fetchPdf(); + + return () => { + aborted = true; + controller.abort(); + if (objectUrl) { + URL.revokeObjectURL(objectUrl); + } + setFileBlobUrl(null); + setError(null); + setLoading(false); + setResolvedFilename(null); + }; + }, [open, pdfId, fallbackFilename]); + + if (!open) return null; + + const handleDownload = () => { + if (!fileBlobUrl) return; + const a = document.createElement("a"); + a.href = fileBlobUrl; + a.download = resolvedFilename ?? `file_${pdfId ?? "unknown"}.pdf`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + }; + + return ( +
+
+
+
+

{resolvedFilename ?? "PDF Preview"}

+

{pdfId ? `ID: ${pdfId}` : ""}

+
+
+ + +
+
+ +
+ {loading &&
Loading PDF…
} + {error &&
Error: {error}
} + {fileBlobUrl && ( +