'use client' import { useEffect, useState } from 'react' import { Badge } from '@/components/ui/badge' import { Panel, PanelContent, PanelHeader, PanelTitle } from '@/components/ui/panel' import { Progress } from '@/components/ui/progress' import { StatCard } from '@/components/ui/stat-card' import { StatusGrid, type SystemEntry } from '@/components/ui/status-grid' import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' import { Terminal, type TerminalLine } from '@/components/ui/terminal' import { CrewPanel } from '@/components/crew-panel' import { LocalSystemPanel } from '@/components/local-system' import { MissionPanel } from '@/components/missions' import { ShipSystemsPanel } from '@/components/ship-systems' import { SkillTreePanel } from '@/components/skill-tree' import { WorldMapPanel } from '@/components/world-map' import { CREW_CAPACITY } from '@/lib/game/ship-costs' import type { CrewDto } from '@/lib/game/save' import type { ShipState } from '@/lib/game/ship' import type { TechDto } from '@/lib/game/tech' // Interface base: layout + visual treatment rebuilt from the scificn-ui Mission Control // showcase (src/app/_pages/showcase/mission-control-content.tsx in baxy5/scificn-ui). The // WORLD MAP, LOCAL SYSTEM, CREW, SHIP SYSTEMS, SKILL TREE and MISSIONS tabs render live data // (/api/world, /api/system, /api/crew, /api/ship, /api/tech, /api/missions); the EVENT LOG tab // keeps placeholder content until its own issue lands. Single player, no auth, by design. function useNarrow(threshold = 900): boolean { const [narrow, setNarrow] = useState( () => typeof window !== 'undefined' && window.innerWidth < threshold ) useEffect(() => { const handle = () => setNarrow(window.innerWidth < threshold) window.addEventListener('resize', handle, { passive: true }) return () => window.removeEventListener('resize', handle) }, [threshold]) return narrow } function now(): string { return new Date().toLocaleTimeString('en-GB', { hour12: false }) } // ── Placeholder content (no game logic — real values land with the turn engine) ── const encounters = [ { name: 'DERELICT FREIGHTER', when: 'T-07:14', id: '#E-102' }, { name: 'PIRATE SKIRMISH', when: 'T-22:40', id: '#E-103' }, { name: 'ANOMALY FIELD', when: 'T-48:12', id: '#E-104' }, ] const shipData = [ { label: 'TOTAL MODULES', value: '34' }, { label: 'HULL INTEGRITY', value: '87%' }, { label: 'REACTOR OUTPUT', value: '94.2%' }, { label: 'CREW ACTIVE', value: '3 / 6' }, { label: 'SECTOR', value: '7G // DRIFT LANE' }, { label: 'DRIFT VELOCITY', value: '0.4 KM/S' }, { label: 'TURN', value: '12' }, ] const logLines: TerminalLine[] = [ { type: 'system', text: 'SPACEWANDERER // INITIALIZING SHIP SYSTEMS...', timestamp: now() }, { type: 'output', text: 'HULL SCAN COMPLETE — INTEGRITY 87%', timestamp: now() }, { type: 'output', text: 'REACTOR OUTPUT STABLE AT 94.2%', timestamp: now() }, { type: 'output', text: 'CREW MANIFEST LOADED — 3 ACTIVE PERSONNEL', timestamp: now() }, { type: 'warn', text: 'WEAPONS OFFLINE — MODULE NOT INSTALLED', timestamp: now() }, { type: 'system', text: 'ALL SYSTEMS NOMINAL. AWAITING FIRST TURN.', timestamp: now() }, ] export function MissionControl() { const narrow = useNarrow() const [tick, setTick] = useState(0) // Ship state is owned here (not in the tab panel) so the left column mirrors ship actions // immediately; the SHIP SYSTEMS tab applies each action's response via setShip. Crew count // feeds the roster bar — refetched on page load, like the other panels. const [ship, setShip] = useState(null) const [techs, setTechs] = useState(null) const [crewCount, setCrewCount] = useState(null) useEffect(() => { let cancelled = false fetch('/api/ship') .then(async (res) => { if (!res.ok) throw new Error(`HTTP ${res.status}`) return (await res.json()) as ShipState }) .then((s) => { if (!cancelled) setShip(s) }) .catch(() => {}) // left column + tab keep their loading state on failure fetch('/api/tech') .then(async (res) => { if (!res.ok) throw new Error(`HTTP ${res.status}`) return (await res.json()) as TechDto[] }) .then((t) => { if (!cancelled) setTechs(t) }) .catch(() => {}) fetch('/api/crew') .then(async (res) => { if (!res.ok) throw new Error(`HTTP ${res.status}`) return (await res.json()) as CrewDto[] }) .then((c) => { if (!cancelled) setCrewCount(c.length) }) .catch(() => {}) return () => { cancelled = true } }, []) useEffect(() => { const id = setInterval(() => setTick((t) => t + 1), 1000) return () => clearInterval(id) }, []) const timeStr = new Date().toLocaleTimeString('en-GB', { hour12: false }) // Slugs of the researched techs — drives the effective caps + build gates in the SHIP SYSTEMS // tab via the same pure helpers the API enforces (lib/game/tech.ts). const researchedSlugs = new Set((techs ?? []).filter((t) => t.state === 'RESEARCHED').map((t) => t.slug)) return (
{/* ── HEADER ── */}
◄ SHIP │

