"use client"; import { useState, useMemo, useCallback, useRef, useEffect } from "react"; import { useLocale, useTranslations } from "next-intl"; import Card from "../Card"; import { getModelColor } from "@/shared/constants/colors"; import { PROVIDER_COLORS } from "./chartColors"; import { fmtCompact as fmt, fmtFull, fmtCost, formatApiKeyLabel as maskApiKeyLabel, } from "@/shared/utils/formatting"; import { getServiceTierDisplayLabel, translateCostText, type TranslationFn, } from "@/shared/utils/serviceTierLabels"; function createDateFormatter(locale: string, options: Intl.DateTimeFormatOptions) { try { return new Intl.DateTimeFormat(locale, options); } catch { return new Intl.DateTimeFormat(undefined, options); } } // ── Sort Indicator (shared by tables) ────────────────────────────────────── export function SortIndicator({ active, sortOrder }: { active: boolean; sortOrder: string }) { if (!active) { return ( unfold_more ); } return ( {sortOrder === "asc" ? "expand_less" : "expand_more"} ); } // ── StatCard (primary KPI) ───────────────────────────────────────────────── export function StatCard({ icon, label, value, subValue, color = "text-text-main", }: { icon: any; label: any; value: any; subValue?: any; color?: string; }) { return (
{icon} {label}
{value} {subValue && {subValue}}
); } // ── CompactStatGrid (secondary metrics in a single card, grouped) ───────── export type CompactStatSection = { title: string; items: Array<{ icon: string; label: string; value: any; color?: string }>; /** On mobile use 1 column instead of 2 — useful when values can be long (model names, etc.) */ wideValues?: boolean; }; export function CompactStatGrid({ sections }: { sections: CompactStatSection[] }) { return (
{sections.map((section, si) => (
{si > 0 && (
)}
{section.title}
{section.items.map((stat, i) => (
{stat.icon} {stat.label}
{stat.value}
))}
))}
); } // ── ActivityHeatmap ──────────────────────────────────────────────────────── export function ActivityHeatmap({ activityMap }) { const t = useTranslations("analytics"); const scrollRef = useRef(null); const cells = useMemo(() => { const today = new Date(); const days = []; let maxVal = 0; for (let i = 364; i >= 0; i--) { const d = new Date(today); d.setDate(d.getDate() - i); const key = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`; const val = activityMap?.[key] || 0; if (val > maxVal) maxVal = val; days.push({ date: key, value: val, dayOfWeek: d.getDay() }); } return { days, maxVal }; }, [activityMap]); const weeks = useMemo(() => { const w = []; let current = []; const firstDay = cells.days[0]?.dayOfWeek || 0; for (let i = 0; i < firstDay; i++) { current.push(null); } for (const day of cells.days) { current.push(day); if (current.length === 7) { w.push(current); current = []; } } if (current.length > 0) w.push(current); return w; }, [cells]); // Auto-scroll to the right edge so the current date is visible useEffect(() => { if (scrollRef.current) { scrollRef.current.scrollLeft = scrollRef.current.scrollWidth; } }, [weeks]); const monthLabels = useMemo(() => { const labels = []; let lastMonth = -1; weeks.forEach((week, weekIdx) => { const firstDay = week.find((d) => d !== null); if (firstDay) { const m = new Date(firstDay.date).getMonth(); if (m !== lastMonth) { const monthNames = [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", ]; labels.push({ weekIdx, label: monthNames[m] }); lastMonth = m; } } }); return labels; }, [weeks]); function getCellColor(value) { if (!value || value === 0) return "bg-white/[0.04]"; const intensity = Math.min(value / (cells.maxVal || 1), 1); if (intensity < 0.25) return "bg-primary/20"; if (intensity < 0.5) return "bg-primary/40"; if (intensity < 0.75) return "bg-primary/60"; return "bg-primary/90"; } return (

{t("overview")}

