'use client' import { useEffect, useState } from 'react' import { Badge } from '@/components/ui/badge' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import type { CrewDto, CrewState } from '@/lib/game/save' // CREW tab — the ship's roster: starting crew plus members found while exploring. Fetches // /api/crew on mount; Radix unmounts inactive tabs, so opening this tab always refetches // (no polling needed — turn-based game). Display-only for now: no dismiss/assign actions yet. // Spec §8: the three combat traits that carry no duration effect yet — their effect lands // with the encounters iteration, so the trait line labels them encounter-ready (STANDBY). // The label is additive — the original name always appears in the text unchanged. const STANDBY_TRAITS = new Set(['SHARP SHOOTER', 'SHORT FUSE', 'JINXED IN A DOGFIGHT']) const standbyLabel = (trait: string) => (STANDBY_TRAITS.has(trait) ? `${trait} (STANDBY — encounter-ready)` : trait) interface CrewPanelProps { narrow?: boolean } export function CrewPanel({ narrow }: CrewPanelProps) { const [roster, setRoster] = useState(null) const [error, setError] = useState(null) useEffect(() => { let cancelled = false fetch('/api/crew') .then(async (res) => { if (!res.ok) throw new Error(`HTTP ${res.status}`) return (await res.json()) as CrewState // { crew, tasks } — the endpoint wraps the roster (JC-7) }) .then((r) => { if (!cancelled) setRoster(r.crew) }) .catch((err: unknown) => { if (!cancelled) setError(err instanceof Error ? err.message : String(err)) }) return () => { cancelled = true } }, []) if (error && !roster) { return (
CREW MANIFEST OFFLINE // {error.toUpperCase()}
) } if (!roster) { return (
LOADING CREW MANIFEST…
) } return (
{roster.map((member) => (
{member.name} ACTIVE

{member.role}

{/* The member's four skill levels, fixed order (JC-27) — one `·`-separated line. */}

{member.skills.map((s) => `${s.skill} L${s.level}`).join(' · ')}

{/* The title only exists at L3+ (SENIOR) / L5 (MASTER) — titleFor returns null below. */} {member.title !== null && (

{member.title}

)}
+ {standbyLabel(member.traitGood)}
- {standbyLabel(member.traitBad)}

ORIGIN // {member.origin.toUpperCase()}

))}
) }