import { useEffect, useMemo, useState } from "react"; import { useQuery, useMutation } from "@tanstack/react-query"; import { Card, CardHeader, CardTitle, CardDescription, CardContent, } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { toast } from "@/hooks/use-toast"; import { apiRequest, queryClient } from "@/lib/queryClient"; import { Eye, Trash, Download, FolderOpen } from "lucide-react"; import { DeleteConfirmationDialog } from "@/components/ui/deleteDialog"; import { PatientTable } from "@/components/patients/patient-table"; import { Patient, PdfFile } from "@repo/db/types"; import { Pagination, PaginationContent, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious, } from "@/components/ui/pagination"; import DocumentsFilePreviewModal from "@/components/documents/file-preview-modal"; import { getPageNumbers } from "@/utils/pageNumberGenerator"; export default function DocumentsPage() { const [selectedPatient, setSelectedPatient] = useState(null); const [expandedGroupId, setExpandedGroupId] = useState(null); // pagination state for the expanded group // pagination state const [currentPage, setCurrentPage] = useState(1); const [limit, setLimit] = useState(5); const offset = (currentPage - 1) * limit; const [totalForExpandedGroup, setTotalForExpandedGroup] = useState< number | null >(null); // PDF view state - viewing / downloading const [previewFileId, setPreviewFileId] = useState(null); const [isPreviewOpen, setIsPreviewOpen] = useState(false); const [previewFileNameHint, setPreviewFileNameHint] = useState( null ); const [currentPdf, setCurrentPdf] = useState(null); // Delete dialog const [isDeletePdfOpen, setIsDeletePdfOpen] = useState(false); // reset UI when patient changes useEffect(() => { setExpandedGroupId(null); setLimit(5); setCurrentPage(1); setTotalForExpandedGroup(null); // close the preview modal and clear any preview hints setIsPreviewOpen(false); setPreviewFileId(null); setPreviewFileNameHint(null); }, [selectedPatient]); // FETCH GROUPS for patient (includes `category` on each group) const { data: groups = [], isLoading: isLoadingGroups } = useQuery({ queryKey: ["groups", selectedPatient?.id], enabled: !!selectedPatient, queryFn: async () => { const res = await apiRequest( "GET", `/api/documents/pdf-groups/patient/${selectedPatient?.id}` ); return res.json(); }, }); // Group groups by titleKey (use titleKey only for grouping/ordering; don't display it) const groupsByTitleKey = useMemo(() => { const map = new Map(); for (const g of groups as any[]) { const key = String(g.titleKey ?? "OTHER"); if (!map.has(key)) map.set(key, []); map.get(key)!.push(g); } // Decide on a stable order for titleKey buckets: prefer enum order then any extras const preferredOrder = [ "INSURANCE_CLAIM", "ELIGIBILITY_STATUS", "CLAIM_STATUS", "OTHER", ]; const orderedKeys: string[] = []; for (const k of preferredOrder) { if (map.has(k)) orderedKeys.push(k); } // append any keys that weren't in preferredOrder for (const k of map.keys()) { if (!orderedKeys.includes(k)) orderedKeys.push(k); } return { map, orderedKeys }; }, [groups]); // FETCH PDFs for selected group with pagination (limit & offset) const { data: groupPdfsResponse, refetch: refetchGroupPdfs, isFetching: isFetchingPdfs, } = useQuery({ queryKey: ["groupPdfs", expandedGroupId, currentPage, limit], enabled: !!expandedGroupId, queryFn: async () => { // API should accept ?limit & ?offset and also return total count const res = await apiRequest( "GET", `/api/documents/recent-pdf-files/group/${expandedGroupId}?limit=${limit}&offset=${offset}` ); // expected shape: { data: PdfFile[], total: number } const json = await res.json(); setTotalForExpandedGroup(json.total ?? null); return json.data ?? []; }, }); const groupPdfs: PdfFile[] = groupPdfsResponse ?? []; // DELETE mutation const deletePdfMutation = useMutation({ mutationFn: async (id: number) => { await apiRequest("DELETE", `/api/documents/pdf-files/${id}`); }, onSuccess: () => { setIsDeletePdfOpen(false); setCurrentPdf(null); queryClient.invalidateQueries({ queryKey: ["groupPdfs", expandedGroupId], }); toast({ title: "Success", description: "PDF deleted successfully!" }); }, onError: (error: any) => { toast({ title: "Error", description: error.message || "Failed to delete PDF", variant: "destructive", }); }, }); const handleConfirmDeletePdf = () => { if (currentPdf) { deletePdfMutation.mutate(Number(currentPdf.id)); } else { toast({ title: "Error", description: "No PDF selected for deletion.", variant: "destructive", }); } }; const handleViewPdf = (pdfId: number, filename?: string) => { setPreviewFileNameHint(filename ?? null); setPreviewFileId(pdfId); setIsPreviewOpen(true); }; 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); }; // Expand / collapse a group — when expanding reset pagination const toggleExpandGroup = (groupId: number) => { if (expandedGroupId === groupId) { setExpandedGroupId(null); setCurrentPage(1); setLimit(5); setTotalForExpandedGroup(null); } else { setExpandedGroupId(groupId); setCurrentPage(1); setLimit(5); setTotalForExpandedGroup(null); } }; // pagintaion helper const totalPages = totalForExpandedGroup ? Math.ceil(totalForExpandedGroup / limit) : 1; const startItem = totalForExpandedGroup ? offset + 1 : 0; const endItem = totalForExpandedGroup ? Math.min(offset + limit, totalForExpandedGroup) : 0; return (

Documents

View and manage recent uploaded claim PDFs

{selectedPatient && ( Document groups of Patient: {selectedPatient.firstName}{" "} {selectedPatient.lastName} {isLoadingGroups ? (
Loading groups…
) : (groups as any[]).length === 0 ? (
No groups found.
) : (
{groupsByTitleKey.orderedKeys.map((key, idx) => { const bucket = groupsByTitleKey.map.get(key) ?? []; return (
{/* subtle divider between buckets (no enum text shown) */} {idx !== 0 && (
)}
{bucket.map((group: any) => (
{/* Only show the group's title string */}
{group.title}
{/* expanded content: show paginated PDFs for this group */} {expandedGroupId === group.id && (
{isFetchingPdfs ? (
Loading PDFs…
) : groupPdfs.length === 0 ? (
No PDFs in this group.
) : (
{groupPdfs.map((pdf) => (
{pdf.filename}
))} {/* Pagination */} {totalPages > 1 && (
Showing {startItem}–{endItem} of{" "} {totalForExpandedGroup || 0}{" "} results
{ e.preventDefault(); if (currentPage > 1) setCurrentPage( currentPage - 1 ); }} className={ currentPage === 1 ? "pointer-events-none opacity-50" : "" } /> {getPageNumbers( currentPage, totalPages ).map((page, idx) => ( {page === "..." ? ( ... ) : ( { e.preventDefault(); setCurrentPage( page as number ); }} isActive={ currentPage === page } > {page} )} ))} { e.preventDefault(); if ( currentPage < totalPages ) setCurrentPage( currentPage + 1 ); }} className={ currentPage === totalPages ? "pointer-events-none opacity-50" : "" } />
)}
)}
)}
))}
); })}
)}
)} { setIsPreviewOpen(false); setPreviewFileId(null); setPreviewFileNameHint(null); }} initialFileName={previewFileNameHint} /> Patient Records Select a patient to view document groups setIsDeletePdfOpen(false)} entityName={`PDF #${currentPdf?.id}`} />
); }