feat(geoportal-v2): cladire characteristics + eterra.live link + collapsible Înscriere
Per Marius's iteration on the panel: 1. AUTO-ENRICH DISABLED — the panel no longer fires /parcel/enrich on sparse-data load. Refresh only via the explicit "Actualizează" button in the Date eTerra header. Keeps the eTerra account pool safe from browse-spam (was draining 500/h on rapid clicks). 2. eterra.live link button — sits beside "Actualizează" in the Date eTerra header. Opens https://eterra.live/harta?siruta=...&cad=... in a new tab so the user can cross-check the full eterra.live panel. 3. ÎNSCRIERE collapsible — Tip înscriere / Data cererii / Act proprietate now hide inside a <details> closed by default (per the highlighted-block screenshot). Keeps the "above the fold" info trimmed to what matters at a glance. 4. CARACTERISTICI CORP — new section, only rendered for CLADIRI_ACTIVE clicks. Shows the cladire-specific enrichment fields the orchestrator populates after a deep-enrich: - Chip row: tip / destinație / subtype (chips) + Condominium chip with unit count + Cu/Fără acte status pill - 3-col metric strip: Regim înălțime / Niveluri / An construire - Suprafață CF, CF IE, Clasă energetică, Părți comune (rows) - Observații (multi-line InfoBlock) Fields wired: CLADIRE_TYPE, CLADIRE_DESTINATIE, CLADIRE_SUBTYPE, CLADIRE_REGIM, CLADIRE_NIVELURI, CLADIRE_AN_CONSTRUIRE, CLADIRE_AREA_CF, CLADIRE_OBSERVATII, CLADIRE_LANDBOOK_IE, CLADIRE_COMMON_PARTS, CLADIRE_UNITS_NO_ANCPI, CLADIRE_ENERGETIC_CLASS, IS_LEGAL_BUILDING, IS_CONDOMINIUM. All show only when populated (no empty "-" rows). LABEL map extended with Romanian translations. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -6,7 +6,7 @@ import {
|
||||
X, RefreshCw, Loader2, FileText, Download, AlertCircle,
|
||||
Home, Building, Building2, MapPin, ChevronRight, Users,
|
||||
Sparkles, User, ShieldCheck, AlertTriangle, HelpCircle,
|
||||
Factory, Warehouse,
|
||||
Factory, Warehouse, ExternalLink, Hash, Layers, CalendarDays,
|
||||
} from "lucide-react";
|
||||
import { cn } from "@/shared/lib/utils";
|
||||
|
||||
@@ -98,6 +98,23 @@ const LABEL: Record<string, string> = {
|
||||
UAT: "UAT",
|
||||
UAT_SIRUTA: "SIRUTA",
|
||||
PARCEL_TECH_ENRICHED_AT: "Actualizat tehnic",
|
||||
// Cladire-specific (CLADIRI_ACTIVE)
|
||||
CLADIRE_TYPE: "Tip clădire",
|
||||
CLADIRE_DESTINATIE: "Destinație",
|
||||
CLADIRE_DESTINATIE_CODE: "Cod destinație",
|
||||
CLADIRE_SUBTYPE: "Subtip",
|
||||
CLADIRE_REGIM: "Regim înălțime",
|
||||
CLADIRE_NIVELURI: "Niveluri",
|
||||
CLADIRE_AN_CONSTRUIRE: "An construire",
|
||||
CLADIRE_AREA_CF: "Suprafață CF",
|
||||
CLADIRE_LANDBOOK_IE: "Carte funciară IE",
|
||||
CLADIRE_COMMON_PARTS: "Părți comune",
|
||||
CLADIRE_OBSERVATII: "Observații",
|
||||
CLADIRE_UNITS_NO_ANCPI: "Nr. unități ANCPI",
|
||||
CLADIRE_ENERGETIC_CLASS: "Clasă energetică",
|
||||
IS_LEGAL_BUILDING: "Clădire legală",
|
||||
IS_CONDOMINIUM: "Condominium",
|
||||
ENRICHED_AT: "Actualizat",
|
||||
};
|
||||
|
||||
// ────────────────────────────────────────────────────────── formatters
|
||||
@@ -657,29 +674,10 @@ export function FeatureInfoPanel({ feature, onClose, onSelectFeature, basic = fa
|
||||
],
|
||||
);
|
||||
|
||||
// Auto-enrich on sparse data
|
||||
useEffect(() => {
|
||||
if (basic || !detail || refreshing) return;
|
||||
const e = (detail.enrichment ?? {}) as Record<string, unknown>;
|
||||
const richPresent = Boolean(e.NR_CF || e.ADRESA || e.PROPRIETARI);
|
||||
if (richPresent) return;
|
||||
const parcelKey =
|
||||
String(detail.id ?? "") ||
|
||||
`${feature.siruta}:${feature.cadastralRef}:${feature.layerId}`;
|
||||
const ssKey = `gis_auto_enrich_${parcelKey}`;
|
||||
if (typeof sessionStorage === "undefined") return;
|
||||
if (sessionStorage.getItem(ssKey)) return;
|
||||
sessionStorage.setItem(ssKey, "1");
|
||||
void refreshFromAncpi();
|
||||
}, [
|
||||
basic,
|
||||
detail,
|
||||
refreshing,
|
||||
feature.siruta,
|
||||
feature.cadastralRef,
|
||||
feature.layerId,
|
||||
refreshFromAncpi,
|
||||
]);
|
||||
// Auto-enrich on sparse data — DISABLED per Marius. Refresh fires
|
||||
// only when the user explicitly hits the "Actualizează" button in
|
||||
// the Date eTerra header. Keeps the eTerra account pool out of
|
||||
// browse-spam territory.
|
||||
|
||||
const handleBuildingSelect = (b: BuildingItem) => {
|
||||
if (!onSelectFeature) return;
|
||||
@@ -746,6 +744,40 @@ export function FeatureInfoPanel({ feature, onClose, onSelectFeature, basic = fa
|
||||
? buildings.length
|
||||
: hasBuildingFlag;
|
||||
|
||||
// Cladire-specific (only meaningful for CLADIRI_ACTIVE)
|
||||
const cladireType = String(enrichment.CLADIRE_TYPE ?? "").trim();
|
||||
const cladireDestinatie = String(enrichment.CLADIRE_DESTINATIE ?? "").trim();
|
||||
const cladireSubtype = String(enrichment.CLADIRE_SUBTYPE ?? "").trim();
|
||||
const cladireRegim = String(enrichment.CLADIRE_REGIM ?? "").trim();
|
||||
const cladireNiveluri = enrichment.CLADIRE_NIVELURI;
|
||||
const cladireAn = String(enrichment.CLADIRE_AN_CONSTRUIRE ?? "").trim();
|
||||
const cladireAreaCf = formatArea(enrichment.CLADIRE_AREA_CF);
|
||||
const cladireObservatii = String(enrichment.CLADIRE_OBSERVATII ?? "").trim();
|
||||
const cladireLandbookIe = String(enrichment.CLADIRE_LANDBOOK_IE ?? "").trim();
|
||||
const cladireCommonParts = String(enrichment.CLADIRE_COMMON_PARTS ?? "").trim();
|
||||
const cladireUnitsNo = enrichment.CLADIRE_UNITS_NO_ANCPI;
|
||||
const cladireEnergetic = String(enrichment.CLADIRE_ENERGETIC_CLASS ?? "").trim();
|
||||
const isLegalBuilding = enrichment.IS_LEGAL_BUILDING;
|
||||
const isCondominium = Number(enrichment.IS_CONDOMINIUM ?? 0) === 1;
|
||||
|
||||
const hasCladireData =
|
||||
isCladiri &&
|
||||
Boolean(
|
||||
cladireType ||
|
||||
cladireDestinatie ||
|
||||
cladireSubtype ||
|
||||
cladireRegim ||
|
||||
cladireAn ||
|
||||
cladireAreaCf ||
|
||||
cladireObservatii ||
|
||||
cladireLandbookIe ||
|
||||
cladireEnergetic ||
|
||||
cladireNiveluri != null ||
|
||||
cladireUnitsNo != null ||
|
||||
isLegalBuilding != null ||
|
||||
isCondominium,
|
||||
);
|
||||
|
||||
const isActive = detail?.isActive !== false;
|
||||
|
||||
const cadrefHeader = feature.cadastralRef || feature.objectId || "—";
|
||||
@@ -882,27 +914,39 @@ export function FeatureInfoPanel({ feature, onClose, onSelectFeature, basic = fa
|
||||
<Sparkles className="h-2.5 w-2.5" />
|
||||
Date eTerra
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => refreshFromAncpi({ manual: true })}
|
||||
disabled={refreshing || !feature.siruta || !feature.cadastralRef}
|
||||
className={cn(
|
||||
"ml-auto inline-flex h-7 items-center gap-1.5 rounded px-2 text-[11px] font-medium transition-colors",
|
||||
"border bg-background hover:bg-muted disabled:opacity-50",
|
||||
)}
|
||||
>
|
||||
{refreshing ? (
|
||||
<Loader2 className="h-3 w-3 shrink-0 animate-spin" />
|
||||
) : (
|
||||
<RefreshCw className="h-3 w-3 shrink-0" />
|
||||
)}
|
||||
<span>{hasEnrich ? "Actualizează" : "Încarcă"}</span>
|
||||
{enrichedAgo && hasEnrich && !refreshing && (
|
||||
<span className="hidden text-muted-foreground/80 sm:inline">
|
||||
· {enrichedAgo}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
<div className="ml-auto flex items-center gap-1">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => refreshFromAncpi({ manual: true })}
|
||||
disabled={refreshing || !feature.siruta || !feature.cadastralRef}
|
||||
className={cn(
|
||||
"inline-flex h-7 items-center gap-1.5 rounded px-2 text-[11px] font-medium transition-colors",
|
||||
"border bg-background hover:bg-muted disabled:opacity-50",
|
||||
)}
|
||||
>
|
||||
{refreshing ? (
|
||||
<Loader2 className="h-3 w-3 shrink-0 animate-spin" />
|
||||
) : (
|
||||
<RefreshCw className="h-3 w-3 shrink-0" />
|
||||
)}
|
||||
<span>{hasEnrich ? "Actualizează" : "Încarcă"}</span>
|
||||
{enrichedAgo && hasEnrich && !refreshing && (
|
||||
<span className="hidden text-muted-foreground/80 sm:inline">
|
||||
· {enrichedAgo}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
<a
|
||||
href={`https://eterra.live/harta?siruta=${encodeURIComponent(feature.siruta)}&cad=${encodeURIComponent(feature.cadastralRef)}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex h-7 items-center gap-1 rounded border bg-background px-2 text-[11px] font-medium hover:bg-muted"
|
||||
title="Deschide parcela în eterra.live"
|
||||
>
|
||||
<ExternalLink className="h-3 w-3 shrink-0" />
|
||||
eterra.live
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={cn("space-y-2 px-2.5 py-2", refreshing && "animate-pulse opacity-70")}>
|
||||
@@ -964,11 +1008,19 @@ export function FeatureInfoPanel({ feature, onClose, onSelectFeature, basic = fa
|
||||
)}
|
||||
|
||||
{(tipInscriere || dataCererii || actProp) && (
|
||||
<div className="space-y-1 border-t pt-2">
|
||||
{tipInscriere && <InfoRow label="Tip înscriere" value={tipInscriere} />}
|
||||
{dataCererii && <InfoRow label="Data cererii" value={dataCererii} />}
|
||||
{actProp && <InfoRow label="Act proprietate" value={actProp} />}
|
||||
</div>
|
||||
<details className="group border-t pt-2">
|
||||
<summary className="-mx-1 flex cursor-pointer list-none items-center gap-1 rounded px-1 py-0.5 hover:bg-muted/40">
|
||||
<ChevronRight className="h-3 w-3 shrink-0 text-muted-foreground transition-transform group-open:rotate-90" />
|
||||
<p className="text-[10px] font-medium uppercase tracking-wider text-muted-foreground">
|
||||
Înscriere
|
||||
</p>
|
||||
</summary>
|
||||
<div className="mt-1 space-y-1 pl-4">
|
||||
{tipInscriere && <InfoRow label="Tip înscriere" value={tipInscriere} />}
|
||||
{dataCererii && <InfoRow label="Data cererii" value={dataCererii} />}
|
||||
{actProp && <InfoRow label="Act proprietate" value={actProp} />}
|
||||
</div>
|
||||
</details>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -992,6 +1044,102 @@ export function FeatureInfoPanel({ feature, onClose, onSelectFeature, basic = fa
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Caracteristici corp (cladiri only) */}
|
||||
{!loading && isCladiri && hasCladireData && (
|
||||
<div className="mx-2 my-2 rounded-md border bg-muted/15">
|
||||
<div className="border-b px-2.5 py-1.5">
|
||||
<p className="flex items-center gap-1 text-[10px] font-semibold uppercase tracking-wider text-muted-foreground">
|
||||
<Building2 className="h-2.5 w-2.5" />
|
||||
Caracteristici corp
|
||||
</p>
|
||||
</div>
|
||||
<div className="space-y-2 px-2.5 py-2">
|
||||
{/* Tip + destinatie + subtype as chips */}
|
||||
<div className="flex flex-wrap items-center gap-1.5">
|
||||
{cladireType && <Chip>{cladireType}</Chip>}
|
||||
{cladireDestinatie && (
|
||||
<Chip title={`Destinație: ${cladireDestinatie}`}>{cladireDestinatie}</Chip>
|
||||
)}
|
||||
{cladireSubtype && cladireSubtype !== cladireDestinatie && (
|
||||
<Chip tone="muted">{cladireSubtype}</Chip>
|
||||
)}
|
||||
{isCondominium && (
|
||||
<Chip
|
||||
tone="default"
|
||||
icon={<Building2 className="h-3 w-3" />}
|
||||
>
|
||||
Condominium{cladireUnitsNo != null && ` · ${cladireUnitsNo} u.`}
|
||||
</Chip>
|
||||
)}
|
||||
{Number(isLegalBuilding) === 1 && (
|
||||
<Chip
|
||||
tone="success"
|
||||
icon={<ShieldCheck className="h-3 w-3" />}
|
||||
>
|
||||
Cu acte
|
||||
</Chip>
|
||||
)}
|
||||
{Number(isLegalBuilding) === 0 && (
|
||||
<Chip
|
||||
tone="warning"
|
||||
icon={<AlertTriangle className="h-3 w-3" />}
|
||||
>
|
||||
Fără acte
|
||||
</Chip>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Grid 3-col metrics: regim / niveluri / an */}
|
||||
{(cladireRegim || cladireNiveluri != null || cladireAn) && (
|
||||
<div className="grid grid-cols-3 gap-2 rounded-md bg-muted/30 px-1 py-1.5 text-center">
|
||||
<div className="flex flex-col">
|
||||
<span className="text-[9px] uppercase tracking-wider text-muted-foreground" title="Regim înălțime">
|
||||
Regim
|
||||
</span>
|
||||
<span className="font-mono text-xs font-semibold">
|
||||
{cladireRegim || "—"}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<span className="text-[9px] uppercase tracking-wider text-muted-foreground">
|
||||
Niveluri
|
||||
</span>
|
||||
<span className="font-mono text-xs font-semibold tabular-nums">
|
||||
{cladireNiveluri != null && cladireNiveluri !== ""
|
||||
? String(cladireNiveluri)
|
||||
: "—"}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<span className="text-[9px] uppercase tracking-wider text-muted-foreground">
|
||||
An
|
||||
</span>
|
||||
<span className="font-mono text-xs font-semibold tabular-nums">
|
||||
{cladireAn || "—"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{cladireAreaCf && (
|
||||
<InfoRow label="Suprafață CF" value={cladireAreaCf} />
|
||||
)}
|
||||
{cladireLandbookIe && (
|
||||
<InfoRow label="CF IE" value={cladireLandbookIe} mono />
|
||||
)}
|
||||
{cladireEnergetic && (
|
||||
<InfoRow label="Clasă energetică" value={cladireEnergetic} />
|
||||
)}
|
||||
{cladireCommonParts && (
|
||||
<InfoRow label="Părți comune" value={cladireCommonParts} />
|
||||
)}
|
||||
{cladireObservatii && (
|
||||
<InfoBlock label="Observații" value={cladireObservatii} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Apartamente (cladiri only) */}
|
||||
{!loading && isCladiri && (condoLoading || (condoOwners && condoOwners.length > 0)) && (
|
||||
<div className="mx-2 my-2">
|
||||
|
||||
Reference in New Issue
Block a user