'use client' import type React from 'react' import { type ComponentPropsWithoutRef, Fragment, useRef, useState, } from 'react' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { CardContent } from '@/components/ui/card' import { Input } from '@/components/ui/input' import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table' import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' import { cn } from '@/lib/utils' import { RANKED_CHANNEL, VANILLA_CHANNEL } from '@/shared/constants' import { api } from '@/trpc/react' import { useVirtualizer } from '@tanstack/react-virtual' import { ArrowDown, ArrowUp, ArrowUpDown, Flame, Info, Medal, Search, TrendingUp, Trophy, Users, } from 'lucide-react' import Link from 'next/link' import { useRouter, useSearchParams } from 'next/navigation' export function LeaderboardPage() { const router = useRouter() const searchParams = useSearchParams() // Get the leaderboard type from URL or default to 'ranked' const leaderboardType = searchParams.get('type') || 'ranked' // State for search and sorting const [searchQuery, setSearchQuery] = useState('') const [sortColumn, setSortColumn] = useState('rank') const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc') // Fetch leaderboard data const [rankedLeaderboard] = api.leaderboard.get_leaderboard.useSuspenseQuery({ channel_id: RANKED_CHANNEL, }) const [vanillaLeaderboard] = api.leaderboard.get_leaderboard.useSuspenseQuery( { channel_id: VANILLA_CHANNEL, } ) // Handle tab change const handleTabChange = (value: string) => { const params = new URLSearchParams(searchParams) params.set('type', value) router.push(`?${params.toString()}`) } // Get the current leaderboard based on selected tab const currentLeaderboard = leaderboardType === 'ranked' ? rankedLeaderboard : vanillaLeaderboard // Filter leaderboard by search query const filteredLeaderboard = currentLeaderboard.filter((entry) => entry.name.toLowerCase().includes(searchQuery.toLowerCase()) ) console.log(filteredLeaderboard) // Sort leaderboard const sortedLeaderboard = [...filteredLeaderboard].sort((a, b) => { // biome-ignore lint/style/useSingleVarDeclarator: // biome-ignore lint/suspicious/noImplicitAnyLet: let valueA, valueB // Handle special case for rank which is already sorted if (sortColumn === 'rank') { valueA = a.rank valueB = b.rank } else if (sortColumn === 'name') { valueA = a.name.toLowerCase() valueB = b.name.toLowerCase() return sortDirection === 'asc' ? valueA.localeCompare(valueB) : valueB.localeCompare(valueA) } else { valueA = a[sortColumn as keyof typeof a] as number valueB = b[sortColumn as keyof typeof b] as number } return sortDirection === 'asc' ? valueA - valueB : valueB - valueA }) // Handle column sort const handleSort = (column: string) => { if (sortColumn === column) { setSortDirection(sortDirection === 'asc' ? 'desc' : 'asc') } else { setSortColumn(column) setSortDirection('asc') } } // Get medal for top 3 players const getMedal = (rank: number) => { if (rank === 1) return if (rank === 2) return if (rank === 3) return return null } return (

Leaderboards

View player rankings and statistics

{currentLeaderboard.length} Players
Ranked Leaderboard Vanilla Leaderboard
setSearchQuery(e.target.value)} />
) } interface LeaderboardTableProps { leaderboard: any[] sortColumn: string sortDirection: 'asc' | 'desc' onSort: (column: string) => void getMedal: (rank: number) => React.ReactNode type: string } function LeaderboardTable({ leaderboard, sortColumn, sortDirection, onSort, getMedal, type, }: LeaderboardTableProps) { const tableContainerRef = useRef(null) // Set a fixed row height for virtualization const ROW_HEIGHT = 39 // Adjust based on your actual row height // Create virtualizer instance const rowVirtualizer = useVirtualizer({ count: leaderboard.length, getScrollElement: () => tableContainerRef.current, estimateSize: () => ROW_HEIGHT, overscan: 12, // Number of items to render before/after the visible area }) // Get the virtualized rows const virtualRows = rowVirtualizer.getVirtualItems() const paddingTop = virtualRows.length > 0 ? (virtualRows?.[0]?.start ?? 0) : 0 const paddingBottom = virtualRows.length > 0 ? rowVirtualizer.getTotalSize() - (virtualRows?.[virtualRows.length - 1]?.end ?? 0) : 0 return (
{paddingTop > 0 && ( )} {leaderboard.length > 0 ? ( virtualRows.map((virtualRow) => { const entry = leaderboard[virtualRow.index] const winrate = entry.winrate * 100 return ( {/* Add padding to the top to push content into view */}
{getMedal(entry.rank)} {entry.rank}
{entry.name} {entry.streak >= 3 && ( Hot Streak )} {Math.round(entry.mmr)}
{Math.round(entry.peak_mmr)}
60 ? 'border-emerald-200 bg-emerald-50 text-emerald-700 dark:border-emerald-800 dark:bg-emerald-950 dark:text-emerald-300' : winrate < 40 ? 'border-rose-200 bg-rose-50 text-rose-700 dark:border-rose-800 dark:bg-rose-950 dark:text-rose-300' : 'border-gray-200 bg-gray-50 text-gray-700 dark:border-zinc-700 dark:bg-zinc-800 dark:text-zinc-300' )} > {Math.round(winrate)}% {entry.wins} {entry.losses} {entry.totalgames} {entry.streak > 0 ? ( {entry.streak} ) : entry.streak < 0 ? ( {Math.abs(entry.streak)} ) : ( 0 )}
) }) ) : (

No players found

)} {paddingBottom > 0 && ( )}
) } interface SortableHeaderProps extends ComponentPropsWithoutRef<'button'> { column: string label: string currentSort: string direction: 'asc' | 'desc' onSort: (column: string) => void } function SortableHeader({ column, label, currentSort, direction, onSort, className, ...rest }: SortableHeaderProps) { const isActive = currentSort === column return ( ) }