Files
gridpilot.gg/apps/website/templates/DashboardTemplate.tsx
Marc Mintel e04282d77e
Some checks failed
CI / lint-typecheck (pull_request) Failing after 10s
CI / tests (pull_request) Has been skipped
CI / contract-tests (pull_request) Has been skipped
CI / e2e-tests (pull_request) Has been skipped
CI / comment-pr (pull_request) Has been skipped
CI / commit-types (pull_request) Has been skipped
code quality
2026-01-27 17:36:39 +01:00

173 lines
7.2 KiB
TypeScript

import { DashboardKpiRow } from '@/components/dashboard/DashboardKpiRow';
import { RecentActivityTable, type ActivityItem } from '@/components/dashboard/RecentActivityTable';
import { TelemetryPanel } from '@/components/dashboard/TelemetryPanel';
import type { DashboardViewData } from '@/lib/view-data/DashboardViewData';
import { routes } from '@/lib/routing/RouteConfig';
import { useRouter } from 'next/navigation';
import { Box } from '@/ui/Box';
import { Button } from '@/ui/Button';
import { Grid } from '@/ui/Grid';
import { Stack } from '@/ui/Stack';
import { Text } from '@/ui/Text';
interface DashboardTemplateProps {
viewData: DashboardViewData;
onNavigateToRaces: () => void;
}
/**
* DashboardTemplate
*
* Redesigned as a "Telemetry Workspace" following the Precision Racing Minimal theme.
* Composes semantic dashboard components into a high-density data environment.
* Complies with architectural constraints by using UI primitives.
*/
export function DashboardTemplate({
viewData,
onNavigateToRaces,
}: DashboardTemplateProps) {
const router = useRouter();
const {
currentDriver,
nextRace,
upcomingRaces,
leagueStandings,
feedItems,
activeLeaguesCount,
hasLeagueStandings,
hasFeedItems,
} = viewData;
const kpiItems = [
{ label: 'Rating', value: currentDriver.rating, intent: 'primary' as const },
{ label: 'Rank', value: `#${currentDriver.rank}`, intent: 'warning' as const },
{ label: 'Starts', value: currentDriver.totalRaces },
{ label: 'Wins', value: currentDriver.wins, intent: 'success' as const },
{ label: 'Podiums', value: currentDriver.podiums, intent: 'warning' as const },
{ label: 'Leagues', value: activeLeaguesCount },
];
const activityItems: ActivityItem[] = feedItems.map(item => ({
id: item.id,
type: item.type.toUpperCase(),
description: item.headline,
timestamp: item.formattedTime,
status: item.type === 'race_result' ? 'success' : 'info'
}));
return (
<Stack gap={6}>
{/* KPI Overview */}
<DashboardKpiRow items={kpiItems} data-testid="dashboard-stats" />
<Grid responsiveGridCols={{ base: 1, lg: 12 }} gap={6}>
{/* Main Content Column */}
<Box responsiveColSpan={{ base: 1, lg: 8 }}>
<Stack direction="col" gap={6}>
{nextRace && (
<TelemetryPanel title="Active Session" data-testid="next-race-section">
<Box display="flex" alignItems="center" justifyContent="between">
<Box>
<Text size="xs" variant="low" mb={1} block>Next Event</Text>
<Text size="lg" weight="bold" block data-testid="next-race-track">{nextRace.track}</Text>
<Text size="xs" variant="primary" font="mono" block data-testid="next-race-car">{nextRace.car}</Text>
</Box>
<Box textAlign="right">
<Text size="xs" variant="low" mb={1} block data-testid="next-race-time">{nextRace.formattedDate} @ {nextRace.formattedTime}</Text>
<Text size="xl" font="mono" weight="bold" variant="warning" block data-testid="next-race-countdown">{nextRace.timeUntil}</Text>
<Text size="xs" variant="low" block>{nextRace.formattedDate} @ {nextRace.formattedTime}</Text>
</Box>
</Box>
</TelemetryPanel>
)}
<TelemetryPanel title="Recent Activity" data-testid="activity-feed-section">
{hasFeedItems ? (
<RecentActivityTable items={activityItems} />
) : (
<Box py={8} textAlign="center" data-testid="activity-empty">
<Text italic variant="low">No recent activity recorded.</Text>
</Box>
)}
</TelemetryPanel>
</Stack>
</Box>
{/* Sidebar Column */}
<Box responsiveColSpan={{ base: 1, lg: 4 }}>
<Stack direction="col" gap={6}>
<TelemetryPanel title="Championship Standings" data-testid="championship-standings-section">
{hasLeagueStandings ? (
<Stack direction="col" gap={3}>
{leagueStandings.map((standing) => (
<Box
key={standing.leagueId}
display="flex"
alignItems="center"
justifyContent="between"
borderBottom
borderColor="var(--ui-color-border-muted)"
pb={2}
data-testid={`league-standing-${standing.leagueId}`}
cursor="pointer"
onClick={() => router.push(routes.league.detail(standing.leagueId))}
>
<Box data-testid="league-standing-link">
<Text size="xs" weight="bold" truncate block maxWidth="180px">{standing.leagueName}</Text>
<Text size="xs" variant="low" block>Pos: {standing.position} / {standing.totalDrivers}</Text>
</Box>
<Text size="sm" font="mono" weight="bold" variant="primary">{standing.points} PTS</Text>
</Box>
))}
</Stack>
) : (
<Box py={4} textAlign="center" data-testid="standings-empty">
<Text italic variant="low">No active championships.</Text>
</Box>
)}
</TelemetryPanel>
<TelemetryPanel title="Upcoming Schedule" data-testid="upcoming-races-section">
<Stack direction="col" gap={4}>
{upcomingRaces.length > 0 ? (
upcomingRaces.slice(0, 3).map((race) => (
<Box
key={race.id}
cursor="pointer"
data-testid={`upcoming-race-${race.id}`}
onClick={() => router.push(routes.race.detail(race.id))}
>
<Box display="flex" justifyContent="between" alignItems="start" mb={1} data-testid="upcoming-race-link">
<Text size="xs" weight="bold">{race.track}</Text>
<Text size="xs" font="mono" variant="low">{race.timeUntil}</Text>
</Box>
<Box display="flex" justifyContent="between">
<Text size="xs" variant="low">{race.car}</Text>
<Text size="xs" variant="low">{race.formattedDate}</Text>
</Box>
</Box>
))
) : (
<Box py={4} textAlign="center" data-testid="upcoming-races-empty">
<Text italic variant="low">No upcoming races.</Text>
</Box>
)}
<Button
variant="secondary"
fullWidth
onClick={onNavigateToRaces}
data-testid="view-full-schedule-link"
>
<Text size="xs" weight="bold" uppercase letterSpacing="widest">View Full Schedule</Text>
</Button>
</Stack>
</TelemetryPanel>
</Stack>
</Box>
</Grid>
</Stack>
);
}