"use client"; import { useState, useEffect, useCallback, useMemo, useRef } from "react"; import { useLocale, useTranslations } from "next-intl"; import Card from "./Card"; import { CardSkeleton } from "./Loading"; import { fmtCompact as fmt, fmtFull, fmtCost } from "@/shared/utils/formatting"; import { readFetchErrorMessage } from "@/shared/utils/fetchError"; import { StatCard, CompactStatGrid, ActivityHeatmap, DailyTrendChart, AccountDonut, ApiKeyDonut, ApiKeyTable, MostActiveDay7d, WeeklySquares7d, ModelTable, ProviderCostDonut, ModelOverTimeChart, ProviderTable, ServiceTierBreakdown, ApiKeyFilterDropdown, CustomRangePicker, RequestCountByProviderDateTable, } from "./analytics"; // ============================================================================ // Main Component // ============================================================================ export default function UsageAnalytics() { const locale = useLocale(); const t = useTranslations("analytics"); const tCommon = useTranslations("common"); const [range, setRange] = useState("30d"); const [analytics, setAnalytics] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); // Custom date range state const [customStart, setCustomStart] = useState(""); const [customEnd, setCustomEnd] = useState(""); const [showCustomPicker, setShowCustomPicker] = useState(false); const customPickerAnchorRef = useRef(null); // API key filter state const [selectedApiKeys, setSelectedApiKeys] = useState([]); const [availableApiKeys, setAvailableApiKeys] = useState<{ id: string; name: string }[]>([]); const fetchAnalytics = useCallback(async () => { try { setLoading(true); const params = new URLSearchParams(); params.set("range", range); if (range === "custom" && customStart && customEnd) { params.set("startDate", customStart); params.set("endDate", customEnd); } if (selectedApiKeys.length > 0) { params.set("apiKeyIds", selectedApiKeys.join(",")); } const res = await fetch(`/api/usage/analytics?${params.toString()}`); if (!res.ok) throw new Error(await readFetchErrorMessage(res, tCommon("error"))); const data = await res.json(); setAnalytics(data); setError(null); // Update available keys from unfiltered data (only when no filter is active). if (selectedApiKeys.length === 0 && data.byApiKey?.length > 0) { const seen = new Set(); const keys: { id: string; name: string }[] = []; for (const k of data.byApiKey) { const id = k.apiKeyId || k.apiKeyName || tCommon("unknownProvider"); const name = k.apiKeyName || k.apiKeyId || tCommon("unknownProvider"); if (seen.has(id)) continue; seen.add(id); keys.push({ id, name }); } setAvailableApiKeys(keys); } } catch (err) { setError((err as any).message); } finally { setLoading(false); } }, [range, customStart, customEnd, selectedApiKeys, tCommon]); useEffect(() => { const timer = window.setTimeout(() => void fetchAnalytics(), 0); return () => window.clearTimeout(timer); }, [fetchAnalytics]); const handleRangeSelect = useCallback((value: string) => { if (value === "custom") { setShowCustomPicker(true); } else { setRange(value); setShowCustomPicker(false); } }, []); const handleCustomApply = useCallback((start: string, end: string) => { setCustomStart(start); setCustomEnd(end); setRange("custom"); setShowCustomPicker(false); }, []); // Format custom range label for display const customRangeLabel = useMemo(() => { if (range !== "custom" || !customStart || !customEnd) return null; const fmt = (iso: string) => { const d = new Date(iso); return d.toLocaleDateString(locale, { month: "short", day: "numeric", hour: "2-digit", minute: "2-digit", }); }; return `${fmt(customStart)} — ${fmt(customEnd)}`; }, [range, customStart, customEnd, locale]); const ranges = [ { value: "1d", label: t("period1D") }, { value: "7d", label: t("period7D") }, { value: "30d", label: t("period30D") }, { value: "90d", label: t("period90D") }, { value: "ytd", label: t("periodYTD") }, { value: "all", label: t("periodAll") }, ]; const topModel = useMemo(() => { const models = analytics?.byModel || []; return models.length > 0 ? models[0].model : "—"; }, [analytics]); const topProvider = useMemo(() => { const providers = analytics?.byProvider || []; return providers.length > 0 ? providers[0].provider : "—"; }, [analytics]); const busiestDay = useMemo(() => { const wp = analytics?.weeklyPattern || []; if (!wp.length) return "—"; const max = wp.reduce((a, b) => (a.avgTokens > b.avgTokens ? a : b), wp[0]); if (max.avgTokens <= 0) return "—"; const weekdayIndex = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"].indexOf(max.day); if (weekdayIndex < 0) return max.day; return new Intl.DateTimeFormat(locale, { weekday: "short" }).format( new Date(2024, 0, 7 + weekdayIndex) ); }, [analytics, locale]); const providerCount = useMemo(() => { return (analytics?.byProvider || []).length; }, [analytics]); const providerDiversity = useMemo(() => { const providers = analytics?.byProvider || []; if (providers.length <= 1) return 0; let totalCalls = 0; for (const p of providers) { totalCalls += p.totalRequests || p.apiCalls || 0; } if (totalCalls === 0) return 0; let h = 0; for (const p of providers) { const p_i = (p.totalRequests || p.apiCalls || 0) / totalCalls; if (p_i > 0) h -= p_i * Math.log2(p_i); } const maxH = Math.log2(providers.length); return maxH > 0 ? (h / maxH) * 100 : 0; }, [analytics]); if (loading && !analytics) return ; if (error) return ( {tCommon("errorShort")}: {error} ); const s = analytics?.summary || {}; // ── Derived insight values ── const avgTokensPerReq = s.totalRequests > 0 ? Math.round(s.totalTokens / s.totalRequests) : 0; const costPerReq = s.totalRequests > 0 ? s.totalCost / s.totalRequests : 0; const ioRatio = s.completionTokens > 0 ? (s.promptTokens / s.completionTokens).toFixed(1) : "—"; return (
{/* Header + Filters */}

analytics {t("usageAnalyticsTitle")}

{/* API Key Filter */} {/* Period Selector + Custom */}
{ranges.map((r) => ( ))} {/* Custom Range Picker Popover */} {showCustomPicker && ( setShowCustomPicker(false)} /> )}
{/* Primary KPI Cards */}
{/* Secondary Metrics — compact grid with sections */} {/* Activity Heatmap + Weekly Widgets */}
{/* Token & Cost Trend + Provider Cost Donut */}
{/* Fast / Standard service tier split */} {/* Model Usage Over Time (stacked area) */} {/* Account Donut + API Key Donut */}
{/* Provider Breakdown Table */} {/* Request Count by Provider & Date — #4009 (some providers bill per-request) */} {/* API Key Table */} {/* Model Breakdown Table */}
); }