'use client'; import { useState, useEffect } from 'react'; import { useRouter } from 'next/navigation'; import { Users, Trophy, Search, Crown, Star, TrendingUp, Shield, Target, Award, ArrowLeft, Medal, Percent, Hash, Globe, Languages, } from 'lucide-react'; import Button from '@/components/ui/Button'; import Input from '@/components/ui/Input'; import Heading from '@/components/ui/Heading'; import { getGetTeamsLeaderboardUseCase } from '@/lib/di-container'; import { TeamsLeaderboardPresenter } from '@/lib/presenters/TeamsLeaderboardPresenter'; import type { TeamLeaderboardItemViewModel, SkillLevel, } from '@gridpilot/racing/application/presenters/ITeamsLeaderboardPresenter'; // ============================================================================ // TYPES // ============================================================================ type SortBy = 'rating' | 'wins' | 'winRate' | 'races'; type TeamDisplayData = TeamLeaderboardItemViewModel; const getSafeRating = (team: TeamDisplayData): number => { const value = typeof team.rating === 'number' ? team.rating : 0; return Number.isFinite(value) ? value : 0; }; const getSafeTotalWins = (team: TeamDisplayData): number => { const raw = team.totalWins; const value = typeof raw === 'number' ? raw : 0; return Number.isFinite(value) ? value : 0; }; const getSafeTotalRaces = (team: TeamDisplayData): number => { const raw = team.totalRaces; const value = typeof raw === 'number' ? raw : 0; return Number.isFinite(value) ? value : 0; }; // ============================================================================ // SKILL LEVEL CONFIG // ============================================================================ const SKILL_LEVELS: { id: SkillLevel; label: string; icon: React.ElementType; color: string; bgColor: string; borderColor: string; }[] = [ { id: 'pro', label: 'Pro', icon: Crown, color: 'text-yellow-400', bgColor: 'bg-yellow-400/10', borderColor: 'border-yellow-400/30', }, { id: 'advanced', label: 'Advanced', icon: Star, color: 'text-purple-400', bgColor: 'bg-purple-400/10', borderColor: 'border-purple-400/30', }, { id: 'intermediate', label: 'Intermediate', icon: TrendingUp, color: 'text-primary-blue', bgColor: 'bg-primary-blue/10', borderColor: 'border-primary-blue/30', }, { id: 'beginner', label: 'Beginner', icon: Shield, color: 'text-green-400', bgColor: 'bg-green-400/10', borderColor: 'border-green-400/30', }, ]; // ============================================================================ // SORT OPTIONS // ============================================================================ const SORT_OPTIONS: { id: SortBy; label: string; icon: React.ElementType }[] = [ { id: 'rating', label: 'Rating', icon: Star }, { id: 'wins', label: 'Total Wins', icon: Trophy }, { id: 'winRate', label: 'Win Rate', icon: Percent }, { id: 'races', label: 'Races', icon: Hash }, ]; // ============================================================================ // TOP THREE PODIUM COMPONENT // ============================================================================ interface TopThreePodiumProps { teams: TeamDisplayData[]; onTeamClick: (teamId: string) => void; } function TopThreePodium({ teams, onTeamClick }: TopThreePodiumProps) { const top3 = teams.slice(0, 3) as [TeamDisplayData, TeamDisplayData, TeamDisplayData]; if (teams.length < 3) return null; // Display order: 2nd, 1st, 3rd const podiumOrder: [TeamDisplayData, TeamDisplayData, TeamDisplayData] = [ top3[1], top3[0], top3[2], ]; const podiumHeights = ['h-28', 'h-36', 'h-20']; const podiumPositions = [2, 1, 3]; const getPositionColor = (position: number) => { switch (position) { case 1: return 'text-yellow-400'; case 2: return 'text-gray-300'; case 3: return 'text-amber-600'; default: return 'text-gray-500'; } }; const getGradient = (position: number) => { switch (position) { case 1: return 'from-yellow-400/30 via-yellow-500/20 to-yellow-600/10'; case 2: return 'from-gray-300/30 via-gray-400/20 to-gray-500/10'; case 3: return 'from-amber-500/30 via-amber-600/20 to-amber-700/10'; default: return 'from-gray-600/30 to-gray-700/10'; } }; const getBorderColor = (position: number) => { switch (position) { case 1: return 'border-yellow-400/50'; case 2: return 'border-gray-300/50'; case 3: return 'border-amber-600/50'; default: return 'border-charcoal-outline'; } }; return (

Top 3 Teams

