fix(08): use getBudgetStatus IPC for company/agent/project scope budget utilization

This commit is contained in:
ZacharyZhang-NY committed 2026-03-27 18:45:32 -04:00
1 parent 907a5dfe2f
commit 76a69494a1
1 file changed
+59 -15
@@ -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<BudgetStatusSummary | null>(null);
useEffect(() => {
window.agentCompany.getBudgetStatus(companyId).then((result) => {
if (result.ok) setBudgetStatus(result.data);
});
}, [companyId]);
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]);
// 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<string, number> = { 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) => (
<div key={row.id}>
<div className="mb-1 flex items-center justify-between">
<span className="text-[13px] font-semibold text-[color:var(--text)]">
<span className="flex items-center gap-2 text-[13px] font-semibold text-[color:var(--text)]">
{row.name}
<span className="text-[11px] font-normal text-[color:var(--muted)]">
{scopeLabel(row.scopeType)}
</span>
</span>
<span
className="text-[13px] text-[color:var(--muted)]"