Files
game-marathon/frontend/src/pages/LeaderboardPage.tsx
2025-12-14 02:42:32 +07:00

120 lines
4.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState, useEffect } from 'react'
import { useParams, Link } from 'react-router-dom'
import { marathonsApi } from '@/api'
import type { LeaderboardEntry } from '@/types'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui'
import { useAuthStore } from '@/store/auth'
import { Trophy, Flame, ArrowLeft, Loader2 } from 'lucide-react'
export function LeaderboardPage() {
const { id } = useParams<{ id: string }>()
const user = useAuthStore((state) => state.user)
const [leaderboard, setLeaderboard] = useState<LeaderboardEntry[]>([])
const [isLoading, setIsLoading] = useState(true)
useEffect(() => {
loadLeaderboard()
}, [id])
const loadLeaderboard = async () => {
if (!id) return
try {
const data = await marathonsApi.getLeaderboard(parseInt(id))
setLeaderboard(data)
} catch (error) {
console.error('Failed to load leaderboard:', error)
} finally {
setIsLoading(false)
}
}
const getRankIcon = (rank: number) => {
switch (rank) {
case 1:
return <Trophy className="w-6 h-6 text-yellow-500" />
case 2:
return <Trophy className="w-6 h-6 text-gray-400" />
case 3:
return <Trophy className="w-6 h-6 text-amber-700" />
default:
return <span className="text-gray-500 font-mono w-6 text-center">{rank}</span>
}
}
if (isLoading) {
return (
<div className="flex justify-center py-12">
<Loader2 className="w-8 h-8 animate-spin text-primary-500" />
</div>
)
}
return (
<div className="max-w-2xl mx-auto">
<div className="flex items-center gap-4 mb-8">
<Link to={`/marathons/${id}`} className="text-gray-400 hover:text-white">
<ArrowLeft className="w-6 h-6" />
</Link>
<h1 className="text-2xl font-bold text-white">Таблица лидеров</h1>
</div>
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Trophy className="w-5 h-5 text-yellow-500" />
Рейтинг
</CardTitle>
</CardHeader>
<CardContent>
{leaderboard.length === 0 ? (
<p className="text-center text-gray-400 py-8">Пока нет участников</p>
) : (
<div className="space-y-2">
{leaderboard.map((entry) => (
<div
key={entry.user.id}
className={`flex items-center gap-4 p-4 rounded-lg ${
entry.user.id === user?.id
? 'bg-primary-500/20 border border-primary-500/50'
: 'bg-gray-900'
}`}
>
<div className="flex items-center justify-center w-8">
{getRankIcon(entry.rank)}
</div>
<div className="flex-1">
<div className="font-medium text-white">
{entry.user.nickname}
{entry.user.id === user?.id && (
<span className="ml-2 text-xs text-primary-400">(Вы)</span>
)}
</div>
<div className="text-sm text-gray-400">
{entry.completed_count} выполнено, {entry.dropped_count} пропущено
</div>
</div>
{entry.current_streak > 0 && (
<div className="flex items-center gap-1 text-yellow-500">
<Flame className="w-4 h-4" />
<span className="text-sm">{entry.current_streak}</span>
</div>
)}
<div className="text-right">
<div className="text-xl font-bold text-primary-400">
{entry.total_points}
</div>
<div className="text-xs text-gray-500">очков</div>
</div>
</div>
))}
</div>
)}
</CardContent>
</Card>
</div>
)
}