"use client"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { signIn } from "next-auth/react"; import { X, RefreshCw, Loader2, FileText, AlertCircle, Home, Building, Building2, MapPin, ChevronRight, Users, Sparkles, ShieldCheck, AlertTriangle, HelpCircle, Factory, Warehouse, Map as MapIcon, FileSpreadsheet, FileBox, Receipt, ScrollText, } from "lucide-react"; import { cn } from "@/shared/lib/utils"; import { CfOrderModal } from "./cf-order-modal"; import { ExportModal, type ExportKind } from "./exports/export-modal"; import { useUatName } from "./uat-lookup"; const AUTH_RETRY_KEY = "gis_panel_auth_retry"; export interface ClickedFeatureLite { id: string; objectId?: string; siruta: string; cadastralRef: string; layerId: string; areaValue?: number; lat?: number; lng?: number; } interface ParcelDetail { id?: string; layerId?: string; siruta?: string; cadastralRef?: string; objectId?: number | string; areaValue?: number; isActive?: boolean; enrichment?: Record; enrichedAt?: string; [k: string]: unknown; } interface BuildingItem { id?: string; cadastralRef: string; areaValue?: number; isLegal?: number | null; destinatie?: string | null; isCondo?: boolean; unitCount?: number; } interface CondoOwner { unitNo?: string; apartmentNo?: string; owners?: string[]; area?: number; cf?: string; [k: string]: unknown; } interface Props { feature: ClickedFeatureLite; onClose: () => void; /** Switch the panel to a different feature (e.g., user clicked a * building row from the parcel's buildings list). Mirrors map-click * behaviour but without going through the map's queryRenderedFeatures. */ onSelectFeature?: (f: ClickedFeatureLite) => void; basic?: boolean; } const LABEL: Record = { NR_CF: "Nr. CF", NR_CF_VECHI: "CF vechi", PARCEL_LANDBOOK_NO: "Nr. CF (tehnic)", ACT_PROPRIETATE: "Act de proprietate", NR_CAD: "Nr. cadastral", NR_TOPO: "Nr. topografic", PARCEL_TOPO_NO: "Nr. topo (alt)", SUPRAFATA: "Suprafață", SUPRAFATA_R: "Suprafață reală", SUPRAFATA_2D: "Suprafață 2D", PARCEL_LEGAL_AREA: "Suprafață legală", ADRESA: "Adresă", PARCEL_POSTAL_NO: "Nr. poștal", PROPRIETARI: "Proprietari", PROPRIETARI_VECHI: "Proprietari anteriori", SOLICITANT: "Solicitant", DATA_CERERE: "Data cererii", TIP_INSCRIERE: "Tip înscriere", DOC: "Documente", CATEGORIE_FOLOSINTA: "Categorie folosință", INTRAVILAN: "Intravilan", HAS_BUILDING: "Are clădire", BUILD_LEGAL: "Clădire legală", NR_CORPURI: "Nr. corpuri", NR_CORPURI_LEGALE: "Nr. corpuri legale", PARCEL_HAS_LANDBOOK: "Are CF", PARCEL_IS_CONDOMINIUM: "Condominium", TARLA: "Tarla", PARCELA: "Parcelă", 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 function formatNum(v: unknown, fractionDigits = 0): string { if (typeof v !== "number" || !Number.isFinite(v)) return String(v ?? "-"); return v.toLocaleString("ro-RO", { minimumFractionDigits: fractionDigits, maximumFractionDigits: fractionDigits, }); } function parseArea(val: unknown): number | null { if (val == null || val === "") return null; if (typeof val === "number") return Number.isFinite(val) ? val : null; if (typeof val === "string") { const n = parseFloat(val.replace(/[^\d.,-]/g, "").replace(",", ".")); return Number.isFinite(n) ? n : null; } return null; } function formatArea(val: unknown): string | null { const n = parseArea(val); if (n == null) return null; return `${Math.round(n).toLocaleString("ro-RO")} mp`; } function formatRelativeTime(iso: string | null | undefined): string | null { if (!iso) return null; const d = new Date(iso); if (Number.isNaN(d.getTime())) return null; const secs = Math.max(0, Math.round((Date.now() - d.getTime()) / 1000)); if (secs < 60) return "acum câteva secunde"; const mins = Math.round(secs / 60); if (mins < 60) return `acum ${mins} min`; const hours = Math.round(mins / 60); if (hours < 24) return `acum ${hours} ${hours === 1 ? "oră" : "ore"}`; const days = Math.round(hours / 24); if (days < 30) return `acum ${days} ${days === 1 ? "zi" : "zile"}`; return d.toLocaleDateString("ro-RO"); } // Building cadref "354686-C1" → "C1". Top-level parcel → keep full. function buildingSuffix(cadref: string): string { const m = /-([^-]+)$/.exec(cadref); return m ? m[1]! : cadref; } // ────────────────────────────────────────────────────────── primitives function StatusDot({ active }: { active: boolean }) { return ( ); } function Chip({ tone = "default", icon, children, title, }: { tone?: "default" | "success" | "warning" | "danger" | "muted"; icon?: React.ReactNode; children: React.ReactNode; title?: string; }) { const toneClass = { default: "border-border bg-background text-foreground", success: "border-emerald-500/30 bg-emerald-500/10 text-emerald-900 dark:text-emerald-200", warning: "border-amber-500/30 bg-amber-500/10 text-amber-900 dark:text-amber-200", danger: "border-destructive/30 bg-destructive/10 text-destructive", muted: "border-dashed border-border text-muted-foreground", }[tone]; return ( {icon} {children} ); } function MetricCell({ label, value, hint, }: { label: string; value: string | null; hint?: string; }) { return (

{label}

{value ?? "—"}

); } function InfoRow({ label, value, mono, }: { label: string; value: string | number | null | undefined; mono?: boolean; }) { if (value === null || value === undefined || value === "") return null; return (
{label} {String(value)}
); } function InfoBlock({ label, value, }: { label: string; value: string | null | undefined; }) { if (!value) return null; return (

{label}

{value}

); } function CollapsibleInfoBlock({ label, value, }: { label: string; value: string | null | undefined; }) { if (!value) return null; return (

{label}

{value}

); } // ────────────────────────────────────────────────────────── buildings function buildingIcon(destinatie: string | null | undefined, isCondo?: boolean) { if (isCondo) return Building2; const d = (destinatie ?? "").toLowerCase(); if (/industrial|edilitar|hala|atelier|comerc/.test(d)) return Factory; if (/anex|magazie|depozit|garaj/.test(d)) return Warehouse; return Home; } function buildingTipLabel(b: BuildingItem): string { if (b.isCondo && b.unitCount) { return `Bloc · ${b.unitCount} ${b.unitCount === 1 ? "unitate" : "unități"}`; } if (b.destinatie) return b.destinatie; return "Construcție"; } function BuildingRow({ b, onSelect, }: { b: BuildingItem; onSelect: (b: BuildingItem) => void; }) { const Icon = buildingIcon(b.destinatie, b.isCondo); const status = b.isLegal === 1 ? { Icon: ShieldCheck, label: "Cu acte", tone: "ok" as const } : b.isLegal === 0 ? { Icon: AlertTriangle, label: "Fără acte", tone: "warn" as const } : { Icon: HelpCircle, label: "Necunoscut", tone: "muted" as const }; const areaStr = b.areaValue != null && b.areaValue > 0 ? `${Math.round(b.areaValue).toLocaleString("ro-RO")} mp` : null; return ( ); } // ────────────────────────────────────────────────────────── owner parsing function parseOwners(val: unknown): string[] { if (val == null || val === "") return []; if (Array.isArray(val)) return val.map((s) => String(s).trim()).filter(Boolean); if (typeof val === "string") { return val .split(/\s*[;|]\s*|\s*,\s*(?=[A-ZȘȚĂÎÂ])/) .map((s) => s.trim()) .filter(Boolean); } return [String(val)]; } // ────────────────────────────────────────────────────────── main panel export function FeatureInfoPanel({ feature, onClose, onSelectFeature, basic = false }: Props) { const [detail, setDetail] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [refreshing, setRefreshing] = useState(false); const [buildings, setBuildings] = useState(null); const [condoOwners, setCondoOwners] = useState(null); const [condoLoading, setCondoLoading] = useState(false); const [cfModalOpen, setCfModalOpen] = useState(false); const [cfInternBusy, setCfInternBusy] = useState(false); const [cfInternError, setCfInternError] = useState(null); const [exportKind, setExportKind] = useState(null); // Close the modals whenever the user switches to a different parcel — // keeps the modal scoped to a single decision instead of silently // re-targeting mid-flight. useEffect(() => { setCfModalOpen(false); setExportKind(null); setCfInternError(null); }, [feature.cadastralRef, feature.siruta, feature.layerId]); const handleCfIntern = useCallback(async () => { if (!feature.cadastralRef || !feature.siruta) return; setCfInternBusy(true); setCfInternError(null); try { const res = await fetch("/api/cf-intern/order", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ nrCadastral: feature.cadastralRef, siruta: feature.siruta, }), }); if (!res.ok) { let msg = `Eroare HTTP ${res.status}`; const text = await res.text(); try { const j = JSON.parse(text); if (typeof j.error === "string") msg = j.error; } catch { if (text) msg += `: ${text.slice(0, 200)}`; } setCfInternError(msg); return; } const blob = await res.blob(); const url = URL.createObjectURL(blob); const cd = res.headers.get("content-disposition") ?? ""; const m = /filename\*?=(?:UTF-8''|"?)([^";]+)/i.exec(cd); const filename = m?.[1] ? decodeURIComponent(m[1]) : `cf_intern_${feature.cadastralRef}.pdf`; const a = document.createElement("a"); a.href = url; a.download = filename; a.click(); setTimeout(() => URL.revokeObjectURL(url), 30_000); } catch (err) { setCfInternError(err instanceof Error ? err.message : String(err)); } finally { setCfInternBusy(false); } }, [feature.cadastralRef, feature.siruta]); const authRetriedRef = useRef( typeof window !== "undefined" && sessionStorage.getItem(AUTH_RETRY_KEY) === "1", ); const isCladiri = feature.layerId === "CLADIRI_ACTIVE"; const isTerenuri = feature.layerId === "TERENURI_ACTIVE"; // ── Hydrate detail ────────────────────────────────────── useEffect(() => { if (basic) return; let cancelled = false; setLoading(true); setError(null); setDetail(null); const run = async () => { try { let r: Response; if (feature.id) { r = await fetch(`/api/gis/parcela/${encodeURIComponent(feature.id)}`); } else { r = await fetch( `/api/gis/parcela/find?siruta=${encodeURIComponent(feature.siruta)}` + `&cad=${encodeURIComponent(feature.cadastralRef)}` + `&layerId=${encodeURIComponent(feature.layerId)}`, ); } if (cancelled) return; if (r.status === 404) { setLoading(false); return; } if (r.status === 403) { if (!authRetriedRef.current && typeof window !== "undefined") { authRetriedRef.current = true; sessionStorage.setItem(AUTH_RETRY_KEY, "1"); void signIn("authentik", { callbackUrl: window.location.href }); return; } setError("forbidden"); setLoading(false); return; } if (!r.ok) { setError("fetch_failed"); setLoading(false); return; } if (typeof window !== "undefined") sessionStorage.removeItem(AUTH_RETRY_KEY); const data = (await r.json()) as ParcelDetail; if (!cancelled) { setDetail(data); setLoading(false); } } catch { if (!cancelled) { setError("network_error"); setLoading(false); } } }; void run(); return () => { cancelled = true; }; }, [feature.id, feature.siruta, feature.cadastralRef, feature.layerId, basic]); // ── Buildings on a parcel ─────────────────────────────── // For TERENURI parcels: search by parent cadref and filter for // CLADIRI siblings whose cadref starts with "-". The search // trigram is loose enough that one call covers all building suffixes // on the parcel. useEffect(() => { if (basic || !isTerenuri || !feature.cadastralRef) return; let cancelled = false; setBuildings(null); const run = async () => { try { const r = await fetch( `/api/gis/search?q=${encodeURIComponent(feature.cadastralRef)}&limit=100`, ); if (!r.ok || cancelled) return; const sd = (await r.json()) as { features?: Array<{ id: string; layerId: string; cadastralRef: string; areaValue?: number; }>; }; const prefix = feature.cadastralRef + "-"; const buildings = (sd.features ?? []) .filter( (f) => f.layerId === "CLADIRI_ACTIVE" && f.cadastralRef.startsWith(prefix), ) .map((f) => ({ id: f.id, cadastralRef: f.cadastralRef, areaValue: f.areaValue, isLegal: null, })) .sort((a, b) => a.cadastralRef.localeCompare(b.cadastralRef)); if (!cancelled) setBuildings(buildings); } catch { if (!cancelled) setBuildings([]); } }; void run(); return () => { cancelled = true; }; }, [basic, isTerenuri, feature.cadastralRef]); // ── Hydrate isLegal/destinatie for each listed building ─ // After we have the bare list, fetch enrichment for each (parallel) // so the status pill can show Cu acte / Fără acte. Bounded concurrency // — gis-api cache-hits are 5ms so even 10 in parallel is fine. useEffect(() => { if (!buildings || buildings.length === 0) return; let cancelled = false; const needHydrate = buildings.some((b) => b.isLegal === null && b.id); if (!needHydrate) return; (async () => { const next = await Promise.all( buildings.map(async (b) => { if (!b.id || b.isLegal !== null) return b; try { const r = await fetch(`/api/gis/parcela/${encodeURIComponent(b.id)}`); if (!r.ok) return b; const d = (await r.json()) as { enrichment?: Record }; const e = d.enrichment ?? {}; const isLegalRaw = e.BUILD_LEGAL ?? e.PARCEL_HAS_LANDBOOK; const isLegal = isLegalRaw == null || isLegalRaw === "" ? null : Number(isLegalRaw) === 1 ? 1 : Number(isLegalRaw) === 0 ? 0 : null; const destinatie = (typeof e.CATEGORIE_FOLOSINTA === "string" ? e.CATEGORIE_FOLOSINTA : null) ?? null; const isCondo = Number(e.PARCEL_IS_CONDOMINIUM ?? 0) === 1; return { ...b, isLegal, destinatie, isCondo }; } catch { return b; } }), ); if (!cancelled) setBuildings(next); })(); return () => { cancelled = true; }; }, [buildings]); // ── Condo owners for buildings ────────────────────────── // Only fires for buildings flagged IS_CONDOMINIUM=1 (or // PARCEL_IS_CONDOMINIUM=1). For everything else the orchestrator's // eTerra round-trip would take ~10s and come back empty — bad UX. // We wait for `detail` to land so we can read the flag from enrichment. useEffect(() => { if (basic || !isCladiri || !feature.siruta || !feature.cadastralRef) return; if (!detail) return; const e = (detail.enrichment ?? {}) as Record; const isCondo = Number(e.IS_CONDOMINIUM ?? 0) === 1 || Number(e.PARCEL_IS_CONDOMINIUM ?? 0) === 1; if (!isCondo) { setCondoOwners(null); setCondoLoading(false); return; } let cancelled = false; setCondoOwners(null); setCondoLoading(true); const run = async () => { try { const r = await fetch("/api/gis/building/condo-owners", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ siruta: feature.siruta, cadastralRef: feature.cadastralRef, }), }); if (cancelled) return; if (!r.ok) { // Treat any failure as "no apartments known" — keep section // visible with a note instead of hiding silently. setCondoOwners([]); setCondoLoading(false); return; } const body = (await r.json()) as | { data?: { owners?: CondoOwner[] } } | { owners?: CondoOwner[] }; const inner = (body as { data?: { owners?: CondoOwner[] } }).data ?? body; const owners = Array.isArray((inner as { owners?: CondoOwner[] }).owners) ? (inner as { owners: CondoOwner[] }).owners : []; if (!cancelled) { setCondoOwners(owners); setCondoLoading(false); } } catch { if (!cancelled) { setCondoOwners([]); setCondoLoading(false); } } }; void run(); return () => { cancelled = true; }; }, [isCladiri, feature.siruta, feature.cadastralRef, basic, detail]); // ── Manual + auto refresh (deep enrich) ───────────────── const refreshFromAncpi = useCallback( async (opts: { manual?: boolean } = {}) => { if (!feature.siruta || !feature.cadastralRef) { setError("missing_siruta_or_cad"); return; } setRefreshing(true); setError(null); try { const enrichResp = await fetch("/api/gis/parcel/enrich", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ siruta: feature.siruta, cadastralRef: feature.cadastralRef, // Pass layerId so orchestrator skips its dash-suffix auto-detect // (more reliable than parsing "-C3" out of cadref). Orchestrator // PR ships 2026-05-20 — accepts CLADIRI_ACTIVE + TERENURI_ACTIVE. layerId: feature.layerId, force: true, ...(opts.manual ? { manualOverride: true } : {}), }), }); if (!enrichResp.ok) { const body = await enrichResp.json().catch(() => ({})); setError(body.error || `enrich_failed_${enrichResp.status}`); return; } const enriched = (await enrichResp.json().catch(() => null)) as | { siruta?: string; cadastralRef?: string; enrichment?: Record; enrichedAt?: string } | null; const id = detail?.id ?? feature.id; let updated: Response | null = null; if (id) { updated = await fetch(`/api/gis/parcela/${encodeURIComponent(id)}`); } else { updated = await fetch( `/api/gis/parcela/find?siruta=${encodeURIComponent(feature.siruta)}` + `&cad=${encodeURIComponent(feature.cadastralRef)}` + `&layerId=${encodeURIComponent(feature.layerId)}`, ); } if (updated && updated.ok) { setDetail(await updated.json()); } else if (enriched?.enrichment) { setDetail({ siruta: enriched.siruta ?? feature.siruta, cadastralRef: enriched.cadastralRef ?? feature.cadastralRef, areaValue: feature.areaValue, layerId: feature.layerId, enrichment: enriched.enrichment, enrichedAt: enriched.enrichedAt, }); } } catch { setError("network_error"); } finally { setRefreshing(false); } }, [ feature.id, feature.siruta, feature.cadastralRef, feature.layerId, feature.areaValue, detail?.id, ], ); // 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; onSelectFeature({ id: b.id ?? "", siruta: feature.siruta, cadastralRef: b.cadastralRef, layerId: "CLADIRI_ACTIVE", areaValue: b.areaValue, lat: feature.lat, lng: feature.lng, }); }; // ── derive view-model ─────────────────────────────────── const enrichment = (detail?.enrichment ?? {}) as Record; const hasEnrich = useMemo( () => Object.keys(enrichment).length > 0, [enrichment], ); const nrCf = String(enrichment.NR_CF ?? "").trim(); const nrCfVechi = String(enrichment.NR_CF_VECHI ?? "").trim(); const nrTopo = String(enrichment.NR_TOPO ?? "").trim() || String(enrichment.PARCEL_TOPO_NO ?? "").trim(); const adresa = String(enrichment.ADRESA ?? "").trim(); const solicitant = String(enrichment.SOLICITANT ?? "").trim(); const tipInscriere = String(enrichment.TIP_INSCRIERE ?? "").trim(); const dataCererii = String(enrichment.DATA_CERERE ?? "").trim(); const proprietari = String(enrichment.PROPRIETARI ?? "").trim(); const proprietariVechi = String(enrichment.PROPRIETARI_VECHI ?? "").trim(); const actProp = String(enrichment.ACT_PROPRIETATE ?? "").trim(); const areaGis = feature.areaValue ?? Number(detail?.areaValue ?? 0) ?? 0; const area2D = formatArea(enrichment.SUPRAFATA_2D); const areaLegala = formatArea(enrichment.PARCEL_LEGAL_AREA) ?? formatArea(enrichment.SUPRAFATA_R); const areaGisStr = areaGis > 0 ? `${Math.round(areaGis).toLocaleString("ro-RO")} mp` : null; const intravilanRaw = String(enrichment.INTRAVILAN ?? "").trim().toLowerCase(); const intravilanLabel = intravilanRaw === "da" ? "Intravilan" : intravilanRaw === "nu" ? "Extravilan" : hasEnrich ? "Extravilan (presupus)" : null; const intravilanTone = intravilanRaw === "da" ? "success" : intravilanRaw === "nu" || (hasEnrich && intravilanRaw === "") ? "warning" : "default"; const categorie = String(enrichment.CATEGORIE_FOLOSINTA ?? "").trim(); const nrCorpuri = Number(enrichment.NR_CORPURI ?? 0) || 0; const hasBuildingFlag = Number(enrichment.HAS_BUILDING ?? 0) || 0; const buildingsCount = nrCorpuri > 0 ? nrCorpuri : Array.isArray(buildings) ? 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 || "—"; const layerLabel = feature.layerId.replace("_ACTIVE", "").toLowerCase(); const uatName = useUatName(feature.siruta); const enrichedAgo = formatRelativeTime(detail?.enrichedAt); // ──────────────────────────────────────────────────────── return (
{/* Header */}
{!basic && detail && }

