From 0992fc03e8279992a0964b85403f9a2618ad543a Mon Sep 17 00:00:00 2001 From: AmanTahiliani Date: Mon, 25 May 2026 02:45:02 -0400 Subject: [PATCH] Add data library UI --- documentations/refactor/README.md | 2 - .../cursor-phase-12-data-library-ui-prompt.md | 71 ---- frontend/src/components/CliCommands.tsx | 65 ++++ .../src/components/LocalDataNavigator.tsx | 59 +--- .../src/components/MeetingDetailPanel.tsx | 106 ++++++ frontend/src/components/Nav.tsx | 3 + .../src/components/SessionCoverageDots.tsx | 17 + frontend/src/components/SourceBadge.tsx | 28 ++ frontend/src/lib/coverage.ts | 76 +++++ frontend/src/pages/DataLibraryPage.tsx | 307 ++++++++++++++++++ frontend/src/router.tsx | 9 +- frontend/src/styles/app.css | 307 ++++++++++++++++++ frontend/src/test/CliCommands.test.tsx | 39 +++ frontend/src/test/DataLibraryPage.test.tsx | 144 ++++++++ frontend/src/test/coverage.test.ts | 69 ++++ tests/data-library.spec.ts | 34 ++ 16 files changed, 1214 insertions(+), 122 deletions(-) delete mode 100644 documentations/refactor/cursor-phase-12-data-library-ui-prompt.md create mode 100644 frontend/src/components/CliCommands.tsx create mode 100644 frontend/src/components/MeetingDetailPanel.tsx create mode 100644 frontend/src/components/SessionCoverageDots.tsx create mode 100644 frontend/src/components/SourceBadge.tsx create mode 100644 frontend/src/lib/coverage.ts create mode 100644 frontend/src/pages/DataLibraryPage.tsx create mode 100644 frontend/src/test/CliCommands.test.tsx create mode 100644 frontend/src/test/DataLibraryPage.test.tsx create mode 100644 frontend/src/test/coverage.test.ts create mode 100644 tests/data-library.spec.ts diff --git a/documentations/refactor/README.md b/documentations/refactor/README.md index 82ea209..cff6ad6 100644 --- a/documentations/refactor/README.md +++ b/documentations/refactor/README.md @@ -75,8 +75,6 @@ not implementation tickets yet. slice for making one command ingest a whole race weekend into the local DB. - [20 Phase 12 Data Library UI](20-phase-12-data-library-ui.md): frontend slice for showing local ingestion coverage and next CLI actions. -- [Cursor Phase 12 Prompt](cursor-phase-12-data-library-ui-prompt.md): current - handoff prompt for the next frontend phase. ## External References diff --git a/documentations/refactor/cursor-phase-12-data-library-ui-prompt.md b/documentations/refactor/cursor-phase-12-data-library-ui-prompt.md deleted file mode 100644 index 496a163..0000000 --- a/documentations/refactor/cursor-phase-12-data-library-ui-prompt.md +++ /dev/null @@ -1,71 +0,0 @@ -# Prompt For Cursor: Phase 12 Data Library UI - -You are working in the `box-box` repository on Phase 12. Build a practical Web -UI surface for inspecting local data coverage and ingestion status. - -## Read First - -Open these files first: - -- `documentations/refactor/20-phase-12-data-library-ui.md` -- `frontend/src/components/LocalDataNavigator.tsx` -- `frontend/src/pages/RaceHubPage.tsx` -- `frontend/src/api.ts` -- `frontend/src/types.ts` -- `frontend/src/main.tsx` -- `frontend/src/styles/app.css` -- `tests/race-hub.spec.ts` - -Only open older docs if you are blocked. - -## Goal - -Let a user inspect what is in the local database and understand what is missing -without needing to open every Race Hub session manually. - -## APIs Available - -- `GET /api/v1/seasons` -- `GET /api/v1/meetings?year=&source=local` -- `GET /api/v1/weekend?meeting_key=` -- `GET /api/v1/race-hub?session_key=` - -Do not fetch OpenF1 from React. - -## Work To Do - -1. Add a Data Library route or reachable section in the React app. -2. Show local years/meetings/sessions and dataset coverage. -3. Make partial vs complete sessions visually clear. -4. Include copyable/reference CLI commands, such as: - - `box-box --ingest-year 2025` - - `box-box --ingest-meeting ` - - `box-box --ingest-session ` -5. Preserve direct Race Hub navigation. -6. Add focused frontend tests and update e2e if stable. - -## Design Notes - -- Keep it operational and table-like. -- Avoid decorative cards and generic dashboard clutter. -- Use the existing dataset/status visual language where possible. -- Mobile should remain usable. - -## Verification - -Run: - -```bash -cd frontend && npm test -- --run -cd frontend && npm run build -npm run test:e2e -``` - -## Report Back - -Summarize: - -- files changed; -- Data Library behavior added; -- tests run and results; -- follow-up API or design gaps. diff --git a/frontend/src/components/CliCommands.tsx b/frontend/src/components/CliCommands.tsx new file mode 100644 index 0000000..65bb68f --- /dev/null +++ b/frontend/src/components/CliCommands.tsx @@ -0,0 +1,65 @@ +import { useState } from 'react' + +interface Command { + comment?: string + cmd: string +} + +interface Props { + commands: Command[] +} + +export function CliCommands({ commands }: Props) { + return ( +
+ {commands.map(({ comment, cmd }, i) => ( +
+ {comment &&
{comment}
} + + {i < commands.length - 1 &&
} +
+ ))} +
+ ) +} + +function CliCommandLine({ cmd }: { cmd: string }) { + const [copied, setCopied] = useState(false) + + async function handleCopy() { + try { + await navigator.clipboard.writeText(cmd) + setCopied(true) + window.setTimeout(() => setCopied(false), 1500) + } catch { + // clipboard may be unavailable in tests + } + } + + return ( +
+ {cmd} + +
+ ) +} + +export function ingestYearCommands(year: number): Command[] { + return [ + { comment: '# Ingest all meetings for a season', cmd: `box-box --ingest-year ${year}` }, + { comment: '# Preview without downloading', cmd: `box-box --ingest-year ${year} --dry-run` }, + ] +} + +export function ingestMeetingCommands(meetingKey: number): Command[] { + return [ + { comment: '# Full weekend ingest (all sessions)', cmd: `box-box --ingest-meeting ${meetingKey}` }, + { comment: '# Preview without downloading', cmd: `box-box --ingest-meeting ${meetingKey} --dry-run` }, + ] +} + +export function ingestSessionCommands(sessionKey: number): Command[] { + return [{ comment: '# Race Hub datasets for one session', cmd: `box-box --ingest-session ${sessionKey}` }] +} diff --git a/frontend/src/components/LocalDataNavigator.tsx b/frontend/src/components/LocalDataNavigator.tsx index 89f2815..e70a94d 100644 --- a/frontend/src/components/LocalDataNavigator.tsx +++ b/frontend/src/components/LocalDataNavigator.tsx @@ -2,43 +2,10 @@ import { useEffect, useState } from 'react' import { useQuery } from '@tanstack/react-query' import { useNavigate } from '@tanstack/react-router' import { fetchLocalMeetings, fetchSeasons, fetchWeekend } from '../api' -import type { DatasetInfo, Meeting, WeekendSession } from '../types' - -const RACE_HUB_DATASETS = [ - 'meeting', - 'session', - 'drivers', - 'results', - 'starting_grid', - 'stints', - 'pit_stops', - 'positions', - 'race_control', - 'weather', - 'laps', -] as const - -export function countRaceHubDatasets(datasets: Record): { available: number; total: number } { - const total = RACE_HUB_DATASETS.length - const available = RACE_HUB_DATASETS.filter((key) => datasets[key]?.status === 'available').length - return { available, total } -} - -export function formatCoverageHint(datasets: Record): string { - const { available, total } = countRaceHubDatasets(datasets) - return `${available}/${total}` -} - -function sourceBadge(source: WeekendSession['source']) { - switch (source) { - case 'local': - return Local - case 'partial': - return Partial - default: - return None - } -} +import { formatCoverageHint } from '../lib/coverage' +import { SourceBadge } from './SourceBadge' +import { SessionCoverageDots } from './SessionCoverageDots' +import type { Meeting, WeekendSession } from '../types' function formatMeetingDates(meeting: Meeting): string { const start = meeting.date_start?.slice(0, 10) @@ -47,6 +14,10 @@ function formatMeetingDates(meeting: Meeting): string { return start || end || '—' } +function sessionSourceBadge(source: WeekendSession['source']) { + return +} + interface Props { onSelectSession?: (sessionKey: number) => void } @@ -269,7 +240,7 @@ export function LocalDataNavigator({ onSelectSession }: Props) { - {sourceBadge(source)} + {sessionSourceBadge(source)}
) diff --git a/frontend/src/components/SessionCoverageDots.tsx b/frontend/src/components/SessionCoverageDots.tsx new file mode 100644 index 0000000..8cd77e3 --- /dev/null +++ b/frontend/src/components/SessionCoverageDots.tsx @@ -0,0 +1,17 @@ +import { RACE_HUB_DATASETS } from '../lib/coverage' +import type { DatasetInfo } from '../types' + +interface Props { + datasets: Record +} + +export function SessionCoverageDots({ datasets }: Props) { + return ( +