|
|
|
|
@@ -52,6 +52,25 @@ const SKILL_LEVELS: {
|
|
|
|
|
{ id: 'beginner', label: 'Beginner', icon: Shield, color: 'text-green-400', bgColor: 'bg-green-400/10', borderColor: 'border-green-400/30', description: 'Learning the ropes' },
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
// ============================================================================
|
|
|
|
|
// CATEGORY CONFIG
|
|
|
|
|
// ============================================================================
|
|
|
|
|
|
|
|
|
|
const CATEGORIES: {
|
|
|
|
|
id: string;
|
|
|
|
|
label: string;
|
|
|
|
|
color: string;
|
|
|
|
|
bgColor: string;
|
|
|
|
|
borderColor: string;
|
|
|
|
|
}[] = [
|
|
|
|
|
{ id: 'beginner', label: 'Beginner', color: 'text-green-400', bgColor: 'bg-green-400/10', borderColor: 'border-green-400/30' },
|
|
|
|
|
{ id: 'intermediate', label: 'Intermediate', color: 'text-primary-blue', bgColor: 'bg-primary-blue/10', borderColor: 'border-primary-blue/30' },
|
|
|
|
|
{ id: 'advanced', label: 'Advanced', color: 'text-purple-400', bgColor: 'bg-purple-400/10', borderColor: 'border-purple-400/30' },
|
|
|
|
|
{ id: 'pro', label: 'Pro', color: 'text-yellow-400', bgColor: 'bg-yellow-400/10', borderColor: 'border-yellow-400/30' },
|
|
|
|
|
{ id: 'endurance', label: 'Endurance', color: 'text-orange-400', bgColor: 'bg-orange-400/10', borderColor: 'border-orange-400/30' },
|
|
|
|
|
{ id: 'sprint', label: 'Sprint', color: 'text-red-400', bgColor: 'bg-red-400/10', borderColor: 'border-red-400/30' },
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
// ============================================================================
|
|
|
|
|
// FEATURED DRIVER CARD COMPONENT
|
|
|
|
|
// ============================================================================
|
|
|
|
|
@@ -64,6 +83,7 @@ interface FeaturedDriverCardProps {
|
|
|
|
|
|
|
|
|
|
function FeaturedDriverCard({ driver, position, onClick }: FeaturedDriverCardProps) {
|
|
|
|
|
const levelConfig = SKILL_LEVELS.find((l) => l.id === driver.skillLevel);
|
|
|
|
|
const categoryConfig = CATEGORIES.find((c) => c.id === driver.category);
|
|
|
|
|
|
|
|
|
|
const getBorderColor = (pos: number) => {
|
|
|
|
|
switch (pos) {
|
|
|
|
|
@@ -98,9 +118,16 @@ function FeaturedDriverCard({ driver, position, onClick }: FeaturedDriverCardPro
|
|
|
|
|
<span className="text-lg font-bold text-gray-400">#{position}</span>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
<span className={`px-2 py-1 rounded-full text-[10px] font-medium ${levelConfig?.bgColor} ${levelConfig?.color} border ${levelConfig?.borderColor}`}>
|
|
|
|
|
{levelConfig?.label}
|
|
|
|
|
</span>
|
|
|
|
|
<div className="flex gap-2">
|
|
|
|
|
{categoryConfig && (
|
|
|
|
|
<span className={`px-2 py-1 rounded-full text-[10px] font-medium ${categoryConfig.bgColor} ${categoryConfig.color} border ${categoryConfig.borderColor}`}>
|
|
|
|
|
{categoryConfig.label}
|
|
|
|
|
</span>
|
|
|
|
|
)}
|
|
|
|
|
<span className={`px-2 py-1 rounded-full text-[10px] font-medium ${levelConfig?.bgColor} ${levelConfig?.color} border ${levelConfig?.borderColor}`}>
|
|
|
|
|
{levelConfig?.label}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Avatar & Name */}
|
|
|
|
|
@@ -150,8 +177,8 @@ function SkillDistribution({ drivers }: SkillDistributionProps) {
|
|
|
|
|
const distribution = SKILL_LEVELS.map((level) => ({
|
|
|
|
|
...level,
|
|
|
|
|
count: drivers.filter((d) => d.skillLevel === level.id).length,
|
|
|
|
|
percentage: drivers.length > 0
|
|
|
|
|
? Math.round((drivers.filter((d) => d.skillLevel === level.id).length / drivers.length) * 100)
|
|
|
|
|
percentage: drivers.length > 0
|
|
|
|
|
? Math.round((drivers.filter((d) => d.skillLevel === level.id).length / drivers.length) * 100)
|
|
|
|
|
: 0,
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
@@ -200,6 +227,66 @@ function SkillDistribution({ drivers }: SkillDistributionProps) {
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ============================================================================
|
|
|
|
|
// CATEGORY DISTRIBUTION COMPONENT
|
|
|
|
|
// ============================================================================
|
|
|
|
|
|
|
|
|
|
interface CategoryDistributionProps {
|
|
|
|
|
drivers: DriverLeaderboardItemViewModel[];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function CategoryDistribution({ drivers }: CategoryDistributionProps) {
|
|
|
|
|
const distribution = CATEGORIES.map((category) => ({
|
|
|
|
|
...category,
|
|
|
|
|
count: drivers.filter((d) => d.category === category.id).length,
|
|
|
|
|
percentage: drivers.length > 0
|
|
|
|
|
? Math.round((drivers.filter((d) => d.category === category.id).length / drivers.length) * 100)
|
|
|
|
|
: 0,
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="mb-10">
|
|
|
|
|
<div className="flex items-center gap-3 mb-4">
|
|
|
|
|
<div className="flex h-10 w-10 items-center justify-center rounded-xl bg-purple-400/10 border border-purple-400/20">
|
|
|
|
|
<BarChart3 className="w-5 h-5 text-purple-400" />
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<h2 className="text-lg font-semibold text-white">Category Distribution</h2>
|
|
|
|
|
<p className="text-xs text-gray-500">Driver population by category</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="grid grid-cols-2 lg:grid-cols-3 gap-4">
|
|
|
|
|
{distribution.map((category) => (
|
|
|
|
|
<div
|
|
|
|
|
key={category.id}
|
|
|
|
|
className={`p-4 rounded-xl ${category.bgColor} border ${category.borderColor}`}
|
|
|
|
|
>
|
|
|
|
|
<div className="flex items-center justify-between mb-3">
|
|
|
|
|
<span className={`text-2xl font-bold ${category.color}`}>{category.count}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<p className="text-white font-medium mb-1">{category.label}</p>
|
|
|
|
|
<div className="w-full h-2 rounded-full bg-deep-graphite/50 overflow-hidden">
|
|
|
|
|
<div
|
|
|
|
|
className={`h-full rounded-full transition-all duration-500 ${
|
|
|
|
|
category.id === 'beginner' ? 'bg-green-400' :
|
|
|
|
|
category.id === 'intermediate' ? 'bg-primary-blue' :
|
|
|
|
|
category.id === 'advanced' ? 'bg-purple-400' :
|
|
|
|
|
category.id === 'pro' ? 'bg-yellow-400' :
|
|
|
|
|
category.id === 'endurance' ? 'bg-orange-400' :
|
|
|
|
|
'bg-red-400'
|
|
|
|
|
}`}
|
|
|
|
|
style={{ width: `${category.percentage}%` }}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<p className="text-xs text-gray-500 mt-1">{category.percentage}% of drivers</p>
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ============================================================================
|
|
|
|
|
// LEADERBOARD PREVIEW COMPONENT
|
|
|
|
|
// ============================================================================
|
|
|
|
|
@@ -258,6 +345,7 @@ function LeaderboardPreview({ drivers, onDriverClick }: LeaderboardPreviewProps)
|
|
|
|
|
<div className="divide-y divide-charcoal-outline/50">
|
|
|
|
|
{top5.map((driver, index) => {
|
|
|
|
|
const levelConfig = SKILL_LEVELS.find((l) => l.id === driver.skillLevel);
|
|
|
|
|
const categoryConfig = CATEGORIES.find((c) => c.id === driver.category);
|
|
|
|
|
const position = index + 1;
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
@@ -285,6 +373,9 @@ function LeaderboardPreview({ drivers, onDriverClick }: LeaderboardPreviewProps)
|
|
|
|
|
<div className="flex items-center gap-2 text-xs text-gray-500">
|
|
|
|
|
<Flag className="w-3 h-3" />
|
|
|
|
|
{driver.nationality}
|
|
|
|
|
{categoryConfig && (
|
|
|
|
|
<span className={categoryConfig.color}>{categoryConfig.label}</span>
|
|
|
|
|
)}
|
|
|
|
|
<span className={levelConfig?.color}>{levelConfig?.label}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
@@ -336,6 +427,7 @@ function RecentActivity({ drivers, onDriverClick }: RecentActivityProps) {
|
|
|
|
|
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-3">
|
|
|
|
|
{activeDrivers.map((driver) => {
|
|
|
|
|
const levelConfig = SKILL_LEVELS.find((l) => l.id === driver.skillLevel);
|
|
|
|
|
const categoryConfig = CATEGORIES.find((c) => c.id === driver.category);
|
|
|
|
|
return (
|
|
|
|
|
<button
|
|
|
|
|
key={driver.id}
|
|
|
|
|
@@ -350,7 +442,12 @@ function RecentActivity({ drivers, onDriverClick }: RecentActivityProps) {
|
|
|
|
|
<p className="text-sm font-medium text-white truncate group-hover:text-performance-green transition-colors">
|
|
|
|
|
{driver.name}
|
|
|
|
|
</p>
|
|
|
|
|
<p className={`text-xs ${levelConfig?.color}`}>{levelConfig?.label}</p>
|
|
|
|
|
<div className="flex items-center justify-center gap-1 text-xs">
|
|
|
|
|
{categoryConfig && (
|
|
|
|
|
<span className={categoryConfig.color}>{categoryConfig.label}</span>
|
|
|
|
|
)}
|
|
|
|
|
<span className={levelConfig?.color}>{levelConfig?.label}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</button>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
@@ -516,6 +613,9 @@ export default function DriversPage() {
|
|
|
|
|
{/* Skill Distribution */}
|
|
|
|
|
{!searchQuery && <SkillDistribution drivers={drivers} />}
|
|
|
|
|
|
|
|
|
|
{/* Category Distribution */}
|
|
|
|
|
{!searchQuery && <CategoryDistribution drivers={drivers} />}
|
|
|
|
|
|
|
|
|
|
{/* Leaderboard Preview */}
|
|
|
|
|
<LeaderboardPreview drivers={filteredDrivers} onDriverClick={handleDriverClick} />
|
|
|
|
|
|
|
|
|
|
|