feat(ui): EVENT LOG tab renders the real game log
Self-fetching EventLogPanel (MissionPanel pattern — refetches on each tab open). One terminal line per event: T<turn> // <MESSAGE>, newest first, with the event's local time as the timestamp; new_game lines render as system lines. Placeholder feed + now() removed from mission-control.
This commit is contained in:
@@ -0,0 +1,59 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import { Terminal, type TerminalLine } from '@/components/ui/terminal'
|
||||||
|
import type { EventDto } from '@/lib/game/events'
|
||||||
|
|
||||||
|
// EVENT LOG tab — live data. Fetches /api/events on mount (Radix Tabs unmounts inactive tabs, so it
|
||||||
|
// refetches whenever the tab is opened). One terminal line per event: T<turn> // <MESSAGE>, newest first.
|
||||||
|
|
||||||
|
export function EventLogPanel() {
|
||||||
|
const [lines, setLines] = useState<TerminalLine[] | null>(null)
|
||||||
|
const [error, setError] = useState<string | null>(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let cancelled = false
|
||||||
|
fetch('/api/events')
|
||||||
|
.then(async (res) => {
|
||||||
|
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
||||||
|
return (await res.json()) as EventDto[]
|
||||||
|
})
|
||||||
|
.then((events) => {
|
||||||
|
if (!cancelled) {
|
||||||
|
setLines(
|
||||||
|
events.map((e) => ({
|
||||||
|
type: e.type === 'new_game' ? 'system' : 'output',
|
||||||
|
text: `T${e.turn} // ${e.message.toUpperCase()}`,
|
||||||
|
timestamp: new Date(e.createdAt).toLocaleTimeString('en-GB', { hour12: false }),
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err: unknown) => {
|
||||||
|
if (!cancelled) setError(err instanceof Error ? err.message : String(err))
|
||||||
|
})
|
||||||
|
return () => {
|
||||||
|
cancelled = true
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<div style={{ border: '1px solid var(--border)', background: 'var(--surface)', padding: '0.75rem' }}>
|
||||||
|
<span style={{ fontSize: '0.62rem', color: 'var(--color-red)', letterSpacing: '0.08em' }}>{error.toUpperCase()}</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!lines) {
|
||||||
|
return (
|
||||||
|
<div style={{ border: '1px solid var(--border)', background: 'var(--surface)', padding: '0.75rem' }}>
|
||||||
|
<span style={{ fontSize: '0.62rem', color: 'var(--text-muted)', letterSpacing: '0.12em' }}>LOADING EVENT LOG…</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Terminal lines={lines} title="SHIP LOG" height="22rem" style={{ width: '100%' }} />
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -7,8 +7,8 @@ import { Progress } from '@/components/ui/progress'
|
|||||||
import { StatCard } from '@/components/ui/stat-card'
|
import { StatCard } from '@/components/ui/stat-card'
|
||||||
import { StatusGrid, type SystemEntry } from '@/components/ui/status-grid'
|
import { StatusGrid, type SystemEntry } from '@/components/ui/status-grid'
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||||
import { Terminal, type TerminalLine } from '@/components/ui/terminal'
|
|
||||||
import { CrewPanel } from '@/components/crew-panel'
|
import { CrewPanel } from '@/components/crew-panel'
|
||||||
|
import { EventLogPanel } from '@/components/event-log'
|
||||||
import { LocalSystemPanel } from '@/components/local-system'
|
import { LocalSystemPanel } from '@/components/local-system'
|
||||||
import { MissionPanel } from '@/components/missions'
|
import { MissionPanel } from '@/components/missions'
|
||||||
import { ShipSystemsPanel } from '@/components/ship-systems'
|
import { ShipSystemsPanel } from '@/components/ship-systems'
|
||||||
@@ -21,9 +21,8 @@ import type { TechDto } from '@/lib/game/tech'
|
|||||||
|
|
||||||
// Interface base: layout + visual treatment rebuilt from the scificn-ui Mission Control
|
// 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
|
// showcase (src/app/_pages/showcase/mission-control-content.tsx in baxy5/scificn-ui). The
|
||||||
// WORLD MAP, LOCAL SYSTEM, CREW, SHIP SYSTEMS, SKILL TREE and MISSIONS tabs render live data
|
// All seven tabs render live data (/api/world, /api/system, /api/crew, /api/ship, /api/tech,
|
||||||
// (/api/world, /api/system, /api/crew, /api/ship, /api/tech, /api/missions); the EVENT LOG tab
|
// /api/missions, /api/events). Single player, no auth, by design.
|
||||||
// keeps placeholder content until its own issue lands. Single player, no auth, by design.
|
|
||||||
|
|
||||||
function useNarrow(threshold = 900): boolean {
|
function useNarrow(threshold = 900): boolean {
|
||||||
const [narrow, setNarrow] = useState(
|
const [narrow, setNarrow] = useState(
|
||||||
@@ -37,10 +36,6 @@ function useNarrow(threshold = 900): boolean {
|
|||||||
return narrow
|
return narrow
|
||||||
}
|
}
|
||||||
|
|
||||||
function now(): string {
|
|
||||||
return new Date().toLocaleTimeString('en-GB', { hour12: false })
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Placeholder content (no game logic — real values land with the turn engine) ──
|
// ── Placeholder content (no game logic — real values land with the turn engine) ──
|
||||||
|
|
||||||
const encounters = [
|
const encounters = [
|
||||||
@@ -59,15 +54,6 @@ const shipData = [
|
|||||||
{ label: 'TURN', value: '12' },
|
{ label: 'TURN', value: '12' },
|
||||||
]
|
]
|
||||||
|
|
||||||
const logLines: TerminalLine[] = [
|
|
||||||
{ type: 'system', text: 'SPACEWANDERER // INITIALIZING SHIP SYSTEMS...', timestamp: now() },
|
|
||||||
{ type: 'output', text: 'HULL SCAN COMPLETE — INTEGRITY 87%', timestamp: now() },
|
|
||||||
{ type: 'output', text: 'REACTOR OUTPUT STABLE AT 94.2%', timestamp: now() },
|
|
||||||
{ type: 'output', text: 'CREW MANIFEST LOADED — 3 ACTIVE PERSONNEL', timestamp: now() },
|
|
||||||
{ type: 'warn', text: 'WEAPONS OFFLINE — MODULE NOT INSTALLED', timestamp: now() },
|
|
||||||
{ type: 'system', text: 'ALL SYSTEMS NOMINAL. AWAITING FIRST TURN.', timestamp: now() },
|
|
||||||
]
|
|
||||||
|
|
||||||
export function MissionControl() {
|
export function MissionControl() {
|
||||||
const narrow = useNarrow()
|
const narrow = useNarrow()
|
||||||
const [tick, setTick] = useState(0)
|
const [tick, setTick] = useState(0)
|
||||||
@@ -393,12 +379,7 @@ export function MissionControl() {
|
|||||||
|
|
||||||
{/* EVENT LOG TAB */}
|
{/* EVENT LOG TAB */}
|
||||||
<TabsContent value="event-log" style={{ margin: 0, padding: '1rem' }}>
|
<TabsContent value="event-log" style={{ margin: 0, padding: '1rem' }}>
|
||||||
<Terminal
|
<EventLogPanel />
|
||||||
lines={logLines}
|
|
||||||
title="SHIP LOG // PLACEHOLDER FEED"
|
|
||||||
height="22rem"
|
|
||||||
style={{ width: '100%' }}
|
|
||||||
/>
|
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</PanelContent>
|
</PanelContent>
|
||||||
|
|||||||
Reference in New Issue
Block a user