Import source: web/src/components/IncidentList

Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
This commit is contained in:
arsam
2026-09-18 23:20:02 +03:30
parent 2a03adf447
commit 5d8c88c151
+92
View File
@@ -0,0 +1,92 @@
import { AnimatePresence, motion } from "motion/react";
import type { Incident, LiveTarget } from "../lib/types";
import { fmtDate, fmtDuration } from "../lib/format";
import { springs, useUI } from "../lib/ui";
interface IncidentListProps {
incidents: Incident[];
targets: LiveTarget[];
compact?: boolean;
hideTarget?: boolean;
/** When set, clicking a row opens that target's detail. */
onSelect?: (id: string) => void;
}
export function IncidentList({ incidents, targets, compact, hideTarget, onSelect }: IncidentListProps) {
const { t, lang, reduced } = useUI();
const nameOf = (id: string) => targets.find((x) => x.id === id)?.name ?? id;
return (
<div>
{!compact ? null : (
<div className="microlabel mb-3 flex items-baseline gap-4">
<span>{t.incidents}</span>
<span className="rule-fade flex-1" />
</div>
)}
{incidents.length === 0 ? (
<p className="text-[11px] text-paper-faint py-3">{t.noIncidents}</p>
) : (
<ul className="flex flex-col">
<AnimatePresence initial={false}>
{incidents.map((inc) => {
const ongoing = inc.ended_at === undefined || inc.ended_at === null;
const duration = (ongoing ? Date.now() : inc.ended_at!) - inc.started_at;
return (
<motion.li
key={inc.id}
layout={!reduced}
initial={reduced ? false : { opacity: 0, x: -14 }}
animate={{ opacity: 1, x: 0 }}
exit={reduced ? undefined : { opacity: 0, x: 14 }}
transition={reduced ? { duration: 0 } : springs.gentle}
onClick={onSelect ? () => onSelect(inc.target_id) : undefined}
className={`flex items-center gap-3 py-[9px] border-b border-ink-800/70 last:border-0 ${
onSelect
? "cursor-pointer -mx-2 px-2 rounded-sm transition-colors hover:bg-ink-850/60"
: ""
}`}
>
<span className="relative flex size-[7px] shrink-0">
{ongoing && !reduced && (
<motion.span
className="absolute inset-0 rounded-full bg-down"
animate={{ scale: [1, 2.6], opacity: [0.7, 0] }}
transition={{ duration: 1.6, repeat: Infinity, ease: "easeOut" }}
/>
)}
<span
className={`relative size-[7px] rounded-full ${
ongoing ? "bg-down" : "bg-idle"
}`}
/>
</span>
{!hideTarget && (
<span className="text-[11px] text-paper font-medium shrink-0 max-w-[34%] truncate">
{nameOf(inc.target_id)}
</span>
)}
<span className="text-[10px] text-paper-faint truncate flex-1 font-mono" dir="ltr">
{inc.error || "—"}
</span>
<span className="text-[10px] text-paper-faint tabular shrink-0 hidden sm:inline font-mono">
{fmtDate(lang, inc.started_at)}
</span>
<span
className={`text-[10px] tabular shrink-0 font-mono ${
ongoing ? "text-down" : "text-paper-dim"
}`}
>
{ongoing ? t.ongoing : fmtDuration(lang, duration)}
</span>
</motion.li>
);
})}
</AnimatePresence>
</ul>
)}
</div>
);
}