diff --git a/src/app/(modules)/rgi-test/page.tsx b/src/app/(modules)/rgi-test/page.tsx index 30a4861..c79d803 100644 --- a/src/app/(modules)/rgi-test/page.tsx +++ b/src/app/(modules)/rgi-test/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState, useCallback } from "react"; +import { useState, useCallback, useMemo } from "react"; import { Button } from "@/shared/components/ui/button"; import { Input } from "@/shared/components/ui/input"; import { Label } from "@/shared/components/ui/label"; @@ -16,6 +16,8 @@ import { CheckCircle2, Clock, AlertTriangle, + Settings2, + Shield, } from "lucide-react"; import { cn } from "@/shared/lib/utils"; @@ -23,35 +25,64 @@ import { cn } from "@/shared/lib/utils"; /* Types */ /* ------------------------------------------------------------------ */ -type Application = Record; -type IssuedDoc = Record; +type App = { + actorName: string; + adminUnit: number; + appDate: number; + appNo: number; + applicationObject: string; + applicationPk: number; + colorNumber: number; + communicationType: string; + deponent: string; + dueDate: number; + hasSolution: number; + identifiers: string; + initialAppNo: string; + orgUnit: string; + requester: string; + resolutionName: string; + stateCode: string; + statusName: string; + totalFee: number; + uat: string; + workspace: string; + workspaceId: number; + [key: string]: unknown; +}; + +type IssuedDoc = { + applicationId: number; + docType: string; + documentPk: number; + documentTypeCode: string; + documentTypeId: number; + fileExtension: string; + digitallySigned: number; + startDate: number; + lastUpdatedDtm: number; + initialAppNo: string; + workspaceId: number; + identifierDetails: string | null; + [key: string]: unknown; +}; /* ------------------------------------------------------------------ */ -/* Helpers */ +/* Column definitions */ /* ------------------------------------------------------------------ */ -function str(obj: Record, ...keys: string[]): string { - for (const k of keys) { - const v = obj[k]; - if (typeof v === "string" && v.length > 0) return v; - if (typeof v === "number") return String(v); - } - return ""; -} +type ColumnDef = { + key: string; + label: string; + defaultVisible: boolean; + render: (app: App) => string; + className?: string; +}; -function num(obj: Record, ...keys: string[]): number | null { - for (const k of keys) { - const v = obj[k]; - if (typeof v === "number") return v; - if (typeof v === "string" && /^\d+$/.test(v)) return parseInt(v, 10); - } - return null; -} - -function fmtDate(raw: unknown): string { - if (!raw) return "-"; - const d = typeof raw === "number" ? new Date(raw) : new Date(String(raw)); - if (isNaN(d.getTime())) return String(raw); +function fmtTs(ts: number | null | undefined): string { + if (!ts) return "-"; + const d = new Date(ts); + if (isNaN(d.getTime())) return "-"; return d.toLocaleDateString("ro-RO", { day: "2-digit", month: "2-digit", @@ -59,355 +90,222 @@ function fmtDate(raw: unknown): string { }); } -function isFutureDate(raw: unknown): boolean { - if (!raw) return false; - const d = typeof raw === "number" ? new Date(raw) : new Date(String(raw)); - return !isNaN(d.getTime()) && d > new Date(); -} - -function isSolved(app: Application): boolean { - const status = str(app, "state", "status", "stare", "applicationState") - .toLowerCase(); - const stateName = str( - app, - "stateName", - "statusName", - "stateDescription", - ).toLowerCase(); - return ( - status.includes("solutionat") || - stateName.includes("solutionat") || - status.includes("finalizat") || - stateName.includes("finalizat") || - status === "closed" || - status === "resolved" - ); -} +const ALL_COLUMNS: ColumnDef[] = [ + { + key: "appNo", + label: "Nr. cerere", + defaultVisible: true, + render: (a) => String(a.appNo ?? "-"), + className: "font-mono font-semibold", + }, + { + key: "initialAppNo", + label: "Nr. initial", + defaultVisible: false, + render: (a) => a.initialAppNo || "-", + className: "font-mono text-xs", + }, + { + key: "applicationObject", + label: "Obiect", + defaultVisible: true, + render: (a) => a.applicationObject || "-", + }, + { + key: "identifiers", + label: "Identificatori (IE/CF)", + defaultVisible: true, + render: (a) => a.identifiers || "-", + className: "text-xs max-w-[300px] truncate", + }, + { + key: "deponent", + label: "Deponent", + defaultVisible: true, + render: (a) => a.deponent || "-", + }, + { + key: "requester", + label: "Solicitant", + defaultVisible: false, + render: (a) => a.requester || "-", + }, + { + key: "appDate", + label: "Data depunere", + defaultVisible: true, + render: (a) => fmtTs(a.appDate), + className: "tabular-nums", + }, + { + key: "dueDate", + label: "Termen", + defaultVisible: true, + render: (a) => fmtTs(a.dueDate), + className: "tabular-nums", + }, + { + key: "statusName", + label: "Status", + defaultVisible: true, + render: (a) => a.statusName || a.stateCode || "-", + }, + { + key: "resolutionName", + label: "Rezolutie", + defaultVisible: true, + render: (a) => a.resolutionName || "-", + }, + { + key: "hasSolution", + label: "Solutionat", + defaultVisible: false, + render: (a) => (a.hasSolution === 1 ? "DA" : "NU"), + }, + { + key: "totalFee", + label: "Taxa (lei)", + defaultVisible: false, + render: (a) => (a.totalFee != null ? String(a.totalFee) : "-"), + className: "tabular-nums", + }, + { + key: "uat", + label: "UAT", + defaultVisible: false, + render: (a) => a.uat || "-", + }, + { + key: "orgUnit", + label: "OCPI", + defaultVisible: false, + render: (a) => a.orgUnit || "-", + }, + { + key: "communicationType", + label: "Comunicare", + defaultVisible: false, + render: (a) => a.communicationType || "-", + className: "text-xs", + }, + { + key: "actorName", + label: "Actor curent", + defaultVisible: false, + render: (a) => a.actorName || "-", + }, + { + key: "applicationPk", + label: "Application PK", + defaultVisible: false, + render: (a) => String(a.applicationPk ?? "-"), + className: "font-mono text-xs", + }, +]; /* ------------------------------------------------------------------ */ -/* Issued Documents sub-component */ +/* Issued Documents panel */ /* ------------------------------------------------------------------ */ function IssuedDocsPanel({ - applicationId, + applicationPk, workspaceId, }: { - applicationId: string; - workspaceId: string; + applicationPk: number; + workspaceId: number; }) { const [docs, setDocs] = useState(null); - const [loading, setLoading] = useState(false); + const [loading, setLoading] = useState(true); const [error, setError] = useState(""); - const [rawData, setRawData] = useState(null); - const loadDocs = useCallback(async () => { - setLoading(true); - setError(""); - try { - const res = await fetch( - `/api/eterra/rgi/issued-docs?applicationId=${encodeURIComponent(applicationId)}&workspaceId=${workspaceId}`, - ); - const data = await res.json(); - setRawData(data); - - // Try to extract docs array from various response shapes - let items: IssuedDoc[] = []; - if (Array.isArray(data)) { - items = data; - } else if (data?.content && Array.isArray(data.content)) { - items = data.content; - } else if (data?.data && Array.isArray(data.data)) { - items = data.data; - } else if (data?.list && Array.isArray(data.list)) { - items = data.list; - } else if (data?.results && Array.isArray(data.results)) { - items = data.results; - } - setDocs(items); - } catch { - setError("Eroare la incarcarea documentelor"); - } - setLoading(false); - }, [applicationId, workspaceId]); - - // Auto-load on mount + // Auto-load useState(() => { - void loadDocs(); + void (async () => { + try { + const res = await fetch( + `/api/eterra/rgi/issued-docs?applicationId=${applicationPk}&workspaceId=${workspaceId}`, + ); + const data = await res.json(); + const items: IssuedDoc[] = Array.isArray(data) + ? data + : data?.content ?? data?.data ?? data?.list ?? []; + setDocs(items); + } catch { + setError("Eroare la incarcarea documentelor"); + } + setLoading(false); + })(); }); if (loading) { return (
- Se incarca documentele eliberate... + Se incarca documentele...
); } if (error) { - return ( -
- {error} - -
- ); + return

{error}

; } if (!docs || docs.length === 0) { return ( -
- Niciun document eliberat gasit. - {rawData != null && ( -
- - Raspuns brut - -
-              {JSON.stringify(rawData, null, 2)}
-            
-
- )} -
+

+ Niciun document eliberat. +

); } return ( -
-

+

+

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

- {docs.map((doc, i) => { - const docName = str( - doc, - "documentName", - "name", - "fileName", - "tipDocument", - "documentType", - "description", - ); - const docId = str( - doc, - "issuedDocumentId", - "documentId", - "id", - "pk", - "docId", - ); - const fileId = str( - doc, - "fileId", - "documentFileId", - "filePk", - ); - const docDate = doc.createdDate ?? doc.dateCreated ?? doc.date ?? doc.issuedDate; - const docStatus = str(doc, "status", "state", "documentStatus"); - - return ( -
-
- -
-

- {docName || `Document #${docId || i + 1}`} -

-
- {docDate != null && {fmtDate(docDate)}} - {docId && ( - ID: {docId} - )} - {docStatus && ( - - {docStatus} - - )} -
+ {docs.map((doc, i) => ( +
+
+ +
+

+ {doc.docType || doc.documentTypeCode || "Document"} +

+
+ {fmtTs(doc.startDate || doc.lastUpdatedDtm)} + + .{(doc.fileExtension || "PDF").toLowerCase()} + + {doc.digitallySigned === 1 && ( + + + semnat + + )} + {doc.identifierDetails && ( + + {doc.identifierDetails} + + )}
- {(docId || fileId) && ( - - )}
- ); - })} -
- ); -} - -/* ------------------------------------------------------------------ */ -/* Application row */ -/* ------------------------------------------------------------------ */ - -function ApplicationRow({ - app, - workspaceId, -}: { - app: Application; - workspaceId: string; -}) { - const [expanded, setExpanded] = useState(false); - - const appNo = str( - app, - "applicationNumber", - "number", - "nrCerere", - "registrationNumber", - "appNo", - "applicationNo", - ); - const appId = str( - app, - "applicationId", - "id", - "pk", - "applicationPk", - ); - const services = str( - app, - "servicesDescription", - "services", - "serviceName", - "serviceType", - "tipServicii", - ); - const deponent = str( - app, - "deponent", - "deponentName", - "applicant", - "applicantName", - "solicitant", - ); - const filingDate = - app.filingDate ?? app.dataDepunere ?? app.createdDate ?? app.registrationDate; - const dueDate = app.dueDate ?? app.termen ?? app.deadlineDate ?? app.deadline; - const status = str( - app, - "stateName", - "state", - "status", - "stare", - "statusName", - "applicationState", - ); - const solved = isSolved(app); - const future = isFutureDate(dueDate); - - return ( -
- {/* Main row */} -
setExpanded(!expanded)} - > - {/* Status icon */} -
- {solved ? ( - - ) : future ? ( - - ) : ( - - )} +
- - {/* App number */} -
-

{appNo || appId || "-"}

-
- - {/* Service description */} -
-

{services || "-"}

- {deponent && ( -

- {deponent} -

- )} -
- - {/* Dates */} -
-
-

- Depus -

-

{fmtDate(filingDate)}

-
-
-

- Termen -

-

- {fmtDate(dueDate)} -

-
-
- - {/* Status badge */} - - {status || "-"} - - - {/* Expand arrow */} -
- {expanded ? ( - - ) : ( - - )} -
-
- - {/* Expanded: issued documents */} - {expanded && appId && ( -
- -
- )} + ))}
); } @@ -422,14 +320,36 @@ export default function RgiTestPage() { const [year, setYear] = useState("2026"); const [loading, setLoading] = useState(false); const [error, setError] = useState(""); - const [applications, setApplications] = useState([]); + const [applications, setApplications] = useState([]); const [totalCount, setTotalCount] = useState(0); const [filterSolved, setFilterSolved] = useState(true); + const [expandedPk, setExpandedPk] = useState(null); + const [showColumnPicker, setShowColumnPicker] = useState(false); + + // Column visibility — saved per session + const [visibleCols, setVisibleCols] = useState>( + () => new Set(ALL_COLUMNS.filter((c) => c.defaultVisible).map((c) => c.key)), + ); + + const toggleColumn = (key: string) => { + setVisibleCols((prev) => { + const next = new Set(prev); + if (next.has(key)) next.delete(key); + else next.add(key); + return next; + }); + }; + + const columns = useMemo( + () => ALL_COLUMNS.filter((c) => visibleCols.has(c.key)), + [visibleCols], + ); const loadApplications = useCallback(async () => { setLoading(true); setError(""); setApplications([]); + setExpandedPk(null); try { const res = await fetch("/api/eterra/rgi/applications", { method: "POST", @@ -443,57 +363,36 @@ export default function RgiTestPage() { }), }); const data = await res.json(); - if (data.error) { setError(data.error); return; } - - // Extract applications array - let items: Application[] = []; - if (Array.isArray(data)) { - items = data; - } else if (data?.content && Array.isArray(data.content)) { - items = data.content; - setTotalCount( - typeof data.totalElements === "number" - ? data.totalElements - : items.length, - ); - } else if (data?.data && Array.isArray(data.data)) { - items = data.data; - } else if (data?.list && Array.isArray(data.list)) { - items = data.list; - } else if (data?.results && Array.isArray(data.results)) { - items = data.results; - } else { - // Try the whole response as an array - setError( - "Format raspuns necunoscut. Verifica consola (F12) pentru detalii.", - ); - console.log("RGI response:", data); - return; - } - + const items: App[] = Array.isArray(data) + ? data + : data?.content ?? data?.data ?? data?.list ?? []; setApplications(items); - if (!totalCount) setTotalCount(items.length); + setTotalCount( + typeof data?.totalElements === "number" + ? data.totalElements + : items.length, + ); } catch { setError("Eroare de retea. Verifica conexiunea la eTerra."); } setLoading(false); - }, [workspaceId, orgUnitId, year, totalCount]); + }, [workspaceId, orgUnitId, year]); - // Apply client-side filter - const filtered = filterSolved - ? applications.filter((app) => { - const dueDate = - app.dueDate ?? app.termen ?? app.deadlineDate ?? app.deadline; - return isSolved(app) && isFutureDate(dueDate); - }) - : applications; + // Client-side filter + const filtered = useMemo(() => { + if (!filterSolved) return applications; + const now = Date.now(); + return applications.filter( + (a) => a.hasSolution === 1 && a.dueDate > now, + ); + }, [applications, filterSolved]); return ( -
+
{/* Header */}

Documente Eliberate eTerra

@@ -504,24 +403,22 @@ export default function RgiTestPage() { {/* Filters */} - +
- + setWorkspaceId(e.target.value)} - className="w-28" - placeholder="127" + className="w-24" />
- + setOrgUnitId(e.target.value)} className="w-28" - placeholder="127002" />
@@ -529,8 +426,7 @@ export default function RgiTestPage() { setYear(e.target.value)} - className="w-24" - placeholder="2026" + className="w-20" />
+
+ {/* Column picker */} + {showColumnPicker && ( +
+ {ALL_COLUMNS.map((col) => ( + + ))} +
+ )} + {/* Filter toggle */} -
+
{applications.length > 0 && ( {filtered.length} din {applications.length} lucrari - {totalCount > applications.length && - ` (${totalCount} total pe server)`} + {totalCount > applications.length && ` (${totalCount} total)`} )}
@@ -583,45 +505,126 @@ export default function RgiTestPage() {

Se incarca lucrarile din eTerra RGI...

-

- Prima incarcare poate dura 10-30s (autentificare + paginare). -

)} - {/* Results */} + {/* Results table */} {!loading && filtered.length > 0 && ( -
-

- {filtered.length} lucrar{filtered.length > 1 ? "i" : "e"} - {filterSolved && " solutionate cu termen viitor"} - . Click pe un rand pentru a vedea documentele eliberate. -

- {filtered.map((app, i) => ( - - ))} -
+ + +
+ + + + + {columns.map((col) => ( + + ))} + + + + + {filtered.map((app) => { + const pk = app.applicationPk; + const isExpanded = expandedPk === pk; + const solved = app.hasSolution === 1; + + return ( + + + + ); + })} + +
+ {col.label} +
+ {/* Row */} +
+ setExpandedPk(isExpanded ? null : pk) + } + > + {/* Status icon */} +
+ {solved ? ( + + ) : ( + + )} +
+ {/* Columns */} + {columns.map((col) => ( +
+ {col.key === "statusName" ? ( + + {col.render(app)} + + ) : col.key === "resolutionName" ? ( + + {col.render(app)} + + ) : ( + col.render(app) + )} +
+ ))} + {/* Expand arrow */} +
+ {isExpanded ? ( + + ) : ( + + )} +
+
+ + {/* Expanded: issued documents */} + {isExpanded && ( +
+ +
+ )} +
+
+
+
)} - {/* Empty state */} + {/* Empty states */} {!loading && applications.length > 0 && filtered.length === 0 && ( -

- Nicio lucrare - {filterSolved - ? " solutionata cu termen viitor" - : ""}{" "} - gasita. -

+

Nicio lucrare {filterSolved ? "solutionata cu termen viitor" : ""} gasita.

{filterSolved && (

Debifati filtrul pentru a vedea toate lucrarile. @@ -631,15 +634,11 @@ export default function RgiTestPage() { )} - {/* Initial state */} {!loading && applications.length === 0 && !error && (

Apasa "Incarca lucrari" pentru a incepe.

-

- Necesita conexiune eTerra activa (se autentifica automat). -

)}