181 lines
6.7 KiB
TypeScript
181 lines
6.7 KiB
TypeScript
'use client';
|
|
|
|
import { RankBadge } from '@/components/leaderboards/RankBadge';
|
|
import { useDriverProfile } from "@/hooks/driver/useDriverProfile";
|
|
import { Card } from '@/ui/Card';
|
|
import { Heading } from '@/ui/Heading';
|
|
import { Stack } from '@/ui/Stack';
|
|
import { StatCard } from '@/ui/StatCard';
|
|
import { Text } from '@/ui/Text';
|
|
import { useMemo } from 'react';
|
|
|
|
interface ProfileStatsProps {
|
|
driverId?: string;
|
|
stats?: {
|
|
totalRaces: number;
|
|
wins: number;
|
|
podiums: number;
|
|
dnfs: number;
|
|
avgFinish: number;
|
|
completionRate: number;
|
|
};
|
|
}
|
|
|
|
export function ProfileStats({ driverId, stats }: ProfileStatsProps) {
|
|
const { data: profileData } = useDriverProfile(driverId ?? '');
|
|
|
|
const driverStats = profileData?.stats ?? null;
|
|
const totalDrivers = profileData?.currentDriver?.totalDrivers ?? 0;
|
|
|
|
const defaultStats = useMemo(() => {
|
|
if (stats) {
|
|
return stats;
|
|
}
|
|
|
|
if (!driverStats) {
|
|
return null;
|
|
}
|
|
|
|
return {
|
|
totalRaces: driverStats.totalRaces,
|
|
wins: driverStats.wins,
|
|
podiums: driverStats.podiums,
|
|
dnfs: driverStats.dnfs,
|
|
avgFinish: driverStats.avgFinish ?? 0,
|
|
completionRate:
|
|
driverStats.totalRaces > 0
|
|
? ((driverStats.totalRaces - driverStats.dnfs) / driverStats.totalRaces) * 100
|
|
: 0,
|
|
};
|
|
}, [stats, driverStats]);
|
|
|
|
const winRate =
|
|
defaultStats && defaultStats.totalRaces > 0
|
|
? ((defaultStats.wins / defaultStats.totalRaces) * 100).toFixed(1)
|
|
: '0.0';
|
|
const podiumRate =
|
|
defaultStats && defaultStats.totalRaces > 0
|
|
? ((defaultStats.podiums / defaultStats.totalRaces) * 100).toFixed(1)
|
|
: '0.0';
|
|
|
|
const getTrendIndicator = (value: number) => {
|
|
if (value > 0) return '↑';
|
|
if (value < 0) return '↓';
|
|
return '→';
|
|
};
|
|
|
|
const getPercentileLabel = (percentile: number) => {
|
|
if (percentile >= 90) return 'Top 10%';
|
|
if (percentile >= 75) return 'Top 25%';
|
|
if (percentile >= 50) return 'Top 50%';
|
|
return `${(100 - percentile).toFixed(0)}th percentile`;
|
|
};
|
|
|
|
const getPercentileColor = (percentile: number) => {
|
|
if (percentile >= 90) return 'text-green-400';
|
|
if (percentile >= 75) return 'text-primary-blue';
|
|
if (percentile >= 50) return 'text-warning-amber';
|
|
return 'text-gray-400';
|
|
};
|
|
|
|
return (
|
|
<Stack gap={6}>
|
|
{driverStats && (
|
|
<Card>
|
|
<Heading level={2} mb={6}>Rankings Dashboard</Heading>
|
|
|
|
<Stack gap={4}>
|
|
<Stack p={4} rounded="lg" bg="bg-deep-graphite" border borderColor="border-charcoal-outline">
|
|
<Stack display="flex" alignItems="center" justifyContent="between" mb={3}>
|
|
<Stack display="flex" alignItems="center" gap={3}>
|
|
<RankBadge rank={driverStats.overallRank ?? 0} size="lg" />
|
|
<Stack>
|
|
<Text color="text-white" weight="medium" size="lg" block>Overall Ranking</Text>
|
|
<Text size="sm" color="text-gray-400" block>
|
|
{driverStats.overallRank ?? 0} of {totalDrivers} drivers
|
|
</Text>
|
|
</Stack>
|
|
</Stack>
|
|
<Stack textAlign="right">
|
|
<Text
|
|
size="sm"
|
|
weight="medium"
|
|
color={getPercentileColor(driverStats.percentile ?? 0)}
|
|
block
|
|
>
|
|
{getPercentileLabel(driverStats.percentile ?? 0)}
|
|
</Text>
|
|
<Text size="xs" color="text-gray-500" block>Global Percentile</Text>
|
|
</Stack>
|
|
</Stack>
|
|
|
|
<Stack display="grid" gridCols={3} gap={4} pt={3} borderTop borderColor="border-charcoal-outline">
|
|
<Stack textAlign="center">
|
|
<Text size="2xl" weight="bold" color="text-primary-blue" block>
|
|
{driverStats.rating ?? 0}
|
|
</Text>
|
|
<Text size="xs" color="text-gray-400" block>Rating</Text>
|
|
</Stack>
|
|
<Stack textAlign="center">
|
|
<Text size="lg" weight="bold" color="text-green-400" block>
|
|
{getTrendIndicator(5)} {winRate}%
|
|
</Text>
|
|
<Text size="xs" color="text-gray-400" block>Win Rate</Text>
|
|
</Stack>
|
|
<Stack textAlign="center">
|
|
<Text size="lg" weight="bold" color="text-warning-amber" block>
|
|
{getTrendIndicator(2)} {podiumRate}%
|
|
</Text>
|
|
<Text size="xs" color="text-gray-400" block>Podium Rate</Text>
|
|
</Stack>
|
|
</Stack>
|
|
</Stack>
|
|
</Stack>
|
|
</Card>
|
|
)}
|
|
|
|
{defaultStats ? (
|
|
<Stack display="grid" responsiveGridCols={{ base: 2, md: 4 }} gap={4}>
|
|
<StatCard label="Total Races" value={defaultStats.totalRaces} variant="blue" />
|
|
<StatCard label="Wins" value={defaultStats.wins} variant="green" />
|
|
<StatCard label="Podiums" value={defaultStats.podiums} variant="orange" />
|
|
<StatCard label="DNFs" value={defaultStats.dnfs} variant="blue" />
|
|
<StatCard label="Avg Finish" value={defaultStats.avgFinish.toFixed(1)} variant="blue" />
|
|
<StatCard label="Completion" value={`${defaultStats.completionRate.toFixed(1)}%`} variant="green" />
|
|
<StatCard label="Win Rate" value={`${winRate}%`} variant="blue" />
|
|
<StatCard label="Podium Rate" value={`${podiumRate}%`} variant="orange" />
|
|
</Stack>
|
|
) : (
|
|
<Card>
|
|
<Heading level={3} mb={2}>Career Statistics</Heading>
|
|
<Text size="sm" color="text-gray-400" block>
|
|
No statistics available yet. Compete in races to start building your record.
|
|
</Text>
|
|
</Card>
|
|
)}
|
|
|
|
<Card bg="bg-charcoal-200/50" borderColor="border-primary-blue/30">
|
|
<Stack display="flex" alignItems="center" gap={3} mb={3}>
|
|
<Text size="2xl">📊</Text>
|
|
<Heading level={3}>Performance by Car Class</Heading>
|
|
</Stack>
|
|
<Text color="text-gray-400" size="sm" block>
|
|
Detailed per-car and per-class performance breakdowns will be available in a future
|
|
version once more race history data is tracked.
|
|
</Text>
|
|
</Card>
|
|
|
|
<Card bg="bg-charcoal-200/50" borderColor="border-primary-blue/30">
|
|
<Stack display="flex" alignItems="center" gap={3} mb={3}>
|
|
<Text size="2xl">📈</Text>
|
|
<Heading level={3}>Coming Soon</Heading>
|
|
</Stack>
|
|
<Text color="text-gray-400" size="sm" block>
|
|
Performance trends, track-specific stats, head-to-head comparisons vs friends, and
|
|
league member comparisons will be available in production.
|
|
</Text>
|
|
</Card>
|
|
</Stack>
|
|
);
|
|
}
|