"use client"; import React, { useState, useCallback, useMemo, useEffect, useRef } from "react"; import { Button } from "@/shared/components/ui/button"; import { Input } from "@/shared/components/ui/input"; import { Label } from "@/shared/components/ui/label"; import { Badge } from "@/shared/components/ui/badge"; import { Card, CardContent } from "@/shared/components/ui/card"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/shared/components/ui/select"; import { Loader2, ChevronDown, ChevronUp, Download, Search, FileText, CheckCircle2, Clock, AlertTriangle, Settings2, Shield, ArrowUpDown, ArrowUp, ArrowDown, Archive, } from "lucide-react"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "@/shared/components/ui/tooltip"; import { cn } from "@/shared/lib/utils"; /* ------------------------------------------------------------------ */ /* Types */ /* ------------------------------------------------------------------ */ 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; }; type SortDir = "asc" | "desc"; type SortState = { key: string; dir: SortDir } | null; /* ------------------------------------------------------------------ */ /* County list */ /* ------------------------------------------------------------------ */ const COUNTIES = [ { id: 10, name: "Alba" }, { id: 29, name: "Arad" }, { id: 38, name: "Arges" }, { id: 47, name: "Bacau" }, { id: 56, name: "Bihor" }, { id: 65, name: "Bistrita-Nasaud" }, { id: 74, name: "Botosani" }, { id: 83, name: "Brasov" }, { id: 92, name: "Braila" }, { id: 108, name: "Buzau" }, { id: 117, name: "Caras-Severin" }, { id: 127, name: "Cluj" }, { id: 136, name: "Constanta" }, { id: 145, name: "Covasna" }, { id: 154, name: "Dambovita" }, { id: 163, name: "Dolj" }, { id: 172, name: "Galati" }, { id: 181, name: "Giurgiu" }, { id: 190, name: "Gorj" }, { id: 199, name: "Harghita" }, { id: 208, name: "Hunedoara" }, { id: 217, name: "Ialomita" }, { id: 226, name: "Iasi" }, { id: 235, name: "Ilfov" }, { id: 244, name: "Maramures" }, { id: 253, name: "Mehedinti" }, { id: 262, name: "Mures" }, { id: 271, name: "Neamt" }, { id: 280, name: "Olt" }, { id: 289, name: "Prahova" }, { id: 298, name: "Satu Mare" }, { id: 307, name: "Salaj" }, { id: 316, name: "Sibiu" }, { id: 325, name: "Suceava" }, { id: 334, name: "Teleorman" }, { id: 343, name: "Timis" }, { id: 352, name: "Tulcea" }, { id: 361, name: "Vaslui" }, { id: 370, name: "Valcea" }, { id: 379, name: "Vrancea" }, { id: 401, name: "Bucuresti" }, ] as const; /* ------------------------------------------------------------------ */ /* Column definitions */ /* ------------------------------------------------------------------ */ type ColumnDef = { key: string; label: string; defaultVisible: boolean; render: (app: App) => string; className?: string; }; 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", year: "numeric", }); } 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", }, ]; /* ------------------------------------------------------------------ */ /* Diacritics-insensitive search helper */ /* ------------------------------------------------------------------ */ function removeDiacritics(str: string): string { return str.normalize("NFD").replace(/[\u0300-\u036f]/g, ""); } function matchesSearch(text: string, query: string): boolean { return removeDiacritics(text.toLowerCase()).includes( removeDiacritics(query.toLowerCase()), ); } /* ------------------------------------------------------------------ */ /* 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 */ /* ------------------------------------------------------------------ */ function IssuedDocsPanel({ applicationPk, workspaceId, appNo, }: { applicationPk: number; workspaceId: number; appNo: number; }) { const [docs, setDocs] = useState(null); 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(() => { let cancelled = false; 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 ?? []; if (!cancelled) setDocs(items); } catch { if (!cancelled) setError("Eroare la incarcarea documentelor"); } if (!cancelled) setLoading(false); })(); return () => { cancelled = true; }; }, [applicationPk, workspaceId]); // Cleanup blocked timer on unmount useEffect(() => { return () => { if (blockedTimerRef.current) clearTimeout(blockedTimerRef.current); }; }, []); 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(); 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 || doc.documentTypeCode || "Document")}` + `&appNo=${appNo}`; try { const res = await fetch(url); const contentType = res.headers.get("content-type") || ""; if (contentType.includes("application/json")) { const json = await res.json(); if (json.blocked || json.error) { setBlockedDocPk(doc.documentPk); if (blockedTimerRef.current) clearTimeout(blockedTimerRef.current); blockedTimerRef.current = setTimeout(() => setBlockedDocPk(null), 5000); return; } } // It's a file — trigger download const blob = await res.blob(); const disposition = res.headers.get("content-disposition") || ""; let filename = `document_${doc.documentPk}.pdf`; const match = disposition.match(/filename="?([^";\n]+)"?/); if (match) { const decoded = match[1]; if (decoded) filename = decodeURIComponent(decoded); } 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); } catch { setBlockedDocPk(doc.documentPk); if (blockedTimerRef.current) clearTimeout(blockedTimerRef.current); blockedTimerRef.current = setTimeout(() => setBlockedDocPk(null), 5000); } }, [workspaceId, applicationPk, appNo], ); if (loading) { return (
Se incarca documentele...
); } if (error) { return

{error}

; } if (!docs || docs.length === 0) { return (

Niciun document eliberat.

); } return (

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

{downloadProgress && ( {downloadProgress} )}
{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} )}
{blockedDocPk === doc.documentPk && (
Documentul nu este inca disponibil pentru descarcare din eTerra
)}
))}
); } /* ------------------------------------------------------------------ */ /* Main page */ /* ------------------------------------------------------------------ */ export default function RgiTestPage() { const [countyId, setCountyId] = useState(127); const orgUnitId = countyId * 1000 + 2; const [year, setYear] = useState("2026"); const [loading, setLoading] = useState(false); const [error, setError] = useState(""); const [applications, setApplications] = useState([]); const [totalCount, setTotalCount] = useState(0); const [expandedPk, setExpandedPk] = useState(null); const [showColumnPicker, setShowColumnPicker] = useState(false); const [filterMode, setFilterMode] = useState<"all" | "solved" | "confirmed">( "solved", ); const [searchQuery, setSearchQuery] = useState(""); const [sortState, setSortState] = useState(null); // Column visibility 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 handleSort = useCallback( (key: string) => { setSortState((prev) => { if (prev && prev.key === key) { return prev.dir === "asc" ? { key, dir: "desc" } : null; } return { key, dir: "asc" }; }); }, [], ); const loadApplications = useCallback(async () => { setLoading(true); setError(""); setApplications([]); setExpandedPk(null); try { const res = await fetch("/api/eterra/rgi/applications", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ workspaceId: countyId, orgUnitId, year, page: 0, nrElements: 200, }), }); const data = await res.json(); if (data.error) { setError(data.error); return; } const items: App[] = Array.isArray(data) ? data : data?.content ?? data?.data ?? data?.list ?? []; setApplications(items); setTotalCount( typeof data?.totalElements === "number" ? data.totalElements : items.length, ); } catch { setError("Eroare de retea. Verifica conexiunea la eTerra."); } setLoading(false); }, [countyId, orgUnitId, year]); // Client-side filter + search + sort pipeline const processed = useMemo(() => { // Step 1: Filter by mode let result = applications; if (filterMode === "solved") { result = result.filter((a) => a.hasSolution === 1); } else if (filterMode === "confirmed") { result = result.filter((a) => a.stateCode === "CONFIRMED"); } // Step 2: Search across visible columns if (searchQuery.trim()) { const q = searchQuery.trim(); result = result.filter((app) => columns.some((col) => matchesSearch(col.render(app), q)), ); } // Step 3: Sort if (sortState) { const col = ALL_COLUMNS.find((c) => c.key === sortState.key); if (col) { const dir = sortState.dir === "asc" ? 1 : -1; result = [...result].sort((a, b) => { 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; }); } } return result; }, [applications, filterMode, searchQuery, columns, sortState]); const SortIcon = ({ colKey }: { colKey: string }) => { if (!sortState || sortState.key !== colKey) { return ; } if (sortState.dir === "asc") { return ; } return ; }; return (
{/* Header */}

Documente Eliberate eTerra

Lucrari depuse cu documente eliberate — descarca direct din eTerra RGI

{/* Filters */}
setYear(e.target.value)} className="w-20" />
{/* Column picker */} {showColumnPicker && (
{ALL_COLUMNS.map((col) => ( ))}
)} {/* Filter toggle + search */}
{( [ { id: "solved" as const, label: "Solutionate", desc: "lucrari cu solutie", }, { id: "confirmed" as const, label: "Confirmate", desc: "solutie confirmata", }, { id: "all" as const, label: "Toate", desc: "" }, ] as const ).map((opt) => ( ))}
{applications.length > 0 && (
setSearchQuery(e.target.value)} placeholder="Cauta in rezultate..." className="pl-8 h-8 text-xs" />
{processed.length} din {applications.length} lucrari {totalCount > applications.length && ` (${totalCount} total)`}
)}
{/* Error */} {error && ( {error} )} {/* Loading */} {loading && (

Se incarca lucrarile din eTerra RGI...

)} {/* Results table */} {!loading && processed.length > 0 && (
{columns.map((col) => ( ))} {processed.map((app) => { const pk = app.applicationPk; const isExpanded = expandedPk === pk; const solved = app.hasSolution === 1; return ( setExpandedPk(isExpanded ? null : pk) } > {columns.map((col) => ( ))} {isExpanded && ( )} ); })}
handleSort(col.key)} > {col.label}

Nr. {app.appNo}

{app.applicationObject || "-"}

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

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

Termen: {fmtTs(app.dueDate)}

{app.identifiers && (

{app.identifiers}

)}
{col.key === "statusName" ? ( {col.render(app)} ) : col.key === "resolutionName" ? ( {col.render(app)} ) : ( col.render(app) )} {isExpanded ? ( ) : ( )}
)} {/* Empty states */} {!loading && applications.length > 0 && processed.length === 0 && (

Nicio lucrare gasita pentru filtrul selectat.

Schimba filtrul sau termenul de cautare.

)} {!loading && applications.length === 0 && !error && (

Apasa "Incarca lucrari" pentru a incepe.

)}
); }