fix(08): use getBudgetStatus IPC for company/agent/project scope budget utilization
This commit is contained in:
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)]"
|
||||
|
||||
Reference in new issue
Block a user