Outages review fixes: reset on new target, memoize marker icons, coverage note in popup (#184)
- useOutagesIn kept the previous incident's answer while fetching the next one (IncidentPanel isn't keyed), so a newly selected incident briefly read 'none reported here'. Drop the old answer when the target changes. - OutageLayer built a fresh divIcon per marker every render; MapView re-renders on live updates, so Leaflet rebuilt every marker's DOM. Memoize. - Popup now carries the provider's coverage note (ODIN: non-reporting utilities, e.g. Con Ed/O&R, show nothing even during an outage). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
d10c2f732a
commit
e7070a0afa
@@ -91,6 +91,7 @@ function OutagePopupBody({ a, feed }: { a: OutageArea; feed?: OutageProviderFeed
|
|||||||
</ul>
|
</ul>
|
||||||
)}
|
)}
|
||||||
<div className="text-[10px] text-ink-muted">County-level count — the marker is the county's centre, not where the outage is.</div>
|
<div className="text-[10px] text-ink-muted">County-level count — the marker is the county's centre, not where the outage is.</div>
|
||||||
|
{feed?.coverage_note && <div className="text-[10px] text-ink-muted">{feed.coverage_note}</div>}
|
||||||
<SourceCredit credit={feed} fallbackName={feed?.name ?? a.provider}
|
<SourceCredit credit={feed} fallbackName={feed?.name ?? a.provider}
|
||||||
updated={[updated && `utility data ${updated}`, feed && `fetched ${fetchedLabel(feed.fetched_at) ?? "—"}`].filter(Boolean).join(" · ") || null} />
|
updated={[updated && `utility data ${updated}`, feed && `fetched ${fetchedLabel(feed.fetched_at) ?? "—"}`].filter(Boolean).join(" · ") || null} />
|
||||||
</div>
|
</div>
|
||||||
@@ -163,7 +164,10 @@ export function OutageLayer({ shown }: { shown: boolean }) {
|
|||||||
const state = useMapOutages(map, shown);
|
const state = useMapOutages(map, shown);
|
||||||
const areas = useMemo(() => state.data?.areas ?? [], [state.data]);
|
const areas = useMemo(() => state.data?.areas ?? [], [state.data]);
|
||||||
const feeds = useMemo(() => new Map((state.data?.providers ?? []).map((p) => [p.id, p])), [state.data]);
|
const feeds = useMemo(() => new Map((state.data?.providers ?? []).map((p) => [p.id, p])), [state.data]);
|
||||||
const drawable = areas.filter((a) => a.lat != null && a.lon != null);
|
const drawable = useMemo(() => areas.filter((a) => a.lat != null && a.lon != null), [areas]);
|
||||||
|
// One icon per area per response: a fresh divIcon each render would make
|
||||||
|
// Leaflet rebuild every marker's DOM whenever MapView re-renders.
|
||||||
|
const icons = useMemo(() => new Map(drawable.map((a) => [a.id, outageIcon(a)])), [drawable]);
|
||||||
const liveCount = areas.filter((a) => !a.stale && a.customers_out > 0).length;
|
const liveCount = areas.filter((a) => !a.stale && a.customers_out > 0).length;
|
||||||
const staleCount = areas.filter((a) => a.stale).length;
|
const staleCount = areas.filter((a) => a.stale).length;
|
||||||
if (!shown) return null;
|
if (!shown) return null;
|
||||||
@@ -171,7 +175,7 @@ export function OutageLayer({ shown }: { shown: boolean }) {
|
|||||||
<>
|
<>
|
||||||
<OutageStatus map={map} state={state} liveCount={liveCount} staleCount={staleCount} />
|
<OutageStatus map={map} state={state} liveCount={liveCount} staleCount={staleCount} />
|
||||||
{drawable.map((a) => (
|
{drawable.map((a) => (
|
||||||
<Marker key={a.id} position={[a.lat!, a.lon!]} icon={outageIcon(a)} zIndexOffset={a.stale ? -100 : 100}
|
<Marker key={a.id} position={[a.lat!, a.lon!]} icon={icons.get(a.id)!} zIndexOffset={a.stale ? -100 : 100}
|
||||||
title={`${countyName(a)}: ${a.stale ? "stale rows only" : `${a.customers_out.toLocaleString()} out`}`}>
|
title={`${countyName(a)}: ${a.stale ? "stale rows only" : `${a.customers_out.toLocaleString()} out`}`}>
|
||||||
<Popup minWidth={240} maxWidth={320}>
|
<Popup minWidth={240} maxWidth={320}>
|
||||||
<OutagePopupBody a={a} feed={feeds.get(a.provider)} />
|
<OutagePopupBody a={a} feed={feeds.get(a.provider)} />
|
||||||
|
|||||||
@@ -34,7 +34,10 @@ function useOutageFetch(
|
|||||||
const seq = useRef(0);
|
const seq = useRef(0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!getBox) { setData(null); setError(null); setLoaded(false); return; }
|
// A new target (another incident, overlay re-enabled) must not be judged
|
||||||
|
// against the previous target's answer: drop it and show "checking…".
|
||||||
|
setData(null); setError(null); setLoaded(false);
|
||||||
|
if (!getBox) return;
|
||||||
const boxFn = getBox;
|
const boxFn = getBox;
|
||||||
let last = 0;
|
let last = 0;
|
||||||
const load = async () => {
|
const load = async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user