fix(geoportal-v2): gate condo-owners on IS_CONDOMINIUM + visible empty state

Two issues from Marius's cladiri screenshots:

1. APARTAMENTE "se încarcă…" sat for ~10s then vanished
   — useEffect fired for every CLADIRI click regardless of whether
   the building was actually a condo. Orchestrator's
   /api/v1/building/condo-owners hit eTerra live, got back an empty
   list for non-condos, returned [], section auto-hid → user saw the
   spinner blink and disappear.

   New gate: useEffect waits for `detail` to land, then reads
   IS_CONDOMINIUM / PARCEL_IS_CONDOMINIUM from enrichment. If neither
   is `1`, skip the fetch entirely. Non-condos no longer pay the 10s
   eTerra round-trip just to show nothing.

2. EMPTY CONDO LISTS WERE HIDING SILENTLY
   — for buildings flagged condo where ANCPI hasn't registered units
   yet, the section would still vanish (`condoOwners.length > 0`
   check). Now: if the fetch returns []  AND the building is a
   condo, render the section with "Fără apartamente înregistrate la
   ANCPI." That's the truthful UX. Same fallback when the fetch
   errors — treat as empty rather than swallow.

Render trigger flipped from
  (condoLoading || (condoOwners && condoOwners.length > 0))
to
  (condoLoading || condoOwners != null)
so the section shows whenever the gate decided to fetch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude VM
2026-05-20 22:03:18 +03:00
parent c7cf1aee49
commit 36840f31f6
@@ -576,8 +576,22 @@ export function FeatureInfoPanel({ feature, onClose, onSelectFeature, basic = fa
}, [buildings]); }, [buildings]);
// ── Condo owners for 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(() => { useEffect(() => {
if (basic || !isCladiri || !feature.siruta || !feature.cadastralRef) return; if (basic || !isCladiri || !feature.siruta || !feature.cadastralRef) return;
if (!detail) return;
const e = (detail.enrichment ?? {}) as Record<string, unknown>;
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; let cancelled = false;
setCondoOwners(null); setCondoOwners(null);
setCondoLoading(true); setCondoLoading(true);
@@ -593,6 +607,9 @@ export function FeatureInfoPanel({ feature, onClose, onSelectFeature, basic = fa
}); });
if (cancelled) return; if (cancelled) return;
if (!r.ok) { if (!r.ok) {
// Treat any failure as "no apartments known" — keep section
// visible with a note instead of hiding silently.
setCondoOwners([]);
setCondoLoading(false); setCondoLoading(false);
return; return;
} }
@@ -608,14 +625,17 @@ export function FeatureInfoPanel({ feature, onClose, onSelectFeature, basic = fa
setCondoLoading(false); setCondoLoading(false);
} }
} catch { } catch {
if (!cancelled) setCondoLoading(false); if (!cancelled) {
setCondoOwners([]);
setCondoLoading(false);
}
} }
}; };
void run(); void run();
return () => { return () => {
cancelled = true; cancelled = true;
}; };
}, [isCladiri, feature.siruta, feature.cadastralRef, basic]); }, [isCladiri, feature.siruta, feature.cadastralRef, basic, detail]);
// ── Manual + auto refresh (deep enrich) ───────────────── // ── Manual + auto refresh (deep enrich) ─────────────────
const refreshFromAncpi = useCallback( const refreshFromAncpi = useCallback(
@@ -1164,8 +1184,11 @@ export function FeatureInfoPanel({ feature, onClose, onSelectFeature, basic = fa
</div> </div>
)} )}
{/* Apartamente (cladiri only) */} {/* Apartamente (cladiri only — only mounted when IS_CONDOMINIUM=1
{!loading && isCladiri && (condoLoading || (condoOwners && condoOwners.length > 0)) && ( 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) && (
<div className="mx-2 my-2"> <div className="mx-2 my-2">
<p className="mb-1 flex items-center gap-1 px-1 text-[10px] font-semibold uppercase tracking-wider text-muted-foreground"> <p className="mb-1 flex items-center gap-1 px-1 text-[10px] font-semibold uppercase tracking-wider text-muted-foreground">
<Users className="h-3 w-3" /> <Users className="h-3 w-3" />
@@ -1181,6 +1204,11 @@ export function FeatureInfoPanel({ feature, onClose, onSelectFeature, basic = fa
<Loader2 className="h-3 w-3 animate-spin" /> se încarcă <Loader2 className="h-3 w-3 animate-spin" /> se încarcă
</div> </div>
)} )}
{!condoLoading && condoOwners != null && condoOwners.length === 0 && (
<p className="px-1 text-[11px] italic text-muted-foreground">
Fără apartamente înregistrate la ANCPI.
</p>
)}
{condoOwners && condoOwners.length > 0 && ( {condoOwners && condoOwners.length > 0 && (
<div className="space-y-1.5"> <div className="space-y-1.5">
{condoOwners.map((u, i) => ( {condoOwners.map((u, i) => (