SPB Git forge

spb/websensor

Public
33commits 1branches 0releases
3.4 MBsize
maindefault branch
10 days agolast push
TypeScript 55.4% Python 43.2% SQL 1.2%
1.8 KB · 39 lines tsx
Raw Blame History
1import Link from "next/link";2import type { EventItem } from "@/lib/api";3import { dayHeader, utcDate, utcTime, typeLabel } from "@/lib/format";4import { Empty, Score, SilentBadge } from "./ui";56/** Git-log style timeline grouped by UTC day (spec §21). */7export function Timeline({ events, showSource = false }: { events: EventItem[]; showSource?: boolean }) {8  if (!events.length) return <Empty>No events recorded yet.</Empty>;9  const groups = new Map<string, EventItem[]>();10  for (const e of events) {11    const d = utcDate(e.detected_at);12    if (!groups.has(d)) groups.set(d, []);13    groups.get(d)!.push(e);14  }15  return (16    <div>17      {[...groups.entries()].map(([day, evs]) => (18        <div key={day}>19          <div className="sticky top-12 z-10 border-y border-line bg-panel-2/95 px-3 py-1 font-mono text-[11px] font-semibold tracking-wider text-fg-muted backdrop-blur">{dayHeader(evs[0]!.detected_at)}</div>20          {evs.map((e) => (21            <div key={e.id} className="grid grid-cols-[3.2rem_1fr_auto] items-start gap-x-3 px-3 py-1.5 hairline text-[13px]">22              <span className="font-mono text-[12px] text-fg-subtle tabular">{utcTime(e.detected_at, false)}</span>23              <div className="min-w-0">24                <div className="flex flex-wrap items-center gap-x-2 text-[11px] text-fg-subtle">25                  {showSource && <span className="font-mono uppercase text-fg-muted">{e.source?.name}</span>}26                  <span>{typeLabel(e.event_type)}</span>27                  {e.silent_change && <SilentBadge compact />}28                </div>29                <Link href={`/event/${e.slug}`} className="block truncate font-medium hover:underline">{e.title}</Link>30              </div>31              <Score value={e.importance} size="sm" />32            </div>33          ))}34        </div>35      ))}36    </div>37  );38}39