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:
@@ -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) => (
|
||||||
|
|||||||
Reference in New Issue
Block a user