81 lines
2.9 KiB
TypeScript
81 lines
2.9 KiB
TypeScript
"use client";
|
|
|
|
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<string, string> = {
|
|
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 (
|
|
<Link
|
|
href={`/incidents/${incident.incident_id}`}
|
|
className={`block border rounded-lg p-3 hover:brightness-110 transition-all ${cls}`}
|
|
>
|
|
<div className="flex items-center justify-between gap-2 mb-1">
|
|
<span className="text-xs font-mono font-semibold uppercase tracking-wide">
|
|
{incident.type ?? "other"}
|
|
</span>
|
|
<span className="text-xs opacity-60 font-mono">
|
|
{incident.call_ids.length} call{incident.call_ids.length !== 1 ? "s" : ""}
|
|
</span>
|
|
</div>
|
|
<p className="text-sm font-bold leading-tight">{incident.title ?? "Incident"}</p>
|
|
{incident.location && (
|
|
<p className="text-xs opacity-70 mt-1 font-mono truncate">{incident.location}</p>
|
|
)}
|
|
{!incident.location_coords && (
|
|
<p className="text-xs opacity-40 mt-1 font-mono italic">location not geocoded yet</p>
|
|
)}
|
|
</Link>
|
|
);
|
|
}
|
|
|
|
export default function MapPage() {
|
|
const { nodes, loading } = useNodes();
|
|
const activeCalls = useActiveCalls();
|
|
const incidents = useActiveIncidents();
|
|
|
|
return (
|
|
<div className="space-y-4">
|
|
<h1 className="text-xl font-bold text-white font-mono">Map</h1>
|
|
|
|
{loading ? (
|
|
<div className="flex items-center justify-center h-96 text-gray-600 font-mono text-sm">
|
|
Loading map…
|
|
</div>
|
|
) : (
|
|
<div className="h-[50vh] sm:h-[65vh] min-h-[400px]">
|
|
<MapView nodes={nodes} activeCalls={activeCalls} incidents={incidents} />
|
|
</div>
|
|
)}
|
|
|
|
{/* Active incidents — shown even without geocoded location */}
|
|
{incidents.length > 0 && (
|
|
<section>
|
|
<h2 className="text-sm font-semibold text-gray-400 uppercase tracking-wider mb-3">
|
|
Active Incidents ({incidents.length})
|
|
</h2>
|
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-3">
|
|
{incidents.map((inc) => (
|
|
<IncidentCard key={inc.incident_id} incident={inc} />
|
|
))}
|
|
</div>
|
|
</section>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|