"use client" import { useEffect, useState, useMemo } from 'react' import LoadingSkeleton from './components/LoadingSkeleton' import PageHeader from './components/PageHeader' import ProviderFilter from './components/ProviderFilter' import StatsGrid from './components/StatsGrid' import Activity from './components/Activity' import ModelUsageCard from './components/ModelUsageCard' import TokenType from './components/TokenType' import TokenComposition from './components/TokenComposition' import RecentSessions from './components/RecentSessions' import TimeRangeFilter from './components/TimeRangeFilter' import { filterDailyByRange, computeTotalsFromDaily } from './components/utils' import type { ExtendedCCData, CCData, TimeRangeKey, DailyData } from '@/lib/types/ai' import { getToolTheme } from '@/app/ai/theme' export default function Usage() { const [data, setData] = useState(null) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [selectedProvider, setSelectedProvider] = useState<'all' | 'claudeCode' | 'codex'>('all') const [timeRange, setTimeRange] = useState('1m') const sortedAllDaily = useMemo(() => { if (!data) return [] const dateMap = new Map() if (data.claudeCode?.daily) { for (const entry of data.claudeCode.daily) { dateMap.set(entry.date, { ...entry }) } } if (data.codex?.daily) { for (const entry of data.codex.daily) { const existing = dateMap.get(entry.date) if (existing) { existing.inputTokens += entry.inputTokens existing.outputTokens += entry.outputTokens existing.cacheCreationTokens += entry.cacheCreationTokens existing.cacheReadTokens += entry.cacheReadTokens existing.totalTokens += entry.totalTokens existing.totalCost += entry.totalCost existing.modelsUsed = [...existing.modelsUsed, ...entry.modelsUsed] existing.modelBreakdowns = [...existing.modelBreakdowns, ...entry.modelBreakdowns] } else { dateMap.set(entry.date, { ...entry }) } } } return Array.from(dateMap.values()).sort((a, b) => a.date.localeCompare(b.date)) }, [data]) const globalEndDate = useMemo(() => { if (!sortedAllDaily.length) return null const last = sortedAllDaily[sortedAllDaily.length - 1] return new Date(last.date + 'T00:00:00Z') }, [sortedAllDaily]) useEffect(() => { fetch('/data/cc.json') .then(res => { if (!res.ok) throw new Error('Failed to fetch data') return res.json() }) .then(data => { setData(data) setLoading(false) }) .catch(err => { setError(err.message) setLoading(false) }) }, []) const providerScopedData = useMemo(() => { if (!data) return null const baseDaily = sortedAllDaily const createEmptyDay = (date: string): DailyData => ({ date, inputTokens: 0, outputTokens: 0, cacheCreationTokens: 0, cacheReadTokens: 0, totalTokens: 0, totalCost: 0, modelsUsed: [], modelBreakdowns: [], }) if (selectedProvider === 'claudeCode' && data.claudeCode) { const byDate = new Map(data.claudeCode.daily.map(day => [day.date, day] as const)) const normalizedDaily = baseDaily.map(day => byDate.get(day.date) ?? createEmptyDay(day.date)) return { daily: normalizedDaily, totals: data.claudeCode.totals, } } if (selectedProvider === 'codex' && data.codex) { const byDate = new Map(data.codex.daily.map(day => [day.date, day] as const)) const normalizedDaily = baseDaily.map(day => byDate.get(day.date) ?? createEmptyDay(day.date)) return { daily: normalizedDaily, totals: data.codex.totals, } } const totals = data.totals || computeTotalsFromDaily(baseDaily) return { daily: baseDaily, totals, } }, [data, selectedProvider, sortedAllDaily]) const filteredData = useMemo(() => { if (!providerScopedData) return null const scopedDaily = filterDailyByRange(providerScopedData.daily, timeRange, { endDate: globalEndDate ?? undefined, }) const totals = timeRange === 'all' ? providerScopedData.totals : computeTotalsFromDaily(scopedDaily) return { daily: scopedDaily, totals } }, [providerScopedData, timeRange, globalEndDate]) const theme = getToolTheme(selectedProvider) if (loading) { return ( ) } if (error || !data || !filteredData) { return (
Error loading data: {error}
) } return (
) }