210 lines
7.4 KiB
TypeScript
210 lines
7.4 KiB
TypeScript
'use client';
|
|
|
|
import { useDriverProfile } from "@/lib/hooks/driver";
|
|
import { useMemo } from 'react';
|
|
import Card from '../ui/Card';
|
|
import RankBadge from './RankBadge';
|
|
|
|
interface ProfileStatsProps {
|
|
driverId?: string;
|
|
stats?: {
|
|
totalRaces: number;
|
|
wins: number;
|
|
podiums: number;
|
|
dnfs: number;
|
|
avgFinish: number;
|
|
completionRate: number;
|
|
};
|
|
}
|
|
|
|
export default function ProfileStats({ driverId, stats }: ProfileStatsProps) {
|
|
const { data: profileData } = useDriverProfile(driverId ?? '');
|
|
|
|
const driverStats = profileData?.stats ?? null;
|
|
const totalDrivers = profileData?.currentDriver?.totalDrivers ?? 0;
|
|
|
|
// League rank widget needs a dedicated API contract; keep it disabled until provided.
|
|
// (Leaving UI block out avoids `never` typing issues.)
|
|
|
|
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 (
|
|
<div className="space-y-6">
|
|
{driverStats && (
|
|
<Card>
|
|
<h3 className="text-xl font-semibold text-white mb-6">Rankings Dashboard</h3>
|
|
|
|
<div className="space-y-4">
|
|
<div className="p-4 rounded-lg bg-deep-graphite border border-charcoal-outline">
|
|
<div className="flex items-center justify-between mb-3">
|
|
<div className="flex items-center gap-3">
|
|
<RankBadge rank={driverStats.overallRank ?? 0} size="lg" />
|
|
<div>
|
|
<div className="text-white font-medium text-lg">Overall Ranking</div>
|
|
<div className="text-sm text-gray-400">
|
|
{driverStats.overallRank ?? 0} of {totalDrivers} drivers
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="text-right">
|
|
<div
|
|
className={`text-sm font-medium ${getPercentileColor(driverStats.percentile ?? 0)}`}
|
|
>
|
|
{getPercentileLabel(driverStats.percentile ?? 0)}
|
|
</div>
|
|
<div className="text-xs text-gray-500">Global Percentile</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-3 gap-4 pt-3 border-t border-charcoal-outline">
|
|
<div className="text-center">
|
|
<div className="text-2xl font-bold text-primary-blue">
|
|
{driverStats.rating ?? 0}
|
|
</div>
|
|
<div className="text-xs text-gray-400">Rating</div>
|
|
</div>
|
|
<div className="text-center">
|
|
<div className="text-lg font-bold text-green-400">
|
|
{getTrendIndicator(5)} {winRate}%
|
|
</div>
|
|
<div className="text-xs text-gray-400">Win Rate</div>
|
|
</div>
|
|
<div className="text-center">
|
|
<div className="text-lg font-bold text-warning-amber">
|
|
{getTrendIndicator(2)} {podiumRate}%
|
|
</div>
|
|
<div className="text-xs text-gray-400">Podium Rate</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Primary-league ranking removed until we have a dedicated API + view model for league ranks. */}
|
|
</div>
|
|
</Card>
|
|
)}
|
|
|
|
{defaultStats ? (
|
|
<>
|
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
|
{[
|
|
{
|
|
label: 'Total Races',
|
|
value: defaultStats.totalRaces,
|
|
color: 'text-primary-blue',
|
|
},
|
|
{ label: 'Wins', value: defaultStats.wins, color: 'text-green-400' },
|
|
{
|
|
label: 'Podiums',
|
|
value: defaultStats.podiums,
|
|
color: 'text-warning-amber',
|
|
},
|
|
{ label: 'DNFs', value: defaultStats.dnfs, color: 'text-red-400' },
|
|
{
|
|
label: 'Avg Finish',
|
|
value: defaultStats.avgFinish.toFixed(1),
|
|
color: 'text-white',
|
|
},
|
|
{
|
|
label: 'Completion',
|
|
value: `${defaultStats.completionRate.toFixed(1)}%`,
|
|
color: 'text-green-400',
|
|
},
|
|
{ label: 'Win Rate', value: `${winRate}%`, color: 'text-primary-blue' },
|
|
{
|
|
label: 'Podium Rate',
|
|
value: `${podiumRate}%`,
|
|
color: 'text-warning-amber',
|
|
},
|
|
].map((stat, index) => (
|
|
<Card key={index} className="text-center">
|
|
<div className="text-sm text-gray-400 mb-1">{stat.label}</div>
|
|
<div className={`text-2xl font-bold ${stat.color}`}>{stat.value}</div>
|
|
</Card>
|
|
))}
|
|
</div>
|
|
</>
|
|
) : (
|
|
<Card>
|
|
<h3 className="text-lg font-semibold text-white mb-2">Career Statistics</h3>
|
|
<p className="text-sm text-gray-400">
|
|
No statistics available yet. Compete in races to start building your record.
|
|
</p>
|
|
</Card>
|
|
)}
|
|
|
|
<Card className="bg-charcoal-200/50 border-primary-blue/30">
|
|
<div className="flex items-center gap-3 mb-3">
|
|
<div className="text-2xl">📊</div>
|
|
<h3 className="text-lg font-semibold text-white">Performance by Car Class</h3>
|
|
</div>
|
|
<p className="text-gray-400 text-sm">
|
|
Detailed per-car and per-class performance breakdowns will be available in a future
|
|
version once more race history data is tracked.
|
|
</p>
|
|
</Card>
|
|
|
|
<Card className="bg-charcoal-200/50 border-primary-blue/30">
|
|
<div className="flex items-center gap-3 mb-3">
|
|
<div className="text-2xl">📈</div>
|
|
<h3 className="text-lg font-semibold text-white">Coming Soon</h3>
|
|
</div>
|
|
<p className="text-gray-400 text-sm">
|
|
Performance trends, track-specific stats, head-to-head comparisons vs friends, and
|
|
league member comparisons will be available in production.
|
|
</p>
|
|
</Card>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
|