import { useMemo } from "react"; import { motion } from "framer-motion"; import { ArrowRight, Briefcase, CurrencyCircleDollar, Robot, ShieldCheck, Warning, XCircle, } from "@phosphor-icons/react"; import type { ActivityRecord, AgentRecord, AgentMessageRecord, ApprovalRecord, CostRecord, GoalRecord, ProjectRecord, RunRecord, SectionId, TaskRecord, } from "@shared/types"; import { formatMoney } from "../../lib/formatters"; import { useT } from "../../i18n"; 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; companyId?: string; agents: AgentRecord[]; tasks: TaskRecord[]; runs: RunRecord[]; approvals: ApprovalRecord[]; goals: GoalRecord[]; projects: ProjectRecord[]; activity: ActivityRecord[]; costs: CostRecord[]; messages?: AgentMessageRecord[]; onNavigate: (section: SectionId, entityId?: string) => void; } export function Dashboard(props: DashboardProps) { const t = useT(); const { companyName, companyId, agents, tasks, runs, approvals, activity, costs, messages = [], onNavigate } = props; const agentMap = useMemo(() => new Map(agents.map((a) => [a.id, a])), [agents]); const taskMap = useMemo(() => new Map(tasks.map((t) => [t.id, t])), [tasks]); const activeAgents = useMemo(() => agents.filter((a) => a.status !== "terminated" && a.status !== "pending_approval"), [agents]); const activeTasks = useMemo(() => tasks.filter((t) => t.status === "in_progress" || t.status === "in_review"), [tasks]); const pendingApprovals = useMemo(() => approvals.filter((a) => a.state === "pending"), [approvals]); const totalSpend = useMemo(() => costs.reduce((sum, c) => sum + (c.amountUsd ?? 0), 0), [costs]); // Attention items const failedRuns = useMemo(() => runs.filter((r) => (r.status === "failed" || r.status === "timed_out") || (r.status === "interrupted" && r.summary !== "Application shut down." && r.summary !== "Worker process crashed.") ), [runs]); const urgentMessages = useMemo(() => messages.filter((m) => !m.readAt && (m.priority === "urgent" || m.channel === "incident")), [messages]); const hasAttentionItems = pendingApprovals.length > 0 || failedRuns.length > 0 || urgentMessages.length > 0; // Active work — agents currently running tasks const activeWork = useMemo(() => { return runs .filter((r) => r.status === "running" || r.status === "queued") .map((run) => { const agent = agentMap.get(run.agentId); const task = taskMap.get(run.taskId); return { run, agent, task }; }) .slice(0, 8); }, [runs, agentMap, taskMap]); return (
{/* Company status line */} {companyName ? (

{companyName}

{activeAgents.length} {activeAgents.length === 1 ? "agent" : "agents"} active {activeTasks.length} {activeTasks.length === 1 ? "task" : "tasks"} in progress {tasks.filter((tk) => tk.status === "done").length} completed
) : null} {/* Conditional banners */} {/* Key Metrics — 4 cards */}
0 ? "accent" : "default"} onClick={() => onNavigate("tasks")} subtitle={`${tasks.filter((t) => t.status === "todo").length} ${t("dash.queued")}`} /> 0 ? "warn" : "default"} onClick={() => onNavigate("approvals")} subtitle={pendingApprovals.length > 0 ? t("dash.needsAttention") : t("dash.allClear")} /> a.status === "running").length > 0 ? "success" : "default"} onClick={() => onNavigate("agents")} subtitle={`${activeAgents.filter((a) => a.status === "running").length} running`} /> onNavigate("settings")} />
{/* Performance Charts */} {companyId ? : null} {/* Needs Attention — moved to top, only shows when items exist */} {hasAttentionItems ? (
{t("dash.needsAttentionSection")}
{pendingApprovals.slice(0, 5).map((approval, i) => { const requester = approval.requestedByAgentId ? agentMap.get(approval.requestedByAgentId) : undefined; return ( onNavigate("approvals", approval.id)} className="focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 flex w-full items-center gap-3 rounded-lg border border-[color:var(--destructive)] border-opacity-20 bg-warning-muted px-4 py-3 text-left transition hover:border-opacity-40" >
{approval.payloadSummary}
{approval.type.replaceAll("_", " ")} {requester ? by {requester.name} : null}
); })} {failedRuns.slice(0, 3).map((run, i) => { const agent = agentMap.get(run.agentId); return ( onNavigate("runs", run.id)} className="focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 flex w-full items-center gap-3 rounded-lg border border-[color:var(--destructive)] border-opacity-20 bg-destructive-muted px-4 py-3 text-left transition hover:border-opacity-40" >
{run.summary || "Run failed"}
{agent?.name} — {run.errorMessage?.slice(0, 80) ?? "Unknown error"}
); })} {urgentMessages.slice(0, 3).map((msg, i) => { const sender = agentMap.get(msg.fromAgentId); return ( onNavigate("communication", msg.id)} className="focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 flex w-full items-center gap-3 rounded-lg border border-[color:var(--destructive)] border-opacity-20 bg-warning-muted px-4 py-3 text-left transition hover:border-opacity-40" >
{msg.subject}
{sender?.name ?? "Unknown"} — {msg.channel}{msg.priority === "urgent" ? " · urgent" : ""}
); })}
) : null} {/* Active Work — what's running right now */}
Active Work
{activeWork.length > 0 ? (
{activeWork.map(({ run, agent, task }) => ( ))}
) : (
{t("dash.allQuiet")}
No tasks currently running
)}
{/* Recent Activity */}
{t("dash.recentActivity")}
); }