feat(ui): MISSIONS tab renders the live mission board

Self-fetching MissionPanel (world-map pattern — refetches on each tab open,
accept responses applied directly). One card per contract: type title + state
badge, CONTRACT // #id line, objective, reward line, EXPLORE progress line.
ACCEPT enabled only when AVAILABLE and nothing else is ACTIVE. Placeholder
encounter content removed.
This commit is contained in:
2026-09-29 01:55:49 +00:00
parent 786a5ec001
commit 684a126a5b
2 changed files with 126 additions and 59 deletions
+6 -59
View File
@@ -2,7 +2,6 @@
import { useEffect, useState } from 'react'
import { Badge } from '@/components/ui/badge'
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
import { Panel, PanelContent, PanelHeader, PanelTitle } from '@/components/ui/panel'
import { Progress } from '@/components/ui/progress'
import { StatCard } from '@/components/ui/stat-card'
@@ -11,6 +10,7 @@ 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'
@@ -21,9 +21,9 @@ 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 and SKILL TREE tabs render live data (/api/world,
// /api/system, /api/crew, /api/ship, /api/tech); the other tabs keep placeholder content until
// their own issues land. Single player, no auth, by design.
// 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(
@@ -386,62 +386,9 @@ export function MissionControl() {
<SkillTreePanel techs={techs} ship={ship} narrow={narrow} onResearched={setTechs} />
</TabsContent>
{/* MISSIONS TAB */}
{/* MISSIONS TAB — live data from /api/missions */}
<TabsContent value="missions" style={{ margin: 0, padding: '1rem' }}>
<div
style={{ display: 'flex', flexDirection: 'column', gap: '0.75rem' }}
>
<Alert variant="STATUS">
<AlertTitle>ENCOUNTER #E-102 — DERELICT FREIGHTER</AlertTitle>
<AlertDescription>
Placeholder briefing. Real encounter resolution lands with the turn engine.
</AlertDescription>
</Alert>
<div
style={{
display: 'grid',
gridTemplateColumns: narrow ? '1fr' : '1fr 1fr',
gap: '0.625rem',
}}
>
{[
{ label: 'DESTINATION', value: 'SECTOR 7G // DRIFT LANE' },
{ label: 'ETA', value: 'T-07:14 (PLACEHOLDER)' },
{ label: 'THREAT LEVEL', value: 'LOW — UNMANNED' },
{ label: 'CARGO MANIFEST', value: 'UNKNOWN' },
].map((item) => (
<div
key={item.label}
style={{
padding: '0.75rem',
background: 'var(--surface)',
border: '1px solid var(--border)',
}}
>
<div
style={{
fontSize: '0.55rem',
color: 'var(--text-muted)',
letterSpacing: '0.12em',
marginBottom: '0.35rem',
}}
>
{item.label}
</div>
<div
style={{
fontSize: '0.8rem',
color: 'var(--text-secondary)',
letterSpacing: '0.04em',
}}
>
{item.value}
</div>
</div>
))}
</div>
</div>
<MissionPanel />
</TabsContent>
{/* EVENT LOG TAB */}