feat(parcel-sync): county-aware UAT autocomplete with workspace resolution

- New /api/eterra/uats endpoint fetches all counties + UATs from eTerra,
  caches server-side for 1 hour, returns enriched data with county name
  and workspacePk for each UAT
- When eTerra is connected, auto-fetches enriched UAT list (replaces
  static uat.json fallback)  shows 'FELEACU (57582), CLUJ' format
- UAT autocomplete now searches both UAT name and county name
- Selected UAT stores workspacePk in state, passes it directly to
  /api/eterra/search  eliminates slow per-search county resolution
- Search route accepts optional workspacePk, falls back to resolveWorkspace()
- Dropdown shows UAT name, SIRUTA code, and county prominently
- Increased autocomplete results from 8 to 12 items
This commit is contained in:
AI Assistant
2026-03-06 20:46:44 +02:00
parent 540b02d8d2
commit d948e5c1cf
4 changed files with 320 additions and 61 deletions
+111
View File
@@ -0,0 +1,111 @@
import { NextResponse } from "next/server";
import { EterraClient } from "@/modules/parcel-sync/services/eterra-client";
import { getSessionCredentials } from "@/modules/parcel-sync/services/session-store";
export const runtime = "nodejs";
export const dynamic = "force-dynamic";
/* ------------------------------------------------------------------ */
/* Server-side cache */
/* ------------------------------------------------------------------ */
type EnrichedUat = {
siruta: string;
name: string;
county: string;
workspacePk: number;
};
const globalRef = globalThis as {
__eterraEnrichedUats?: { data: EnrichedUat[]; ts: number };
};
/** Cache TTL — 1 hour (county/UAT data rarely changes) */
const CACHE_TTL_MS = 60 * 60 * 1000;
/* ------------------------------------------------------------------ */
/* GET /api/eterra/uats */
/* */
/* Returns all UATs enriched with county name + workspacePk. */
/* Cached server-side for 1 hour. */
/* ------------------------------------------------------------------ */
export async function GET() {
try {
// Return from cache if fresh
const cached = globalRef.__eterraEnrichedUats;
if (cached && Date.now() - cached.ts < CACHE_TTL_MS) {
return NextResponse.json({ uats: cached.data, cached: true });
}
// Need eTerra credentials
const session = getSessionCredentials();
const username = String(
session?.username || process.env.ETERRA_USERNAME || "",
).trim();
const password = String(
session?.password || process.env.ETERRA_PASSWORD || "",
).trim();
if (!username || !password) {
return NextResponse.json(
{ error: "Conectează-te la eTerra mai întâi.", uats: [] },
{ status: 401 },
);
}
const client = await EterraClient.create(username, password);
// Fetch all counties
const counties = await client.fetchCounties();
const enriched: EnrichedUat[] = [];
for (const county of counties) {
const countyPk = county?.nomenPk ?? county?.pk ?? county?.id;
const countyName = String(county?.name ?? "").trim();
if (!countyPk || !countyName) continue;
try {
const uats = await client.fetchAdminUnitsByCounty(countyPk);
for (const uat of uats) {
const uatPk = String(uat?.nomenPk ?? uat?.pk ?? "");
const uatName = String(uat?.name ?? "").trim();
if (!uatPk || !uatName) continue;
enriched.push({
siruta: uatPk,
name: uatName,
county: countyName,
workspacePk: Number(countyPk),
});
}
} catch {
// Skip county if UAT fetch fails
continue;
}
}
// Update cache
globalRef.__eterraEnrichedUats = { data: enriched, ts: Date.now() };
// Also populate the workspace cache used by search route
const wsGlobal = globalThis as {
__eterraWorkspaceCache?: Map<string, number>;
};
if (!wsGlobal.__eterraWorkspaceCache) {
wsGlobal.__eterraWorkspaceCache = new Map();
}
for (const u of enriched) {
wsGlobal.__eterraWorkspaceCache.set(u.siruta, u.workspacePk);
}
return NextResponse.json({
uats: enriched,
cached: false,
total: enriched.length,
});
} catch (error) {
const message = error instanceof Error ? error.message : "Eroare server";
return NextResponse.json({ error: message, uats: [] }, { status: 500 });
}
}