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) <noreply@anthropic.com>
This commit is contained in:
@@ -67,25 +67,28 @@ export async function GET(req: Request) {
|
|||||||
// Search by cadastral reference
|
// Search by cadastral reference
|
||||||
const parcels = await prisma.$queryRaw`
|
const parcels = await prisma.$queryRaw`
|
||||||
SELECT
|
SELECT
|
||||||
id,
|
f.id,
|
||||||
"cadastralRef",
|
f."cadastralRef",
|
||||||
"areaValue",
|
f."areaValue",
|
||||||
siruta,
|
f.siruta,
|
||||||
enrichment,
|
f.enrichment,
|
||||||
ST_X(ST_Centroid(ST_Transform(geom, 4326))) as lng,
|
u.name as uat_name,
|
||||||
ST_Y(ST_Centroid(ST_Transform(geom, 4326))) as lat
|
ST_X(ST_Centroid(ST_Transform(f.geom, 4326))) as lng,
|
||||||
FROM "GisFeature"
|
ST_Y(ST_Centroid(ST_Transform(f.geom, 4326))) as lat
|
||||||
WHERE geom IS NOT NULL
|
FROM "GisFeature" f
|
||||||
AND "layerId" LIKE 'TERENURI%'
|
LEFT JOIN "GisUat" u ON u.siruta = f.siruta
|
||||||
AND ("cadastralRef" ILIKE ${pattern}
|
WHERE f.geom IS NOT NULL
|
||||||
OR enrichment::text ILIKE ${`%"NR_CAD":"${q}%`})
|
AND f."layerId" LIKE 'TERENURI%'
|
||||||
ORDER BY "cadastralRef"
|
AND (f."cadastralRef" ILIKE ${pattern}
|
||||||
|
OR f.enrichment::text ILIKE ${`%"NR_CAD":"${q}%`})
|
||||||
|
ORDER BY f."cadastralRef"
|
||||||
LIMIT ${limit}
|
LIMIT ${limit}
|
||||||
` as Array<{
|
` as Array<{
|
||||||
id: string;
|
id: string;
|
||||||
cadastralRef: string | null;
|
cadastralRef: string | null;
|
||||||
areaValue: number | null;
|
areaValue: number | null;
|
||||||
siruta: string;
|
siruta: string;
|
||||||
|
uat_name: string | null;
|
||||||
enrichment: Record<string, unknown> | null;
|
enrichment: Record<string, unknown> | null;
|
||||||
lng: number;
|
lng: number;
|
||||||
lat: number;
|
lat: number;
|
||||||
@@ -94,11 +97,12 @@ export async function GET(req: Request) {
|
|||||||
for (const p of parcels) {
|
for (const p of parcels) {
|
||||||
const nrCad = (p.enrichment?.NR_CAD as string) ?? p.cadastralRef ?? "?";
|
const nrCad = (p.enrichment?.NR_CAD as string) ?? p.cadastralRef ?? "?";
|
||||||
const area = p.areaValue ? `${Math.round(p.areaValue)} mp` : "";
|
const area = p.areaValue ? `${Math.round(p.areaValue)} mp` : "";
|
||||||
|
const uatLabel = p.uat_name ?? `SIRUTA ${p.siruta}`;
|
||||||
results.push({
|
results.push({
|
||||||
id: `parcel-${p.id}`,
|
id: `parcel-${p.id}`,
|
||||||
type: "parcel",
|
type: "parcel",
|
||||||
label: `Parcela ${nrCad}`,
|
label: `Parcela ${nrCad} — ${uatLabel}`,
|
||||||
sublabel: [area, `SIRUTA ${p.siruta}`].filter(Boolean).join(" | "),
|
sublabel: [area, p.uat_name ? `SIRUTA ${p.siruta}` : ""].filter(Boolean).join(" | "),
|
||||||
coordinates: p.lng && p.lat ? [p.lng, p.lat] : undefined,
|
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
|
// Search by owner name in enrichment JSON
|
||||||
const parcels = await prisma.$queryRaw`
|
const parcels = await prisma.$queryRaw`
|
||||||
SELECT
|
SELECT
|
||||||
id,
|
f.id,
|
||||||
"cadastralRef",
|
f."cadastralRef",
|
||||||
"areaValue",
|
f."areaValue",
|
||||||
siruta,
|
f.siruta,
|
||||||
enrichment,
|
f.enrichment,
|
||||||
ST_X(ST_Centroid(ST_Transform(geom, 4326))) as lng,
|
u.name as uat_name,
|
||||||
ST_Y(ST_Centroid(ST_Transform(geom, 4326))) as lat
|
ST_X(ST_Centroid(ST_Transform(f.geom, 4326))) as lng,
|
||||||
FROM "GisFeature"
|
ST_Y(ST_Centroid(ST_Transform(f.geom, 4326))) as lat
|
||||||
WHERE geom IS NOT NULL
|
FROM "GisFeature" f
|
||||||
AND "layerId" LIKE 'TERENURI%'
|
LEFT JOIN "GisUat" u ON u.siruta = f.siruta
|
||||||
AND enrichment IS NOT NULL
|
WHERE f.geom IS NOT NULL
|
||||||
AND enrichment::text ILIKE ${pattern}
|
AND f."layerId" LIKE 'TERENURI%'
|
||||||
ORDER BY "cadastralRef"
|
AND f.enrichment IS NOT NULL
|
||||||
|
AND f.enrichment::text ILIKE ${pattern}
|
||||||
|
ORDER BY f."cadastralRef"
|
||||||
LIMIT ${limit}
|
LIMIT ${limit}
|
||||||
` as Array<{
|
` as Array<{
|
||||||
id: string;
|
id: string;
|
||||||
cadastralRef: string | null;
|
cadastralRef: string | null;
|
||||||
areaValue: number | null;
|
areaValue: number | null;
|
||||||
siruta: string;
|
siruta: string;
|
||||||
|
uat_name: string | null;
|
||||||
enrichment: Record<string, unknown> | null;
|
enrichment: Record<string, unknown> | null;
|
||||||
lng: number;
|
lng: number;
|
||||||
lat: number;
|
lat: number;
|
||||||
@@ -133,11 +140,13 @@ export async function GET(req: Request) {
|
|||||||
for (const p of parcels) {
|
for (const p of parcels) {
|
||||||
const nrCad = (p.enrichment?.NR_CAD as string) ?? p.cadastralRef ?? "?";
|
const nrCad = (p.enrichment?.NR_CAD as string) ?? p.cadastralRef ?? "?";
|
||||||
const owner = (p.enrichment?.PROPRIETARI as string) ?? "";
|
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({
|
results.push({
|
||||||
id: `parcel-${p.id}`,
|
id: `parcel-${p.id}`,
|
||||||
type: "parcel",
|
type: "parcel",
|
||||||
label: `Parcela ${nrCad}`,
|
label: `Parcela ${nrCad} — ${uatLabel}`,
|
||||||
sublabel: owner.length > 60 ? owner.slice(0, 60) + "..." : owner,
|
sublabel: [ownerShort, p.uat_name ? `SIRUTA ${p.siruta}` : ""].filter(Boolean).join(" | "),
|
||||||
coordinates: p.lng && p.lat ? [p.lng, p.lat] : undefined,
|
coordinates: p.lng && p.lat ? [p.lng, p.lat] : undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,8 +32,6 @@ export function GeoportalModule() {
|
|||||||
const [clickedFeature, setClickedFeature] = useState<ClickedFeature | null>(null);
|
const [clickedFeature, setClickedFeature] = useState<ClickedFeature | null>(null);
|
||||||
const [selectionMode, setSelectionMode] = useState<SelectionMode>("off");
|
const [selectionMode, setSelectionMode] = useState<SelectionMode>("off");
|
||||||
const [selectedFeatures, setSelectedFeatures] = useState<SelectedFeature[]>([]);
|
const [selectedFeatures, setSelectedFeatures] = useState<SelectedFeature[]>([]);
|
||||||
const [flyTarget, setFlyTarget] = useState<{ center: [number, number]; zoom?: number } | undefined>();
|
|
||||||
|
|
||||||
const handleFeatureClick = useCallback((feature: ClickedFeature | null) => {
|
const handleFeatureClick = useCallback((feature: ClickedFeature | null) => {
|
||||||
// null = clicked on empty space, close panel
|
// null = clicked on empty space, close panel
|
||||||
if (!feature || !feature.properties) {
|
if (!feature || !feature.properties) {
|
||||||
@@ -45,7 +43,7 @@ export function GeoportalModule() {
|
|||||||
|
|
||||||
const handleSearchResult = useCallback((result: SearchResult) => {
|
const handleSearchResult = useCallback((result: SearchResult) => {
|
||||||
if (result.coordinates) {
|
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}
|
onFeatureClick={handleFeatureClick}
|
||||||
onSelectionChange={setSelectedFeatures}
|
onSelectionChange={setSelectedFeatures}
|
||||||
layerVisibility={layerVisibility}
|
layerVisibility={layerVisibility}
|
||||||
center={flyTarget?.center}
|
|
||||||
zoom={flyTarget?.zoom}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Setup banner (auto-hides when ready) */}
|
{/* Setup banner (auto-hides when ready) */}
|
||||||
|
|||||||
@@ -845,12 +845,6 @@ export const MapViewer = forwardRef<MapViewerHandle, MapViewerProps>(
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [resolvedMartinUrl, basemap]);
|
}, [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 ---- */
|
/* ---- Disable interactions when in drawing modes ---- */
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const map = mapRef.current;
|
const map = mapRef.current;
|
||||||
|
|||||||
Reference in New Issue
Block a user