fix(frontend): issues from the 10-25 Chrome sweep
- Incident detail "← Incidents" used router.back(), so arriving from Live or a shared link it went to the map / out of the app. Now a Link. - Resolved incidents showed "0m elapsed" (manual resolve sets no resolved_at, updated_at can predate the last call): end is now the latest of resolved_at, updated_at and the newest call. - Admin: the max-width toggle wrapped the tab bar, so it reflowed on every tab switch and left the cursor over e.g. "Close calls". Body only now. - STT Eval sat on "Loading…" forever when a scanned page had zero matches but a next_cursor — the refill effect didn't depend on cursor. - Trip event blocks used dark-only bg-indigo-950/60: unreadable in light mode. Theme tokens now. - Reassign modal said "connected for the first time" on configured nodes. - Dark-mode placeholders dimmed + italic; they read as filled-in values. Frontend unverified locally (no Node, #207); CI build typechecks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
33056e2269
commit
94898d1af1
@@ -2,7 +2,8 @@
|
||||
|
||||
import dynamic from "next/dynamic";
|
||||
import { useMemo, useState } from "react";
|
||||
import { useParams, useRouter } from "next/navigation";
|
||||
import Link from "next/link";
|
||||
import { useParams } from "next/navigation";
|
||||
import { useIncident } from "@/lib/useIncidents";
|
||||
import { useCallsByIncident } from "@/lib/useCalls";
|
||||
import { useAuth } from "@/components/AuthProvider";
|
||||
@@ -21,9 +22,9 @@ const MapView = dynamic(() => import("@/components/MapView"), { ssr: false });
|
||||
|
||||
const EARLIER_PAGE_SIZE = 8;
|
||||
|
||||
function elapsedLabel(startedAt: string, active: boolean, updatedAt: string): string {
|
||||
function elapsedLabel(startedAt: string, active: boolean, endedAt: string): string {
|
||||
const start = new Date(startedAt).getTime();
|
||||
const end = active ? Date.now() : new Date(updatedAt).getTime();
|
||||
const end = active ? Date.now() : new Date(endedAt).getTime();
|
||||
const mins = Math.max(0, Math.round((end - start) / 60000));
|
||||
if (mins < 60) return `${mins}m`;
|
||||
const hrs = Math.floor(mins / 60);
|
||||
@@ -33,7 +34,6 @@ function elapsedLabel(startedAt: string, active: boolean, updatedAt: string): st
|
||||
export default function IncidentDetailPage() {
|
||||
const params = useParams();
|
||||
const id = params.id as string;
|
||||
const router = useRouter();
|
||||
|
||||
const { incident, loading } = useIncident(id);
|
||||
const { calls, loading: callsLoading } = useCallsByIncident(id);
|
||||
@@ -82,18 +82,23 @@ export default function IncidentDetailPage() {
|
||||
const unitsCleared = incident.units_cleared ?? [];
|
||||
const vehicles = incident.vehicles ?? [];
|
||||
const active = incident.status === "active";
|
||||
// A manual resolve doesn't set resolved_at and updated_at can predate the
|
||||
// last call, which showed "0m elapsed" on an 11-call incident — so a
|
||||
// resolved incident runs to the latest of those and its newest call.
|
||||
const resolvedEnd = [incident.resolved_at, incident.updated_at, newestFirst[0]?.ended_at, newestFirst[0]?.started_at]
|
||||
.filter((t): t is string => !!t)
|
||||
.reduce((a, b) => (b > a ? b : a), incident.started_at);
|
||||
|
||||
const visible = newestFirst.slice(0, earlierShown);
|
||||
const remaining = newestFirst.length - visible.length;
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<button
|
||||
onClick={() => router.back()}
|
||||
className="text-xs text-ink-muted hover:text-ink-2 transition-colors"
|
||||
>
|
||||
{/* A real link, not router.back(): arriving from Live or a shared URL,
|
||||
"back" went to the map or out of the app. */}
|
||||
<Link href="/incidents" className="text-xs text-ink-muted hover:text-ink-2 transition-colors">
|
||||
← Incidents
|
||||
</button>
|
||||
</Link>
|
||||
|
||||
{/* Header — glyph, severity chip, status, title at 27px, elapsed/path/count */}
|
||||
<div className="flex flex-col sm:flex-row sm:items-start sm:justify-between gap-3">
|
||||
@@ -109,7 +114,7 @@ export default function IncidentDetailPage() {
|
||||
{incident.title ?? "Incident"}
|
||||
</h1>
|
||||
<p className="text-xs text-ink-muted font-mono">
|
||||
{elapsedLabel(incident.started_at, active, incident.updated_at)} elapsed
|
||||
{elapsedLabel(incident.started_at, active, resolvedEnd)} elapsed
|
||||
{path?.movement && path.lengthKm > 0 && <> · {path.lengthKm.toFixed(1)} km path</>}
|
||||
{path?.mayBeMerged && (
|
||||
<span title={`The calls' locations span ${path.lengthKm.toFixed(0)} km (over ${MAY_BE_MERGED_KM} km) — this may be more than one event`}>
|
||||
|
||||
Reference in New Issue
Block a user