feat(parcel-sync): search by cadastral number with full details

Search tab now uses eTerra application API (same as the web UI):

- POST /api/eterra/search queries /api/immovable/list with exact
  identifierDetails filter + /api/documentation/data for full details
- Returns: nr cad, nr CF, CF vechi, nr topo, suprafata, intravilan,
  categorii folosinta, adresa, proprietari, solicitant
- Automatic workspace (county) resolution from SIRUTA with cache
- Support for multiple cadastral numbers (comma separated)

UI changes:
- Detail cards instead of flat ArcGIS feature table
- Copy details to clipboard button per parcel
- Add parcels to list + CSV export
- Search list with summary table + CSV download
- No more layer filter or pagination (not needed for app API)

New EterraClient methods:
- searchImmovableByIdentifier (exact cadaster lookup)
- fetchCounties / fetchAdminUnitsByCounty (workspace resolution)
This commit is contained in:
AI Assistant
2026-03-06 19:58:33 +02:00
parent c98ce81cb7
commit 540b02d8d2
3 changed files with 737 additions and 316 deletions
@@ -463,6 +463,50 @@ export class EterraClient {
return this.getRawJson(url);
}
/**
* Search immovable list by exact cadastral number (identifierDetails).
* This is the eTerra application API that the web UI uses when you type
* a cadastral number in the search box.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
async searchImmovableByIdentifier(workspaceId: string | number, adminUnitId: string | number, identifierDetails: string, page = 0, size = 10): Promise<any> {
const url = `${BASE_URL}/api/immovable/list`;
const filters: Array<{ value: string | number; type: "NUMBER" | "STRING"; key: string; op: string }> = [
{ value: Number(workspaceId), type: "NUMBER", key: "workspace.nomenPk", op: "=" },
{ value: Number(adminUnitId), type: "NUMBER", key: "adminUnit.nomenPk", op: "=" },
{ value: identifierDetails, type: "STRING", key: "identifierDetails", op: "=" },
{ value: -1, type: "NUMBER", key: "inscrisCF", op: "=" },
{ value: "P", type: "STRING", key: "immovableType", op: "<>C" },
];
const payload = { filters, nrElements: size, page, sorters: [] };
return this.requestRaw(() =>
this.client.post(url, payload, {
headers: { "Content-Type": "application/json;charset=UTF-8" },
timeout: this.timeoutMs,
}),
);
}
/**
* Fetch all counties (workspaces) from eTerra nomenclature.
* Returns array of { nomenPk, name, parentNomenPk, ... }
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
async fetchCounties(): Promise<any[]> {
const url = `${BASE_URL}/api/adm/nomen/COUNTY/list`;
return this.getRawJson(url);
}
/**
* Fetch administrative units (UATs) under a county workspace.
* Returns array of { nomenPk, name, parentNomenPk, ... }
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
async fetchAdminUnitsByCounty(countyNomenPk: string | number): Promise<any[]> {
const url = `${BASE_URL}/api/adm/nomen/ADMINISTRATIVEUNIT/filterByParent/${countyNomenPk}`;
return this.getRawJson(url);
}
/* ---- Internals ------------------------------------------------ */
private layerQueryUrl(layer: LayerConfig) {