import { useState } from "react"; /** Grouped/stacked daily bars: ok vs bad. Thin marks, rounded ends, 2px gaps, hover tooltip. */ export function DailyBars({ data, width = 640, height = 170 }: { data: { day: string; ok: number; bad: number; n: number }[]; width?: number; height?: number }) { const [tip, setTip] = useState<{ x: number; y: number; d: (typeof data)[number] } | null>(null); if (!data.length) return
No executions in the last 14 days.
; const m = { l: 34, r: 8, t: 10, b: 24 }; const w = width - m.l - m.r, h = height - m.t - m.b; const max = Math.max(1, ...data.map((d) => d.ok + d.bad)); const bw = w / data.length; const ticks = [0, 0.5, 1].map((f) => Math.round(max * f)); return (
succeededfailed / blocked
{ticks.map((t) => )} {ticks.map((t) => {t})} {data.map((d, i) => { const x = m.l + i * bw + bw * 0.2, bwi = Math.max(3, bw * 0.6); const hOk = (d.ok / max) * h, hBad = (d.bad / max) * h; const yBad = m.t + h - hBad, yOk = yBad - hOk - (hOk && hBad ? 2 : 0); return ( setTip({ x: e.clientX, y: e.clientY, d })} onMouseMove={(e) => setTip({ x: e.clientX, y: e.clientY, d })} onMouseLeave={() => setTip(null)}> {hBad > 0 && } {hOk > 0 && } {(i % Math.ceil(data.length / 7) === 0 || data.length <= 7) && {d.day.slice(5)}} ); })} {tip &&
{tip.d.day}
succeeded {tip.d.ok} · failed/blocked {tip.d.bad} · total {tip.d.n}
}
); } /** Horizontal breakdown bars for categorical counts (one hue, labels in text tokens). */ export function Breakdown({ items, color = "var(--s1)" }: { items: { label: string; n: number; tone?: string }[]; color?: string }) { if (!items.length) return
Nothing yet.
; const max = Math.max(1, ...items.map((i) => i.n)); return (
{items.map((it) => (
{it.label} {it.n}
))}
); } export function Sparkline({ points, width = 320, height = 48, label }: { points: { x: string; y: number }[]; width?: number; height?: number; label: string }) { const [tip, setTip] = useState<{ x: number; y: number; p: (typeof points)[number] } | null>(null); if (points.length < 2) return
not enough data for {label}
; const max = Math.max(1, ...points.map((p) => p.y)); const step = width / (points.length - 1); const path = points.map((p, i) => `${i === 0 ? "M" : "L"}${i * step},${height - 4 - (p.y / max) * (height - 8)}`).join(" "); return (
setTip(null)} onMouseMove={(e) => { const r = (e.currentTarget as SVGSVGElement).getBoundingClientRect(); const i = Math.round(((e.clientX - r.left) / r.width) * (points.length - 1)); const p = points[Math.max(0, Math.min(points.length - 1, i))]; if (p) setTip({ x: e.clientX, y: e.clientY, p }); }}> {tip &&
{tip.p.x}: {tip.p.y}
}
); }