feat(rgi): sortable/filterable table, county selector, smart filenames, soft blocked msg
Page improvements:
- County dropdown with all 41 Romanian counties (default Cluj)
- orgUnitId auto-computed (countyId * 1000 + 2)
- Sortable columns: click header to sort asc/desc with arrow indicators
- Search input: filters across all visible columns (diacritics-insensitive)
- Soft blocked message: amber toast "Documentul nu este inca disponibil"
auto-hides after 5s (no more redirect errors)
Download improvements:
- Meaningful filenames: {docType}_{appNo}.pdf (e.g. Harti_planuri_66903.pdf)
- Romanian diacritics stripped from filenames
- Returns { blocked: true } JSON instead of redirect when unavailable
Bug fix: replaced incorrect useState() side-effect with proper useEffect()
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,12 +5,48 @@ export const runtime = "nodejs";
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
/**
|
||||
* GET /api/eterra/rgi/download-doc?workspaceId=127&applicationId=X&documentPk=Y&documentTypeId=Z
|
||||
* Strip Romanian diacritics and replace non-alphanumeric chars with underscores.
|
||||
*/
|
||||
function sanitizeFilename(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, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract file extension from content-type or server filename.
|
||||
*/
|
||||
function getExtension(contentType: string, serverFilename: string): string {
|
||||
// Try extension from server filename first
|
||||
const dotIdx = serverFilename.lastIndexOf(".");
|
||||
if (dotIdx > 0) {
|
||||
return serverFilename.slice(dotIdx + 1).toLowerCase();
|
||||
}
|
||||
// Fallback to content-type mapping
|
||||
const map: Record<string, string> = {
|
||||
"application/pdf": "pdf",
|
||||
"image/png": "png",
|
||||
"image/jpeg": "jpg",
|
||||
"application/zip": "zip",
|
||||
"application/xml": "xml",
|
||||
"text/xml": "xml",
|
||||
};
|
||||
return map[contentType] ?? "pdf";
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/eterra/rgi/download-doc?workspaceId=127&applicationId=X&documentPk=Y&documentTypeId=Z&docType=...&appNo=...&initialAppNo=...
|
||||
*
|
||||
* Downloads an issued document from eTerra RGI.
|
||||
* Tries server-side download first. If that fails (some documents are
|
||||
* restricted to the current actor), returns a direct eTerra URL that
|
||||
* works in the user's browser session.
|
||||
* restricted to the current actor), returns a JSON blocked response
|
||||
* so the frontend can show a soft message.
|
||||
*/
|
||||
export async function GET(req: NextRequest) {
|
||||
try {
|
||||
@@ -18,6 +54,9 @@ export async function GET(req: NextRequest) {
|
||||
const applicationId = req.nextUrl.searchParams.get("applicationId");
|
||||
const documentPk = req.nextUrl.searchParams.get("documentPk");
|
||||
const documentTypeId = req.nextUrl.searchParams.get("documentTypeId");
|
||||
const docType = req.nextUrl.searchParams.get("docType");
|
||||
const appNo = req.nextUrl.searchParams.get("appNo");
|
||||
const initialAppNo = req.nextUrl.searchParams.get("initialAppNo");
|
||||
|
||||
if (!workspaceId || !applicationId || !documentPk) {
|
||||
return NextResponse.json(
|
||||
@@ -70,10 +109,27 @@ export async function GET(req: NextRequest) {
|
||||
|
||||
// Try download (even if fileVisibility failed — context might be enough)
|
||||
try {
|
||||
const { data, contentType, filename } = await client.rgiDownload(
|
||||
const { data, contentType, filename: serverFilename } = await client.rgiDownload(
|
||||
`rgi/appdetail/loadDocument/downloadFile/${workspaceId}/${documentPk}`,
|
||||
);
|
||||
if (data.length > 0) {
|
||||
// Build meaningful filename from query params, fallback to server filename
|
||||
const ext = getExtension(contentType, serverFilename);
|
||||
let filename: string;
|
||||
if (docType && appNo) {
|
||||
filename = `${sanitizeFilename(docType)}_${sanitizeFilename(appNo)}.${ext}`;
|
||||
} else if (docType) {
|
||||
filename = `${sanitizeFilename(docType)}.${ext}`;
|
||||
} else if (appNo) {
|
||||
filename = `document_${sanitizeFilename(appNo)}.${ext}`;
|
||||
} else {
|
||||
// Use server filename, but still sanitize it
|
||||
const serverBase = serverFilename.replace(/\.[^.]+$/, "");
|
||||
filename = serverBase && serverBase !== "document"
|
||||
? `${sanitizeFilename(serverBase)}.${ext}`
|
||||
: serverFilename;
|
||||
}
|
||||
|
||||
return new NextResponse(new Uint8Array(data), {
|
||||
status: 200,
|
||||
headers: {
|
||||
@@ -84,13 +140,18 @@ export async function GET(req: NextRequest) {
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
// Fall through to redirect
|
||||
// Fall through to blocked response
|
||||
}
|
||||
|
||||
// Server-side download not available — redirect to eTerra direct URL
|
||||
// User's browser session (if logged into eTerra) can download it
|
||||
const eterraUrl = `https://eterra.ancpi.ro/eterra/api/rgi/appdetail/loadDocument/downloadFile/${workspaceId}/${documentPk}`;
|
||||
return NextResponse.redirect(eterraUrl, 302);
|
||||
// Server-side download not available — return soft blocked response
|
||||
// so the frontend can show a user-friendly message
|
||||
return NextResponse.json(
|
||||
{
|
||||
blocked: true,
|
||||
message: "Documentul nu este inca disponibil pentru descarcare din eTerra.",
|
||||
},
|
||||
{ status: 200 },
|
||||
);
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : "Eroare server";
|
||||
return NextResponse.json({ error: message }, { status: 500 });
|
||||
|
||||
Reference in New Issue
Block a user