ui: real SKILL TREE tab (research) + SHIP SYSTEMS buttons gated by researched techs
This commit is contained in:
@@ -3,8 +3,6 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Panel, PanelContent, PanelHeader, PanelTitle } from '@/components/ui/panel'
|
||||
import { Progress } from '@/components/ui/progress'
|
||||
import { StatCard } from '@/components/ui/stat-card'
|
||||
@@ -14,16 +12,18 @@ import { Terminal, type TerminalLine } from '@/components/ui/terminal'
|
||||
import { CrewPanel } from '@/components/crew-panel'
|
||||
import { LocalSystemPanel } from '@/components/local-system'
|
||||
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 and SHIP SYSTEMS tabs render live data (/api/world, /api/system,
|
||||
// /api/crew, /api/ship); the other tabs keep placeholder content until their own issues land.
|
||||
// Single player, no auth, by design.
|
||||
// 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.
|
||||
|
||||
function useNarrow(threshold = 900): boolean {
|
||||
const [narrow, setNarrow] = useState(
|
||||
@@ -43,13 +43,6 @@ function now(): string {
|
||||
|
||||
// ── Placeholder content (no game logic — real values land with the turn engine) ──
|
||||
|
||||
const skillCards = [
|
||||
{ name: 'ADVANCED FUSION CORE', unlocks: 'REACTOR LV 4 + OVERCLOCK', state: 'AVAILABLE' as const },
|
||||
{ name: 'HULL PLATING SCHEMATICS', unlocks: 'BUILD ARMOR BAY MODULES', state: 'AVAILABLE' as const },
|
||||
{ name: 'CREW TRAINING PROTOCOLS', unlocks: 'RETRAIN CREW TRAITS', state: 'LOCKED' as const },
|
||||
{ name: 'JUMP DRIVE TUNING', unlocks: 'SHORTER JUMP COOLDOWN', state: 'LOCKED' as const },
|
||||
]
|
||||
|
||||
const encounters = [
|
||||
{ name: 'DERELICT FREIGHTER', when: 'T-07:14', id: '#E-102' },
|
||||
{ name: 'PIRATE SKIRMISH', when: 'T-22:40', id: '#E-103' },
|
||||
@@ -82,6 +75,7 @@ export function MissionControl() {
|
||||
// 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<ShipState | null>(null)
|
||||
const [techs, setTechs] = useState<TechDto[] | null>(null)
|
||||
const [crewCount, setCrewCount] = useState<number | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
@@ -95,6 +89,15 @@ export function MissionControl() {
|
||||
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}`)
|
||||
@@ -116,6 +119,10 @@ export function MissionControl() {
|
||||
|
||||
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 (
|
||||
<div
|
||||
style={{
|
||||
@@ -371,41 +378,12 @@ export function MissionControl() {
|
||||
|
||||
{/* SHIP SYSTEMS TAB — live data from /api/ship (state owned by MissionControl) */}
|
||||
<TabsContent value="ship-systems" style={{ margin: 0, padding: '1rem' }}>
|
||||
<ShipSystemsPanel ship={ship} narrow={narrow} onChanged={setShip} />
|
||||
<ShipSystemsPanel ship={ship} narrow={narrow} onChanged={setShip} researchedSlugs={researchedSlugs} />
|
||||
</TabsContent>
|
||||
|
||||
{/* SKILL TREE TAB */}
|
||||
{/* SKILL TREE TAB — live data from /api/tech (state owned by MissionControl) */}
|
||||
<TabsContent value="skill-tree" style={{ margin: 0, padding: '1rem' }}>
|
||||
<div
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: narrow ? '1fr' : 'repeat(2, 1fr)',
|
||||
gap: '0.75rem',
|
||||
}}
|
||||
>
|
||||
{skillCards.map((skill) => (
|
||||
<Card key={skill.name}>
|
||||
<CardHeader>
|
||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||
<CardTitle>{skill.name}</CardTitle>
|
||||
<Badge variant={skill.state === 'AVAILABLE' ? 'ACTIVE' : 'OFFLINE'} className="ml-auto">
|
||||
{skill.state}
|
||||
</Badge>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p style={{ fontSize: '0.65rem', color: 'var(--text-muted)', letterSpacing: '0.08em' }}>
|
||||
UNLOCKS: {skill.unlocks}
|
||||
</p>
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
<Button variant="OUTLINE" size="SM">
|
||||
RESEARCH
|
||||
</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
<SkillTreePanel techs={techs} ship={ship} narrow={narrow} onResearched={setTechs} />
|
||||
</TabsContent>
|
||||
|
||||
{/* MISSIONS TAB */}
|
||||
|
||||
Reference in New Issue
Block a user