diff --git a/src/renderer/components/dashboard/charts/BudgetUtilization.tsx b/src/renderer/components/dashboard/charts/BudgetUtilization.tsx index 8c2a41fd..8facfde6 100644 --- a/src/renderer/components/dashboard/charts/BudgetUtilization.tsx +++ b/src/renderer/components/dashboard/charts/BudgetUtilization.tsx @@ -1,5 +1,5 @@ -import { useMemo } from "react"; -import type { AgentRecord } from "@shared/types"; +import { useEffect, useMemo, useState } from "react"; +import type { AgentRecord, BudgetStatusSummary } from "@shared/types"; interface BudgetUtilizationProps { companyId: string; @@ -9,6 +9,7 @@ interface BudgetUtilizationProps { interface BudgetRow { id: string; name: string; + scopeType: string; spent: number; budget: number; pct: number; @@ -20,19 +21,59 @@ function getBarColor(pct: number): string { return "bg-[color:var(--success)]"; } -export function BudgetUtilization({ companyId: _companyId, agents }: BudgetUtilizationProps) { +function scopeLabel(scopeType: string): string { + if (scopeType === "company") return "Company"; + if (scopeType === "project") return "Project"; + return "Agent"; +} + +export function BudgetUtilization({ companyId, agents }: BudgetUtilizationProps) { + const [budgetStatus, setBudgetStatus] = useState(null); + + useEffect(() => { + window.agentCompany.getBudgetStatus(companyId).then((result) => { + if (result.ok) setBudgetStatus(result.data); + }); + }, [companyId]); + const rows = useMemo(() => { - 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]); + // Build rows from budget policies (company/agent/project scopes) + const policyRows: BudgetRow[] = (budgetStatus?.policies ?? []) + .filter((p) => p.amount > 0) + .map((p) => { + const agent = p.scopeType === "agent" ? agents.find((a) => a.id === p.scopeId) : undefined; + const name = agent ? agent.name : `${scopeLabel(p.scopeType)} budget`; + return { + id: p.policyId, + name, + scopeType: p.scopeType, + spent: p.spent, + budget: p.amount, + pct: Math.round((p.spent / p.amount) * 100), + }; + }); + + // Fallback: if no policies, use agent-level budgets from AgentRecord + if (policyRows.length === 0) { + return agents + .filter((a) => a.budgetMonthlyUsd > 0) + .map((a) => ({ + id: a.id, + name: a.name, + scopeType: "agent", + spent: a.spentMonthlyUsd, + budget: a.budgetMonthlyUsd, + pct: Math.round((a.spentMonthlyUsd / a.budgetMonthlyUsd) * 100), + })) + .sort((a, b) => a.name.localeCompare(b.name)); + } + + // Sort: company first, then project, then agent + const order: Record = { company: 0, project: 1, agent: 2 }; + return policyRows.sort( + (a, b) => (order[a.scopeType] ?? 3) - (order[b.scopeType] ?? 3) || a.name.localeCompare(b.name), + ); + }, [budgetStatus, agents]); if (rows.length === 0) { return ( @@ -55,8 +96,11 @@ export function BudgetUtilization({ companyId: _companyId, agents }: BudgetUtili {rows.map((row) => (
- + {row.name} + + {scopeLabel(row.scopeType)} +