fix(geoportal-v2): handle PMTiles features without uuid id
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) <noreply@anthropic.com>
This commit is contained in:
@@ -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<string | null>(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 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<string, unknown> | undefined) ?? techData;
|
||||
setDetail({
|
||||
siruta: feature.siruta,
|
||||
cadastralRef: feature.cadastralRef,
|
||||
areaValue: feature.areaValue,
|
||||
enrichment: inner as Record<string, unknown>,
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
setError("network_error");
|
||||
} finally {
|
||||
@@ -227,6 +259,12 @@ export function FeatureInfoPanel({ feature, onClose }: Props) {
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{!detail && !loading && !error && (
|
||||
<div className="rounded-md border border-dashed bg-muted/30 p-2 text-xs text-muted-foreground">
|
||||
Apasă „Citește din ANCPI" pentru a încărca detaliile parcelei.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap gap-1 border-t bg-muted/30 p-2">
|
||||
|
||||
@@ -240,19 +240,31 @@ export const MapViewer = forwardRef<MapViewerHandle, Props>(function MapViewer(
|
||||
return;
|
||||
}
|
||||
const p = f.properties as Record<string, unknown>;
|
||||
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,
|
||||
|
||||
Reference in New Issue
Block a user