Merge branch 'worktree-agent-a5b08020'

# Conflicts:
#	.planning/STATE.md
#	package.json
#	pnpm-lock.yaml
This commit is contained in:
ZacharyZhang-NY committed 2026-03-27 09:04:16 -04:00
commit 907a5dfe2f
7 files changed
+420

No files matched your search

@@ -0,0 +1,97 @@
import { useMemo } from "react";
import { motion } from "framer-motion";
import type { RunRecord, CostRecord, AgentRecord } from "@shared/types";
import { useReducedMotion } from "../ui";
import {
aggregateRunsByDay,
aggregateCostsByDay,
aggregateAgentWorkload,
} from "./charts/chart-utils";
import { ChartCard } from "./charts/ChartCard";
import { RunActivityChart } from "./charts/RunActivityChart";
import { SuccessRateChart } from "./charts/SuccessRateChart";
import { CostTrendChart } from "./charts/CostTrendChart";
import { AgentWorkloadChart } from "./charts/AgentWorkloadChart";
import { BudgetUtilization } from "./charts/BudgetUtilization";
interface ChartSectionProps {
runs: RunRecord[];
costs: CostRecord[];
agents: AgentRecord[];
companyId: string;
}
export function ChartSection({ runs, costs, agents, companyId }: ChartSectionProps) {
const reducedMotion = useReducedMotion();
const dailyRuns = useMemo(() => aggregateRunsByDay(runs, 14), [runs]);
const dailyCosts = useMemo(() => aggregateCostsByDay(costs, 14), [costs]);
const agentWorkload = useMemo(
() => aggregateAgentWorkload(runs, agents),
[runs, agents],
);
const hasRunData = dailyRuns.some((d) => d.total > 0);
const hasCostData = dailyCosts.some((d) => d.total > 0);
const content = (
<div className="space-y-6">
{/* Section header */}
<div className="text-[11px] font-semibold uppercase tracking-[0.08em] text-[color:var(--muted)]">
Performance
</div>
{/* 2-column chart grid */}
<div className="grid grid-cols-1 gap-3 md:grid-cols-2">
<ChartCard
title="Run Activity"
ariaLabel={`Run activity chart showing ${dailyRuns.reduce((s, d) => s + d.total, 0)} total runs over 14 days`}
isEmpty={!hasRunData}
>
<RunActivityChart data={dailyRuns} reducedMotion={reducedMotion} />
</ChartCard>
<ChartCard
title="Success Rate"
ariaLabel="Success rate chart showing percentage of succeeded runs over 14 days"
isEmpty={!hasRunData}
>
<SuccessRateChart data={dailyRuns} reducedMotion={reducedMotion} />
</ChartCard>
<ChartCard
title="Cost Trend"
ariaLabel="Cost trend chart showing daily spend over 14 days"
isEmpty={!hasCostData}
>
<CostTrendChart data={dailyCosts} reducedMotion={reducedMotion} />
</ChartCard>
<ChartCard
title="Agent Workload"
ariaLabel="Agent workload chart showing run distribution across agents"
isEmpty={agentWorkload.length === 0}
>
<AgentWorkloadChart data={agentWorkload} reducedMotion={reducedMotion} />
</ChartCard>
</div>
{/* Budget Utilization section */}
<div className="text-[11px] font-semibold uppercase tracking-[0.08em] text-[color:var(--muted)]">
Budget Utilization
</div>
<BudgetUtilization companyId={companyId} agents={agents} />
</div>
);
if (reducedMotion) {
return content;
}
return (
<motion.div
initial={{ opacity: 0, y: 8 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3, ease: [0.16, 1, 0.3, 1] }}
>
{content}
</motion.div>
);
}
@@ -27,6 +27,7 @@ import { MetricCard } from "./MetricsGrid";
import { ActivityFeed } from "./ActivityFeed";
import { RecoveryBanner } from "./RecoveryBanner";
import { BudgetAlertBanner } from "./BudgetAlertBanner";
import { ChartSection } from "./ChartSection";
export interface DashboardProps {
companyName?: string;
@@ -132,6 +133,9 @@ export function Dashboard(props: DashboardProps) {
/>
</div>
{/* Performance Charts */}
{companyId ? <ChartSection runs={runs} costs={costs} agents={agents} companyId={companyId} /> : null}
{/* Needs Attention — moved to top, only shows when items exist */}
{hasAttentionItems ? (
<section>
@@ -0,0 +1,52 @@
import {
BarChart,
Bar,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
ResponsiveContainer,
} from "recharts";
import type { AgentWorkloadData } from "./chart-utils";
import { ChartTooltip } from "./ChartTooltip";
interface AgentWorkloadChartProps {
data: AgentWorkloadData[];
reducedMotion: boolean;
}
export function AgentWorkloadChart({ data, reducedMotion }: AgentWorkloadChartProps) {
return (
<ResponsiveContainer width="100%" height={220}>
<BarChart data={data}>
<CartesianGrid
strokeDasharray="3 3"
stroke="var(--line)"
horizontal={true}
vertical={false}
/>
<XAxis
dataKey="name"
tick={{ fontSize: 11, fill: "var(--muted)" }}
axisLine={{ stroke: "var(--line)" }}
tickLine={false}
/>
<YAxis
allowDecimals={false}
tick={{ fontSize: 11, fill: "var(--muted)" }}
axisLine={false}
tickLine={false}
/>
<Tooltip content={<ChartTooltip valueSuffix=" runs" />} />
<Bar
dataKey="runs"
fill="var(--accent)"
fillOpacity={0.85}
radius={[4, 4, 0, 0]}
maxBarSize={40}
isAnimationActive={!reducedMotion}
/>
</BarChart>
</ResponsiveContainer>
);
}
@@ -0,0 +1,87 @@
import { useMemo } from "react";
import type { AgentRecord } from "@shared/types";
interface BudgetUtilizationProps {
companyId: string;
agents: AgentRecord[];
}
interface BudgetRow {
id: string;
name: string;
spent: number;
budget: number;
pct: number;
}
function getBarColor(pct: number): string {
if (pct >= 100) return "bg-[color:var(--danger)]";
if (pct >= 80) return "bg-[color:var(--warn)]";
return "bg-[color:var(--success)]";
}
export function BudgetUtilization({ companyId: _companyId, agents }: BudgetUtilizationProps) {
const rows = useMemo<BudgetRow[]>(() => {
return agents
.filter((a) => a.budgetMonthlyUsd > 0)
.map((a) => ({
id: a.id,
name: a.name,
spent: a.spentMonthlyUsd,
budget: a.budgetMonthlyUsd,
pct: Math.round((a.spentMonthlyUsd / a.budgetMonthlyUsd) * 100),
}))
.sort((a, b) => a.name.localeCompare(b.name));
}, [agents]);
if (rows.length === 0) {
return (
<div className="rounded-[8px] border border-[color:var(--line)] bg-[color:var(--panel)] px-4 py-4">
<div className="flex h-[120px] flex-col items-center justify-center gap-2">
<span className="text-[13px] font-semibold text-[color:var(--muted)]">
No budget policies configured
</span>
<span className="max-w-[200px] text-center text-[11px] text-[color:var(--muted)]">
Set budget policies in Settings to track utilization here.
</span>
</div>
</div>
);
}
return (
<div className="rounded-[8px] border border-[color:var(--line)] bg-[color:var(--panel)] px-4 py-4">
<div className="space-y-3">
{rows.map((row) => (
<div key={row.id}>
<div className="mb-1 flex items-center justify-between">
<span className="text-[13px] font-semibold text-[color:var(--text)]">
{row.name}
</span>
<span
className="text-[13px] text-[color:var(--muted)]"
style={{
fontFamily: "var(--font-mono)",
fontVariantNumeric: "tabular-nums",
}}
>
{row.pct}%
</span>
</div>
<div className="relative h-1.5 rounded-full bg-[color:var(--panel-soft)]">
<div
className={`h-1.5 rounded-full transition-all duration-300 ${getBarColor(row.pct)}`}
style={{ width: `${Math.min(row.pct, 100)}%` }}
/>
{/* 80% warning marker */}
<div
className="absolute top-0 h-full w-px bg-[color:var(--warn)] opacity-50"
style={{ left: "80%" }}
/>
</div>
</div>
))}
</div>
</div>
);
}
@@ -0,0 +1,57 @@
import {
AreaChart,
Area,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
ResponsiveContainer,
} from "recharts";
import type { DailyCostData } from "./chart-utils";
import { formatShortDate } from "./chart-utils";
import { ChartTooltip } from "./ChartTooltip";
interface CostTrendChartProps {
data: DailyCostData[];
reducedMotion: boolean;
}
export function CostTrendChart({ data, reducedMotion }: CostTrendChartProps) {
return (
<ResponsiveContainer width="100%" height={220}>
<AreaChart data={data}>
<CartesianGrid
strokeDasharray="3 3"
stroke="var(--line)"
horizontal={true}
vertical={false}
/>
<XAxis
dataKey="date"
tickFormatter={formatShortDate}
tick={{ fontSize: 11, fill: "var(--muted)" }}
axisLine={{ stroke: "var(--line)" }}
tickLine={false}
/>
<YAxis
tickFormatter={(val: number) => "$" + val.toFixed(0)}
tick={{ fontSize: 11, fill: "var(--muted)" }}
axisLine={false}
tickLine={false}
/>
<Tooltip content={<ChartTooltip valuePrefix="$" />} />
<Area
type="monotone"
dataKey="total"
stroke="var(--accent)"
fill="var(--accent-soft)"
fillOpacity={0.15}
strokeWidth={2}
dot={false}
activeDot={{ r: 4, fill: "var(--accent)" }}
isAnimationActive={!reducedMotion}
/>
</AreaChart>
</ResponsiveContainer>
);
}
@@ -0,0 +1,57 @@
import {
AreaChart,
Area,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
ResponsiveContainer,
} from "recharts";
import type { DailyRunData } from "./chart-utils";
import { formatShortDate } from "./chart-utils";
import { ChartTooltip } from "./ChartTooltip";
interface RunActivityChartProps {
data: DailyRunData[];
reducedMotion: boolean;
}
export function RunActivityChart({ data, reducedMotion }: RunActivityChartProps) {
return (
<ResponsiveContainer width="100%" height={220}>
<AreaChart data={data}>
<CartesianGrid
strokeDasharray="3 3"
stroke="var(--line)"
horizontal={true}
vertical={false}
/>
<XAxis
dataKey="date"
tickFormatter={formatShortDate}
tick={{ fontSize: 11, fill: "var(--muted)" }}
axisLine={{ stroke: "var(--line)" }}
tickLine={false}
/>
<YAxis
allowDecimals={false}
tick={{ fontSize: 11, fill: "var(--muted)" }}
axisLine={false}
tickLine={false}
/>
<Tooltip content={<ChartTooltip valueSuffix=" runs" />} />
<Area
type="monotone"
dataKey="total"
stroke="var(--accent)"
fill="var(--accent-soft)"
fillOpacity={0.3}
strokeWidth={2}
dot={false}
activeDot={{ r: 4, fill: "var(--accent)" }}
isAnimationActive={!reducedMotion}
/>
</AreaChart>
</ResponsiveContainer>
);
}
@@ -0,0 +1,66 @@
import { useMemo } from "react";
import {
LineChart,
Line,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
ResponsiveContainer,
} from "recharts";
import type { DailyRunData } from "./chart-utils";
import { formatShortDate } from "./chart-utils";
import { ChartTooltip } from "./ChartTooltip";
interface SuccessRateChartProps {
data: DailyRunData[];
reducedMotion: boolean;
}
export function SuccessRateChart({ data, reducedMotion }: SuccessRateChartProps) {
const rateData = useMemo(
() =>
data.map((d) => ({
date: d.date,
rate: d.total > 0 ? Math.round((d.succeeded / d.total) * 100) : 0,
})),
[data],
);
return (
<ResponsiveContainer width="100%" height={220}>
<LineChart data={rateData}>
<CartesianGrid
strokeDasharray="3 3"
stroke="var(--line)"
horizontal={true}
vertical={false}
/>
<XAxis
dataKey="date"
tickFormatter={formatShortDate}
tick={{ fontSize: 11, fill: "var(--muted)" }}
axisLine={{ stroke: "var(--line)" }}
tickLine={false}
/>
<YAxis
domain={[0, 100]}
tickFormatter={(val: number) => `${val}%`}
tick={{ fontSize: 11, fill: "var(--muted)" }}
axisLine={false}
tickLine={false}
/>
<Tooltip content={<ChartTooltip valueSuffix="%" />} />
<Line
type="monotone"
dataKey="rate"
stroke="var(--success)"
strokeWidth={2}
dot={false}
activeDot={{ r: 4, fill: "var(--success)" }}
isAnimationActive={!reducedMotion}
/>
</LineChart>
</ResponsiveContainer>
);
}