{cadrefHeader}

{layerLabel} {feature.areaValue != null && ( · {formatNum(feature.areaValue)} m² )} {uatName && ( · {uatName} )} {feature.siruta && ( · {feature.siruta} )}

{basic && (
Acces restricționat — afișăm doar identificatorul cadastral și suprafața GIS. Contactează administratorul pentru drepturi extinse.
)} {!basic && (
{loading && (
Se încarcă datele parcelei…
)} {refreshing && !loading && (
Se preiau date suplimentare din ANCPI…
)} {error === "forbidden" && (
Datele detaliate nu pot fi încărcate momentan.
)} {error && error !== "forbidden" && (
{error === "no_available_account" ? "Pool-ul ANCPI e temporar epuizat — încearcă din nou peste câteva minute." : error === "no_immovable_match" ? "Parcela nu există în baza eTerra (cadref + SIRUTA nu se potrivesc)." : error === "parcel_not_found" ? isCladiri ? "Deep-enrich nu suportă încă construcțiile — datele clădirii vin via parcela părinte (gis-api orchestrator side)." : "Parcela nu există în baza centrală gis_core." : error === "eterra_fetch_failed" ? "eTerra ANCPI nu răspunde momentan. Reîncearcă în 1-2 minute." : error === "search_limit_exceeded" ? "Numărul cadastral e foarte comun (sute de parcele). gis-api are limit 50 la căutare — aceasta nu apare." : `Eroare: ${error}`}
)} {/* Caracteristici chips */} {!loading && detail && (
{intravilanLabel ? ( } > {intravilanLabel} ) : ( Intravilan ? )} {categorie && ( {categorie} )} {buildingsCount > 0 && ( } title={`${buildingsCount} construcție${buildingsCount > 1 ? "i" : ""} pe parcelă`} > {buildingsCount} corp{buildingsCount > 1 ? "uri" : ""} )}
)} {/* Metric strip — Suprafețe */} {!loading && detail && (areaGisStr || area2D || areaLegala) && (
)} {/* Date eTerra card */} {!loading && detail && (

Date eTerra

{!hasEnrich && !refreshing && (

Date ne-încărcate — apasă{" "} din dreapta sus.

)} {(nrCf || nrTopo) && (
{nrCf && (

Nr. CF

{nrCf}

)} {nrTopo && (

Nr. topo

{nrTopo}

)}
)} {nrCfVechi && nrCfVechi !== nrCf && ( )} {adresa && (

{adresa}

{feature.lat != null && feature.lng != null && ( Google Maps )}
)} {proprietari && ( )} {proprietariVechi && ( )} {/* Înscriere — collapsed. Holds the most-recent application metadata (who applied, when, what document) which is NOT the same as current ownership. SOLICITANT lives here, not next to PROPRIETARI, to avoid the "Bojan Elena = proprietar?" confusion. */} {(solicitant || tipInscriere || dataCererii || actProp) && (

Înscriere

{solicitant && } {tipInscriere && } {dataCererii && } {actProp && }
)} {enrichedAgo && hasEnrich && (

Actualizat din ANCPI · {enrichedAgo}

)}
)} {/* Construcții list (terenuri only) */} {!loading && isTerenuri && buildings && buildings.length > 0 && (

Construcții ({buildings.length})

{buildings.map((b) => ( ))}
)} {/* Caracteristici corp (cladiri only) */} {!loading && isCladiri && hasCladireData && (

Caracteristici corp

{/* Tip + destinatie + subtype as chips */}
{cladireType && {cladireType}} {cladireDestinatie && ( {cladireDestinatie} )} {cladireSubtype && cladireSubtype !== cladireDestinatie && ( {cladireSubtype} )} {isCondominium && ( } > Condominium{cladireUnitsNo != null && ` · ${cladireUnitsNo} u.`} )} {Number(isLegalBuilding) === 1 && ( } > Cu acte )} {Number(isLegalBuilding) === 0 && ( } > Fără acte )}
{/* Grid 3-col metrics: regim / niveluri / an */} {(cladireRegim || cladireNiveluri != null || cladireAn) && (
Regim {cladireRegim || "—"}
Niveluri {cladireNiveluri != null && cladireNiveluri !== "" ? String(cladireNiveluri) : "—"}
An {cladireAn || "—"}
)} {cladireAreaCf && ( )} {cladireLandbookIe && ( )} {cladireEnergetic && ( )} {cladireCommonParts && ( )} {cladireObservatii && ( )}
)} {/* Apartamente (cladiri only — only mounted when IS_CONDOMINIUM=1 per the gating useEffect; non-condos skip the fetch entirely so we never sit on a 10s eTerra round-trip just to discover there's nothing to show). */} {!loading && isCladiri && (condoLoading || condoOwners != null) && (

Apartamente {condoOwners && condoOwners.length > 0 && ( ({condoOwners.length}) )}

{condoLoading && (
se încarcă…
)} {!condoLoading && condoOwners != null && condoOwners.length === 0 && (

Fără apartamente înregistrate la ANCPI.

)} {condoOwners && condoOwners.length > 0 && (
{condoOwners.map((u, i) => (
{u.unitNo ?? u.apartmentNo ?? `Unitate #${i + 1}`} {u.area != null && ( {Math.round(u.area).toLocaleString("ro-RO")} mp )}
{u.cf && (

CF: {u.cf}

)} {Array.isArray(u.owners) && u.owners.length > 0 && (
    {parseOwners(u.owners).map((o, j) => (
  • {o}
  • ))}
)}
))}
)}
)} {/* Localizare — collapsible. Coords + ObjectId for those who need them (cadastrali, etc.). Skipped when ADRESA is shown above (Google Maps link moves there); otherwise this is the only way to get a map link, so keep it. */} {feature.lat != null && feature.lng != null && (

Localizare

{feature.objectId && ( ID {feature.objectId} )}
WGS84 {feature.lat.toFixed(6)}, {feature.lng.toFixed(6)}
SIRUTA {feature.siruta || "—"}
Deschide în Google Maps
)} {!loading && !detail && !error && (
Parcela nu există încă în baza de date centrală. Apasă „Încarcă" în secțiunea Date eTerra pentru a o adăuga.
)}
)} {/* Actions toolbar — exports + CF split */}
{/* 4 export buttons row: PIZ / Plan situație / Coord / DXF */}
{/* CF row: intern (free, ~2-3s) vs ANCPI extract (1 credit ePay) */}
{cfInternError && (
{cfInternError}
)}
{/* CF order modal — confirmation + animated multi-step progress */} setCfModalOpen(false)} /> {/* Export modal — used by all 4 of PIZ/PAD/Coord/DXF buttons */} {exportKind && ( setExportKind(null)} /> )}
); }