mirror of
https://github.com/AmanTahiliani/box-box.git
synced 2026-08-07 11:54:59 -04:00
feat(#89): recut weekend command center
This commit is contained in:
@@ -178,6 +178,16 @@ export function CommandCenterPage() {
|
||||
|
||||
const lastRacePodium = lastRaceHubQuery.data?.results ?? []
|
||||
const lastRaceName = champHub?.last_race ?? lastPastMeeting?.meeting_name ?? ''
|
||||
const focusNarrative =
|
||||
heroStateKind === 'between'
|
||||
? lastRaceName
|
||||
? `${lastRaceName} is in the archive. The next chapter begins at ${focusMeeting?.circuit_short_name || focusMeeting?.meeting_name || 'the next round'}.`
|
||||
: 'The season is between race weekends. Use the timeline to revisit a completed round or look ahead.'
|
||||
: currentSession
|
||||
? `${currentSession.session_name} is the active chapter of this weekend. Session detail remains available as it lands locally.`
|
||||
: nextSession
|
||||
? `${nextSession.session_name} is next on the timetable. The weekend rail keeps every session and its local analysis in reach.`
|
||||
: 'The weekend timetable is ready to explore.'
|
||||
|
||||
if (seasonsQuery.isLoading) {
|
||||
return <div className="page loading-state">loading command center…</div>
|
||||
@@ -226,7 +236,7 @@ export function CommandCenterPage() {
|
||||
<div className="cc-topbar">
|
||||
<span className="cc-topbar-label mono">box-box · command center</span>
|
||||
<span className="cc-topbar-meta mono">
|
||||
{latestSeason} season · {meetingStats.full}/{meetingStats.total || 0} weekends full
|
||||
{latestSeason} season · weekend desk
|
||||
</span>
|
||||
<span className="cc-live-pill" data-testid="cc-live-status">
|
||||
<span className={`cc-live-dot ${liveActive ? 'live' : ''}`} />
|
||||
@@ -262,6 +272,26 @@ export function CommandCenterPage() {
|
||||
/>
|
||||
)}
|
||||
|
||||
{focusMeeting && (
|
||||
<section className="cc-context" data-testid="cc-circuit-context">
|
||||
<div className="cc-context-decal mono" aria-hidden="true">
|
||||
{countryDecal(focusMeeting)}
|
||||
</div>
|
||||
<div className="cc-context-copy">
|
||||
<span className="cc-context-kicker mono">Circuit context</span>
|
||||
<h2>{focusMeeting.circuit_short_name || focusMeeting.meeting_name}</h2>
|
||||
<p>{focusNarrative}</p>
|
||||
</div>
|
||||
<div className="cc-context-meta mono">
|
||||
<span>{focusMeeting.location || focusMeeting.country_name}</span>
|
||||
<span>{formatGpDateRange(focusMeeting)}</span>
|
||||
<Link to="/preview" className="cc-context-preview">
|
||||
Weekend preview →
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
<div className="cc-dashboard-grid">
|
||||
<div className="cc-dashboard-main">
|
||||
<div
|
||||
@@ -298,7 +328,9 @@ export function CommandCenterPage() {
|
||||
<div className="cc-calendar-accent" aria-hidden="true" />
|
||||
<div className="cc-calendar-top mono">
|
||||
<span className="cc-calendar-round">R{String(index + 1).padStart(2, '0')}</span>
|
||||
<span className={`cc-cov-dot cc-cov-${weekend?.source ?? 'none'}`} aria-hidden="true" />
|
||||
<span className={`cc-calendar-status cc-calendar-status-${status}`}>
|
||||
{status === 'past' ? 'Archive' : status === 'focus' ? 'Now' : 'Ahead'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="cc-calendar-id">
|
||||
{countryFlag(meeting) && <span className="cc-calendar-flag">{countryFlag(meeting)}</span>}
|
||||
@@ -404,9 +436,9 @@ export function CommandCenterPage() {
|
||||
</div>
|
||||
<div className="cc-session-name">{session.session_name}</div>
|
||||
<div className="cc-session-time mono">{formatSessionScheduleTime(session.date_start)}</div>
|
||||
<div className="cc-session-cov mono">
|
||||
<div className="cc-session-cov mono" title={formatCoverageHint(datasets)}>
|
||||
<span className={`cc-cov-dot cc-cov-${source}`} aria-hidden="true" />
|
||||
{formatCoverageHint(datasets)}
|
||||
{source === 'local' ? 'Analysis ready' : formatCoverageHint(datasets)}
|
||||
</div>
|
||||
</Link>
|
||||
)
|
||||
@@ -416,6 +448,10 @@ export function CommandCenterPage() {
|
||||
)}
|
||||
|
||||
<PaddockBriefing />
|
||||
<div className="cc-data-note mono" data-testid="cc-data-note">
|
||||
<span className="cc-cov-dot cc-cov-local" aria-hidden="true" />
|
||||
{meetingStats.full}/{meetingStats.total || 0} locally complete · availability is shown per session
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -448,4 +484,3 @@ function FormSparkline({ form, color }: { form: number[], color: string }) {
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -4764,3 +4764,137 @@ a { color: inherit; text-decoration: none; }
|
||||
background: #f82f34;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
/* Weekend recut: put the active circuit and season journey ahead of data plumbing. */
|
||||
.cc-context {
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(84px, 0.3fr) minmax(0, 1fr) auto;
|
||||
gap: var(--s5);
|
||||
align-items: center;
|
||||
min-height: 112px;
|
||||
padding: var(--s5) var(--s6);
|
||||
overflow: hidden;
|
||||
border-top: 1px solid var(--border);
|
||||
border-bottom: 1px solid var(--border);
|
||||
background:
|
||||
linear-gradient(90deg, color-mix(in srgb, var(--gp-accent) 9%, transparent), transparent 46%),
|
||||
var(--surface);
|
||||
}
|
||||
|
||||
.cc-context-decal {
|
||||
color: color-mix(in srgb, var(--gp-accent) 58%, var(--text));
|
||||
font-size: clamp(38px, 6vw, 72px);
|
||||
font-weight: 800;
|
||||
letter-spacing: -0.08em;
|
||||
line-height: 0.75;
|
||||
opacity: 0.72;
|
||||
}
|
||||
|
||||
.cc-context-copy h2 {
|
||||
font-size: 18px;
|
||||
line-height: 1.15;
|
||||
}
|
||||
|
||||
.cc-context-kicker {
|
||||
display: block;
|
||||
margin-bottom: var(--s2);
|
||||
color: var(--text-3);
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.13em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.cc-context-copy p {
|
||||
max-width: 64ch;
|
||||
margin-top: var(--s2);
|
||||
color: var(--text-2);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.cc-context-meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: var(--s2);
|
||||
color: var(--text-3);
|
||||
font-size: 10px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.cc-context-preview {
|
||||
margin-top: var(--s2);
|
||||
color: var(--text-2);
|
||||
font-weight: 700;
|
||||
}
|
||||
.cc-context-preview:hover { color: var(--text); }
|
||||
|
||||
.cc-calendar-grid {
|
||||
display: flex;
|
||||
gap: 0;
|
||||
padding: var(--s3) 0 var(--s4);
|
||||
overflow-x: auto;
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
|
||||
.cc-calendar-card {
|
||||
min-width: 112px;
|
||||
min-height: 138px;
|
||||
flex: 1 0 112px;
|
||||
padding: var(--s3) var(--s4);
|
||||
border-radius: 0;
|
||||
border-width: 1px 1px 1px 0;
|
||||
background: transparent;
|
||||
backdrop-filter: none;
|
||||
-webkit-backdrop-filter: none;
|
||||
}
|
||||
.cc-calendar-card:first-child { border-left-width: 1px; }
|
||||
.cc-calendar-card:hover { transform: none; box-shadow: inset 0 -2px 0 var(--gp-card-accent); }
|
||||
.cc-calendar-card::before { display: none; }
|
||||
.cc-calendar-accent { top: auto; bottom: 0; height: 2px; opacity: 0.65; }
|
||||
.cc-calendar-past { filter: none; opacity: 0.62; }
|
||||
.cc-calendar-past:hover { filter: none; opacity: 1; }
|
||||
.cc-calendar-focus { background: color-mix(in srgb, var(--gp-card-accent) 10%, transparent); }
|
||||
.cc-calendar-decal { font-size: 60px; bottom: -5px; }
|
||||
.cc-calendar-status {
|
||||
font-size: 9px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
color: var(--text-3);
|
||||
}
|
||||
.cc-calendar-status-focus { color: var(--gp-card-accent); }
|
||||
.cc-calendar-status-future { color: var(--text-2); }
|
||||
|
||||
.cc-data-note {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--s2);
|
||||
padding-top: var(--s4);
|
||||
border-top: 1px solid var(--border);
|
||||
color: var(--text-3);
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
@media (max-width: 899px) {
|
||||
.cc-dashboard-grid { gap: var(--s5); }
|
||||
.cc-dashboard-sidebar { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s5); }
|
||||
.cc-briefing, .cc-data-note { grid-column: 1 / -1; }
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.cc-context { grid-template-columns: 1fr auto; gap: var(--s4); padding: var(--s4); }
|
||||
.cc-context-decal { display: none; }
|
||||
.cc-context-meta { align-items: flex-start; grid-column: 1 / -1; text-align: left; }
|
||||
.cc-context-copy h2 { font-size: 17px; }
|
||||
.cc-context-copy p { font-size: 11px; }
|
||||
.cc-dashboard-sidebar { display: flex; }
|
||||
.cc-calendar-grid { margin: 0 calc(var(--s4) * -1); padding-left: var(--s4); padding-right: var(--s4); }
|
||||
.cc-calendar-card { min-width: 104px; flex-basis: 104px; }
|
||||
.cc-calendar-circuit { display: none; }
|
||||
.cc-champ-snapshot { order: 2; }
|
||||
.cc-schedule { order: 1; }
|
||||
.cc-briefing { order: 3; margin-top: var(--s4); }
|
||||
.cc-data-note { order: 4; }
|
||||
}
|
||||
|
||||
@@ -189,6 +189,9 @@ describe('CommandCenterPage', () => {
|
||||
expect(screen.getByTestId('cc-focus')).toHaveTextContent('Monaco')
|
||||
expect(screen.getByTestId('cc-season-calendar')).toHaveTextContent('Season Calendar')
|
||||
expect(screen.getByTestId('cc-calendar-1229')).toHaveTextContent('R01')
|
||||
expect(screen.getByTestId('cc-circuit-context')).toHaveTextContent('Circuit context')
|
||||
expect(screen.getByTestId('cc-circuit-context')).toHaveTextContent('Weekend preview')
|
||||
expect(screen.getByTestId('cc-data-note')).toHaveTextContent('availability is shown per session')
|
||||
expect(screen.getByText('No live session')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('hero-last-race-link')).toHaveTextContent('Monaco')
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user