{Object.keys(activityMap || {}).length} active days ·{" "} {fmt(Object.values(activityMap || {}).reduce((a: number, b: number) => a + b, 0))} tokens · 365 days
{monthLabels.map((m, i) => ( {m.label} ))}
Mon Wed Fri
{weeks.map((week, wi) => (
{week.map((day, di) => (
))}
))}
Less
More
); } export function ApiKeyTable({ byApiKey }) { const t = useTranslations("analytics"); const [query, setQuery] = useState(""); const [sortBy, setSortBy] = useState("totalTokens"); const [sortOrder, setSortOrder] = useState("desc"); const data = useMemo(() => byApiKey || [], [byApiKey]); const filtered = useMemo(() => { const q = query.trim().toLowerCase(); if (!q) return data; return data.filter( (row) => (row.apiKeyName || "").toLowerCase().includes(q) || (row.apiKeyId || "").toLowerCase().includes(q) ); }, [data, query]); const sorted = useMemo(() => { const arr = [...filtered]; arr.sort((a, b) => { const va = a[sortBy] ?? 0; const vb = b[sortBy] ?? 0; if (typeof va === "string") { return sortOrder === "asc" ? va.localeCompare(vb) : vb.localeCompare(va); } return sortOrder === "asc" ? va - vb : vb - va; }); return arr; }, [filtered, sortBy, sortOrder]); const toggleSort = useCallback( (field) => { if (sortBy === field) { setSortOrder((prev) => (prev === "asc" ? "desc" : "asc")); return; } setSortBy(field); setSortOrder("desc"); }, [sortBy] ); const hasData = data.length > 0; if (!hasData) { return (

API Key Breakdown

{t("chartNoData")}
); } return (

API Key Breakdown

setQuery(e.target.value)} placeholder={t("filterSearchKeys")} className="w-full max-w-[220px] px-3 py-1.5 rounded-lg bg-bg-subtle border border-border text-xs text-text-primary placeholder:text-text-muted focus:outline-none focus:border-primary" />
{sorted.map((row, i) => ( ))} {sorted.length === 0 && ( )}
toggleSort("apiKeyName")} > API Key toggleSort("requests")} > {t("chartRequests")}{" "} toggleSort("promptTokens")} > {t("chartInput")}{" "} toggleSort("completionTokens")} > {t("chartOutput")}{" "} toggleSort("totalTokens")} > {t("chartTotal")}{" "} toggleSort("cost")} > {t("chartCost")}
{maskApiKeyLabel(row.apiKeyName, row.apiKeyId)} {fmtFull(row.requests)} {fmt(row.promptTokens)} {fmt(row.completionTokens)} {fmt(row.totalTokens)} {fmtCost(row.cost)}
{t("filterNoKeysMatch")}
); } export function MostActiveDay7d({ activityMap }) { const locale = useLocale(); const weekdayFormatter = useMemo( () => createDateFormatter(locale, { weekday: "long" }), [locale] ); const dateFormatter = useMemo( () => createDateFormatter(locale, { month: "short", day: "numeric" }), [locale] ); const data = useMemo(() => { if (!activityMap) return null; const today = new Date(); let peakKey = null; let peakVal = 0; for (let i = 0; i < 7; i++) { const d = new Date(today); d.setDate(d.getDate() - i); const key = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`; const val = activityMap[key] || 0; if (val > peakVal) { peakVal = val; peakKey = key; } } if (!peakKey || peakVal === 0) return null; const peakDate = new Date(peakKey + "T12:00:00"); return { weekday: weekdayFormatter.format(peakDate), label: dateFormatter.format(peakDate), tokens: peakVal, }; }, [activityMap, dateFormatter, weekdayFormatter]); return (

Most Active Day

{data ? ( <> {data.weekday} {data.label} · {fmt(data.tokens)} tokens ) : ( No data in the last 7 days )}
); } // ── WeeklySquares7d ──────────────────────────────────────────────────────── export function WeeklySquares7d({ activityMap }) { const t = useTranslations("analytics"); const locale = useLocale(); const weekdayFormatter = useMemo( () => createDateFormatter(locale, { weekday: "short" }), [locale] ); const dateFormatter = useMemo( () => createDateFormatter(locale, { month: "short", day: "numeric" }), [locale] ); const days = useMemo(() => { if (!activityMap) return []; const today = new Date(); const result = []; let maxVal = 0; for (let i = 6; i >= 0; i--) { const d = new Date(today); d.setDate(d.getDate() - i); const key = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`; const val = activityMap[key] || 0; if (val > maxVal) maxVal = val; result.push({ key, val, label: weekdayFormatter.format(d), dateLabel: dateFormatter.format(d), }); } return result.map((d) => ({ ...d, intensity: maxVal > 0 ? d.val / maxVal : 0 })); }, [activityMap, dateFormatter, weekdayFormatter]); function getSquareStyle(intensity) { if (intensity === 0) return { background: "rgba(255,255,255,0.04)" }; const opacity = 0.15 + intensity * 0.75; return { background: `rgba(229, 77, 94, ${opacity.toFixed(2)})` }; } return (

{t("chartWeekly")}

{days.map((d, i) => (
{d.label}
))}
); } // ── ModelTable ────────────────────────────────────────────────────────────── export function ModelTable({ byModel, summary }) { const t = useTranslations("analytics"); const [sortBy, setSortBy] = useState("totalTokens"); const [sortOrder, setSortOrder] = useState("desc"); const toggleSort = useCallback( (field) => { if (sortBy === field) { setSortOrder((prev) => (prev === "asc" ? "desc" : "asc")); } else { setSortBy(field); setSortOrder("desc"); } }, [sortBy] ); const sorted = useMemo(() => { const arr = [...(byModel || [])]; arr.sort((a, b) => { const va = a[sortBy] ?? 0; const vb = b[sortBy] ?? 0; if (typeof va === "string") return sortOrder === "asc" ? va.localeCompare(vb) : vb.localeCompare(va); return sortOrder === "asc" ? va - vb : vb - va; }); return arr; }, [byModel, sortBy, sortOrder]); return (

