"use client"; 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); const { calls, loading } = useCalls(limitCount); const { systems } = useSystems(); const { isAdmin } = useAuth(); const systemMap = Object.fromEntries(systems.map((s) => [s.system_id, s])); 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
{/* 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 && (

Live ({active.length})

{active.map((c) => ( ))}
Time Talkgroup System Node Duration Audio
)} {/* History */}

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

{loading ? (

Loading…

) : filtered.length === 0 ? (

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

) : ( <>
{filtered.map((c) => ( ))}
Time Talkgroup System Node Duration Audio
{!activeFilters && ended.length >= limitCount && ( )} )}
); }