/** * 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;