From 4006232c85a6d4257fc25a1dc9a265e9873fb210 Mon Sep 17 00:00:00 2001 From: Logan Date: Sun, 10 May 2026 22:17:20 -0400 Subject: [PATCH] Filter calls in ui --- drb-frontend/app/calls/page.tsx | 186 ++++++++++++++++++++++++++++++-- 1 file changed, 177 insertions(+), 9 deletions(-) diff --git a/drb-frontend/app/calls/page.tsx b/drb-frontend/app/calls/page.tsx index dc542d5..52bf715 100644 --- a/drb-frontend/app/calls/page.tsx +++ b/drb-frontend/app/calls/page.tsx @@ -1,10 +1,69 @@ "use client"; -import { useState } from "react"; +import { useState, useMemo } from "react"; import { useCalls } from "@/lib/useCalls"; import { useSystems } from "@/lib/useSystems"; import { CallRow } from "@/components/CallRow"; import { useAuth } from "@/components/AuthProvider"; +import type { CallRecord } from "@/lib/types"; + +const inputCls = + "bg-gray-800 border border-gray-700 rounded-lg px-3 py-1.5 text-sm text-white font-mono " + + "placeholder:text-gray-600 focus:outline-none focus:border-indigo-500 w-full"; + +function filterCalls(calls: CallRecord[], filters: Filters): CallRecord[] { + const q = filters.query.trim().toLowerCase(); + const tgid = filters.tgid.trim(); + const fromMs = filters.dateFrom ? new Date(filters.dateFrom).getTime() : null; + const toMs = filters.dateTo ? new Date(filters.dateTo + "T23:59:59").getTime() : null; + + return calls.filter((c) => { + // System filter + if (filters.systemId && c.system_id !== filters.systemId) return false; + + // TGID filter (exact match on the number) + if (tgid && String(c.talkgroup_id ?? "") !== tgid) return false; + + // Date range + const ts = new Date(c.started_at).getTime(); + if (fromMs !== null && ts < fromMs) return false; + if (toMs !== null && ts > toMs) return false; + + // Free-text: talkgroup name, node_id, transcript, tags + if (q) { + const hay = [ + c.talkgroup_name ?? "", + c.node_id, + c.transcript ?? "", + c.transcript_corrected ?? "", + ...(c.tags ?? []), + ].join(" ").toLowerCase(); + if (!hay.includes(q)) return false; + } + + return true; + }); +} + +interface Filters { + query: string; + tgid: string; + systemId: string; + dateFrom: string; + dateTo: string; +} + +const DEFAULT_FILTERS: Filters = { + query: "", + tgid: "", + systemId: "", + dateFrom: "", + dateTo: "", +}; + +function isActive(f: Filters) { + return f.query || f.tgid || f.systemId || f.dateFrom || f.dateTo; +} export default function CallsPage() { const [limitCount, setLimitCount] = useState(100); @@ -13,16 +72,122 @@ export default function CallsPage() { const { isAdmin } = useAuth(); const systemMap = Object.fromEntries(systems.map((s) => [s.system_id, s])); - const active = calls.filter((c) => c.status === "active"); - const ended = calls.filter((c) => c.status === "ended"); + const [filters, setFilters] = useState(DEFAULT_FILTERS); + const [showFilters, setShowFilters] = useState(false); + + function set(key: K, value: string) { + setFilters((f) => ({ ...f, [key]: value })); + } + + const active = calls.filter((c) => c.status === "active"); + const ended = calls.filter((c) => c.status === "ended"); + const filtered = useMemo(() => filterCalls(ended, filters), [ended, filters]); + + const activeFilters = isActive(filters); return (

Calls

- {calls.length} loaded +
+ {calls.length} loaded + +
+ {/* Filter bar */} + {showFilters && ( +
+
+ {/* Text search */} +
+ + set("query", e.target.value)} + placeholder="fire, Engine 5, dispatch…" + className={inputCls} + /> +
+ + {/* TGID */} +
+ + set("tgid", e.target.value)} + placeholder="e.g. 9048" + className={inputCls} + /> +
+ + {/* System */} +
+ + +
+ + {/* Date from */} +
+ + set("dateFrom", e.target.value)} + className={inputCls} + /> +
+ + {/* Date to */} +
+ + set("dateTo", e.target.value)} + className={inputCls} + /> +
+
+ + {activeFilters && ( +
+

+ {filtered.length} of {ended.length} calls match +

+ +
+ )} +
+ )} + + {/* Live calls — never filtered */} {active.length > 0 && (

@@ -51,14 +216,17 @@ export default function CallsPage() {

)} + {/* History */}

- History + History{activeFilters && ({filtered.length} filtered)}

{loading ? (

Loading…

- ) : ended.length === 0 ? ( -

No calls recorded yet.

+ ) : filtered.length === 0 ? ( +

+ {activeFilters ? "No calls match the current filters." : "No calls recorded yet."} +

) : ( <>
@@ -74,13 +242,13 @@ export default function CallsPage() { - {ended.map((c) => ( + {filtered.map((c) => ( ))}
- {ended.length >= limitCount && ( + {!activeFilters && ended.length >= limitCount && (