feat(ui): active-tasks strip bottom-left — active first, pending dimmed (#44)
This commit is contained in:
@@ -16,6 +16,7 @@ import { MissionPanel } from '@/components/missions'
|
||||
import { NextTurnButton } from '@/components/next-turn-button'
|
||||
import { ShipSystemsPanel } from '@/components/ship-systems'
|
||||
import { SkillTreePanel } from '@/components/skill-tree'
|
||||
import { TaskStrip } from '@/components/task-strip'
|
||||
import { WorldMapPanel } from '@/components/world-map'
|
||||
import { CREW_CAPACITY } from '@/lib/game/ship-costs'
|
||||
import type { CrewState, SystemState } from '@/lib/game/save'
|
||||
@@ -562,6 +563,12 @@ export function MissionControl() {
|
||||
stays visible on every tab (Radix unmounts inactive tabs — the button only refreshes
|
||||
the snapshots this shell owns) */}
|
||||
<NextTurnButton turn={ship?.turn ?? null} onTicked={refreshAll} />
|
||||
|
||||
{/* #44 — the active-tasks strip: fixed bottom-left (same mount convention as the button —
|
||||
outside the tab grid so it stays visible on every tab). Data: the shell's system
|
||||
snapshot (JC-15) — refreshAll re-runs /api/system on every turn, so it updates on
|
||||
every shell refresh with no fetch of its own. */}
|
||||
<TaskStrip tasks={system?.tasks ?? []} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
'use client'
|
||||
|
||||
import type { QueueDto } from '@/lib/game/save'
|
||||
|
||||
// TASK STRIP — the save's task queue, fixed bottom-left next to the NEXT TURN button (#44).
|
||||
// The array arrives pre-ordered from the snapshot builder (Task 6: ACTIVE block, then PENDING
|
||||
// block, each by (queuedAtTurn, id)) — the strip renders it as-is, dimming the PENDING rows,
|
||||
// and never fetches or re-sorts. Empty queue → null, no box at all. The shell re-renders it on
|
||||
// every refreshAll (Task 7) — a pure display of the array it is handed.
|
||||
|
||||
export function TaskStrip({ tasks }: { tasks: QueueDto[] }) {
|
||||
if (tasks.length === 0) return null
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
position: 'fixed',
|
||||
left: '1rem',
|
||||
bottom: '1rem',
|
||||
zIndex: 60, // above the header/footer's 50
|
||||
background: 'var(--surface)',
|
||||
border: '1px solid var(--border)',
|
||||
padding: '0.5rem 0.75rem',
|
||||
maxWidth: '26rem',
|
||||
}}
|
||||
>
|
||||
<span style={{ display: 'block', fontSize: '0.6rem', color: 'var(--text-muted)', letterSpacing: '0.12em', marginBottom: '0.3rem' }}>
|
||||
ACTIVE TASKS
|
||||
</span>
|
||||
{tasks.map((t) => (
|
||||
<div
|
||||
key={t.id}
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
gap: '0.75rem',
|
||||
padding: '0.2rem 0',
|
||||
opacity: t.status === 'PENDING' ? 0.45 : 1, // dim, don't reorder — the array is the order
|
||||
}}
|
||||
>
|
||||
<span style={{ fontSize: '0.62rem', color: 'var(--text-secondary)', letterSpacing: '0.06em' }}>
|
||||
{t.type} // {t.targetLabel}
|
||||
</span>
|
||||
<span style={{ fontSize: '0.62rem', color: 'var(--text-muted)', letterSpacing: '0.04em', whiteSpace: 'nowrap' }}>
|
||||
{t.leadName}
|
||||
{t.helperNames.length > 0 ? ` +${t.helperNames.join(' +')}` : ''}
|
||||
{' — '}
|
||||
{t.status === 'ACTIVE' ? `${t.remaining} turn(s) left` : 'queued'}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user