Live wall: filter bar, discipline presets and text search (#235)

lib/liveFilter.ts (pure): WallFilter {types, sevFloor, systems, talkgroups,
ageH, tags, q, preset}, applyFilter over loaded incidents + calls, parse /
serialise to the f.* hash keys #234 reserved. Presets: Fire (fire + EMS
assists heard on a fire channel; Radar on), Storm (weather/wires/tree/flood
tags; Radar on), Traffic (accident / MVA tags; Traffic Events on).

components/live/FilterBar.tsx: one wrapping row under the wall bar; less-used
controls fold behind a toggle below md; read-only summary in kiosk.

LiveView applies the filter once and feeds the filtered lists to MapView
(pins, paths, rail), RegionPanel, TranscriptTicker, TimeScrubber and the
auto-tour; KPIs count the filtered set and say "filtered". Presets turn
layers on through the saved-view layer state, not a MapView edit. Pipeline
health stays unfiltered. liveWall.ts keeps commas literal in f.* values.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Logan Cusano
2026-10-05 03:11:02 -04:00
co-authored by Claude Opus 5.5
parent f3b21cd432
commit f479578942
4 changed files with 504 additions and 15 deletions
+6 -4
View File
@@ -213,9 +213,10 @@ export interface WallLink extends WallView {
/** Wall/kiosk presentation (no nav, large type, wake lock). */
kiosk?: boolean;
/**
* Extension point for the filter bar (server-26#235): every `f.<name>=<v>`
* hash key round-trips here untouched, so filters can be added without
* reshaping WallLink or breaking older links.
* The filter bar's state (server-26#235): every `f.<name>=<v>` hash key
* round-trips here untouched, so filters can be added without reshaping
* WallLink or breaking older links. lib/liveFilter.ts parseFilter /
* serialiseFilter give it meaning.
*/
filter?: Record<string, string>;
}
@@ -280,7 +281,8 @@ export function wallHash(link: WallLink): string {
}
if (link.base) parts.push(`base=${link.base}`);
if (link.filter) {
for (const k of Object.keys(link.filter).sort()) parts.push(`f.${enc(k)}=${enc(link.filter[k])}`);
// Filter lists (f.type=fire,ems) keep literal commas too (server-26#235).
for (const k of Object.keys(link.filter).sort()) parts.push(`f.${enc(k)}=${enc(link.filter[k]).replace(/%2C/g, ",")}`);
}
return parts.length ? `#${parts.join("&")}` : "";
}