'use client' import { useState } from 'react' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { Card, CardContent, CardFooter, CardHeader, CardTitle } from '@/components/ui/card' import { BUILD_PART_COST, expandCost, totalQty, upgradeCost } from '@/lib/game/ship-costs' import type { ShipState, ShipSystemDto } from '@/lib/game/ship' import { buildGate, effectiveCapacityCap, effectiveLevelCap } from '@/lib/game/tech' // SHIP SYSTEMS tab — live data. The parent (MissionControl) owns the ship state so the left // column mirrors actions immediately; this panel applies each action's response directly via // onChanged (no polling). Buttons are disabled when unaffordable, capped or gated by an // unresearched tech, using the same pure helpers the API enforces (lib/game/ship-costs.ts + // lib/game/tech.ts). interface ShipSystemsPanelProps { ship: ShipState | null narrow?: boolean onChanged: (next: ShipState) => void researchedSlugs: Set // slugs of researched techs — raises caps, lifts build gates } export function ShipSystemsPanel({ ship, narrow, onChanged, researchedSlugs }: ShipSystemsPanelProps) { const [error, setError] = useState(null) const [busy, setBusy] = useState(false) // one action at a time — turn-based game if (!ship) { return (
LOADING SHIP SYSTEMS…
) } const resources = totalQty(ship.inventory, 'resource') const parts = totalQty(ship.inventory, 'part') async function act(url: string, body: unknown) { setBusy(true) setError(null) try { const res = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }) const data = (await res.json()) as ShipState & { error?: string } if (!res.ok) throw new Error(data.error ?? `HTTP ${res.status}`) onChanged(data) // response is the fresh ship state — apply directly } catch (err: unknown) { setError(err instanceof Error ? err.message : String(err)) } finally { setBusy(false) } } return (
{error && (
{error.toUpperCase()}
)}
{ship.systems.map((system) => ( ))}
) } function SystemCard({ system, resources, parts, busy, researchedSlugs, onAct, }: { system: ShipSystemDto resources: number parts: number busy: boolean researchedSlugs: Set onAct: (url: string, body: unknown) => void }) { const maxLevel = effectiveLevelCap(system.type, researchedSlugs) const maxCapacity = effectiveCapacityCap(system.type, researchedSlugs) const gate = buildGate(system.type, researchedSlugs) // name of the required tech, or null when ungated/researched const upgradeDisabled = busy || !system.built || system.level >= maxLevel || resources < upgradeCost(system.level) const expandDisabled = busy || !system.built || system.capacity >= maxCapacity || resources < expandCost(system.capacity) const buildDisabled = busy || system.built || gate !== null || parts < BUILD_PART_COST return (
{system.type} {system.built ? `LV ${system.level}` : 'OFFLINE'}

{system.built ? `LEVEL ${system.level} · CAPACITY ${system.capacity}` : 'NOT INSTALLED'}

{system.built ? `UPGRADE ${upgradeCost(system.level)}× RES · EXPAND ${expandCost(system.capacity)}× RES` : `BUILD ${BUILD_PART_COST}× PARTS`}

{!system.built && gate !== null && (

REQUIRES // {gate}

)}
{system.built ? ( <> ) : ( )}
) }