From aebe1d521c3a5feae073fa819100f87a37322e76 Mon Sep 17 00:00:00 2001 From: AI Assistant Date: Tue, 24 Mar 2026 23:41:51 +0200 Subject: [PATCH] feat(rgi): download all docs button + tooltips on status icon Download all: - "Descarca toate" button in expanded docs panel - Downloads each document sequentially with correct filename (e.g. Receptie_tehnica_66903_10183217654.pdf) - Progress indicator: "2/5: Harti & planuri..." - Skips blocked docs, shows summary "3 descarcate, 2 indisponibile" - 300ms delay between downloads to avoid browser blocking Status icon tooltip: - Hover on green/clock icon shows: Nr cerere, Obiect, Status, Rezolutie, Termen, Identificatori Co-Authored-By: Claude Opus 4.6 (1M context) --- src/app/(modules)/rgi-test/page.tsx | 147 +++++++++++++++++++++++++--- 1 file changed, 134 insertions(+), 13 deletions(-) diff --git a/src/app/(modules)/rgi-test/page.tsx b/src/app/(modules)/rgi-test/page.tsx index f742851..c16eb79 100644 --- a/src/app/(modules)/rgi-test/page.tsx +++ b/src/app/(modules)/rgi-test/page.tsx @@ -28,7 +28,14 @@ import { ArrowUpDown, ArrowUp, ArrowDown, + Archive, } from "lucide-react"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/shared/components/ui/tooltip"; import { cn } from "@/shared/lib/utils"; /* ------------------------------------------------------------------ */ @@ -278,6 +285,22 @@ function matchesSearch(text: string, query: string): boolean { ); } +/* ------------------------------------------------------------------ */ +/* Filename sanitizer (client-side) */ +/* ------------------------------------------------------------------ */ + +function sanitize(raw: string): string { + return raw + .replace(/[ăâ]/g, "a") + .replace(/[ĂÂ]/g, "A") + .replace(/[îÎ]/g, "i") + .replace(/[țȚ]/g, "t") + .replace(/[șȘ]/g, "s") + .replace(/[^a-zA-Z0-9._-]/g, "_") + .replace(/_+/g, "_") + .replace(/^_|_$/g, ""); +} + /* ------------------------------------------------------------------ */ /* Issued Documents panel */ /* ------------------------------------------------------------------ */ @@ -295,6 +318,8 @@ function IssuedDocsPanel({ const [loading, setLoading] = useState(true); const [error, setError] = useState(""); const [blockedDocPk, setBlockedDocPk] = useState(null); + const [downloadingAll, setDownloadingAll] = useState(false); + const [downloadProgress, setDownloadProgress] = useState(""); const blockedTimerRef = useRef | null>(null); useEffect(() => { @@ -326,6 +351,58 @@ function IssuedDocsPanel({ }; }, []); + const handleDownloadAll = useCallback(async () => { + if (!docs || docs.length === 0 || downloadingAll) return; + setDownloadingAll(true); + let downloaded = 0; + let blocked = 0; + + for (const doc of docs) { + const docName = sanitize(doc.docType || doc.documentTypeCode || "Document"); + const ext = (doc.fileExtension || "pdf").toLowerCase(); + const filename = `${docName}_${appNo}_${doc.documentPk}.${ext}`; + setDownloadProgress(`${downloaded + blocked + 1}/${docs.length}: ${doc.docType || "Document"}...`); + + const url = + `/api/eterra/rgi/download-doc?workspaceId=${doc.workspaceId || workspaceId}` + + `&applicationId=${doc.applicationId || applicationPk}` + + `&documentPk=${doc.documentPk}` + + `&documentTypeId=${doc.documentTypeId}` + + `&docType=${encodeURIComponent(doc.docType || "")}` + + `&appNo=${appNo}`; + + try { + const res = await fetch(url); + const ct = res.headers.get("content-type") || ""; + if (ct.includes("application/json")) { + blocked++; + continue; + } + const blob = await res.blob(); + const a = document.createElement("a"); + a.href = URL.createObjectURL(blob); + a.download = filename; + document.body.appendChild(a); + a.click(); + URL.revokeObjectURL(a.href); + document.body.removeChild(a); + downloaded++; + // Small delay between downloads so browser doesn't block them + await new Promise((r) => setTimeout(r, 300)); + } catch { + blocked++; + } + } + + setDownloadProgress( + blocked > 0 + ? `${downloaded} descarcat${downloaded !== 1 ? "e" : ""}, ${blocked} indisponibil${blocked !== 1 ? "e" : ""}` + : `${downloaded} document${downloaded !== 1 ? "e" : ""} descarcat${downloaded !== 1 ? "e" : ""}`, + ); + setDownloadingAll(false); + setTimeout(() => setDownloadProgress(""), 5000); + }, [docs, downloadingAll, workspaceId, applicationPk, appNo]); + const handleDownload = useCallback( async (doc: IssuedDoc, e: React.MouseEvent) => { e.stopPropagation(); @@ -399,10 +476,31 @@ function IssuedDocsPanel({ return (
-

- {docs.length} document{docs.length > 1 ? "e" : ""} eliberat - {docs.length > 1 ? "e" : ""}: -

+
+

+ {docs.length} document{docs.length > 1 ? "e" : ""} eliberat + {docs.length > 1 ? "e" : ""} +

+
+ {downloadProgress && ( + {downloadProgress} + )} + +
+
{docs.map((doc, i) => (
@@ -795,15 +893,38 @@ export default function RgiTestPage() { setExpandedPk(isExpanded ? null : pk) } > - - {solved ? ( - - ) : ( - - )} + + + + + + + +

Nr. {app.appNo}

+

{app.applicationObject || "-"}

+

Status: {app.statusName || app.stateCode}

+

Rezolutie: {app.resolutionName || "-"}

+

Termen: {fmtTs(app.dueDate)}

+ {app.identifiers && ( +

{app.identifiers}

+ )} +
+
+
{columns.map((col) => (