{podiumOrder.map((team, index) => { const position = podiumPositions[index] ?? 0; const levelConfig = SKILL_LEVELS.find((l) => l.id === team.performanceLevel); const LevelIcon = levelConfig?.icon || Shield; return ( ); })}
); } // ============================================================================ // MAIN PAGE COMPONENT // ============================================================================ export default function TeamLeaderboardPage() { const router = useRouter(); const [teams, setTeams] = useState([]); const [loading, setLoading] = useState(true); const [searchQuery, setSearchQuery] = useState(''); const [sortBy, setSortBy] = useState('rating'); const [filterLevel, setFilterLevel] = useState('all'); useEffect(() => { const loadTeams = async () => { try { const useCase = getGetTeamsLeaderboardUseCase(); const presenter = new TeamsLeaderboardPresenter(); await useCase.execute(undefined as void, presenter); const viewModel = presenter.getViewModel(); if (viewModel) { setTeams(viewModel.teams); } } catch (error) { console.error('Failed to load teams:', error); } finally { setLoading(false); } }; void loadTeams(); }, []); const handleTeamClick = (teamId: string) => { if (teamId.startsWith('demo-team-')) { return; } router.push(`/teams/${teamId}`); }; // Filter and sort teams const filteredAndSortedTeams = teams .filter((team) => { // Search filter if (searchQuery) { const query = searchQuery.toLowerCase(); if (!team.name.toLowerCase().includes(query) && !(team.description ?? '').toLowerCase().includes(query)) { return false; } } // Level filter if (filterLevel !== 'all' && team.performanceLevel !== filterLevel) { return false; } // Must have rating for leaderboard return team.rating !== null; }) .sort((a, b) => { switch (sortBy) { case 'rating': { const aRating = getSafeRating(a); const bRating = getSafeRating(b); return bRating - aRating; } case 'wins': { const aWinsSort = getSafeTotalWins(a); const bWinsSort = getSafeTotalWins(b); return bWinsSort - aWinsSort; } case 'winRate': { const aRaces = getSafeTotalRaces(a); const bRaces = getSafeTotalRaces(b); const aWins = getSafeTotalWins(a); const bWins = getSafeTotalWins(b); const aRate = aRaces > 0 ? aWins / aRaces : 0; const bRate = bRaces > 0 ? bWins / bRaces : 0; return bRate - aRate; } case 'races': { const aRacesSort = getSafeTotalRaces(a); const bRacesSort = getSafeTotalRaces(b); return bRacesSort - aRacesSort; } default: return 0; } }); const getMedalColor = (position: number) => { switch (position) { case 0: return 'text-yellow-400'; case 1: return 'text-gray-300'; case 2: return 'text-amber-600'; default: return 'text-gray-500'; } }; const getMedalBg = (position: number) => { switch (position) { case 0: return 'bg-gradient-to-br from-yellow-400/20 to-yellow-600/10 border-yellow-400/40'; case 1: return 'bg-gradient-to-br from-gray-300/20 to-gray-400/10 border-gray-300/40'; case 2: return 'bg-gradient-to-br from-amber-600/20 to-amber-700/10 border-amber-600/40'; default: return 'bg-iron-gray/50 border-charcoal-outline'; } }; if (loading) { return (

Loading leaderboard...

); } return (
{/* Header */}
Team Leaderboard

Rankings of all teams by performance metrics

{/* Filters and Search */}
{/* Search and Level Filter Row */}
setSearchQuery(e.target.value)} className="pl-11" />
{/* Level Filter */}
{SKILL_LEVELS.map((level) => { const LevelIcon = level.icon; return ( ); })}
{/* Sort Options */}
Sort by:
{SORT_OPTIONS.map((option) => ( ))}
{/* Podium for Top 3 - only show when viewing by rating without filters */} {sortBy === 'rating' && filterLevel === 'all' && !searchQuery && filteredAndSortedTeams.length >= 3 && ( )} {/* Stats Summary */}
Total Teams

{filteredAndSortedTeams.length}

Pro Teams

{filteredAndSortedTeams.filter((t) => t.performanceLevel === 'pro').length}

Total Wins

{filteredAndSortedTeams.reduce( (sum, t) => sum + getSafeTotalWins(t), 0, )}

Total Races

{filteredAndSortedTeams.reduce( (sum, t) => sum + getSafeTotalRaces(t), 0, )}

{/* Leaderboard Table */}
{/* Table Header */}
Rank
Team
Members
Rating
Wins
Win Rate
{/* Table Body */}
{filteredAndSortedTeams.map((team, index) => { const levelConfig = SKILL_LEVELS.find((l) => l.id === team.performanceLevel); const LevelIcon = levelConfig?.icon || Shield; const totalRaces = getSafeTotalRaces(team); const totalWins = getSafeTotalWins(team); const winRate = totalRaces > 0 ? ((totalWins / totalRaces) * 100).toFixed(1) : '0.0'; return ( ); })}
{/* Empty State */} {filteredAndSortedTeams.length === 0 && (

No teams found

Try adjusting your filters or search query

)}
); }