Bot Token Pool
diff --git a/drb-frontend/components/MapView.tsx b/drb-frontend/components/MapView.tsx
index 17db9f9..391248f 100644
--- a/drb-frontend/components/MapView.tsx
+++ b/drb-frontend/components/MapView.tsx
@@ -1,6 +1,6 @@
"use client";
-import { MapContainer, TileLayer, Marker, Popup } from "react-leaflet";
+import { MapContainer, TileLayer, Marker, Popup, LayersControl, FeatureGroup } from "react-leaflet";
import L from "leaflet";
import type { NodeRecord, CallRecord, IncidentRecord } from "@/lib/types";
@@ -59,7 +59,6 @@ export default function MapView({ nodes, activeCalls, incidents = [] }: Props) {
activeCalls.map((c) => [c.node_id, c])
);
- // Only show incidents that have been geocoded (location_coords set by the server).
const plottedIncidents = incidents.flatMap((inc) =>
inc.location_coords
? [{ inc, pos: [inc.location_coords.lat, inc.location_coords.lng] as [number, number] }]
@@ -81,64 +80,104 @@ export default function MapView({ nodes, activeCalls, incidents = [] }: Props) {
: 4;
return (
-
-
+
+
+
+ {/* Base layers */}
+
+
+
+
+
+
+
+
+
- {/* Node markers */}
- {nodes.map((node) => (
-
-
-
-
{node.name}
-
{node.node_id}
-
{node.status}
- {activeByNode[node.node_id] && (
-
- ● TG {activeByNode[node.node_id].talkgroup_id ?? "—"}{" "}
- {activeByNode[node.node_id].talkgroup_name}
-
- )}
-
-
-
- ))}
+ {/* Overlay: Nodes */}
+
+
+ {nodes.map((node) => (
+
+
+
+
{node.name}
+
{node.node_id}
+
{node.status}
+ {activeByNode[node.node_id] && (
+
+ ● TG {activeByNode[node.node_id].talkgroup_id ?? "—"}{" "}
+ {activeByNode[node.node_id].talkgroup_name}
+
+ )}
+
+
+
+ ))}
+
+
- {/* Incident markers — positioned at the node covering the incident's system */}
- {plottedIncidents.map(({ inc, pos }) => (
-
-
-
-
{inc.title ?? "Incident"}
-
- {inc.type ?? "other"}
-
-
{inc.status}
- {inc.location &&
{inc.location}
}
-
{inc.call_ids.length} call{inc.call_ids.length !== 1 ? "s" : ""}
- {inc.summary &&
{inc.summary}
}
-
- View incident →
-
-
-
-
- ))}
-
+ {/* Overlay: Active Incidents */}
+
+
+ {plottedIncidents.map(({ inc, pos }) => (
+
+
+
+
{inc.title ?? "Incident"}
+
+ {inc.type ?? "other"}
+
+
{inc.status}
+ {inc.location &&
{inc.location}
}
+
{inc.call_ids.length} call{inc.call_ids.length !== 1 ? "s" : ""}
+ {inc.summary &&
{inc.summary}
}
+
+ View incident →
+
+
+
+
+ ))}
+
+
+
+
+
+ {/* Legend overlay — inside the map wrapper, above tiles */}
+
+
● Online
+
● Recording
+
● Unconfigured
+
● Offline
+
+
■ Fire
+
■ Police
+
■ EMS
+
■ Accident
+
+
);
}
diff --git a/drb-frontend/components/Nav.tsx b/drb-frontend/components/Nav.tsx
index 434dfbf..afe91e1 100644
--- a/drb-frontend/components/Nav.tsx
+++ b/drb-frontend/components/Nav.tsx
@@ -1,10 +1,12 @@
"use client";
+import { useState } from "react";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { useUnconfiguredNodes } from "@/lib/useNodes";
import { useUnacknowledgedAlerts } from "@/lib/useAlerts";
import { useAuth } from "@/components/AuthProvider";
+import { useTheme } from "@/components/ThemeProvider";
const links = [
{ href: "/dashboard", label: "Dashboard" },
@@ -21,48 +23,145 @@ const adminLinks = [
{ href: "/admin", label: "Admin" },
];
+function SunIcon() {
+ return (
+
+ );
+}
+
+function MoonIcon() {
+ return (
+
+ );
+}
+
export function Nav() {
const { user, isAdmin, signOut } = useAuth();
const pathname = usePathname();
const { nodes: pending } = useUnconfiguredNodes();
const unackedAlerts = useUnacknowledgedAlerts();
+ const { theme, toggle } = useTheme();
+ const [mobileOpen, setMobileOpen] = useState(false);
if (!user) return null;
+ const allLinks = [...links, ...(isAdmin ? adminLinks : [])];
+
+ function navLinkClass(href: string) {
+ return `text-sm font-mono transition-colors shrink-0 ${
+ pathname.startsWith(href) ? "text-white" : "text-gray-500 hover:text-gray-300"
+ }`;
+ }
+
return (
-