From 9df6c9f542e84b898f4072b5b66c3301e7056e24 Mon Sep 17 00:00:00 2001 From: AI Assistant Date: Wed, 25 Mar 2026 00:03:39 +0200 Subject: [PATCH] fix(rgi): default columns, date sort, clean filenames, green icon downloads all MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Default columns: Nr. cerere, Solicitant, Termen, Status, Rezolutie, UAT (matching user's preferred view). Obiect, Identificatori, Deponent, Data depunere now off by default. Date sort: dueDate and appDate columns now sort by raw timestamp (not by DD.MM.YYYY string which sorted incorrectly). Filenames: removed long documentPk from filename. Now uses DocType_AppNo.pdf (e.g. Receptie_tehnica_66903.pdf). Duplicate types get suffix: Receptie_tehnica_66903_2.pdf. Green icon: click downloads ALL documents from that application sequentially. Shows spinner while downloading. Tooltip shows "Nr. 66903 — click descarca toate" + details. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/app/(modules)/rgi-test/page.tsx | 116 ++++++++++++++++++++++------ 1 file changed, 92 insertions(+), 24 deletions(-) diff --git a/src/app/(modules)/rgi-test/page.tsx b/src/app/(modules)/rgi-test/page.tsx index c16eb79..afee236 100644 --- a/src/app/(modules)/rgi-test/page.tsx +++ b/src/app/(modules)/rgi-test/page.tsx @@ -176,32 +176,32 @@ const ALL_COLUMNS: ColumnDef[] = [ { key: "applicationObject", label: "Obiect", - defaultVisible: true, + defaultVisible: false, render: (a) => a.applicationObject || "-", }, { key: "identifiers", label: "Identificatori (IE/CF)", - defaultVisible: true, + defaultVisible: false, render: (a) => a.identifiers || "-", className: "text-xs max-w-[300px] truncate", }, { key: "deponent", label: "Deponent", - defaultVisible: true, + defaultVisible: false, render: (a) => a.deponent || "-", }, { key: "requester", label: "Solicitant", - defaultVisible: false, + defaultVisible: true, render: (a) => a.requester || "-", }, { key: "appDate", label: "Data depunere", - defaultVisible: true, + defaultVisible: false, render: (a) => fmtTs(a.appDate), className: "tabular-nums", }, @@ -240,7 +240,7 @@ const ALL_COLUMNS: ColumnDef[] = [ { key: "uat", label: "UAT", - defaultVisible: false, + defaultVisible: true, render: (a) => a.uat || "-", }, { @@ -357,10 +357,18 @@ function IssuedDocsPanel({ let downloaded = 0; let blocked = 0; + // Count duplicates by docType for naming (e.g. Receptie_tehnica_66903_2.pdf) + const typeCounts: Record = {}; + for (const d of docs) typeCounts[d.docType || "Document"] = (typeCounts[d.docType || "Document"] || 0) + 1; + const typeIdx: Record = {}; + 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}`; + const typeKey = doc.docType || "Document"; + typeIdx[typeKey] = (typeIdx[typeKey] || 0) + 1; + const suffix = (typeCounts[typeKey] ?? 0) > 1 ? `_${typeIdx[typeKey]}` : ""; + const filename = `${docName}_${appNo}${suffix}.${ext}`; setDownloadProgress(`${downloaded + blocked + 1}/${docs.length}: ${doc.docType || "Document"}...`); const url = @@ -564,6 +572,7 @@ export default function RgiTestPage() { const [totalCount, setTotalCount] = useState(0); const [expandedPk, setExpandedPk] = useState(null); const [showColumnPicker, setShowColumnPicker] = useState(false); + const [downloadingAppPk, setDownloadingAppPk] = useState(null); const [filterMode, setFilterMode] = useState<"all" | "solved" | "confirmed">( "solved", ); @@ -589,6 +598,68 @@ export default function RgiTestPage() { [visibleCols], ); + const downloadAllForApp = useCallback(async (app: App) => { + if (downloadingAppPk) return; + setDownloadingAppPk(app.applicationPk); + try { + // Fetch issued docs + const res = await fetch( + `/api/eterra/rgi/issued-docs?applicationId=${app.applicationPk}&workspaceId=${app.workspaceId}`, + ); + const data = await res.json(); + const docs: IssuedDoc[] = Array.isArray(data) + ? data + : data?.content ?? data?.data ?? data?.list ?? []; + + if (docs.length === 0) { + setDownloadingAppPk(null); + return; + } + + // Count by type for dedup naming + const typeCounts: Record = {}; + for (const d of docs) typeCounts[d.docType || "Document"] = (typeCounts[d.docType || "Document"] || 0) + 1; + const typeIdx: Record = {}; + + for (const doc of docs) { + const docName = sanitize(doc.docType || doc.documentTypeCode || "Document"); + const ext = (doc.fileExtension || "pdf").toLowerCase(); + const typeKey = doc.docType || "Document"; + typeIdx[typeKey] = (typeIdx[typeKey] || 0) + 1; + const suffix = (typeCounts[typeKey] ?? 0) > 1 ? `_${typeIdx[typeKey]}` : ""; + const filename = `${docName}_${app.appNo}${suffix}.${ext}`; + + const url = + `/api/eterra/rgi/download-doc?workspaceId=${doc.workspaceId || app.workspaceId}` + + `&applicationId=${doc.applicationId || app.applicationPk}` + + `&documentPk=${doc.documentPk}` + + `&documentTypeId=${doc.documentTypeId}` + + `&docType=${encodeURIComponent(doc.docType || "")}` + + `&appNo=${app.appNo}`; + + try { + const r = await fetch(url); + const ct = r.headers.get("content-type") || ""; + if (ct.includes("application/json")) continue; // blocked + const blob = await r.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); + await new Promise((resolve) => setTimeout(resolve, 300)); + } catch { + // skip + } + } + } catch { + // silent + } + setDownloadingAppPk(null); + }, [downloadingAppPk]); + const handleSort = useCallback( (key: string) => { setSortState((prev) => { @@ -661,24 +732,19 @@ export default function RgiTestPage() { const col = ALL_COLUMNS.find((c) => c.key === sortState.key); if (col) { const dir = sortState.dir === "asc" ? 1 : -1; + // Use raw timestamps for date columns + const dateKeys = new Set(["dueDate", "appDate"]); result = [...result].sort((a, b) => { + if (dateKeys.has(sortState.key)) { + const va = (a[sortState.key] as number) || 0; + const vb = (b[sortState.key] as number) || 0; + return (va - vb) * dir; + } const va = col.render(a); const vb = col.render(b); - // Try numeric comparison first const na = parseFloat(va); const nb = parseFloat(vb); if (!isNaN(na) && !isNaN(nb)) return (na - nb) * dir; - // Date comparison (dd.mm.yyyy format) - if (va.includes(".") && vb.includes(".")) { - const pa = va.split("."); - const pb = vb.split("."); - if (pa.length === 3 && pb.length === 3) { - const da = new Date(`${pa[2]}-${pa[1]}-${pa[0]}`).getTime(); - const db = new Date(`${pb[2]}-${pb[1]}-${pb[0]}`).getTime(); - if (!isNaN(da) && !isNaN(db)) return (da - db) * dir; - } - } - // String comparison return va.localeCompare(vb, "ro") * dir; }); } @@ -902,19 +968,21 @@ export default function RgiTestPage() { className="focus:outline-none" onClick={(e) => { e.stopPropagation(); - // Download all docs for this app - setExpandedPk(pk); + void downloadAllForApp(app); }} + disabled={downloadingAppPk === pk} > - {solved ? ( - + {downloadingAppPk === pk ? ( + + ) : solved ? ( + ) : ( )} -

Nr. {app.appNo}

+

Nr. {app.appNo} — click descarca toate

{app.applicationObject || "-"}

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

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