/**
* Usage Dashboard Page (S-7.4)
*
* Dashboard page showing:
* - Event counts, API call volume, storage usage
* - Per-tier quota bars
* - Time range selector (7d/30d/90d)
* - Ingestion volume chart over time
* - 80% quota warning banner
*/
import { getErrorMessage } from '@agentkitai/agentlens-core';
import React, { useState, useCallback, useEffect, useMemo } from 'react';
import { useOrg } from './OrgContext';
import {
getUsage,
type UsageBreakdown,
type UsageTimeRange,
} from './api';
const TIME_RANGES: { value: UsageTimeRange; label: string }[] = [
{ value: '7d', label: '7 days' },
{ value: '30d', label: '30 days' },
{ value: '90d', label: '90 days' },
];
function formatNumber(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(1)}K`;
return n.toString();
}
function formatBytes(bytes: number): string {
if (bytes >= 1_073_741_824) return `${(bytes / 1_073_741_824).toFixed(1)} GB`;
if (bytes >= 1_048_576) return `${(bytes / 1_048_576).toFixed(1)} MB`;
if (bytes >= 1024) return `${(bytes / 1024).toFixed(1)} KB`;
return `${bytes} B`;
}
/** Simple bar chart using div widths */
function QuotaBar({
label,
current,
max,
formatter,
testId,
}: {
label: string;
current: number;
max: number;
formatter: (n: number) => string;
testId: string;
}): React.ReactElement {
const pct = max > 0 ? Math.min((current / max) * 100, 100) : 0;
const isWarning = pct >= 80;
const isCritical = pct >= 95;
return (
{label}
{formatter(current)} / {formatter(max)} ({pct.toFixed(0)}%)
);
}
/** Simple ASCII-style bar chart for timeseries */
function TimeseriesChart({
data,
dataKey,
}: {
data: { timestamp: string; events: number; api_calls: number }[];
dataKey: 'events' | 'api_calls';
}): React.ReactElement {
const maxVal = Math.max(...data.map((d) => d[dataKey]), 1);
return (
{data.map((point, i) => {
const height = (point[dataKey] / maxVal) * 100;
return (
);
})}
);
}
export function UsageDashboard(): React.ReactElement {
const { currentOrg } = useOrg();
const [range, setRange] = useState('30d');
const [usage, setUsage] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const orgId = currentOrg?.id;
const refresh = useCallback(async () => {
if (!orgId) return;
setLoading(true);
setError(null);
try {
const data = await getUsage(orgId, range);
setUsage(data);
} catch (err: unknown) {
setError(getErrorMessage(err) || 'Failed to load usage data');
} finally {
setLoading(false);
}
}, [orgId, range]);
useEffect(() => {
refresh();
}, [refresh]);
const quotaWarning = useMemo(() => {
if (!usage) return null;
const pct = usage.summary.quota_events > 0
? (usage.summary.events_count / usage.summary.quota_events) * 100
: 0;
if (pct >= 80) return pct;
return null;
}, [usage]);
if (!currentOrg) {
return Select an organization to view usage.
;
}
return (
Usage
{TIME_RANGES.map((tr) => (
))}
{error && (
{error}
)}
{/* 80% quota warning */}
{quotaWarning !== null && (
= 95
? 'bg-red-50 border-red-200 text-red-700'
: 'bg-yellow-50 border-yellow-200 text-yellow-700'
}`}
data-testid="quota-warning"
role="alert"
>
⚠️ You've used {quotaWarning.toFixed(0)}% of your event quota this period.
{quotaWarning >= 95 ? ' Consider upgrading your plan.' : ''}
)}
{loading ? (
Loading usage data...
) : usage ? (
<>
{/* Summary cards */}
Events
{formatNumber(usage.summary.events_count)}
{usage.summary.period_start} — {usage.summary.period_end}
API Calls
{formatNumber(usage.summary.api_calls)}
Storage
{formatBytes(usage.summary.storage_bytes)}
{/* Quota bars */}
Quota Usage ({usage.summary.plan} plan)
{/* Timeseries chart */}
{usage.timeseries.length > 0 && (
Ingestion Volume Over Time
{new Date(usage.timeseries[0].timestamp).toLocaleDateString()}
{new Date(usage.timeseries[usage.timeseries.length - 1].timestamp).toLocaleDateString()}
)}
>
) : null}
);
}
export default UsageDashboard;