import { useMemo } from "react"; import type { CostByProviderModel, CostWindowSpendRow, QuotaWindow } from "@paperclipai/shared"; import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"; import { Skeleton } from "@/components/ui/skeleton"; import { QuotaBar } from "./QuotaBar"; import { ClaudeSubscriptionPanel } from "./ClaudeSubscriptionPanel"; import { CodexSubscriptionPanel } from "./CodexSubscriptionPanel"; import { billingTypeDisplayName, formatCents, formatTokens, providerDisplayName, quotaSourceDisplayName, } from "@/lib/utils"; // ordered display labels for rolling-window rows const ROLLING_WINDOWS = ["5h", "24h", "7d"] as const; interface ProviderQuotaCardProps { provider: string; rows: CostByProviderModel[]; /** company monthly budget in cents (0 means unlimited) */ budgetMonthlyCents: number; /** total company spend in this period in cents, all providers */ totalCompanySpendCents: number; /** spend in the current calendar week in cents, this provider only */ weekSpendCents: number; /** rolling window rows for this provider: 5h, 24h, 7d */ windowRows: CostWindowSpendRow[]; showDeficitNotch: boolean; /** live subscription quota windows from the provider's own api */ quotaWindows?: QuotaWindow[]; quotaError?: string | null; quotaSource?: string | null; quotaLoading?: boolean; } export function ProviderQuotaCard({ provider, rows, budgetMonthlyCents, totalCompanySpendCents, weekSpendCents, windowRows, showDeficitNotch, quotaWindows = [], quotaError = null, quotaSource = null, quotaLoading = false, }: ProviderQuotaCardProps) { // single-pass aggregation over rows — memoized so the 8 derived values are not // recomputed on every parent render tick (providers tab polls every 30s, and each // card is mounted twice: once in the "all" tab grid and once in its per-provider tab). const totals = useMemo(() => { let inputTokens = 0, outputTokens = 0, costCents = 0; let apiRunCount = 0, subRunCount = 0, subInputTokens = 0, subOutputTokens = 0; for (const r of rows) { inputTokens += r.inputTokens; outputTokens += r.outputTokens; costCents += r.costCents; apiRunCount += r.apiRunCount; subRunCount += r.subscriptionRunCount; subInputTokens += r.subscriptionInputTokens; subOutputTokens += r.subscriptionOutputTokens; } const totalTokens = inputTokens + outputTokens; const subTokens = subInputTokens + subOutputTokens; // denominator: api-billed tokens (from cost_events) + subscription tokens (from heartbeat_runs) const allTokens = totalTokens + subTokens; return { totalInputTokens: inputTokens, totalOutputTokens: outputTokens, totalTokens, totalCostCents: costCents, totalApiRuns: apiRunCount, totalSubRuns: subRunCount, totalSubInputTokens: subInputTokens, totalSubOutputTokens: subOutputTokens, totalSubTokens: subTokens, subSharePct: allTokens > 0 ? (subTokens / allTokens) * 100 : 0, }; }, [rows]); const { totalInputTokens, totalOutputTokens, totalTokens, totalCostCents, totalApiRuns, totalSubRuns, totalSubInputTokens, totalSubOutputTokens, totalSubTokens, subSharePct, } = totals; // budget bars: use this provider's own spend vs its pro-rata share of budget // pro-rata: if a provider is 40% of total spend, it gets 40% of the budget allocated. // falls back to raw provider spend vs total budget when totalCompanySpend is 0. const providerBudgetShare = budgetMonthlyCents > 0 && totalCompanySpendCents > 0 ? (totalCostCents / totalCompanySpendCents) * budgetMonthlyCents : budgetMonthlyCents; const budgetPct = providerBudgetShare > 0 ? Math.min(100, (totalCostCents / providerBudgetShare) * 100) : 0; // 4.33 = average weeks per calendar month (52 / 12) const weeklyBudgetShare = providerBudgetShare > 0 ? providerBudgetShare / 4.33 : 0; const weekPct = weeklyBudgetShare > 0 ? Math.min(100, (weekSpendCents / weeklyBudgetShare) * 100) : 0; const hasBudget = budgetMonthlyCents > 0; // memoized so the Map and max are not reconstructed on every parent render tick const windowMap = useMemo( () => new Map(windowRows.map((r) => [r.window, r])), [windowRows], ); const maxWindowCents = useMemo( () => Math.max(...windowRows.map((r) => r.costCents), 0), [windowRows], ); const isClaudeQuotaPanel = provider === "anthropic"; const isCodexQuotaPanel = provider === "openai" && quotaSource?.startsWith("codex-"); const supportsSubscriptionQuota = provider === "anthropic" || provider === "openai"; const showSubscriptionQuotaSection = supportsSubscriptionQuota && (quotaLoading || quotaWindows.length > 0 || quotaError != null); return (
{providerDisplayName(provider)} {formatTokens(totalInputTokens)} in {" · "} {formatTokens(totalOutputTokens)} out {(totalApiRuns > 0 || totalSubRuns > 0) && ( ·{" "} {totalApiRuns > 0 && `~${totalApiRuns} api`} {totalApiRuns > 0 && totalSubRuns > 0 && " / "} {totalSubRuns > 0 && `~${totalSubRuns} sub`} {" runs"} )}
{formatCents(totalCostCents)}
{hasBudget && (
= 100} />
)} {/* rolling window consumption — always shown when data is available */} {windowRows.length > 0 && ( <>

Rolling windows

{ROLLING_WINDOWS.map((w) => { const row = windowMap.get(w); // omit windows with no data rather than showing false $0.00 zeros if (!row) return null; const cents = row.costCents; const tokens = row.inputTokens + row.outputTokens; const barPct = maxWindowCents > 0 ? (cents / maxWindowCents) * 100 : 0; return (
{w} {formatTokens(tokens)} tok {formatCents(cents)}
); })}
)} {/* subscription usage — shown when any subscription-billed runs exist */} {totalSubRuns > 0 && ( <>

Subscription

{totalSubRuns} runs {" · "} {totalSubTokens > 0 && ( <> {formatTokens(totalSubTokens)} total {" · "} )} {formatTokens(totalSubInputTokens)} in {" · "} {formatTokens(totalSubOutputTokens)} out

{subSharePct > 0 && ( <>

{Math.round(subSharePct)}% of token usage via subscription

)}
)} {/* model breakdown — always shown, with token-share bars */} {rows.length > 0 && ( <>
{rows.map((row) => { const rowTokens = row.inputTokens + row.outputTokens; const tokenPct = totalTokens > 0 ? (rowTokens / totalTokens) * 100 : 0; const costPct = totalCostCents > 0 ? (row.costCents / totalCostCents) * 100 : 0; return (
{/* model name and cost */}
{row.model} {providerDisplayName(row.biller)} · {billingTypeDisplayName(row.billingType)}
{formatTokens(rowTokens)} tok {formatCents(row.costCents)}
{/* token share bar */}
{/* cost share overlay — narrower, opaque, shows relative cost weight */}
); })}
)} {/* subscription quota windows from provider api — shown when data is available */} {showSubscriptionQuotaSection && ( <>

Subscription quota

{quotaSource && !isClaudeQuotaPanel && !isCodexQuotaPanel ? ( {quotaSourceDisplayName(quotaSource)} ) : null}
{quotaLoading ? ( ) : isClaudeQuotaPanel ? ( ) : isCodexQuotaPanel ? ( ) : ( <> {quotaError ? (

{quotaError}

) : null}
{quotaWindows.map((qw) => { const fillColor = qw.usedPercent == null ? null : qw.usedPercent >= 90 ? "bg-red-400" : qw.usedPercent >= 70 ? "bg-yellow-400" : "bg-green-400"; return (
{qw.label} {qw.valueLabel != null ? ( {qw.valueLabel} ) : qw.usedPercent != null ? ( {qw.usedPercent}% used ) : null}
{qw.usedPercent != null && fillColor != null && (
)} {qw.detail ? (

{qw.detail}

) : qw.resetsAt ? (

resets {new Date(qw.resetsAt).toLocaleDateString(undefined, { month: "short", day: "numeric" })}

) : null}
); })}
)}
)} ); } function QuotaPanelSkeleton() { return (
{Array.from({ length: 3 }).map((_, index) => (
))}
); }