From e0610b057302ba21bad102a9f4a25d60adbdca39 Mon Sep 17 00:00:00 2001 From: Claude VM Date: Mon, 18 May 2026 21:42:40 +0300 Subject: [PATCH] fix(geoportal-v2): handle PMTiles features without uuid id MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PMTiles overview tiles may carry only object_id + siruta + cadastral_ref (not the gis_core.GisFeature uuid). Old click handler required `id` and silently dropped clicks. Now: - map-viewer click handler: extract objectId AND id; require only siruta+cadastral_ref to dispatch. Logs the props when fields are missing for further diagnosis. - feature-info-panel: skip auto-fetch when feature.id is empty; show basic info from tile properties + nudge user to "Citește din ANCPI" to populate enrichment. - Refresh button: project orchestrator response straight into the panel when no gis-uuid available (no second parcela.get roundtrip). Falls back to detail.siruta when click came from search (which only returns id + cadastralRef, no siruta). Co-Authored-By: Claude Opus 4.7 (1M context) --- .../geoportal/v2/feature-info-panel.tsx | 56 ++++++++++++++++--- src/modules/geoportal/v2/map-viewer.tsx | 22 ++++++-- 2 files changed, 64 insertions(+), 14 deletions(-) diff --git a/src/modules/geoportal/v2/feature-info-panel.tsx b/src/modules/geoportal/v2/feature-info-panel.tsx index f04bd76..591ece7 100644 --- a/src/modules/geoportal/v2/feature-info-panel.tsx +++ b/src/modules/geoportal/v2/feature-info-panel.tsx @@ -7,7 +7,9 @@ import { import { cn } from "@/shared/lib/utils"; export interface ClickedFeatureLite { + /** GisFeature uuid — empty when tile only has object_id */ id: string; + objectId?: string; siruta: string; cadastralRef: string; layerId: string; @@ -15,7 +17,7 @@ export interface ClickedFeatureLite { } interface ParcelDetail { - id: string; + id?: string; layerId?: string; siruta?: string; cadastralRef?: string; @@ -60,8 +62,17 @@ export function FeatureInfoPanel({ feature, onClose }: Props) { const [error, setError] = useState(null); const [refreshing, setRefreshing] = useState(false); - // Fetch detail when feature changes + // Fetch detail when feature changes. + // If tile carries the GisFeature uuid: use the fast path (parcela.get, + // stored enrichment, no ANCPI roundtrip). If only siruta+cadastralRef are + // available: skip auto-fetch — user clicks "Citește din ANCPI" to load. useEffect(() => { + if (!feature.id) { + setDetail(null); + setError(null); + setLoading(false); + return; + } let cancelled = false; setLoading(true); setError(null); @@ -92,6 +103,12 @@ export function FeatureInfoPanel({ feature, onClose }: Props) { }, [feature.id]); const refreshFromAncpi = async () => { + const siruta = feature.siruta || detail?.siruta || ""; + const cadastralRef = feature.cadastralRef || detail?.cadastralRef || ""; + if (!siruta || !cadastralRef) { + setError("missing_siruta_or_cad"); + return; + } setRefreshing(true); setError(null); try { @@ -99,8 +116,8 @@ export function FeatureInfoPanel({ feature, onClose }: Props) { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ - siruta: feature.siruta, - cadastralRef: feature.cadastralRef, + siruta, + cadastralRef, force: true, }), }); @@ -109,11 +126,26 @@ export function FeatureInfoPanel({ feature, onClose }: Props) { setError(body.error || "refresh_failed"); return; } - // Re-fetch parcela detail - const updated = await fetch( - `/api/gis/parcela/${encodeURIComponent(feature.id)}`, - ); - if (updated.ok) setDetail(await updated.json()); + const techData = await res.json().catch(() => null); + + // If the tile carried a GisFeature uuid, refresh stored detail. + // Otherwise project the orchestrator response into the panel directly. + if (feature.id) { + const updated = await fetch( + `/api/gis/parcela/${encodeURIComponent(feature.id)}`, + ); + if (updated.ok) setDetail(await updated.json()); + } else if (techData) { + // Orchestrator returns {status, data: {…enrichment fields}} verbatim. + const inner = + (techData?.data as Record | undefined) ?? techData; + setDetail({ + siruta: feature.siruta, + cadastralRef: feature.cadastralRef, + areaValue: feature.areaValue, + enrichment: inner as Record, + }); + } } catch { setError("network_error"); } finally { @@ -227,6 +259,12 @@ export function FeatureInfoPanel({ feature, onClose }: Props) { )} )} + + {!detail && !loading && !error && ( +
+ Apasă „Citește din ANCPI" pentru a încărca detaliile parcelei. +
+ )}
diff --git a/src/modules/geoportal/v2/map-viewer.tsx b/src/modules/geoportal/v2/map-viewer.tsx index 246372c..bbb7cdd 100644 --- a/src/modules/geoportal/v2/map-viewer.tsx +++ b/src/modules/geoportal/v2/map-viewer.tsx @@ -240,19 +240,31 @@ export const MapViewer = forwardRef(function MapViewer( return; } const p = f.properties as Record; - const id = (p.id as string) ?? ""; - const siruta = String(p.siruta ?? ""); - const cadastralRef = String(p.cadastral_ref ?? ""); const sourceLayer = (f as unknown as { sourceLayer?: string }).sourceLayer ?? ""; const layerId = sourceLayer === "gis_cladiri" ? "CLADIRI_ACTIVE" : "TERENURI_ACTIVE"; - if (!id || !cadastralRef) { + + const id = typeof p.id === "string" ? p.id : ""; + const objectId = + typeof p.object_id === "number" + ? String(p.object_id) + : typeof p.object_id === "string" + ? p.object_id + : ""; + const siruta = String(p.siruta ?? ""); + const cadastralRef = String(p.cadastral_ref ?? ""); + + // Need at least cadastralRef + siruta to identify the parcel. + if (!cadastralRef || !siruta) { + console.warn("[v2-click] tile props missing siruta/cadastral_ref:", p); onFeatureClick(null); return; } + onFeatureClick({ - id, + id, // may be empty when tile lacks uuid; panel falls back to parcel/tech + objectId, siruta, cadastralRef, layerId,