fix(geoportal): all layers OFF by default + full enrichment display

Layers:
- ALL layers OFF by default (just basemap on load)
- User activates what they need

Feature panel:
- Shows ALL enrichment fields: proprietari (full text, wrapping),
  CF vechi, nr topo, adresa, solicitant, intravilan, categorie
- Building info with icon (cu acte / fara acte warning)
- hasEnrichment check relaxed (any non-empty field counts)
- Panel scrollable (max-h 60vh) for long data
- WrapRow for multi-line text (proprietari, adresa)
- Enrichment button visible when no enrichment data
- Enrichment auto-updates panel on success

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
AI Assistant
2026-03-24 13:09:41 +02:00
parent dfa4815d75
commit d2b69d5ec6
2 changed files with 68 additions and 40 deletions
@@ -1,7 +1,7 @@
"use client";
import { useEffect, useState } from "react";
import { X, Loader2, Sparkles, FileDown, Download, ClipboardCopy } from "lucide-react";
import { X, Loader2, Sparkles, FileDown, Download, ClipboardCopy, Building2, AlertTriangle } from "lucide-react";
import { Button } from "@/shared/components/ui/button";
import type { ClickedFeature, FeatureDetail, FeatureEnrichmentData } from "../types";
@@ -19,10 +19,8 @@ export function FeatureInfoPanel({ feature, onClose }: FeatureInfoPanelProps) {
const [enrichMsg, setEnrichMsg] = useState("");
const [cfStatus, setCfStatus] = useState<CfStatus | null>(null);
// Fetch feature detail
useEffect(() => {
if (!feature) { setDetail(null); setCfStatus(null); return; }
const objectId = feature.properties.object_id ?? feature.properties.objectId;
const siruta = feature.properties.siruta;
if (!objectId || !siruta) { setDetail(null); return; }
@@ -37,7 +35,6 @@ export function FeatureInfoPanel({ feature, onClose }: FeatureInfoPanelProps) {
.then((data: { feature: FeatureDetail }) => {
if (cancelled) return;
setDetail(data.feature);
// Check CF status if we have a cadastral ref
const e = data.feature.enrichment as FeatureEnrichmentData | null;
const nrCad = e?.NR_CAD ?? data.feature.cadastralRef;
if (nrCad) {
@@ -61,7 +58,9 @@ export function FeatureInfoPanel({ feature, onClose }: FeatureInfoPanelProps) {
const title = isUat
? String(feature.properties.name ?? "UAT")
: cadRef ? `Parcela ${cadRef}` : `#${feature.properties.object_id ?? "?"}`;
const hasEnrichment = !!e && !!e.NR_CAD;
// Has ANY enrichment data (not just NR_CAD)
const hasEnrichment = !!e && Object.values(e).some((v) => v != null && v !== "" && v !== "-" && v !== 0);
const siruta = String(feature.properties.siruta ?? detail?.siruta ?? "");
const handleEnrich = async () => {
@@ -77,21 +76,16 @@ export function FeatureInfoPanel({ feature, onClose }: FeatureInfoPanelProps) {
});
const d = await resp.json();
if (resp.ok) {
// Update detail with enrichment data directly from response
if (d.enrichment && detail) {
setDetail({ ...detail, enrichment: d.enrichment, enrichedAt: new Date().toISOString() });
setEnrichMsg("");
} else {
// Fallback: reload from API
// Reload from API
const oid = feature.properties.object_id ?? feature.properties.objectId;
if (oid) {
const r = await fetch(`/api/geoportal/feature?objectId=${oid}&siruta=${siruta}&sourceLayer=${feature.sourceLayer}`);
if (r.ok) {
const data = await r.json();
setDetail(data.feature);
}
}
const r = await fetch(`/api/geoportal/feature?objectId=${oid}&siruta=${siruta}&sourceLayer=${feature.sourceLayer}`);
if (r.ok) { const data = await r.json(); setDetail(data.feature); }
setEnrichMsg("");
}
setEnrichMsg("");
} else {
setEnrichMsg(d.error ?? "Eroare la enrichment");
}
@@ -102,28 +96,17 @@ export function FeatureInfoPanel({ feature, onClose }: FeatureInfoPanelProps) {
}
};
const handleCopy = () => {
const text = [
cadRef && `Nr. cad: ${cadRef}`,
e?.NR_CF && `CF: ${e.NR_CF}`,
(e?.SUPRAFATA_2D ?? feature.properties.area_value) && `S: ${e?.SUPRAFATA_2D ?? feature.properties.area_value} mp`,
e?.PROPRIETARI && e.PROPRIETARI !== "-" && `Prop: ${e.PROPRIETARI}`,
siruta && `SIRUTA: ${siruta}`,
].filter(Boolean).join("\n");
navigator.clipboard.writeText(text);
};
return (
<div className="bg-background/95 backdrop-blur-sm border rounded-lg shadow-lg w-72 overflow-hidden">
<div className="bg-background/95 backdrop-blur-sm border rounded-lg shadow-lg w-80 max-h-[60vh] overflow-y-auto">
{/* Header */}
<div className="flex items-center justify-between px-3 py-2 border-b">
<div className="flex items-center justify-between px-3 py-2 border-b sticky top-0 bg-background/95 backdrop-blur-sm">
<h3 className="text-sm font-semibold truncate">{title}</h3>
<Button variant="ghost" size="sm" className="h-6 w-6 p-0 shrink-0 ml-2" onClick={onClose}>
<X className="h-3.5 w-3.5" />
</Button>
</div>
<div className="px-3 py-2 text-xs space-y-1">
<div className="px-3 py-2 text-xs space-y-1.5">
{loading && (
<div className="flex items-center gap-2 text-muted-foreground py-2 justify-center">
<Loader2 className="h-3.5 w-3.5 animate-spin" /> Se incarca...
@@ -140,16 +123,39 @@ export function FeatureInfoPanel({ feature, onClose }: FeatureInfoPanelProps) {
{!loading && !isUat && (
<>
{/* Basic info */}
<Row label="SIRUTA" value={siruta} />
<Row label="Nr. cadastral" value={e?.NR_CAD ?? cadRef} />
<Row label="Nr. CF" value={e?.NR_CF} />
<Row label="CF vechi" value={e?.NR_CF_VECHI} />
<Row label="Nr. topo" value={e?.NR_TOPO} />
<Row label="Suprafata" value={formatArea(e?.SUPRAFATA_2D ?? feature.properties.area_value)} />
{/* Enrichment data */}
{hasEnrichment && (
<>
{e?.PROPRIETARI && e.PROPRIETARI !== "-" && <Row label="Proprietari" value={e.PROPRIETARI} />}
{e?.INTRAVILAN && e.INTRAVILAN !== "-" && <Row label="Intravilan" value={e.INTRAVILAN} />}
{e?.CATEGORIE_FOLOSINTA && e.CATEGORIE_FOLOSINTA !== "-" && <Row label="Categorie" value={e.CATEGORIE_FOLOSINTA} />}
<div className="border-t pt-1.5 mt-1.5" />
<WrapRow label="Proprietari" value={e?.PROPRIETARI} />
<WrapRow label="Proprietari vechi" value={e?.PROPRIETARI_VECHI} />
<Row label="Intravilan" value={e?.INTRAVILAN} />
<Row label="Categorie" value={e?.CATEGORIE_FOLOSINTA} />
<WrapRow label="Adresa" value={e?.ADRESA} />
<Row label="Solicitant" value={e?.SOLICITANT} />
{/* Building info */}
{(e?.HAS_BUILDING === 1) && (
<div className="flex items-center gap-1.5 pt-1">
<Building2 className="h-3 w-3 text-blue-500" />
<span className="text-muted-foreground">Constructie:</span>
<span className="font-medium">
{e?.BUILD_LEGAL === 1 ? "Cu acte" : (
<span className="flex items-center gap-1 text-amber-600">
<AlertTriangle className="h-3 w-3" /> Fara acte
</span>
)}
</span>
</div>
)}
</>
)}
@@ -159,10 +165,10 @@ export function FeatureInfoPanel({ feature, onClose }: FeatureInfoPanelProps) {
<Button
variant="outline" size="sm" className="h-7 text-xs gap-1 flex-1"
onClick={handleEnrich} disabled={enriching}
title="Obtine date detaliate (proprietari, CF, categorie) de la eTerra. Datele se salveaza permanent in baza de date."
title="Obtine date detaliate de la eTerra (proprietari, CF, categorie). Se salveaza permanent."
>
{enriching ? <Loader2 className="h-3 w-3 animate-spin" /> : <Sparkles className="h-3 w-3" />}
Enrichment
{enriching ? "Se incarca..." : "Enrichment"}
</Button>
)}
@@ -188,14 +194,24 @@ export function FeatureInfoPanel({ feature, onClose }: FeatureInfoPanelProps) {
<Button
variant="outline" size="sm" className="h-7 text-xs gap-1"
onClick={handleCopy} title="Copiaza informatiile in clipboard"
onClick={() => {
const text = [
cadRef && `Nr. cad: ${cadRef}`,
e?.NR_CF && `CF: ${e.NR_CF}`,
(e?.SUPRAFATA_2D ?? feature.properties.area_value) && `S: ${e?.SUPRAFATA_2D ?? feature.properties.area_value} mp`,
e?.PROPRIETARI && e.PROPRIETARI !== "-" && `Prop: ${e.PROPRIETARI}`,
siruta && `SIRUTA: ${siruta}`,
].filter(Boolean).join("\n");
navigator.clipboard.writeText(text);
}}
title="Copiaza informatiile in clipboard"
>
<ClipboardCopy className="h-3 w-3" />
</Button>
</div>
{enrichMsg && (
<p className="text-xs text-muted-foreground mt-1">{enrichMsg}</p>
<p className="text-xs text-destructive mt-1">{enrichMsg}</p>
)}
</>
)}
@@ -204,12 +220,24 @@ export function FeatureInfoPanel({ feature, onClose }: FeatureInfoPanelProps) {
);
}
/** Single-line row (value truncated only if very long) */
function Row({ label, value }: { label: string; value: unknown }) {
if (!value || value === "-" || value === "") return null;
if (!value || value === "-" || value === "" || value === 0) return null;
return (
<div className="flex justify-between gap-2">
<span className="text-muted-foreground shrink-0">{label}</span>
<span className="text-right font-medium truncate">{String(value)}</span>
<span className="text-right font-medium break-words max-w-[60%]">{String(value)}</span>
</div>
);
}
/** Multi-line row (long text wraps) */
function WrapRow({ label, value }: { label: string; value: unknown }) {
if (!value || value === "-" || value === "") return null;
return (
<div>
<span className="text-muted-foreground">{label}</span>
<p className="font-medium text-xs leading-relaxed mt-0.5">{String(value)}</p>
</div>
);
}
@@ -26,14 +26,14 @@ const LAYER_GROUPS: LayerGroupDef[] = [
label: "Terenuri",
description: "Parcele cadastrale (zoom >= 13)",
color: "#22c55e",
defaultVisible: true,
defaultVisible: false,
},
{
id: "cladiri",
label: "Cladiri",
description: "Constructii (zoom >= 14)",
color: "#3b82f6",
defaultVisible: true,
defaultVisible: false,
},
{
id: "uats",