"use client";
import { useMemo } from "react";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Skeleton } from "@/components/ui/skeleton";
import { Badge } from "@/components/ui/badge";
import { HelpTip } from "@/components/ui/help-tip";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import {
useAllMemberScorecards,
useCeoScorecard,
useOrgScorecard,
} from "@/hooks/use-observability";
import { useAgents } from "@/hooks/use-agents";
import { AgentRole, type Agent, type MemberScorecard } from "@/types";
function pctOrNa(rate: number | null): string {
return rate === null ? "n/a" : (rate * 100).toFixed(0) + "%";
}
function numOrNa(value: number | null, digits = 1): string {
return value === null ? "n/a" : value.toFixed(digits);
}
function hoursOrDash(seconds: number): string {
return (seconds / 3600).toFixed(1) + "h";
}
/** One member's row. Data comes from the batched useAllMemberScorecards
* fetch (one request for the whole table) rather than each row self-fetching
* — ~20 agents used to mean ~20 parallel `/metrics/member/{id}` requests
* (each 3 DB queries) on every table poll. */
function MemberRow({
agent,
data,
}: {
agent: Agent;
data: MemberScorecard | undefined;
}) {
if (!data) {
return (
{agent.name || agent.slug}
);
}
return (
{agent.name || agent.slug}
{data.includes_live_inflight && (
live
)}
{data.tasks_completed}
{pctOrNa(data.first_pass_yield)}
{data.active_runtime_hours.toFixed(1)}h
{numOrNa(data.turns_per_task)}
{pctOrNa(data.qa_pass_rate)}
{data.escalations}
{data.blocked_others}
{pctOrNa(data.utilization)}
);
}
function OrgSummary() {
const { data, isLoading, isError } = useOrgScorecard();
if (isError)
return (
Failed to load organization metrics.
);
if (isLoading || !data) return ;
const cells: [string, string, string?][] = [
["Members", String(data.member_count)],
["Completed", String(data.tasks_completed)],
[
"First-pass yield",
pctOrNa(data.first_pass_yield),
"Share of completed tasks that shipped without a QA/PR-gate/PM/CEO bounce",
],
[
"Throughput/hr",
numOrNa(data.effort_throughput_per_hour, 2),
"Tasks completed per hour of active (non-idle) agent runtime",
],
[
"Active effort",
data.active_runtime_hours.toFixed(1) + "h",
"Total hours agents spent actively working — idle time excluded",
],
["Cost", "$" + data.cost_usd.toFixed(2)],
];
return (
{cells.map(([k, v, tip]) => (
))}
);
}
function CeoCard() {
const { data, isLoading, isError } = useCeoScorecard();
if (isError)
return (
Failed to load CEO metrics.
);
if (isLoading || !data) return ;
const cells: [string, string, string?][] = [
["Approvals", String(data.approval_count)],
[
"Approval p50",
hoursOrDash(data.approval_p50_seconds),
"Median time from a task reaching your queue to your approval",
],
[
"Approval p90",
hoursOrDash(data.approval_p90_seconds),
"90th percentile — the slowest 10% of approvals took at least this long",
],
["Unblocks", String(data.unblock_count)],
[
"Unblock p50",
hoursOrDash(data.unblock_p50_seconds),
"Median time from a task blocking to you unblocking it",
],
[
"God-mode actions",
String(data.godmode_actions),
"Direct admin overrides you made outside the normal approval flow",
],
];
return (
{cells.map(([k, v, tip]) => (
))}
);
}
export function ScorecardsTabContent() {
const { data: agents } = useAgents();
const members = (agents ?? []).filter(
(a) => a.role !== AgentRole.CEO && a.role !== AgentRole.SYSTEM,
);
const { data: scorecards, isError: scorecardsError } =
useAllMemberScorecards();
const scorecardById = useMemo(
() => new Map((scorecards ?? []).map((s) => [s.id, s])),
[scorecards],
);
return (
Organization
CEO (you)
Members
{scorecardsError && (
Failed to load member scorecards.
)}
Member
Done
FPY
Effort
Turns/task
QA pass
Escal.
Blocked others
Util.
{members.map((a) => (
))}
);
}