'use client'; import { useState, useEffect, useCallback } from 'react'; import { Users, DollarSign, Zap, TrendingUp, UserPlus, Activity, ArrowUpRight, ArrowDownRight, RefreshCw, Cpu, } from 'lucide-react'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Skeleton } from '@/components/ui/skeleton'; import { mockSummaryStats, formatNumber, formatCurrency, type DailyMetric, type User, type ModelUsage, } from '@/lib/mock-data'; import { apiGetDashboardStats, apiGetUsage, apiListUsers, apiGetRevenueAnalytics, type DashboardStats, type ApiUsageRecord, type RevenueAnalytics, } from '@/lib/api'; import { PageHeader } from '@bytelyst/dashboard-components'; import { Reveal, StaggerList } from '@bytelyst/motion'; import { AreaChart, BarChart } from '@/components/charts'; import { seriesValues, dateBars } from '@/lib/chart-data'; function buildKpiCards(stats: typeof mockSummaryStats, revenue?: RevenueAnalytics | null) { const fmt = (n: number) => (n >= 0 ? `+${n}%` : `${n}%`); const mrrChange = revenue?.mrrChange ?? 0; const churnRate = revenue?.churnRate ?? stats.churnRate; return [ { title: 'Total Users', value: stats.totalUsers.toString(), change: revenue ? fmt( Math.round( ((revenue.newSubscriptions - revenue.canceledSubscriptions) / (stats.totalUsers || 1)) * 100 * 10 ) / 10 ) : '—', trend: (revenue ? revenue.newSubscriptions >= revenue.canceledSubscriptions ? 'up' : 'down' : 'up') as 'up' | 'down', icon: Users, subtitle: `${stats.activeUsers} active`, }, { title: 'Monthly Revenue', value: formatCurrency(revenue?.mrr ?? stats.monthlyRecurring), change: revenue ? fmt(mrrChange) : '—', trend: (mrrChange >= 0 ? 'up' : 'down') as 'up' | 'down', icon: DollarSign, subtitle: `${formatCurrency(revenue?.totalRevenue ?? stats.totalRevenue)} total`, }, { title: 'Tokens This Month', value: formatNumber(stats.totalTokensThisMonth), change: '—', trend: 'up' as const, icon: Zap, subtitle: `${formatNumber(stats.avgTokensPerUser)} avg/user`, }, { title: 'New Users', value: (revenue?.newSubscriptions ?? stats.newUsersThisMonth).toString(), change: '—', trend: 'up' as const, icon: UserPlus, subtitle: `${stats.conversionRate}% conversion`, }, { title: 'Requests This Month', value: formatNumber(stats.totalRequestsThisMonth), change: '—', trend: 'up' as const, icon: Activity, subtitle: 'API calls', }, { title: 'Churn Rate', value: `${churnRate}%`, change: revenue ? `${revenue.churnCount} canceled` : '—', trend: (churnRate <= 5 ? 'down' : 'up') as 'up' | 'down', icon: TrendingUp, subtitle: 'Month over month', }, ]; } function mergeApiStats( base: typeof mockSummaryStats, api: DashboardStats ): typeof mockSummaryStats { const totalUsers = api.users.total || base.totalUsers; const totalTokens = api.usage.totalWords || base.totalTokensThisMonth; return { ...base, totalUsers, activeUsers: totalUsers, totalTokensThisMonth: totalTokens, totalRequestsThisMonth: api.usage.totalDictations || base.totalRequestsThisMonth, avgTokensPerUser: totalUsers > 0 ? Math.round(totalTokens / totalUsers) : 0, }; } function usageRecordsToDailyMetrics(records: ApiUsageRecord[]): DailyMetric[] { const byDate = new Map(); for (const r of records) { const existing = byDate.get(r.date); if (existing) { existing.totalTokens += r.tokensUsed; existing.totalRequests += r.dictations; existing.revenue += r.costUsd; existing.activeUsers += 1; } else { byDate.set(r.date, { date: r.date, activeUsers: 1, totalRequests: r.dictations, totalTokens: r.tokensUsed, revenue: r.costUsd, }); } } return Array.from(byDate.values()).sort((a, b) => a.date.localeCompare(b.date)); } function buildModelUsage(records: ApiUsageRecord[]): ModelUsage[] { // Aggregate per-model from real usage records (each record now has optional model field) const byModel: Record = {}; for (const r of records) { const model = (r as unknown as { model?: string }).model || 'gpt-4o-mini'; if (!byModel[model]) byModel[model] = { tokens: 0, requests: 0, cost: 0 }; byModel[model].tokens += r.tokensUsed; byModel[model].requests += r.dictations; byModel[model].cost += r.costUsd; } const totalTokens = Object.values(byModel).reduce((s, m) => s + m.tokens, 0); if (totalTokens === 0) return []; return Object.entries(byModel).map(([model, stats]) => ({ model, tokens: stats.tokens, requests: stats.requests, cost: stats.cost, percentage: Math.round((stats.tokens / totalTokens) * 100), })); } function KpiSkeleton() { return ( ); } function ChartSkeleton() { return ( ); } export default function DashboardPage() { const [stats, setStats] = useState({ ...mockSummaryStats, totalUsers: 0, activeUsers: 0, totalRevenue: 0, monthlyRecurring: 0, totalTokensThisMonth: 0, totalRequestsThisMonth: 0, avgTokensPerUser: 0, newUsersThisMonth: 0, conversionRate: 0, churnRate: 0, }); const [dailyMetrics, setDailyMetrics] = useState([]); const [recentUsers, setRecentUsers] = useState([]); const [modelUsage, setModelUsage] = useState([]); const [revenue, setRevenue] = useState(null); const [loading, setLoading] = useState(true); const [refreshing, setRefreshing] = useState(false); const [lastUpdated, setLastUpdated] = useState(null); const fetchData = useCallback(async (isRefresh = false) => { if (isRefresh) setRefreshing(true); try { const [statsRes, usageRes, usersRes, revenueRes] = await Promise.allSettled([ apiGetDashboardStats(), apiGetUsage(30), apiListUsers(10), apiGetRevenueAnalytics(6), ]); if (statsRes.status === 'fulfilled' && statsRes.value.data) { setStats(prev => mergeApiStats(prev, statsRes.value.data!)); } if (usageRes.status === 'fulfilled' && usageRes.value.data?.records?.length) { const metrics = usageRecordsToDailyMetrics(usageRes.value.data.records); setDailyMetrics(metrics); setModelUsage(buildModelUsage(usageRes.value.data.records)); } if (usersRes.status === 'fulfilled' && usersRes.value.data?.users?.length) { setRecentUsers( usersRes.value.data.users .sort((a, b) => b.lastActive.localeCompare(a.lastActive)) .slice(0, 6) .map(u => ({ id: u.id, name: u.name, email: u.email, plan: u.plan as User['plan'], status: u.status as User['status'], createdAt: u.createdAt, lastActive: u.lastActive, totalTokensUsed: u.totalTokensUsed, totalRequests: u.totalRequests, monthlySpend: u.monthlySpend, })) ); } if (revenueRes.status === 'fulfilled' && revenueRes.value.data) { setRevenue(revenueRes.value.data); } setLastUpdated(new Date()); } finally { setLoading(false); setRefreshing(false); } }, []); useEffect(() => { fetchData(); const interval = setInterval(() => fetchData(), 60000); return () => clearInterval(interval); }, [fetchData]); const kpiCards = buildKpiCards(stats, revenue); return (
{/* Header */}
fetchData(true)} disabled={refreshing} > Refresh } />

Platform overview and key metrics {lastUpdated && ( · Updated {lastUpdated.toLocaleTimeString()} )}

{/* KPI Cards */} {loading ? (
{Array.from({ length: 6 }).map((_, i) => ( ))}
) : ( {kpiCards.map(card => ( {card.title}
{card.value}
{card.trend === 'up' && card.title !== 'Churn Rate' ? ( ) : ( )} {card.change} {card.subtitle}
))}
)} {/* Charts Row */} {loading ? (
) : (
{/* Active Users Chart */} Daily Active Users (30 days) {/* Revenue Chart */} Daily Revenue (30 days)
)} {/* Bottom Row: Model Usage + Recent Users */} {/* Model Usage */} Model Usage Breakdown {modelUsage.length === 0 ? (

No model usage data yet

Usage will appear once users start dictating

) : (
{modelUsage.map(m => (
{m.model} {formatNumber(m.tokens)} tokens · {formatCurrency(m.cost)}
))}
)} {/* Recent Users */} Recent Users {recentUsers.length === 0 ? (

No users yet

) : (
{recentUsers.map(user => (
{user.name .split(' ') .map(n => n[0]) .join('')}

{user.name}

{user.email}

{user.plan}
))}
)}
); }