{t("chartModelBreakdown")}

{sorted.map((m, i) => ( ))}
toggleSort("model")} > {t("chartModel")}{" "} toggleSort("requests")} > {t("chartRequests")}{" "} toggleSort("promptTokens")} > {t("chartInput")}{" "} toggleSort("completionTokens")} > {t("chartOutput")}{" "} toggleSort("totalTokens")} > {t("chartTotal")}{" "} toggleSort("cost")} > {t("chartCost")} {t("chartShare")}
{m.model}
{fmtFull(m.requests)} {fmt(m.promptTokens)} {fmt(m.completionTokens)} {fmt(m.totalTokens)} {fmtCost(m.cost)}
{m.pct}%
); } function getServiceTierIcon(serviceTier) { if (serviceTier === "priority") return "bolt"; if (serviceTier === "flex") return "savings"; return "speed"; } function getServiceTierIconClass(serviceTier) { if (serviceTier === "priority") return "text-sky-500"; if (serviceTier === "flex") return "text-emerald-500"; return "text-text-muted"; } function getServiceTierBarClass(serviceTier) { if (serviceTier === "priority") return "bg-sky-500"; if (serviceTier === "flex") return "bg-emerald-500"; return "bg-text-muted/50"; } function getServiceTierCostClass(serviceTier) { return serviceTier === "flex" ? "text-emerald-500" : "text-amber-500"; } export function ServiceTierBreakdown({ byServiceTier, summary }) { const t = useTranslations("costs") as TranslationFn; const data = useMemo(() => byServiceTier || [], [byServiceTier]); const totalRequests = Number(summary?.totalRequests || 0); const totalCost = Number(summary?.totalCost || 0); if (!data.length) { return null; } return (

{translateCostText(t, "serviceTierBreakdownTitle", "Service Tier")}

{translateCostText(t, "serviceTierBreakdownSubtitle", "Fast / Flex / Standard split")}
{data.map((tier) => { const isFlex = tier.serviceTier === "flex"; const tierLabel = getServiceTierDisplayLabel(t, tier.serviceTier, tier.label); const requestPct = totalRequests > 0 ? ((Number(tier.requests || 0) / totalRequests) * 100).toFixed(1) : "0"; const costPct = totalCost > 0 ? ((Number(tier.cost || 0) / totalCost) * 100).toFixed(1) : "0"; const usageSavings = Number(tier.usageSavingsTokens || 0); const costSavings = Number(tier.savings || 0); const usageSavingsText = isFlex && usageSavings > 0 ? ` · ${fmt(usageSavings)} ${translateCostText( t, "serviceTierUsageSaved", "usage saved" )}` : ""; const costDetailText = isFlex && costSavings > 0 ? `${fmtCost(costSavings)} ${translateCostText(t, "serviceTierCostSaved", "saved")}` : `${costPct}% ${translateCostText(t, "serviceTierCostShareSuffix", "of cost")}`; return (
{getServiceTierIcon(tier.serviceTier)}
{tierLabel}
{fmtFull(tier.requests)} requests · {fmt(tier.totalTokens)} tokens {usageSavingsText}
{fmtCost(tier.cost)}
{costDetailText}
); })}
); } // ── UsageDetail ──────────────────────────────────────────────────────────── export function UsageDetail({ summary }) { const t = useTranslations("analytics"); const items = [ { label: t("chartInput"), value: summary?.promptTokens, color: "text-primary" }, { label: t("chartCacheRead"), value: 0, color: "text-text-muted" }, { label: t("chartOutput"), value: summary?.completionTokens, color: "text-emerald-500" }, ]; return (

