From 52c31e3c4d6639349656d035bf537aada9adb95c Mon Sep 17 00:00:00 2001
From: Claude VM
Date: Wed, 20 May 2026 17:49:46 +0300
Subject: [PATCH] =?UTF-8?q?feat(geoportal-v2):=20UAT=20name=20+=20SOLICITA?=
=?UTF-8?q?NT=20into=20=C3=8Enscriere=20+=20Google=20Maps=20inline?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Iteration on the info panel per Marius's feedback.
1. UAT NAME IN HEADER
New uat-lookup.ts hook loads public/uat.json (3,186 rows, ~95 KB,
one-shot fetch + Map cache + subscribers) and exposes
useUatName(siruta). Header reads:
Terenuri · 2.400 m² · FELEACU · 57582
instead of just "SIRUTA 57582". The localitate name lives in front
of the bare siruta number (muted, smaller weight) — siruta is
still there for ops + tooltip, just not the primary signal.
2. SOLICITANT MOVED INTO ÎNSCRIERE
Was rendered as a prominent User-icon line right above PROPRIETARI,
which led to "BOJAN ELENA = current owner?" confusion. The two
fields semantically differ: SOLICITANT is the person who filed the
most recent ANCPI application (e.g. the new buyer initiating a
transfer), PROPRIETARI is who's currently registered as owner. Now
SOLICITANT is collapsed into the existing Înscriere next
to TIP_INSCRIERE / DATA_CERERE / ACT_PROPRIETATE — the
registration-metadata bucket where it belongs.
3. GOOGLE MAPS INLINE WITH ADDRESS
When ADRESA exists, the Google Maps text-link sits right of the
address (using feature.lat/lng for the query). One-tap go-to-map
without a separate Localizare section.
4. LOCALIZARE → COLLAPSIBLE
Bottom Localizare card becomes a closed-by-default .
Inside: WGS84 lat/lng, SIRUTA, and a separate Google Maps link.
ID (objectId) shows in the summary line. Mirrors eterra.live's
approach. The redundant Feleacu/coords echo at the bottom is
gone — coords are still one click away when needed.
NOT in this commit (parked for follow-up):
- PIZ / Plan situație / Coord. / DXF actions — would mean porting
eterra.live's three /api/geoportal/{piz,pad,coords-xlsx} document
generators. Substantial work (mapbox-static-image render +
server-side PDF layout); needs its own session.
- CF intern (gratuit) vs Extras CF (1 credit) split — current
"Comandă CF" modal already handles both pool/connection states,
but the two-button visual split mirroring eterra.live's catalog-
hit fast path is a smaller follow-up.
Co-Authored-By: Claude Opus 4.7 (1M context)
---
.../geoportal/v2/feature-info-panel.tsx | 83 ++++++++++++++-----
src/modules/geoportal/v2/uat-lookup.ts | 57 +++++++++++++
2 files changed, 119 insertions(+), 21 deletions(-)
create mode 100644 src/modules/geoportal/v2/uat-lookup.ts
diff --git a/src/modules/geoportal/v2/feature-info-panel.tsx b/src/modules/geoportal/v2/feature-info-panel.tsx
index db48d78..fed69b9 100644
--- a/src/modules/geoportal/v2/feature-info-panel.tsx
+++ b/src/modules/geoportal/v2/feature-info-panel.tsx
@@ -5,11 +5,12 @@ import { signIn } from "next-auth/react";
import {
X, RefreshCw, Loader2, FileText, AlertCircle,
Home, Building, Building2, MapPin, ChevronRight, Users,
- Sparkles, User, ShieldCheck, AlertTriangle, HelpCircle,
+ Sparkles, ShieldCheck, AlertTriangle, HelpCircle,
Factory, Warehouse,
} from "lucide-react";
import { cn } from "@/shared/lib/utils";
import { CfOrderModal } from "./cf-order-modal";
+import { useUatName } from "./uat-lookup";
const AUTH_RETRY_KEY = "gis_panel_auth_retry";
@@ -791,6 +792,7 @@ export function FeatureInfoPanel({ feature, onClose, onSelectFeature, basic = fa
const cadrefHeader = feature.cadastralRef || feature.objectId || "—";
const layerLabel = feature.layerId.replace("_ACTIVE", "").toLowerCase();
+ const uatName = useUatName(feature.siruta);
const enrichedAgo = formatRelativeTime(detail?.enrichedAt);
@@ -811,8 +813,14 @@ export function FeatureInfoPanel({ feature, onClose, onSelectFeature, basic = fa
{feature.areaValue != null && (
· {formatNum(feature.areaValue)} m²
)}
+ {uatName && (
+ · {uatName}
+ )}
{feature.siruta && (
- · SIRUTA {feature.siruta}
+ · {feature.siruta}
)}
@@ -979,14 +987,18 @@ export function FeatureInfoPanel({ feature, onClose, onSelectFeature, basic = fa
{adresa && (
- )}
-
- {solicitant && (
-
-
-
{solicitant}
+
{adresa}
+ {feature.lat != null && feature.lng != null && (
+
+ Google Maps
+
+ )}
)}
@@ -1001,7 +1013,12 @@ export function FeatureInfoPanel({ feature, onClose, onSelectFeature, basic = fa
/>
)}
- {(tipInscriere || dataCererii || actProp) && (
+ {/* Înscriere — collapsed. Holds the most-recent application
+ metadata (who applied, when, what document) which is
+ NOT the same as current ownership. SOLICITANT lives
+ here, not next to PROPRIETARI, to avoid the "Bojan
+ Elena = proprietar?" confusion. */}
+ {(solicitant || tipInscriere || dataCererii || actProp) && (
@@ -1010,6 +1027,7 @@ export function FeatureInfoPanel({ feature, onClose, onSelectFeature, basic = fa
+ {solicitant && }
{tipInscriere && }
{dataCererii && }
{actProp && }
@@ -1193,24 +1211,47 @@ export function FeatureInfoPanel({ feature, onClose, onSelectFeature, basic = fa
)}
- {/* Localizare */}
+ {/* Localizare — collapsible. Coords + ObjectId for those who need
+ them (cadastrali, etc.). Skipped when ADRESA is shown above
+ (Google Maps link moves there); otherwise this is the only
+ way to get a map link, so keep it. */}
{feature.lat != null && feature.lng != null && (
-
-
-
-
- {feature.lat.toFixed(5)}, {feature.lng.toFixed(5)}
-
+
+
+
+
+ Localizare
+
+ {feature.objectId && (
+
+ ID {feature.objectId}
+
+ )}
+
+
-
+
)}
{!loading && !detail && !error && (
diff --git a/src/modules/geoportal/v2/uat-lookup.ts b/src/modules/geoportal/v2/uat-lookup.ts
new file mode 100644
index 0000000..d5b52a7
--- /dev/null
+++ b/src/modules/geoportal/v2/uat-lookup.ts
@@ -0,0 +1,57 @@
+"use client";
+
+// Lazy, browser-side lookup of UAT (siruta → name).
+//
+// `public/uat.json` is a 3,186-row static asset (~95 KB) generated once
+// from `gis_core.GisUat`. The V2 panel uses it to display the UAT
+// name in the header ("FELEACU · SIRUTA 57582" instead of just "SIRUTA
+// 57582"). Single fetch per tab, cached in a module-level Map.
+//
+// Hook callers re-render once the map resolves. No suspense, no
+// loading flash — the header just upgrades from "SIRUTA 57582" to
+// "FELEACU · SIRUTA 57582" the instant the fetch completes.
+
+import { useEffect, useState } from "react";
+
+type UatEntry = { siruta: string; name: string };
+
+let cache: Map
| null = null;
+let inflight: Promise