'use client' import { useCallback, useEffect, useState } from 'react' import { NodeGraph } from '@/components/ui/node-graph' import type { WorldState } from '@/lib/game/save' // WORLD MAP tab — renders real world state from /api/world. Clicking a system adjacent to the // current one on the galactic chart jumps there (same POST /api/jump flow as before); the // current and unreachable systems are dimmed and not clickable. Turn-based: fetch on mount, // refetch via the jump response — no polling. interface WorldMapPanelProps { height?: number } export function WorldMapPanel({ height = 380 }: WorldMapPanelProps) { const [world, setWorld] = useState(null) const [error, setError] = useState(null) const [jumpingId, setJumpingId] = useState(null) useEffect(() => { let cancelled = false fetch('/api/world') .then(async (res) => { if (!res.ok) throw new Error(`HTTP ${res.status}`) return (await res.json()) as WorldState }) .then((state) => { if (!cancelled) setWorld(state) }) .catch((err: unknown) => { if (!cancelled) setError(err instanceof Error ? err.message : String(err)) }) return () => { cancelled = true } }, []) const jump = useCallback(async (systemId: number) => { setJumpingId(systemId) try { const res = await fetch('/api/jump', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ systemId }), }) const body = (await res.json()) as WorldState & { error?: string } if (!res.ok) throw new Error(body.error ?? `HTTP ${res.status}`) setWorld(body) // fresh state straight from the response — no refetch needed } catch (err: unknown) { setError(err instanceof Error ? err.message : String(err)) } finally { setJumpingId(null) } }, []) if (error && !world) { return (
CHART OFFLINE // {error.toUpperCase()}
) } if (!world) { return (
SCANNING CHART…
) } const current = world.systems.find((s) => s.current)! // Systems one jump away — the only clickable chart fields (the same set as the old JUMP ROUTES list). const reachableIds = new Set( world.edges.filter(([a, b]) => a === current.id || b === current.id).map(([a, b]) => (a === current.id ? b : a)) ) return (
{ const jumpable = !s.current && reachableIds.has(s.id) return { id: String(s.id), label: s.name, x: s.x, y: s.y, status: s.current ? 'ACTIVE' : jumpable ? 'NEUTRAL' : 'OFFLINE', sublabel: s.current ? 'CURRENT' : jumpable ? (s.discovered ? 'DISCOVERED // JUMP' : 'UNKNOWN // JUMP') : s.discovered ? 'DISCOVERED' : 'UNKNOWN', disabled: !jumpable || jumpingId !== null, // dimmed + not clickable; all locked while a jump is in flight } })} edges={world.edges.map(([a, b]) => ({ from: String(a), to: String(b) }))} variant="ACTIVE" title={`GALACTIC CHART // JUMP ROUTES — TURN ${world.turn}`} height={height} style={{ width: '100%' }} onNodeClick={(id) => void jump(Number(id))} /> {error && (
JUMP FAILED // {error.toUpperCase()}
)}
) }