import { useEffect, useMemo, useState } from 'react' import { useNavigate } from 'react-router-dom' import { useAppPath } from '@core/hooks/useAppPath' import { useWorkbenchRole } from '../../hooks/useWorkbenchRole' import { apiFetch } from '@core/lib/api' import { Button } from '@core/components/ui/button' import { Input } from '@core/components/ui/input' import { Card, CardContent, CardHeader, CardTitle } from '@core/components/ui/card' import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@core/components/ui/table' import { Badge } from '@core/components/ui/badge' import { Checkbox } from '@core/components/ui/checkbox' import { Search, Siren, Plus } from 'lucide-react' import { SeverityBadge, TeamBadge } from '../../components/work/badges' const RESOLVED_INCIDENT_STATUSES = ['resolved', 'postmortem_complete'] interface IncidentRecord { id: string title: string status: string data: Record updated_at?: string } function isActiveIncident(item: IncidentRecord): boolean { return item.data?.work_type === 'incident_followup' && !RESOLVED_INCIDENT_STATUSES.includes(item.data?.incident_status) } export default function IncidentDashboard() { const navigate = useNavigate() const appPath = useAppPath() const { role } = useWorkbenchRole() const [items, setItems] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [filter, setFilter] = useState('') const [showResolved, setShowResolved] = useState(false) const [sort, setSort] = useState<{ key: string; direction: 'asc' | 'desc' }>({ key: 'updated_at', direction: 'desc' }) useEffect(() => { const load = async () => { setLoading(true) setError(null) try { const res = await apiFetch('/api/admin-data?entity=items&type_slug=workbench_work_item&limit=500') const json = await res.json() const data = Array.isArray(json?.data) ? json.data : [] setItems(data) } catch (err) { setError(err instanceof Error ? err.message : 'Failed to load incidents') } finally { setLoading(false) } } load() }, []) const incidents = useMemo(() => { let rows = items.filter((item) => item.data?.work_type === 'incident_followup') if (!showResolved) { rows = rows.filter((item) => !RESOLVED_INCIDENT_STATUSES.includes(item.data?.incident_status)) } const term = filter.trim().toLowerCase() if (term) { rows = rows.filter((item) => [item.title, item.description, item.status, item.id] .concat(Object.values(item.data || {})) .some((v) => String(v || '').toLowerCase().includes(term)) ) } rows = [...rows].sort((a, b) => { const av = a[sort.key as keyof IncidentRecord] ?? a.data?.[sort.key] ?? '' const bv = b[sort.key as keyof IncidentRecord] ?? b.data?.[sort.key] ?? '' if (av < bv) return sort.direction === 'asc' ? -1 : 1 if (av > bv) return sort.direction === 'asc' ? 1 : -1 return 0 }) return rows }, [items, showResolved, filter, sort]) const stats = useMemo(() => { const active = items.filter(isActiveIncident) return { active: active.length, sev1: active.filter((i) => i.data?.severity === 'sev1').length, investigating: active.filter((i) => i.data?.incident_status === 'investigating').length, mitigating: active.filter((i) => i.data?.incident_status === 'mitigating').length, monitoring: active.filter((i) => i.data?.incident_status === 'monitoring').length, postmortem: active.filter((i) => i.data?.incident_status === 'postmortem_pending').length, } }, [items]) const handleSort = (key: string) => { setSort((prev) => ({ key, direction: prev.key === key && prev.direction === 'asc' ? 'desc' : 'asc', })) } const incidentStatus = (value?: string) => { if (!value) return Unknown const label = value.replace(/_/g, ' ').replace(/\b\w/g, (l) => l.toUpperCase()) return {label} } const arrayCount = (value: any) => { if (Array.isArray(value)) return value.length if (typeof value === 'string' && value.trim().startsWith('[')) { try { const parsed = JSON.parse(value) return Array.isArray(parsed) ? parsed.length : 0 } catch { return 0 } } return 0 } return (

Incident Dashboard

Active Incidents
{stats.active}
Active SEV1 SEV1
{stats.sev1}
Investigating Investigating
{stats.investigating}
Postmortem Pending Pending
{stats.postmortem}
setFilter(e.target.value)} placeholder="Filter incidents…" className="pl-9" />
setShowResolved(v === true)} />
{showResolved ? 'All Incidents' : 'Active Incidents'} {loading ? (
Loading incidents…
) : error ? (
{error}
) : incidents.length === 0 ? (
No incidents found.
) : ( handleSort('title')}>Title Severity Incident Status handleSort('status')}>Record Status Team Started Responders Services {incidents.map((item) => ( navigate(appPath(`/${role}/items/${item.id}`))} > {item.title || 'Untitled'} {incidentStatus(item.data?.incident_status)} {item.status || '—'} {item.data?.started_at || '—'} {arrayCount(item.data?.responders)} {arrayCount(item.data?.affected_services)} ))}
)}
) }