feat(ui): Session-aware live timing enhancements

This commit is contained in:
2026-07-03 01:46:35 -04:00
parent c3ad7743e6
commit 8d5ab4d7a7
17 changed files with 311 additions and 102 deletions

View File

@@ -17,6 +17,7 @@ import {
import { countryAccent, countryDecal, countryFlag, formatGpDateRange } from '../lib/gpIdentity'
import type { Meeting, Session, Weekend, WeekendSession } from '../types'
import { PaddockBriefing } from '../components/PaddockBriefing'
import { Play, Activity, Calendar } from 'lucide-react'
type WeekendStatusKind = 'live' | 'current' | 'next' | 'recent' | 'fallback'
@@ -217,7 +218,7 @@ export function CommandCenterPage() {
)}
{focusMeeting && (
<section className="cc-weekend-band" data-testid="cc-focus">
<section className="cc-weekend-band ui-card glass-panel" data-testid="cc-focus">
<div className="cc-band-accent" aria-hidden="true" />
<div className="cc-band-body">
<div className="cc-band-row">
@@ -250,19 +251,19 @@ export function CommandCenterPage() {
<div className="cc-actions-row" data-testid="cc-actions">
<Link
to="/live"
className={`cc-pri-action ${liveActive ? 'is-live' : ''}`}
className={`cc-pri-action ui-card interactive ${liveActive ? 'is-live ui-cta-primary' : ''}`}
data-testid="cc-action-live"
>
<span className="cc-pri-label">Watch Live</span>
<span className="cc-pri-label" style={{ display: 'flex', alignItems: 'center', gap: '8px' }}><Play size={16} /> Watch Live</span>
<span className="cc-pri-meta mono">{liveActive ? 'Feed active' : 'Standby'}</span>
</Link>
<Link
to="/race-hub"
search={analysisSessionKey ? { session_key: analysisSessionKey } : {}}
className="cc-pri-action"
className="cc-pri-action ui-card interactive"
data-testid="cc-action-race-hub"
>
<span className="cc-pri-label">Open Analysis</span>
<span className="cc-pri-label" style={{ display: 'flex', alignItems: 'center', gap: '8px' }}><Activity size={16} /> Open Analysis</span>
<span className="cc-pri-meta mono">
{actionSession
? `${actionSession.session_name} · session ${actionSession.session_key}`
@@ -272,14 +273,14 @@ export function CommandCenterPage() {
{nextSession && sessionStartTime(nextSession) && (
<a
href="#cc-schedule"
className="cc-pri-action"
className="cc-pri-action ui-card interactive"
data-testid="cc-action-schedule"
onClick={(e) => {
e.preventDefault()
document.getElementById('cc-schedule')?.scrollIntoView({ behavior: 'smooth' })
}}
>
<span className="cc-pri-label">Schedule</span>
<span className="cc-pri-label" style={{ display: 'flex', alignItems: 'center', gap: '8px' }}><Calendar size={16} /> Schedule</span>
<span className="cc-pri-meta mono">
next: {nextSession.session_name}
</span>
@@ -299,13 +300,14 @@ export function CommandCenterPage() {
const status = classifySessionStatus(session, nowDate)
const isNext = nextSession?.session_key === session.session_key
const isCurrent = currentSession?.session_key === session.session_key
const isLive = isCurrent && liveActive
return (
<Link
key={session.session_key}
to="/race-hub"
search={{ session_key: session.session_key }}
className={`cc-session-card cc-status-${status}${isNext ? ' is-next' : ''}${
isCurrent ? ' is-current' : ''
className={`cc-session-card ui-card interactive cc-status-${status}${isNext ? ' is-next' : ''}${
isLive ? ' is-live glass-panel' : ''
}`}
data-testid={`cc-session-${session.session_key}`}
role="listitem"

View File

@@ -18,6 +18,7 @@ import { TimingTower } from '../components/live/TimingTower'
import { BattleChips } from '../components/live/BattleChips'
import { PinnedDrivers } from '../components/live/PinnedDrivers'
import { RaceControlFeed } from '../components/live/RaceControlFeed'
import { Radio } from 'lucide-react'
type StreamStatus = 'connecting' | 'connected' | 'disconnected' | 'error'
@@ -128,14 +129,15 @@ export function LiveTimingPage() {
)}
{!isLoading && !snapshot && (
<div className="empty-state" data-testid="live-empty">
<div className="live-empty-status">
<div className="empty-state ui-card glass-panel" style={{ padding: '40px', textAlign: 'center', marginTop: '20vh', maxWidth: '400px', marginLeft: 'auto', marginRight: 'auto' }} data-testid="live-empty">
<div className="live-empty-status" style={{ marginBottom: '16px' }}>
<span className={`live-conn live-conn-${streamStatus}`}>{streamStatus}</span>
</div>
<div className="empty-state-title">No live session active</div>
<div className="empty-state-desc">
No timing data in the current snapshot. The feed will update automatically when an F1 session goes live.
</div>
<Radio size={48} style={{ color: 'var(--text-3)', margin: '0 auto 16px auto', display: 'block' }} />
<h2 className="empty-state-title" style={{ fontSize: '20px', marginBottom: '8px' }}>No live session active</h2>
<p className="empty-state-desc" style={{ color: 'var(--text-2)' }}>
The telemetry feed is currently offline. <br /><br /> Check the <a href="/" style={{ color: 'var(--red)', textDecoration: 'underline' }}>Command Center</a> for the weekend schedule or explore historical data in the <a href="/race-hub" style={{ color: 'var(--red)', textDecoration: 'underline' }}>Race Hub</a>.
</p>
</div>
)}
@@ -158,6 +160,7 @@ export function LiveTimingPage() {
battleNumbers={inBattle}
pinned={pinned}
onTogglePin={handleTogglePin}
sessionType={snapshot.Session?.SessionType}
/>
</div>
<div className="live-rc-col">