/* ActivitySparkline — 24-bucket success/failure strip for a worker. * * Each column is one hour of activity. Successes stack from the top * down in brand blue; failures stack from the top down in destructive * red over the success column so the worst hours read first. Heights * scale to the loudest hour in the series so a quiet worker still * shows shape. * * No tooltip — hover-title gives the count. At 24px tall this is a * stacked bar histogram, not a line chart, so absolute counts read * directly. */ 'use client'; import { cn } from '@/lib/utils'; import { useUserTimezone } from '@/lib/hooks/use-user-timezone'; interface ActivityBucket { hour: string; success: number; failure: number; } interface ActivitySparklineProps { data: ActivityBucket[]; className?: string; height?: number; } function fmtHour(iso: string, tz: string): string { return new Date(iso).toLocaleTimeString(undefined, { hour: 'numeric', minute: '2-digit', timeZone: tz, }); } export function ActivitySparkline({ data, className, height = 32, }: ActivitySparklineProps) { const tz = useUserTimezone(); if (data.length === 0) { return null; } const max = Math.max(1, ...data.map((d) => d.success + d.failure)); return (
{data.map((bucket, i) => { const total = bucket.success + bucket.failure; const totalPct = (total / max) * 100; const failPct = total > 0 ? (bucket.failure / total) * 100 : 0; const succPct = 100 - failPct; const empty = total === 0; return ( {/* Success segment (bottom) */} {bucket.success > 0 && ( )} {/* Failure segment (top) */} {bucket.failure > 0 && ( )} {/* Idle hour — render a thin baseline */} {empty && ( )} ); })}
); }