From e55412d8c75547d294dcf2eb0dbe5082a2963030 Mon Sep 17 00:00:00 2001 From: Logan Date: Wed, 3 Jun 2026 01:08:21 -0400 Subject: [PATCH] UI Updates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit app/map/page.tsx Removed IncidentCard component and the incidents grid below the map — the on-map sidebar inside MapView is the single display Moved kiosk exit button from top-3 left-3 (overlapping zoom controls) to bottom-[5.5rem] left-3 components/MapView.tsx Fixed popup "View incident →" link — adds stopPropagation() + window.location.href to prevent Leaflet intercepting the click Added "View details →" link on each sidebar incident card so you can navigate from the map panel without opening a popup Added "News Alerts" overlay layer (placeholder, ready for RSS/feed integration) lib/types.ts Added preferred_token_id?: string | null to SystemRecord lib/c2api.ts Added setPreferredToken(tokenId, systemId) calling PUT /tokens/{tokenId}/prefer/{systemId} (backend already existed) app/systems/page.tsx Added PreferredTokenPanel component — loads the token pool lazily on expand, shows radio buttons to set/clear the preferred token, displayed on each system card above the AI flags panel --- drb-frontend/app/map/page.tsx | 50 +----------- drb-frontend/app/systems/page.tsx | 118 ++++++++++++++++++++++++++++ drb-frontend/components/MapView.tsx | 74 ++++++++++------- drb-frontend/lib/c2api.ts | 4 + drb-frontend/lib/types.ts | 1 + 5 files changed, 169 insertions(+), 78 deletions(-) diff --git a/drb-frontend/app/map/page.tsx b/drb-frontend/app/map/page.tsx index ce25386..f5ef68a 100644 --- a/drb-frontend/app/map/page.tsx +++ b/drb-frontend/app/map/page.tsx @@ -2,48 +2,12 @@ import { useEffect, useState } from "react"; import dynamic from "next/dynamic"; -import Link from "next/link"; import { useNodes } from "@/lib/useNodes"; import { useActiveCalls } from "@/lib/useCalls"; import { useActiveIncidents } from "@/lib/useIncidents"; -import type { IncidentRecord } from "@/lib/types"; const MapView = dynamic(() => import("@/components/MapView"), { ssr: false }); -const TYPE_COLORS: Record = { - fire: "border-red-800 bg-red-950 text-red-300", - police: "border-blue-800 bg-blue-950 text-blue-300", - ems: "border-yellow-800 bg-yellow-950 text-yellow-300", - accident: "border-orange-800 bg-orange-950 text-orange-300", - other: "border-gray-700 bg-gray-900 text-gray-300", -}; - -function IncidentCard({ incident }: { incident: IncidentRecord }) { - const cls = TYPE_COLORS[incident.type ?? "other"] ?? TYPE_COLORS.other; - return ( - -
- - {incident.type ?? "other"} - - - {incident.call_ids.length} call{incident.call_ids.length !== 1 ? "s" : ""} - -
-

{incident.title ?? "Incident"}

- {incident.location && ( -

{incident.location}

- )} - {!incident.location_coords && ( -

location not geocoded yet

- )} - - ); -} - export default function MapPage() { const { nodes, loading } = useNodes(); const activeCalls = useActiveCalls(); @@ -69,7 +33,7 @@ export default function MapPage() { + + {open && ( +
+ {tokens === null ? ( +

Loading tokens…

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

No tokens in pool.

+ ) : ( + <> +

+ When a node on this system joins a voice channel, this token is tried first. +

+
+ {tokens.map((t) => ( + + ))} +
+ {preferredId && ( + + )} + {!preferredId && ( +

No preference — any free token will be used.

+ )} + {currentToken && ( +

Preferred: {currentToken.name}

+ )} + + )} +
+ )} + + ); +} + // ── Per-system AI flags panel ───────────────────────────────────────────────── interface SystemAiFlags { @@ -1157,6 +1274,7 @@ export default function SystemsPage() { Delete + diff --git a/drb-frontend/components/MapView.tsx b/drb-frontend/components/MapView.tsx index e266d32..cf574b4 100644 --- a/drb-frontend/components/MapView.tsx +++ b/drb-frontend/components/MapView.tsx @@ -293,6 +293,7 @@ function FanIncidentLayer({ {inc.location &&

{inc.location}

} { e.stopPropagation(); window.location.href = `/incidents/${inc.incident_id}`; e.preventDefault(); }} className="text-xs text-blue-600 hover:underline block mt-0.5" > View incident → @@ -451,6 +452,11 @@ export default function MapView({ nodes, activeCalls, incidents = [], lastUpdate /> + {/* Overlay: News / RSS alerts — placeholder for future integration */} + + + + {/* Overlay: ADS-B — placeholder for future integration */} @@ -513,40 +519,50 @@ export default function MapView({ nodes, activeCalls, incidents = [], lastUpdate const age = inc.started_at ? timeAgo(new Date(inc.started_at)) : null; const unitCount = inc.units?.length ?? 0; return ( - +
+ {age && {age}} + {unitCount > 0 && ( + {unitCount} unit{unitCount !== 1 ? "s" : ""} + )} +
+ {!inc.location_coords && ( +

no coords

+ )} + +
+ View details → + + ); })} diff --git a/drb-frontend/lib/c2api.ts b/drb-frontend/lib/c2api.ts index 6fb44bb..e0c76a4 100644 --- a/drb-frontend/lib/c2api.ts +++ b/drb-frontend/lib/c2api.ts @@ -130,6 +130,10 @@ export const c2api = { getCorrelationDebug: (limit: number, orphanHours: number) => request(`/admin/debug/correlation?limit=${limit}&orphan_hours=${orphanHours}`), + // Preferred bot token per system + setPreferredToken: (tokenId: string, systemId: string) => + request<{ ok: boolean; preferred_for_system_id: string | null }>(`/tokens/${tokenId}/prefer/${systemId}`, { method: "PUT" }), + // Per-system AI flag overrides setSystemAiFlags: (systemId: string, flags: { stt_enabled?: boolean | null; correlation_enabled?: boolean | null }) => request<{ ok: boolean; ai_flags: Record }>(`/systems/${systemId}/ai-flags`, { diff --git a/drb-frontend/lib/types.ts b/drb-frontend/lib/types.ts index e9520f5..8f16264 100644 --- a/drb-frontend/lib/types.ts +++ b/drb-frontend/lib/types.ts @@ -31,6 +31,7 @@ export interface SystemRecord { vocabulary_pending?: VocabularyPendingTerm[]; vocabulary_bootstrapped?: boolean; ten_codes?: Record; // {"10-10": "Commercial Alarm", ...} + preferred_token_id?: string | null; } export interface TranscriptSegment {