From 177f2104c132a07c9d5da5e98c73fee1632daad8 Mon Sep 17 00:00:00 2001 From: Claude VM Date: Tue, 7 Apr 2026 21:21:09 +0300 Subject: [PATCH] fix(geoportal): show UAT name in search results + fix map snap-back Search results now JOIN GisUat to display UAT name prominently instead of just SIRUTA codes. Map flyTo uses imperative handle instead of stateful props that re-triggered on re-renders. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/app/api/geoportal/search/route.ts | 69 +++++++++++-------- .../geoportal/components/geoportal-module.tsx | 6 +- .../geoportal/components/map-viewer.tsx | 6 -- 3 files changed, 40 insertions(+), 41 deletions(-) diff --git a/src/app/api/geoportal/search/route.ts b/src/app/api/geoportal/search/route.ts index 2349218..12334e9 100644 --- a/src/app/api/geoportal/search/route.ts +++ b/src/app/api/geoportal/search/route.ts @@ -67,25 +67,28 @@ export async function GET(req: Request) { // Search by cadastral reference const parcels = await prisma.$queryRaw` SELECT - id, - "cadastralRef", - "areaValue", - siruta, - enrichment, - ST_X(ST_Centroid(ST_Transform(geom, 4326))) as lng, - ST_Y(ST_Centroid(ST_Transform(geom, 4326))) as lat - FROM "GisFeature" - WHERE geom IS NOT NULL - AND "layerId" LIKE 'TERENURI%' - AND ("cadastralRef" ILIKE ${pattern} - OR enrichment::text ILIKE ${`%"NR_CAD":"${q}%`}) - ORDER BY "cadastralRef" + f.id, + f."cadastralRef", + f."areaValue", + f.siruta, + f.enrichment, + u.name as uat_name, + ST_X(ST_Centroid(ST_Transform(f.geom, 4326))) as lng, + ST_Y(ST_Centroid(ST_Transform(f.geom, 4326))) as lat + FROM "GisFeature" f + LEFT JOIN "GisUat" u ON u.siruta = f.siruta + WHERE f.geom IS NOT NULL + AND f."layerId" LIKE 'TERENURI%' + AND (f."cadastralRef" ILIKE ${pattern} + OR f.enrichment::text ILIKE ${`%"NR_CAD":"${q}%`}) + ORDER BY f."cadastralRef" LIMIT ${limit} ` as Array<{ id: string; cadastralRef: string | null; areaValue: number | null; siruta: string; + uat_name: string | null; enrichment: Record | null; lng: number; lat: number; @@ -94,11 +97,12 @@ export async function GET(req: Request) { for (const p of parcels) { const nrCad = (p.enrichment?.NR_CAD as string) ?? p.cadastralRef ?? "?"; const area = p.areaValue ? `${Math.round(p.areaValue)} mp` : ""; + const uatLabel = p.uat_name ?? `SIRUTA ${p.siruta}`; results.push({ id: `parcel-${p.id}`, type: "parcel", - label: `Parcela ${nrCad}`, - sublabel: [area, `SIRUTA ${p.siruta}`].filter(Boolean).join(" | "), + label: `Parcela ${nrCad} — ${uatLabel}`, + sublabel: [area, p.uat_name ? `SIRUTA ${p.siruta}` : ""].filter(Boolean).join(" | "), coordinates: p.lng && p.lat ? [p.lng, p.lat] : undefined, }); } @@ -106,25 +110,28 @@ export async function GET(req: Request) { // Search by owner name in enrichment JSON const parcels = await prisma.$queryRaw` SELECT - id, - "cadastralRef", - "areaValue", - siruta, - enrichment, - ST_X(ST_Centroid(ST_Transform(geom, 4326))) as lng, - ST_Y(ST_Centroid(ST_Transform(geom, 4326))) as lat - FROM "GisFeature" - WHERE geom IS NOT NULL - AND "layerId" LIKE 'TERENURI%' - AND enrichment IS NOT NULL - AND enrichment::text ILIKE ${pattern} - ORDER BY "cadastralRef" + f.id, + f."cadastralRef", + f."areaValue", + f.siruta, + f.enrichment, + u.name as uat_name, + ST_X(ST_Centroid(ST_Transform(f.geom, 4326))) as lng, + ST_Y(ST_Centroid(ST_Transform(f.geom, 4326))) as lat + FROM "GisFeature" f + LEFT JOIN "GisUat" u ON u.siruta = f.siruta + WHERE f.geom IS NOT NULL + AND f."layerId" LIKE 'TERENURI%' + AND f.enrichment IS NOT NULL + AND f.enrichment::text ILIKE ${pattern} + ORDER BY f."cadastralRef" LIMIT ${limit} ` as Array<{ id: string; cadastralRef: string | null; areaValue: number | null; siruta: string; + uat_name: string | null; enrichment: Record | null; lng: number; lat: number; @@ -133,11 +140,13 @@ export async function GET(req: Request) { for (const p of parcels) { const nrCad = (p.enrichment?.NR_CAD as string) ?? p.cadastralRef ?? "?"; const owner = (p.enrichment?.PROPRIETARI as string) ?? ""; + const uatLabel = p.uat_name ?? `SIRUTA ${p.siruta}`; + const ownerShort = owner.length > 60 ? owner.slice(0, 60) + "..." : owner; results.push({ id: `parcel-${p.id}`, type: "parcel", - label: `Parcela ${nrCad}`, - sublabel: owner.length > 60 ? owner.slice(0, 60) + "..." : owner, + label: `Parcela ${nrCad} — ${uatLabel}`, + sublabel: [ownerShort, p.uat_name ? `SIRUTA ${p.siruta}` : ""].filter(Boolean).join(" | "), coordinates: p.lng && p.lat ? [p.lng, p.lat] : undefined, }); } diff --git a/src/modules/geoportal/components/geoportal-module.tsx b/src/modules/geoportal/components/geoportal-module.tsx index 8c37d8d..15537b7 100644 --- a/src/modules/geoportal/components/geoportal-module.tsx +++ b/src/modules/geoportal/components/geoportal-module.tsx @@ -32,8 +32,6 @@ export function GeoportalModule() { const [clickedFeature, setClickedFeature] = useState(null); const [selectionMode, setSelectionMode] = useState("off"); const [selectedFeatures, setSelectedFeatures] = useState([]); - const [flyTarget, setFlyTarget] = useState<{ center: [number, number]; zoom?: number } | undefined>(); - const handleFeatureClick = useCallback((feature: ClickedFeature | null) => { // null = clicked on empty space, close panel if (!feature || !feature.properties) { @@ -45,7 +43,7 @@ export function GeoportalModule() { const handleSearchResult = useCallback((result: SearchResult) => { if (result.coordinates) { - setFlyTarget({ center: result.coordinates, zoom: result.type === "uat" ? 12 : 17 }); + mapHandleRef.current?.flyTo(result.coordinates, result.type === "uat" ? 12 : 17); } }, []); @@ -67,8 +65,6 @@ export function GeoportalModule() { onFeatureClick={handleFeatureClick} onSelectionChange={setSelectedFeatures} layerVisibility={layerVisibility} - center={flyTarget?.center} - zoom={flyTarget?.zoom} /> {/* Setup banner (auto-hides when ready) */} diff --git a/src/modules/geoportal/components/map-viewer.tsx b/src/modules/geoportal/components/map-viewer.tsx index 6156e89..f9fd069 100644 --- a/src/modules/geoportal/components/map-viewer.tsx +++ b/src/modules/geoportal/components/map-viewer.tsx @@ -845,12 +845,6 @@ export const MapViewer = forwardRef( // eslint-disable-next-line react-hooks/exhaustive-deps }, [resolvedMartinUrl, basemap]); - /* ---- Sync center/zoom prop changes (from search flyTo) ---- */ - useEffect(() => { - if (!mapReady || !mapRef.current || !center) return; - mapRef.current.flyTo({ center, zoom: zoom ?? mapRef.current.getZoom(), duration: 1500 }); - }, [center, zoom, mapReady]); - /* ---- Disable interactions when in drawing modes ---- */ useEffect(() => { const map = mapRef.current;