import { useEffect, useMemo, useState } from "react"; import { useQuery } from "@tanstack/react-query"; import type { CostByProviderModel, CostWindowSpendRow, QuotaWindow } from "@paperclipai/shared"; import { costsApi } from "../api/costs"; import { useCompany } from "../context/CompanyContext"; import { useBreadcrumbs } from "../context/BreadcrumbContext"; import { queryKeys } from "../lib/queryKeys"; import { EmptyState } from "../components/EmptyState"; import { PageSkeleton } from "../components/PageSkeleton"; import { ProviderQuotaCard } from "../components/ProviderQuotaCard"; import { PageTabBar } from "../components/PageTabBar"; import { formatCents, formatTokens, providerDisplayName } from "../lib/utils"; import { Button } from "@/components/ui/button"; import { Tabs, TabsContent } from "@/components/ui/tabs"; import { Gauge } from "lucide-react"; type DatePreset = "mtd" | "7d" | "30d" | "ytd" | "all" | "custom"; const PRESET_LABELS: Record = { mtd: "Month to Date", "7d": "Last 7 Days", "30d": "Last 30 Days", ytd: "Year to Date", all: "All Time", custom: "Custom", }; function computeRange(preset: DatePreset): { from: string; to: string } { const now = new Date(); const to = now.toISOString(); switch (preset) { case "mtd": { const d = new Date(now.getFullYear(), now.getMonth(), 1); return { from: d.toISOString(), to }; } case "7d": { const d = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); return { from: d.toISOString(), to }; } case "30d": { const d = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000); return { from: d.toISOString(), to }; } case "ytd": { const d = new Date(now.getFullYear(), 0, 1); return { from: d.toISOString(), to }; } case "all": return { from: "", to: "" }; case "custom": return { from: "", to: "" }; } } /** current week mon-sun boundaries as iso strings */ function currentWeekRange(): { from: string; to: string } { const now = new Date(); const day = now.getDay(); // 0 = Sun, 1 = Mon, … const diffToMon = (day === 0 ? -6 : 1 - day); const mon = new Date(now.getFullYear(), now.getMonth(), now.getDate() + diffToMon, 0, 0, 0, 0); const sun = new Date(mon.getTime() + 6 * 24 * 60 * 60 * 1000 + 23 * 3600 * 1000 + 3599 * 1000 + 999); return { from: mon.toISOString(), to: sun.toISOString() }; } function ProviderTabLabel({ provider, rows }: { provider: string; rows: CostByProviderModel[] }) { const totalTokens = rows.reduce((s, r) => s + r.inputTokens + r.outputTokens, 0); const totalCost = rows.reduce((s, r) => s + r.costCents, 0); return ( {providerDisplayName(provider)} {formatTokens(totalTokens)} {formatCents(totalCost)} ); } export function Usage() { const { selectedCompanyId } = useCompany(); const { setBreadcrumbs } = useBreadcrumbs(); const [preset, setPreset] = useState("mtd"); const [customFrom, setCustomFrom] = useState(""); const [customTo, setCustomTo] = useState(""); const [activeProvider, setActiveProvider] = useState("all"); useEffect(() => { setBreadcrumbs([{ label: "Usage" }]); }, [setBreadcrumbs]); const { from, to } = useMemo(() => { if (preset === "custom") { // treat custom date strings as local-date boundaries so the full day is included // regardless of the user's timezone. "from" starts at local midnight (00:00:00), // "to" ends at local 23:59:59.999 (converted to utc via Date constructor). const fromDate = customFrom ? new Date(customFrom + "T00:00:00") : null; const toDate = customTo ? new Date(customTo + "T23:59:59.999") : null; return { from: fromDate ? fromDate.toISOString() : "", to: toDate ? toDate.toISOString() : "", }; } const range = computeRange(preset); // floor `to` to the nearest minute so the query key is stable across 30s refetch ticks // (prevents a new cache entry being created on every poll cycle) if (range.to) { const d = new Date(range.to); d.setSeconds(0, 0); range.to = d.toISOString(); } return range; }, [preset, customFrom, customTo]); // key to today's date string so the range auto-refreshes after midnight on the next 30s refetch const today = new Date().toDateString(); const weekRange = useMemo(() => currentWeekRange(), [today]); // for custom preset, only fetch once both dates are selected const customReady = preset !== "custom" || (!!customFrom && !!customTo); const { data, isLoading, error } = useQuery({ queryKey: queryKeys.usageByProvider(selectedCompanyId!, from || undefined, to || undefined), queryFn: () => costsApi.byProvider(selectedCompanyId!, from || undefined, to || undefined), enabled: !!selectedCompanyId && customReady, refetchInterval: 30_000, staleTime: 10_000, }); const { data: summary } = useQuery({ queryKey: queryKeys.costs(selectedCompanyId!, from || undefined, to || undefined), queryFn: () => costsApi.summary(selectedCompanyId!, from || undefined, to || undefined), enabled: !!selectedCompanyId && customReady, refetchInterval: 30_000, staleTime: 10_000, }); const { data: weekData } = useQuery({ queryKey: queryKeys.usageByProvider(selectedCompanyId!, weekRange.from, weekRange.to), queryFn: () => costsApi.byProvider(selectedCompanyId!, weekRange.from, weekRange.to), enabled: !!selectedCompanyId, refetchInterval: 30_000, staleTime: 10_000, }); const { data: windowData } = useQuery({ queryKey: queryKeys.usageWindowSpend(selectedCompanyId!), queryFn: () => costsApi.windowSpend(selectedCompanyId!), enabled: !!selectedCompanyId, refetchInterval: 30_000, staleTime: 10_000, }); const { data: quotaData } = useQuery({ queryKey: queryKeys.usageQuotaWindows(selectedCompanyId!), queryFn: () => costsApi.quotaWindows(selectedCompanyId!), enabled: !!selectedCompanyId, // quota windows change infrequently; refresh every 5 minutes refetchInterval: 300_000, staleTime: 60_000, }); // rows grouped by provider const byProvider = useMemo(() => { const map = new Map(); for (const row of data ?? []) { const arr = map.get(row.provider) ?? []; arr.push(row); map.set(row.provider, arr); } return map; }, [data]); // week spend per provider const weekSpendByProvider = useMemo(() => { const map = new Map(); for (const row of weekData ?? []) { map.set(row.provider, (map.get(row.provider) ?? 0) + row.costCents); } return map; }, [weekData]); // window spend rows per provider, keyed by provider with the 3-window array const windowSpendByProvider = useMemo(() => { const map = new Map(); for (const row of windowData ?? []) { const arr = map.get(row.provider) ?? []; arr.push(row); map.set(row.provider, arr); } return map; }, [windowData]); // quota windows from the provider's own api, keyed by provider const quotaWindowsByProvider = useMemo(() => { const map = new Map(); for (const result of quotaData ?? []) { if (result.ok && result.windows.length > 0) { map.set(result.provider, result.windows); } } return map; }, [quotaData]); // compute deficit notch per provider: only meaningful for mtd — projects spend to month end // and flags when that projection exceeds the provider's pro-rata budget share. function providerDeficitNotch(providerKey: string): boolean { if (preset !== "mtd") return false; const budget = summary?.budgetCents ?? 0; if (budget <= 0) return false; const totalSpend = summary?.spendCents ?? 0; const providerCostCents = (byProvider.get(providerKey) ?? []).reduce((s, r) => s + r.costCents, 0); const providerShare = totalSpend > 0 ? providerCostCents / totalSpend : 0; const providerBudget = budget * providerShare; if (providerBudget <= 0) return false; const now = new Date(); const daysElapsed = now.getDate(); const daysInMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0).getDate(); const burnRate = providerCostCents / Math.max(daysElapsed, 1); return providerCostCents + burnRate * (daysInMonth - daysElapsed) > providerBudget; } const providers = Array.from(byProvider.keys()); if (!selectedCompanyId) { return ; } if (isLoading) { return ; } const presetKeys: DatePreset[] = ["mtd", "7d", "30d", "ytd", "all", "custom"]; const tabItems = [ { value: "all", label: ( All providers {data && data.length > 0 && ( <> {formatTokens(data.reduce((s, r) => s + r.inputTokens + r.outputTokens, 0))} {formatCents(data.reduce((s, r) => s + r.costCents, 0))} )} ), }, ...providers.map((p) => ({ value: p, label: , })), ]; return (
{/* date range selector */}
{presetKeys.map((p) => ( ))} {preset === "custom" && (
setCustomFrom(e.target.value)} className="h-8 rounded-md border border-input bg-background px-2 text-sm text-foreground" /> to setCustomTo(e.target.value)} className="h-8 rounded-md border border-input bg-background px-2 text-sm text-foreground" />
)}
{error &&

{(error as Error).message}

} {preset === "custom" && !customReady ? (

Select a start and end date to load data.

) : ( {providers.length === 0 ? (

No cost events in this period.

) : (
{providers.map((p) => ( ))}
)}
{providers.map((p) => ( ))}
)}
); }