"use client";
import { useTranslations } from "next-intl";
import { useEffect, useState, useCallback } from "react";
interface DailyUsage {
date: string;
inputTokens: number;
outputTokens: number;
spend: number;
}
interface UsageData {
month: string;
currentPeriod: {
inputTokens: number;
outputTokens: number;
totalSpend: number;
requestCount: number;
};
budget: { maxBudget: number | null; spend: number; remaining: number | null };
rateLimits: { rpm: number | null; tpm: number | null };
dailyUsage: DailyUsage[];
}
function fmt(n: number): string {
if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`;
if (n >= 1_000) return `${(n / 1_000).toFixed(0)}k`;
return n.toString();
}
function usd(n: number): string {
return `$${n.toFixed(4)}`;
}
function getCurrentMonth(): string {
const now = new Date();
return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`;
}
function shiftMonth(month: string, delta: number): string {
const [y, m] = month.split("-").map(Number);
const d = new Date(y, m - 1 + delta, 1);
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}`;
}
function formatMonth(month: string, locale: string): string {
const [y, m] = month.split("-").map(Number);
return new Date(y, m - 1).toLocaleDateString(locale, { year: "numeric", month: "long" });
}
function UsageChart({ data }: { data: DailyUsage[] }) {
if (!data.length) return null;
const maxTokens = Math.max(...data.map((d) => d.inputTokens + d.outputTokens), 1);
const barW = Math.max(4, Math.floor(600 / data.length) - 2);
const h = 120;
return (
Input
Output
);
}
/**
* Usage display widget.
*
* Pass `tenant=` for the canonical path — works for both
* customers and admins, the API resolves team+alias from the tenant
* CR's status. The visibility check on the API ensures users can't
* query tenants they shouldn't see.
*
* `teamId`/`keyAlias` remain available as a platform-admin escape
* hatch for cross-org debugging, but the tenant-detail and dashboard
* paths should always use `tenant`.
*
* Bug 19 fix: previous version omitted both props for customer
* sessions, expecting the API to "figure it out". The API's fallback
* was "first visible tenant", which meant siblings in the same org
* showed identical numbers regardless of which detail page was open.
* Now the page passes the tenant name explicitly; no fallback exists.
*/
export function UsageDisplay({
tenant,
teamId,
keyAlias,
}: {
tenant?: string | null;
teamId?: string | null;
keyAlias?: string | null;
}) {
const t = useTranslations("usage");
const [month, setMonth] = useState(getCurrentMonth);
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const isCurrentMonth = month === getCurrentMonth();
const fetchUsage = useCallback(() => {
setLoading(true);
setError(null);
const params = new URLSearchParams({ month });
if (tenant) {
params.set("tenant", tenant);
} else if (teamId) {
// Admin escape hatch — only honoured by the API when the
// viewer is platform-role.
params.set("teamId", teamId);
if (keyAlias) params.set("keyAlias", keyAlias);
}
fetch(`/api/usage?${params}`)
.then((res) => { if (!res.ok) throw new Error(`${res.status}`); return res.json(); })
.then(setData)
.catch((e) => setError(e.message))
.finally(() => setLoading(false));
}, [tenant, teamId, keyAlias, month]);
useEffect(() => { fetchUsage(); }, [fetchUsage]);
return (