SPACEWANDERER

{!narrow && ( // SHIP STATUS // CREW MANIFEST )}
{!narrow && ( SHIP NOMINAL )} SECTOR SCAN {timeStr}
{/* ── BODY ── */}
{/* STAT CARDS — placeholder ship readouts */}
{/* 3-COLUMN GRID */}
{/* ── LEFT: SHIP SYSTEMS + VESSEL METRICS (mirrors the SHIP SYSTEMS tab) ── */}
SHIP SYSTEMS
s.built) ? 'ACTIVE' : 'OFFLINE'}> {ship ? `${ship.systems.filter((s) => s.built).length} ONLINE` : '—'}
{ship ? ( ({ name: s.type, status: s.built ? ('ACTIVE' as const) : ('OFFLINE' as const), detail: s.built ? `LV ${s.level} · CAP ${s.capacity}` : undefined, }))} /> ) : (
LOADING…
)}
VESSEL METRICS {ship ? ( // Bars driven by real values: hull/reactor from system levels (level × 20, capped), // crew roster vs the display cap.
s.type === 'HULL')?.level ?? 0) * 20)} /> s.type === 'REACTOR')?.level ?? 0) * 20)} />
) : ( LOADING… )}
{/* ── CENTER: GAME TEMPLATE TABS (visual only — no handlers, placeholder data) ── */}
OPERATIONS // SHIP COMMAND
SYS NOMINAL
WORLD MAP LOCAL SYSTEM CREW SHIP SYSTEMS SKILL TREE MISSIONS EVENT LOG {/* WORLD MAP TAB — live data from /api/world */} {/* LOCAL SYSTEM TAB — live data from /api/system */} {/* CREW TAB — live data from /api/crew */} {/* SHIP SYSTEMS TAB — live data from /api/ship (state owned by MissionControl) */} {/* SKILL TREE TAB — live data from /api/tech (state owned by MissionControl) */} {/* MISSIONS TAB — live data from /api/missions */} {/* EVENT LOG TAB */}
{/* ── RIGHT: UPCOMING ENCOUNTERS + SHIP DATA ── */}
UPCOMING ENCOUNTERS
{encounters.length} QUEUED
{encounters.map((enc, i) => { const isNext = i === 0 const isLast = i === encounters.length - 1 return (
{enc.name}
{enc.when} // {enc.id}
{isNext ? 'NEXT' : 'SCHED'}
) })}
SHIP DATA
{shipData.map((item, i) => (
{item.label} {item.value}
))}
{/* ── FOOTER ── */}
) }