website refactor
This commit is contained in:
@@ -2,64 +2,211 @@
|
||||
|
||||
import React from 'react';
|
||||
import type { DashboardViewData } from '@/lib/view-data/DashboardViewData';
|
||||
import { DashboardShell } from '@/components/dashboard/DashboardShell';
|
||||
import { DashboardRail } from '@/components/dashboard/DashboardRail';
|
||||
import { DashboardControlBar } from '@/components/dashboard/DashboardControlBar';
|
||||
import { TelemetryPanel } from '@/components/dashboard/TelemetryPanel';
|
||||
import { DashboardKpiRow } from '@/components/dashboard/DashboardKpiRow';
|
||||
import { RecentActivityTable, type ActivityItem } from '@/components/dashboard/RecentActivityTable';
|
||||
import { LayoutDashboard, Trophy, Calendar, Users, Settings, Bell, Search } from 'lucide-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { routes } from '@/lib/routing/RouteConfig';
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Container } from '@/ui/Container';
|
||||
import { Grid } from '@/ui/Grid';
|
||||
import { GridItem } from '@/ui/GridItem';
|
||||
import { DashboardHero } from '@/components/dashboard/DashboardHeroWrapper';
|
||||
import { NextRaceCard } from '@/components/races/NextRaceCardWrapper';
|
||||
import { ChampionshipStandings } from '@/components/leagues/ChampionshipStandings';
|
||||
import { ActivityFeed } from '@/components/feed/ActivityFeed';
|
||||
import { UpcomingRaces } from '@/components/races/UpcomingRaces';
|
||||
import { FriendsSidebar } from '@/components/social/FriendsSidebar';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { IconButton } from '@/ui/IconButton';
|
||||
import { Avatar } from '@/ui/Avatar';
|
||||
import { Grid } from '@/ui/Grid';
|
||||
import { Button } from '@/ui/Button';
|
||||
|
||||
interface DashboardTemplateProps {
|
||||
viewData: DashboardViewData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 }: DashboardTemplateProps) {
|
||||
const router = useRouter();
|
||||
const {
|
||||
currentDriver,
|
||||
nextRace,
|
||||
upcomingRaces,
|
||||
leagueStandings,
|
||||
feedItems,
|
||||
friends,
|
||||
activeLeaguesCount,
|
||||
hasUpcomingRaces,
|
||||
hasLeagueStandings,
|
||||
hasFeedItems,
|
||||
hasFriends,
|
||||
} = viewData;
|
||||
|
||||
return (
|
||||
<Box as="main">
|
||||
<DashboardHero
|
||||
currentDriver={currentDriver}
|
||||
activeLeaguesCount={activeLeaguesCount}
|
||||
const kpiItems = [
|
||||
{ label: 'Rating', value: currentDriver.rating, color: 'var(--color-telemetry)' },
|
||||
{ label: 'Rank', value: `#${currentDriver.rank}`, color: 'var(--color-warning)' },
|
||||
{ label: 'Starts', value: currentDriver.totalRaces },
|
||||
{ label: 'Wins', value: currentDriver.wins, color: 'var(--color-success)' },
|
||||
{ label: 'Podiums', value: currentDriver.podiums, color: 'var(--color-warning)' },
|
||||
{ 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'
|
||||
}));
|
||||
|
||||
const railContent = (
|
||||
<DashboardRail>
|
||||
<Stack direction="col" align="center" gap={6} fullWidth>
|
||||
<Box h="8" w="8" rounded="sm" bg="primary-accent" display="flex" alignItems="center" justifyContent="center">
|
||||
<Text size="xs" weight="bold">GP</Text>
|
||||
</Box>
|
||||
<IconButton
|
||||
icon={LayoutDashboard}
|
||||
onClick={() => router.push(routes.protected.dashboard)}
|
||||
variant="ghost"
|
||||
color="primary-accent"
|
||||
/>
|
||||
<IconButton
|
||||
icon={Trophy}
|
||||
onClick={() => router.push(routes.public.leagues)}
|
||||
variant="ghost"
|
||||
color="var(--color-text-low)"
|
||||
/>
|
||||
<IconButton
|
||||
icon={Calendar}
|
||||
onClick={() => router.push(routes.public.races)}
|
||||
variant="ghost"
|
||||
color="var(--color-text-low)"
|
||||
/>
|
||||
<IconButton
|
||||
icon={Users}
|
||||
onClick={() => router.push(routes.public.teams)}
|
||||
variant="ghost"
|
||||
color="var(--color-text-low)"
|
||||
/>
|
||||
</Stack>
|
||||
<Box mt="auto" display="flex" flexDirection="col" alignItems="center" gap={6} pb={4}>
|
||||
<IconButton
|
||||
icon={Settings}
|
||||
onClick={() => router.push(routes.protected.profile)}
|
||||
variant="ghost"
|
||||
color="var(--color-text-low)"
|
||||
/>
|
||||
</Box>
|
||||
</DashboardRail>
|
||||
);
|
||||
|
||||
const controlBarActions = (
|
||||
<Stack direction="row" align="center" gap={4}>
|
||||
<IconButton icon={Search} variant="ghost" color="var(--color-text-low)" />
|
||||
<Box position="relative">
|
||||
<IconButton icon={Bell} variant="ghost" color="var(--color-text-low)" />
|
||||
<Box position="absolute" top="0" right="0" h="1.5" w="1.5" rounded="full" bg="critical-red" />
|
||||
</Box>
|
||||
<Avatar
|
||||
src={currentDriver.avatarUrl}
|
||||
alt={currentDriver.name}
|
||||
size={32}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
|
||||
<Container size="lg" py={8}>
|
||||
<Grid cols={12} gap={6}>
|
||||
{/* Left Column - Main Content */}
|
||||
<GridItem colSpan={12} lgSpan={8}>
|
||||
<Stack gap={6}>
|
||||
{nextRace && <NextRaceCard nextRace={nextRace} />}
|
||||
{hasLeagueStandings && <ChampionshipStandings standings={leagueStandings} />}
|
||||
<ActivityFeed items={feedItems} hasItems={hasFeedItems} />
|
||||
</Stack>
|
||||
</GridItem>
|
||||
return (
|
||||
<DashboardShell
|
||||
rail={railContent}
|
||||
controlBar={<DashboardControlBar title="Telemetry Workspace" actions={controlBarActions} />}
|
||||
>
|
||||
{/* KPI Overview */}
|
||||
<DashboardKpiRow items={kpiItems} />
|
||||
|
||||
{/* Right Column - Sidebar */}
|
||||
<GridItem colSpan={12} lgSpan={4}>
|
||||
<Stack gap={6}>
|
||||
<UpcomingRaces races={upcomingRaces} hasRaces={hasUpcomingRaces} />
|
||||
<FriendsSidebar friends={friends} hasFriends={hasFriends} />
|
||||
</Stack>
|
||||
</GridItem>
|
||||
</Grid>
|
||||
</Container>
|
||||
</Box>
|
||||
<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">
|
||||
<Box display="flex" alignItems="center" justifyContent="between">
|
||||
<Box>
|
||||
<Text size="xs" color="var(--color-text-low)" mb={1} block>Next Event</Text>
|
||||
<Text size="lg" weight="bold" block>{nextRace.track}</Text>
|
||||
<Text size="xs" color="var(--color-telemetry)" font="mono" block>{nextRace.car}</Text>
|
||||
</Box>
|
||||
<Box textAlign="right">
|
||||
<Text size="xs" color="var(--color-text-low)" mb={1} block>Starts In</Text>
|
||||
<Text size="xl" font="mono" weight="bold" color="var(--color-warning)" block>{nextRace.timeUntil}</Text>
|
||||
<Text size="xs" color="var(--color-text-low)" block>{nextRace.formattedDate} @ {nextRace.formattedTime}</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
</TelemetryPanel>
|
||||
)}
|
||||
|
||||
<TelemetryPanel title="Recent Activity">
|
||||
{hasFeedItems ? (
|
||||
<RecentActivityTable items={activityItems} />
|
||||
) : (
|
||||
<Box py={8} textAlign="center">
|
||||
<Text italic color="var(--color-text-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">
|
||||
{hasLeagueStandings ? (
|
||||
<Stack direction="col" gap={3}>
|
||||
{leagueStandings.map((standing) => (
|
||||
<Box key={standing.leagueId} display="flex" alignItems="center" justifyContent="between" borderBottom borderColor="rgba(35, 39, 43, 0.3)" pb={2}>
|
||||
<Box>
|
||||
<Text size="xs" weight="bold" truncate block maxWidth="180px">{standing.leagueName}</Text>
|
||||
<Text size="xs" color="var(--color-text-low)" block>Pos: {standing.position} / {standing.totalDrivers}</Text>
|
||||
</Box>
|
||||
<Text size="sm" font="mono" weight="bold" color="var(--color-telemetry)">{standing.points} PTS</Text>
|
||||
</Box>
|
||||
))}
|
||||
</Stack>
|
||||
) : (
|
||||
<Box py={4} textAlign="center">
|
||||
<Text italic color="var(--color-text-low)">No active championships.</Text>
|
||||
</Box>
|
||||
)}
|
||||
</TelemetryPanel>
|
||||
|
||||
<TelemetryPanel title="Upcoming Schedule">
|
||||
<Stack direction="col" gap={4}>
|
||||
{upcomingRaces.slice(0, 3).map((race) => (
|
||||
<Box key={race.id} group cursor="pointer">
|
||||
<Box display="flex" justifyContent="between" alignItems="start" mb={1}>
|
||||
<Text size="xs" weight="bold" groupHoverTextColor="var(--color-primary)" transition>{race.track}</Text>
|
||||
<Text size="xs" font="mono" color="var(--color-text-low)">{race.timeUntil}</Text>
|
||||
</Box>
|
||||
<Box display="flex" justifyContent="between">
|
||||
<Text size="xs" color="var(--color-text-low)">{race.car}</Text>
|
||||
<Text size="xs" color="var(--color-text-low)">{race.formattedDate}</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
))}
|
||||
<Button
|
||||
variant="secondary"
|
||||
fullWidth
|
||||
onClick={() => router.push(routes.public.races)}
|
||||
>
|
||||
<Text size="xs" weight="bold" uppercase letterSpacing="widest">View Full Schedule</Text>
|
||||
</Button>
|
||||
</Stack>
|
||||
</TelemetryPanel>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Grid>
|
||||
</DashboardShell>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user