This repository has been archived on 2026-08-04. You can view files and clone it, but cannot push or open issues or pull requests.
orchestrator/agent-team/web/src/components/TopBar.tsx
Adam Moussa 05c92be874 feat(agent-team): render all dashboard times as Eastern 24h (EDT/EST)
Add src/lib/time.ts formatEastern() — parses both backend timestamp shapes
(the 'YYYY-MM-DD HH:MM:SS UTC' snapshot string and ISO offset transition times),
renders them in America/New_York (DST-aware: EDT/EST) as 24h
'YYYY-MM-DD HH:MM:SS ZZZ'. Wire into TopBar (snapshot clock + tooltip) and the
TaskDrawer timeline (entered_at). Adds time.test.ts (8 cases incl. DST + day
boundary). 44 frontend tests green.
2026-06-24 18:56:56 -04:00

127 lines
3.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { cn } from "@/lib/utils";
import { formatEastern } from "@/lib/time";
import type { StateResponse } from "../api";
interface Props {
state: StateResponse | null;
connected: boolean;
generatedAt: string | null;
}
interface ChipDef {
key: keyof StateResponse["summary"];
label: string;
numCls: string;
bgCls: string;
}
const CHIPS: ChipDef[] = [
{
key: "active",
label: "ACTIVE",
numCls: "text-status-active",
bgCls: "bg-status-active-bg",
},
{
key: "waiting",
label: "AWAITING HUMAN",
numCls: "text-status-waiting",
bgCls: "bg-status-waiting-bg",
},
{
key: "parked",
label: "PARKED",
numCls: "text-status-parked",
bgCls: "bg-status-parked-bg",
},
{
key: "open_questions",
label: "OPEN QUESTIONS",
numCls: "text-status-waiting",
bgCls: "bg-status-waiting-bg",
},
{
key: "total",
label: "TOTAL",
numCls: "text-muted-foreground",
bgCls: "bg-muted",
},
];
// Top status bar: brand, summary chips, connection health, and snapshot clock.
export function TopBar({ state, connected, generatedAt }: Props) {
const summary = state?.summary;
const snapshot = formatEastern(generatedAt);
const title = connected
? `Live — snapshot at ${snapshot}`
: `Disconnected — last snapshot at ${snapshot}`;
return (
<header
className="flex items-center gap-3 py-2 px-3 bg-card border-b border-border"
role="banner"
>
{/* Brand */}
<div className="flex items-center gap-2 shrink-0">
<span
className="w-3 h-3 rounded-full bg-gradient-to-br from-primary to-primary/50 shadow-sm"
aria-hidden
/>
<span className="text-sm font-bold text-foreground tracking-tight leading-none">
agent-team
</span>
<span className="text-xs text-muted-foreground leading-none">
pipeline
</span>
</div>
{/* Divider */}
<div className="w-px h-5 bg-border shrink-0" aria-hidden />
{/* Summary chips */}
<div
className="flex items-center gap-2 flex-1 min-w-0 overflow-x-auto"
role="list"
aria-label="Pipeline summary"
>
{CHIPS.map((c) => (
<div
key={c.key}
role="listitem"
className={cn(
"flex flex-col items-center justify-center rounded-md px-2.5 py-1 shrink-0",
c.bgCls,
)}
>
<span className={cn("text-sm font-bold leading-none tabular-nums", c.numCls)}>
{summary ? summary[c.key] : "–"}
</span>
<span className="text-[9px] font-semibold tracking-widest text-muted-foreground leading-none mt-0.5 whitespace-nowrap">
{c.label}
</span>
</div>
))}
</div>
{/* Health indicator */}
<div
className="flex items-center gap-2 shrink-0 ml-auto"
title={title}
aria-label={title}
>
<span
aria-hidden
className={cn(
"w-2.5 h-2.5 rounded-full",
connected
? "bg-status-active shadow-[0_0_6px_2px_hsl(var(--status-active)/0.45)]"
: "bg-status-failed",
)}
/>
<span className="text-xs text-muted-foreground tabular-nums">
{snapshot}
</span>
</div>
</header>
);
}