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";
|
import { cn } from "@/shared/lib/utils";
|
||||||
|
|
||||||
export interface ClickedFeatureLite {
|
export interface ClickedFeatureLite {
|
||||||
|
/** GisFeature uuid — empty when tile only has object_id */
|
||||||
id: string;
|
id: string;
|
||||||
|
objectId?: string;
|
||||||
siruta: string;
|
siruta: string;
|
||||||
cadastralRef: string;
|
cadastralRef: string;
|
||||||
layerId: string;
|
layerId: string;
|
||||||
@@ -15,7 +17,7 @@ export interface ClickedFeatureLite {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface ParcelDetail {
|
interface ParcelDetail {
|
||||||
id: string;
|
id?: string;
|
||||||
layerId?: string;
|
layerId?: string;
|
||||||
siruta?: string;
|
siruta?: string;
|
||||||
cadastralRef?: string;
|
cadastralRef?: string;
|
||||||
@@ -60,8 +62,17 @@ export function FeatureInfoPanel({ feature, onClose }: Props) {
|
|||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [refreshing, setRefreshing] = useState(false);
|
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(() => {
|
useEffect(() => {
|
||||||
|
if (!feature.id) {
|
||||||
|
setDetail(null);
|
||||||
|
setError(null);
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
@@ -92,6 +103,12 @@ export function FeatureInfoPanel({ feature, onClose }: Props) {
|
|||||||
}, [feature.id]);
|
}, [feature.id]);
|
||||||
|
|
||||||
const refreshFromAncpi = async () => {
|
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);
|
setRefreshing(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
try {
|
try {
|
||||||
@@ -99,8 +116,8 @@ export function FeatureInfoPanel({ feature, onClose }: Props) {
|
|||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
siruta: feature.siruta,
|
siruta,
|
||||||
cadastralRef: feature.cadastralRef,
|
cadastralRef,
|
||||||
force: true,
|
force: true,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
@@ -109,11 +126,26 @@ export function FeatureInfoPanel({ feature, onClose }: Props) {
|
|||||||
setError(body.error || "refresh_failed");
|
setError(body.error || "refresh_failed");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Re-fetch parcela detail
|
const techData = await res.json().catch(() => null);
|
||||||
const updated = await fetch(
|
|
||||||
`/api/gis/parcela/${encodeURIComponent(feature.id)}`,
|
// If the tile carried a GisFeature uuid, refresh stored detail.
|
||||||
);
|
// Otherwise project the orchestrator response into the panel directly.
|
||||||
if (updated.ok) setDetail(await updated.json());
|
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 {
|
} catch {
|
||||||
setError("network_error");
|
setError("network_error");
|
||||||
} finally {
|
} 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>
|
||||||
|
|
||||||
<div className="flex flex-wrap gap-1 border-t bg-muted/30 p-2">
|
<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;
|
return;
|
||||||
}
|
}
|
||||||
const p = f.properties as Record<string, unknown>;
|
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 =
|
const sourceLayer =
|
||||||
(f as unknown as { sourceLayer?: string }).sourceLayer ?? "";
|
(f as unknown as { sourceLayer?: string }).sourceLayer ?? "";
|
||||||
const layerId =
|
const layerId =
|
||||||
sourceLayer === "gis_cladiri" ? "CLADIRI_ACTIVE" : "TERENURI_ACTIVE";
|
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);
|
onFeatureClick(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
onFeatureClick({
|
onFeatureClick({
|
||||||
id,
|
id, // may be empty when tile lacks uuid; panel falls back to parcel/tech
|
||||||
|
objectId,
|
||||||
siruta,
|
siruta,
|
||||||
cadastralRef,
|
cadastralRef,
|
||||||
layerId,
|
layerId,
|
||||||
|
|||||||
Reference in New Issue
Block a user