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,
|
X, RefreshCw, Loader2, FileText, Download, AlertCircle,
|
||||||
Home, Building, Building2, MapPin, ChevronRight, Users,
|
Home, Building, Building2, MapPin, ChevronRight, Users,
|
||||||
Sparkles, User, ShieldCheck, AlertTriangle, HelpCircle,
|
Sparkles, User, ShieldCheck, AlertTriangle, HelpCircle,
|
||||||
Factory, Warehouse,
|
Factory, Warehouse, ExternalLink, Hash, Layers, CalendarDays,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { cn } from "@/shared/lib/utils";
|
import { cn } from "@/shared/lib/utils";
|
||||||
|
|
||||||
@@ -98,6 +98,23 @@ const LABEL: Record<string, string> = {
|
|||||||
UAT: "UAT",
|
UAT: "UAT",
|
||||||
UAT_SIRUTA: "SIRUTA",
|
UAT_SIRUTA: "SIRUTA",
|
||||||
PARCEL_TECH_ENRICHED_AT: "Actualizat tehnic",
|
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
|
// ────────────────────────────────────────────────────────── formatters
|
||||||
@@ -657,29 +674,10 @@ export function FeatureInfoPanel({ feature, onClose, onSelectFeature, basic = fa
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
// Auto-enrich on sparse data
|
// Auto-enrich on sparse data — DISABLED per Marius. Refresh fires
|
||||||
useEffect(() => {
|
// only when the user explicitly hits the "Actualizează" button in
|
||||||
if (basic || !detail || refreshing) return;
|
// the Date eTerra header. Keeps the eTerra account pool out of
|
||||||
const e = (detail.enrichment ?? {}) as Record<string, unknown>;
|
// browse-spam territory.
|
||||||
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,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const handleBuildingSelect = (b: BuildingItem) => {
|
const handleBuildingSelect = (b: BuildingItem) => {
|
||||||
if (!onSelectFeature) return;
|
if (!onSelectFeature) return;
|
||||||
@@ -746,6 +744,40 @@ export function FeatureInfoPanel({ feature, onClose, onSelectFeature, basic = fa
|
|||||||
? buildings.length
|
? buildings.length
|
||||||
: hasBuildingFlag;
|
: 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 isActive = detail?.isActive !== false;
|
||||||
|
|
||||||
const cadrefHeader = feature.cadastralRef || feature.objectId || "—";
|
const cadrefHeader = feature.cadastralRef || feature.objectId || "—";
|
||||||
@@ -882,12 +914,13 @@ export function FeatureInfoPanel({ feature, onClose, onSelectFeature, basic = fa
|
|||||||
<Sparkles className="h-2.5 w-2.5" />
|
<Sparkles className="h-2.5 w-2.5" />
|
||||||
Date eTerra
|
Date eTerra
|
||||||
</p>
|
</p>
|
||||||
|
<div className="ml-auto flex items-center gap-1">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => refreshFromAncpi({ manual: true })}
|
onClick={() => refreshFromAncpi({ manual: true })}
|
||||||
disabled={refreshing || !feature.siruta || !feature.cadastralRef}
|
disabled={refreshing || !feature.siruta || !feature.cadastralRef}
|
||||||
className={cn(
|
className={cn(
|
||||||
"ml-auto inline-flex h-7 items-center gap-1.5 rounded px-2 text-[11px] font-medium transition-colors",
|
"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",
|
"border bg-background hover:bg-muted disabled:opacity-50",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -903,6 +936,17 @@ export function FeatureInfoPanel({ feature, onClose, onSelectFeature, basic = fa
|
|||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</button>
|
</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>
|
||||||
|
|
||||||
<div className={cn("space-y-2 px-2.5 py-2", refreshing && "animate-pulse opacity-70")}>
|
<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) && (
|
{(tipInscriere || dataCererii || actProp) && (
|
||||||
<div className="space-y-1 border-t pt-2">
|
<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} />}
|
{tipInscriere && <InfoRow label="Tip înscriere" value={tipInscriere} />}
|
||||||
{dataCererii && <InfoRow label="Data cererii" value={dataCererii} />}
|
{dataCererii && <InfoRow label="Data cererii" value={dataCererii} />}
|
||||||
{actProp && <InfoRow label="Act proprietate" value={actProp} />}
|
{actProp && <InfoRow label="Act proprietate" value={actProp} />}
|
||||||
</div>
|
</div>
|
||||||
|
</details>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -992,6 +1044,102 @@ export function FeatureInfoPanel({ feature, onClose, onSelectFeature, basic = fa
|
|||||||
</div>
|
</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) */}
|
{/* Apartamente (cladiri only) */}
|
||||||
{!loading && isCladiri && (condoLoading || (condoOwners && condoOwners.length > 0)) && (
|
{!loading && isCladiri && (condoLoading || (condoOwners && condoOwners.length > 0)) && (
|
||||||
<div className="mx-2 my-2">
|
<div className="mx-2 my-2">
|
||||||
|
|||||||
Reference in New Issue
Block a user