import type React from 'react'; import { useGameStore, type ActivePage } from '@/store'; import { formatMoney, formatNumber, formatPercent, formatDuration } from '@ai-tycoon/shared'; import type { Era } from '@ai-tycoon/shared'; import { TECH_TREE } from '@ai-tycoon/game-engine'; import { DollarSign, TrendingUp, TrendingDown, Minus, Cpu, Brain, Users, Shield, ChevronRight, Zap, Wifi, Sparkles, FlaskConical, Building2, HardDrive, Clock, } from 'lucide-react'; import { XAxis, YAxis, Tooltip, ResponsiveContainer, Area, AreaChart, Line, LineChart, } from 'recharts'; import { TutorialHint } from '@/components/game/TutorialHint'; const ERA_ORDER: Era[] = ['startup', 'scaleup', 'bigtech', 'agi']; function isEraAtLeast(current: Era, threshold: Era): boolean { return ERA_ORDER.indexOf(current) >= ERA_ORDER.indexOf(threshold); } export function DashboardPage() { const money = useGameStore((s) => s.economy.money); const revenuePerTick = useGameStore((s) => s.economy.revenuePerTick); const expensesPerTick = useGameStore((s) => s.economy.expensesPerTick); const financialHistory = useGameStore((s) => s.economy.financialHistory); const totalFlops = useGameStore((s) => s.infrastructure.totalFlops); const totalDCs = useGameStore((s) => s.infrastructure.totalDataCenterCount); const clusters = useGameStore((s) => s.infrastructure.clusters); const baseModels = useGameStore((s) => s.models.baseModels); const activePipelines = useGameStore((s) => s.models.activeTrainingPipelines); const subscribers = useGameStore((s) => s.market.consumerTiers.totalUsers); const satisfaction = useGameStore((s) => s.market.consumerTiers.satisfaction); const reputation = useGameStore((s) => s.reputation.score); const reputationHistory = useGameStore((s) => s.reputation.reputationHistory); const inferenceUtil = useGameStore((s) => s.compute.inferenceUtilization); const effectiveInferenceFlops = useGameStore((s) => s.compute.effectiveInferenceFlops); const trainingAllocation = useGameStore((s) => s.compute.trainingAllocation); const computeHistory = useGameStore((s) => s.compute.computeHistory); const totalUptime = useGameStore((s) => s.infrastructure.totalUptime); const modelFreshness = useGameStore((s) => s.market.obsolescence.playerModelFreshness); const era = useGameStore((s) => s.meta.currentEra); const activeResearch = useGameStore((s) => s.research.activeResearch); const tam = useGameStore((s) => s.market.tam); const competitors = useGameStore((s) => s.competitors.rivals); const netIncome = revenuePerTick - expensesPerTick; const scaleup = isEraAtLeast(era, 'scaleup'); const hasDeployedModel = baseModels.some(m => m.isDeployed); const bestDeployedCapability = baseModels .filter(m => m.isDeployed) .reduce((best, m) => Math.max(best, m.rawCapability), 0); const revenueTrend = (() => { if (financialHistory.length < 6) return 'neutral' as const; const recent = financialHistory[financialHistory.length - 1].revenue; const earlier = financialHistory[financialHistory.length - 6].revenue; if (recent > earlier * 1.01) return 'up' as const; if (recent < earlier * 0.99) return 'down' as const; return 'neutral' as const; })(); const repTrend = (() => { if (reputationHistory.length < 2) return 'neutral' as const; const last = reputationHistory[reputationHistory.length - 1].score; const prev = reputationHistory[reputationHistory.length - 2].score; if (last > prev) return 'up' as const; if (last < prev) return 'down' as const; return 'neutral' as const; })(); const constructingDCs = clusters.reduce((count, cluster) => { for (const campus of cluster.campuses) { for (const dc of campus.dataCenters) { if (dc.status === 'constructing') count++; } } return count; }, 0); const deployingRacks = clusters.reduce((count, cluster) => { for (const campus of cluster.campuses) { for (const dc of campus.dataCenters) { count += dc.deploymentCohorts.length; } } return count; }, 0); const navigate = useGameStore.getState().setActivePage; return (

Dashboard

{totalDCs === 0 && ( Welcome to AI Tycoon! Start by building a cluster in the Infrastructure tab, then add a campus and data center to deploy racks and train your first AI model. )} {totalDCs > 0 && baseModels.length === 0 && activePipelines.length === 0 && ( You have compute available! Head to the Models tab to allocate compute for training and start your first model. )} {baseModels.length > 0 && !baseModels.some(m => m.isDeployed) && ( Your model is trained! Deploy it from the Models tab to start serving customers and earning revenue. )} {/* Section 1: Stat Cards */}
= 0 ? '+' : ''}${formatMoney(netIncome)}/s`} trend={netIncome > 0 ? 'up' : netIncome < 0 ? 'down' : 'neutral'} color="text-green-400" onClick={() => navigate('finance')} /> {scaleup && ( navigate('finance')} /> )} 0.9 ? 'down' : inferenceUtil < 0.5 ? 'up' : 'neutral'} color="text-cyan-400" onClick={() => navigate('infrastructure')} /> p.status === 'active').length > 0 ? `Training: ${activePipelines.filter(p => p.status === 'active').length} active` : hasDeployedModel ? `Best: ${bestDeployedCapability.toFixed(1)}` : 'Idle' } color="text-purple-400" onClick={() => navigate('models')} /> {scaleup && ( navigate('market')} /> )} {scaleup && ( )}
{/* Section 2: Primary Charts */}

Compute: Capacity vs Demand

Capacity Demand
{computeHistory.length > 1 ? ( `${formatNumber(v)}`} tick={{ fontSize: 10, fill: '#64748b' }} axisLine={false} tickLine={false} /> { const label = name === 'tokensPerSecondCapacity' ? 'Capacity' : 'Demand'; return [`${formatNumber(value)} tok/s`, label]; }} /> ) : (
No compute data yet — deploy racks to start tracking
)}

Revenue vs Expenses

Revenue Expenses
{financialHistory.length > 1 ? ( formatMoney(v)} tick={{ fontSize: 10, fill: '#64748b' }} axisLine={false} tickLine={false} /> [formatMoney(value), name === 'revenue' ? 'Revenue' : 'Expenses']} /> ) : (
No financial data yet — start earning revenue
)}
{/* Section 3: System Health + Active Operations */}

System Status

0.9 ? 'bg-danger' : inferenceUtil > 0.7 ? 'bg-warning' : 'bg-success'} /> {scaleup && ( )} {scaleup && hasDeployedModel && ( )}

Active Operations

{/* Section 4: Secondary Charts (scaleup+) */} {scaleup && (

Subscribers Over Time

{(useGameStore.getState().market.subscriberHistory?.length ?? 0) > 1 ? ( formatNumber(v)} tick={{ fontSize: 10, fill: '#64748b' }} axisLine={false} tickLine={false} /> [formatNumber(value), 'Subscribers']} /> ) : (
No subscriber data yet
)}

Market Position

)} {/* Section 5: Competitor Snapshot (scaleup+) */} {scaleup && competitors.filter(r => r.status === 'active').length > 0 && (

Competitors

{competitors.filter(r => r.status === 'active').map(rival => (
{rival.name} {rival.archetype.replace('-', ' ')}
Capability {rival.estimatedCapability.toFixed(1)}
You: {bestDeployedCapability.toFixed(1)} Them: {rival.estimatedCapability.toFixed(1)}
Latest: {rival.latestModelName || 'None'}
))}
)} {totalDCs === 0 && (

Get Started

Build your first data center to start training AI models.

)}
); } function ActiveOperations({ pipelines, activeResearch, constructingDCs, deployingRacks, navigate, }: { pipelines: { modelName: string; status: string; currentStage: string; stages: Record }[]; activeResearch: { researchId: string; progressTicks: number; totalTicks: number } | null; constructingDCs: number; deployingRacks: number; navigate: (page: ActivePage) => void; }) { const activePipes = pipelines.filter(p => p.status === 'active'); const items: React.ReactNode[] = []; for (let i = 0; i < Math.min(2, activePipes.length); i++) { const p = activePipes[i]; const stage = p.stages[p.currentStage as keyof typeof p.stages]; const progress = stage ? stage.progressTicks / stage.totalTicks : 0; const eta = stage ? stage.totalTicks - stage.progressTicks : 0; items.push( navigate('models')} />, ); } if (activePipes.length > 2) { items.push( , ); } if (activeResearch) { const researchName = TECH_TREE.find(n => n.id === activeResearch.researchId)?.name ?? activeResearch.researchId; const progress = activeResearch.progressTicks / activeResearch.totalTicks; const eta = activeResearch.totalTicks - activeResearch.progressTicks; items.push( navigate('research')} />, ); } if (constructingDCs > 0) { items.push(
navigate('infrastructure')}> {constructingDCs} DC{constructingDCs > 1 ? 's' : ''} building
, ); } if (deployingRacks > 0) { items.push(
navigate('infrastructure')}> {deployingRacks} rack batch{deployingRacks > 1 ? 'es' : ''} deploying
, ); } if (items.length === 0) { return (
All systems idle
); } return
{items}
; } function OperationRow({ icon: Icon, label, detail, progress, eta, onClick, }: { icon: typeof Brain; label: string; detail: string; progress: number; eta: number; onClick: () => void; }) { return (
{label} {detail}
{formatDuration(eta)}
); } function MarketShareBar({ label, color, segment, }: { label: string; color: string; segment: { shares: { playerId: string; sharePercent: number }[] }; }) { const playerShare = segment.shares.find(s => s.playerId === 'player')?.sharePercent ?? 0; return (
{label} {formatPercent(playerShare / 100)}
); } function StatCard({ icon: Icon, label, value, subValue, trend, color, onClick, }: { icon: typeof DollarSign; label: string; value: string; subValue?: string; trend?: 'up' | 'down' | 'neutral'; color?: string; onClick?: () => void; }) { return (
{label}
{onClick && }
{value}
{subValue && (
{trend === 'up' && } {trend === 'down' && } {trend === 'neutral' && } {subValue}
)}
); } function StatusRow({ icon: Icon, label, value, bar, barColor, }: { icon: typeof Cpu; label: string; value: string; bar: number; barColor: string; }) { const severity = barColor.includes('danger') ? 'Critical' : barColor.includes('warning') ? 'Warning' : null; return (
{label} {severity && {severity}}
{value}
); }