Live wall: selected incident loads its own calls; resolved deep links; scrubber marks unloaded time (#239)

- LiveView: the selected incident merges useCallsByIncident (org_id +
  incident_ids CONTAINS, existing index, no orderBy/limit) into the calls fed
  to IncidentPanel and MapView, so a long incident keeps its full path and
  spine past the 500-call window. Stale rows from the previous selection are
  re-checked against the current id before merging.
- #inc= not in the active set is fetched with useIncident: a resolved one is
  selected, drawn on the map and shown under a "Resolved HH:MM" banner; a
  missing or other-org id shows "Incident not found". Auto-tour stays on
  active incidents.
- TimeScrubber: new loadedSince prop (oldest call when the window came back
  full). The axis before it is hatched and labelled "not loaded" instead of
  reading as a quiet stretch.
- useCalls returns hasMore (full page, counted before the duplicate_of
  filter); useIncident resets loading on a new id so a lookup doesn't flash
  "not found".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Logan Cusano
2026-10-05 03:25:13 -04:00
co-authored by Claude Opus 5.5
parent 6e462720a1
commit a267fdf982
4 changed files with 166 additions and 18 deletions
+6 -1
View File
@@ -11,6 +11,10 @@ export function useCalls(limitCount = 50, dateFrom?: Date, dateTo?: Date) {
const [calls, setCalls] = useState<CallRecord[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
// True when the query returned a full page (snap.size, counted BEFORE the
// duplicate_of filter below) — there may be older calls past the limit.
// calls.length can't answer that: dedup drops rows from a full page.
const [hasMore, setHasMore] = useState(false);
const { orgId } = useAuth();
// Stable ms values so the effect dependency doesn't fire on every render
@@ -63,6 +67,7 @@ export function useCalls(limitCount = 50, dateFrom?: Date, dateTo?: Date) {
// index alongside the existing org_id/started_at query.
.filter((c) => !c.duplicate_of);
setCalls(docs);
setHasMore(snap.size >= limitCount);
setLoading(false);
}, (err: FirestoreError) => { console.error("useCalls:", err); setError(err.message); setLoading(false); });
});
@@ -73,7 +78,7 @@ export function useCalls(limitCount = 50, dateFrom?: Date, dateTo?: Date) {
};
}, [limitCount, dateFromMs, dateToMs, orgId]);
return { calls, loading, error };
return { calls, loading, error, hasMore };
}
export function useCallsByIncident(incidentId: string | null) {