Files
ArchiTools/src/app/api/geoportal/search/route.ts
T
Claude VM 177f2104c1 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>
2026-04-07 21:21:09 +03:00

162 lines
5.4 KiB
TypeScript

/**
* GET /api/geoportal/search?q=...&type=...&limit=...
*
* Searches parcels (by cadastral ref, owner) and UATs (by name).
* Returns centroids in EPSG:4326 (WGS84) for map flyTo.
*/
import { NextResponse } from "next/server";
import { prisma } from "@/core/storage/prisma";
export const runtime = "nodejs";
export const dynamic = "force-dynamic";
type SearchResultItem = {
id: string;
type: "parcel" | "uat" | "building";
label: string;
sublabel?: string;
coordinates?: [number, number];
};
export async function GET(req: Request) {
try {
const url = new URL(req.url);
const q = url.searchParams.get("q")?.trim() ?? "";
const typeFilter = url.searchParams.get("type") ?? "";
const limit = Math.min(parseInt(url.searchParams.get("limit") ?? "20", 10), 50);
if (q.length < 2) {
return NextResponse.json({ results: [] });
}
const results: SearchResultItem[] = [];
const pattern = `%${q}%`;
// Search UATs by name
if (!typeFilter || typeFilter === "uat") {
const uats = await prisma.$queryRaw`
SELECT
siruta,
name,
county,
ST_X(ST_Centroid(ST_Transform(geom, 4326))) as lng,
ST_Y(ST_Centroid(ST_Transform(geom, 4326))) as lat
FROM "GisUat"
WHERE geom IS NOT NULL
AND (name ILIKE ${pattern} OR county ILIKE ${pattern})
ORDER BY name
LIMIT ${limit}
` as Array<{ siruta: string; name: string; county: string | null; lng: number; lat: number }>;
for (const u of uats) {
results.push({
id: `uat-${u.siruta}`,
type: "uat",
label: u.name,
sublabel: u.county ? `Jud. ${u.county}` : undefined,
coordinates: u.lng && u.lat ? [u.lng, u.lat] : undefined,
});
}
}
// Search parcels by cadastral ref or enrichment data
if (!typeFilter || typeFilter === "parcel") {
const isNumericish = /^\d/.test(q);
if (isNumericish) {
// Search by cadastral reference
const parcels = await prisma.$queryRaw`
SELECT
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<string, unknown> | null;
lng: number;
lat: number;
}>;
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}${uatLabel}`,
sublabel: [area, p.uat_name ? `SIRUTA ${p.siruta}` : ""].filter(Boolean).join(" | "),
coordinates: p.lng && p.lat ? [p.lng, p.lat] : undefined,
});
}
} else {
// Search by owner name in enrichment JSON
const parcels = await prisma.$queryRaw`
SELECT
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<string, unknown> | null;
lng: number;
lat: number;
}>;
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}${uatLabel}`,
sublabel: [ownerShort, p.uat_name ? `SIRUTA ${p.siruta}` : ""].filter(Boolean).join(" | "),
coordinates: p.lng && p.lat ? [p.lng, p.lat] : undefined,
});
}
}
}
return NextResponse.json({ results: results.slice(0, limit) });
} catch (error) {
const msg = error instanceof Error ? error.message : "Eroare la cautare";
return NextResponse.json({ error: msg }, { status: 500 });
}
}