{t("chartUsageDetail")}

{items.map((item, i) => (
{item.label} {fmtFull(item.value)}
))}
); } // ── ProviderTable ────────────────────────────────────────────────────────── export function ProviderTable({ byProvider }) { const t = useTranslations("analytics"); const [sortBy, setSortBy] = useState("totalTokens"); const [sortOrder, setSortOrder] = useState("desc"); const data = useMemo(() => byProvider || [], [byProvider]); const totalTokens = useMemo(() => data.reduce((acc, p) => acc + p.totalTokens, 0), [data]); const toggleSort = useCallback( (field) => { if (sortBy === field) { setSortOrder((prev) => (prev === "asc" ? "desc" : "asc")); } else { setSortBy(field); setSortOrder("desc"); } }, [sortBy] ); const sorted = useMemo(() => { const arr = [...data]; arr.sort((a, b) => { const va = a[sortBy] ?? 0; const vb = b[sortBy] ?? 0; if (typeof va === "string") return sortOrder === "asc" ? va.localeCompare(vb) : vb.localeCompare(va); return sortOrder === "asc" ? va - vb : vb - va; }); return arr; }, [data, sortBy, sortOrder]); if (!data.length) { return (

{t("chartProviderBreakdown")}

{t("chartNoData")}
); } return (

{t("chartProviderBreakdown")}

{sorted.map((p, i) => { const pct = totalTokens > 0 ? ((p.totalTokens / totalTokens) * 100).toFixed(1) : "0"; return ( ); })}
toggleSort("provider")} > {t("chartProvider")}{" "} toggleSort("requests")} > {t("chartRequests")}{" "} toggleSort("promptTokens")} > {t("chartInput")}{" "} toggleSort("completionTokens")} > {t("chartOutput")}{" "} toggleSort("totalTokens")} > {t("chartTotal")}{" "} toggleSort("cost")} > {t("chartCost")} {t("chartShare")}
{p.provider}
{fmtFull(p.requests)} {fmt(p.promptTokens)} {fmt(p.completionTokens)} {fmt(p.totalTokens)} {fmtCost(p.cost)}
{pct}%
); }