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 && (
-

{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} + + )} +
+
+
+ WGS84 + + {feature.lat.toFixed(6)}, {feature.lng.toFixed(6)} + +
+
+ SIRUTA + + {feature.siruta || "—"} + +
- Google Maps + + Deschide în Google Maps
-
+
)} {!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> | null = null; +const subscribers = new Set<() => void>(); + +async function loadUatMap(): Promise> { + if (cache) return cache; + if (inflight) return inflight; + inflight = (async () => { + try { + const res = await fetch("/uat.json", { cache: "force-cache" }); + if (!res.ok) throw new Error(`uat.json HTTP ${res.status}`); + const arr = (await res.json()) as UatEntry[]; + const map = new Map(); + for (const u of arr) { + if (u?.siruta && u?.name) map.set(String(u.siruta), u.name); + } + cache = map; + subscribers.forEach((cb) => cb()); + return map; + } finally { + inflight = null; + } + })(); + return inflight; +} + +export function useUatName(siruta: string | undefined): string | null { + const [, force] = useState(0); + useEffect(() => { + if (cache) return; + const cb = () => force((n) => n + 1); + subscribers.add(cb); + void loadUatMap(); + return () => { + subscribers.delete(cb); + }; + }, []); + if (!siruta) return null; + return cache?.get(String(siruta)) ?? null; +}