spb/internetpressure
Public
TypeScript 36.3%
Python 31.8%
Go 18%
JavaScript 9.8%
Shell 1.9%
SQL 1.4%
CSS 0.5%
1'use client';23import Link from 'next/link';4import { useState } from 'react';5import { PNum, StatusDot } from '@/components/ui/primitives';6import { adminFetch } from '@/lib/admin-fetch';7import { fmtDuration } from '@/lib/format';8import { Time } from '@/lib/time';9import type { AdminIncident } from '@/lib/types';10import { AdminPage, ErrorNote, Toast, btnCls, inputCls, useAdmin } from './shared';1112export function IncidentsReview() {13 const [status, setStatus] = useState('all');14 const { data, err, reload } = useAdmin<{ incidents: AdminIncident[] }>('/incidents', { status });15 const [notes, setNotes] = useState<Record<string, string>>({});16 const [msg, setMsg] = useState<string | null>(null);17 const [actErr, setActErr] = useState<string | null>(null);1819 const review = async (i: AdminIncident, verdict: 'confirmed' | 'dismissed' | 'unreviewed') => {20 setActErr(null);21 try {22 await adminFetch(`/incidents/${encodeURIComponent(i.event_id)}`, { method: 'PATCH', body: { review: verdict, note: notes[i.event_id] ?? i.note ?? '' } });23 setMsg(`${i.title}: ${verdict}`);24 reload();25 } catch (e) {26 setActErr(String(e));27 } finally {28 setTimeout(() => setMsg(null), 3000);29 }30 };3132 return (33 <AdminPage34 title="Incidents review"35 desc="Confirm or dismiss detected incidents and leave a note. Reviews feed the replay/validation dataset; they never alter the public record of what the engine detected."36 right={37 <select value={status} onChange={(e) => setStatus(e.target.value)} className={inputCls} aria-label="Status filter">38 {['all', 'active', 'resolved'].map((s) => (39 <option key={s}>{s}</option>40 ))}41 </select>42 }43 >44 <ErrorNote err={err ?? actErr} />45 <Toast msg={msg} />46 <ul className="divide-y divide-line">47 {(data?.incidents ?? []).map((i) => (48 <li key={i.event_id} className="grid gap-3 py-3 lg:grid-cols-[minmax(0,1fr)_minmax(0,1fr)_auto]">49 <div className="min-w-0">50 <div className="flex flex-wrap items-baseline gap-x-3">51 <PNum value={i.peak_pressure} className="text-[18px]" />52 <Link href={`/event/${i.slug}`} className="text-[13.5px] text-ink hover:text-accent">53 {i.title}54 </Link>55 <StatusDot status={i.status} />56 <span className="label" style={{ color: i.review === 'confirmed' ? 'var(--ok)' : i.review === 'dismissed' ? 'var(--bad)' : 'var(--ink-3)' }}>57 {i.review ?? 'unreviewed'}58 </span>59 </div>60 <p className="num mt-0.5 text-[11px] text-ink-3">61 {i.type} · {i.scope_label} · <Time ts={i.started_at} /> · {fmtDuration(i.duration_s)} · conf {Math.round(i.confidence * 100)} %62 </p>63 <p className="mt-1 text-[12px] text-ink-2">{i.summary}</p>64 </div>65 <textarea value={notes[i.event_id] ?? i.note ?? ''} onChange={(e) => setNotes({ ...notes, [i.event_id]: e.target.value })} placeholder="review note…" rows={2} className={`${inputCls} h-auto w-full py-1.5`} aria-label={`Note for ${i.title}`} />66 <div className="flex gap-2 lg:flex-col">67 <button type="button" className={`${btnCls} border-ok/60 text-ok`} onClick={() => void review(i, 'confirmed')}>68 confirm69 </button>70 <button type="button" className={`${btnCls} border-bad/60 text-bad`} onClick={() => void review(i, 'dismissed')}>71 dismiss72 </button>73 <button type="button" className={btnCls} onClick={() => void review(i, 'unreviewed')}>74 save note75 </button>76 </div>77 </li>78 ))}79 </ul>80 </AdminPage>81 );82}83