import { useState, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import { api, LeaderboardEntry, LeaderboardResponse } from '../api/client'; import styles from './LeaderboardPage.module.css'; function initials(name: string) { return name.split(' ').map(n => n[0]).join('').toUpperCase().slice(0, 2); } function TrendIcon({ entry, prev }: { entry: LeaderboardEntry; prev: LeaderboardEntry | undefined }) { if (!prev) return β†’; if (entry.total_points > prev.total_points) return β†—; if (entry.total_points < prev.total_points) return β†˜; return β†’; } export default function LeaderboardPage() { const [data, setData] = useState(null); const [tippableCount, setTippableCount] = useState(0); const [loading, setLoading] = useState(true); const navigate = useNavigate(); useEffect(() => { Promise.all([ api.getLeaderboard(), api.getMatches(), ]).then(([lb, matches]) => { setData(lb); setTippableCount(matches.matches.filter(m => m.tippable && !m.userTip).length); }).finally(() => setLoading(false)); }, []); if (loading) return (
); if (!data) return null; const { entries, currentUserId, currentUserRank, totalParticipants } = data; const top3 = entries.slice(0, 3); const rest = entries.slice(3); // Podium order: 2nd left, 1st center, 3rd right type PodiumSlot = { entry: LeaderboardEntry; rank: 1 | 2 | 3; medal: string; colorClass: string; barHeight: string }; const podiumSlots: PodiumSlot[] = []; if (top3[1]) podiumSlots.push({ entry: top3[1], rank: 2, medal: 'πŸ₯ˆ', colorClass: styles.silver, barHeight: '64px' }); if (top3[0]) podiumSlots.push({ entry: top3[0], rank: 1, medal: 'πŸ₯‡', colorClass: styles.gold, barHeight: '96px' }); if (top3[2]) podiumSlots.push({ entry: top3[2], rank: 3, medal: 'πŸ₯‰', colorClass: styles.bronze, barHeight: '48px' }); return (

Rangliste

{totalParticipants} Teilnehmer{currentUserRank ? ` Β· Du: Platz ${currentUserRank}` : ''}
{entries.length === 0 ? (
Noch keine Punkte vergeben. Spiele mΓΌssen erst abgeschlossen sein.
) : ( <> {/* ── Podium ── */} {top3.length > 0 && (
{podiumSlots.map(({ entry, rank, medal, colorClass, barHeight }) => { const isMe = entry.user_id === currentUserId; const isFirst = rank === 1; return (
{medal}
{initials(entry.full_name)}
{entry.full_name.split(' ')[0]}{isMe ? ' (Ich)' : ''}
{entry.total_points.toLocaleString('de-DE')} Pkt
); })}
)} {/* ── List header ── */} {rest.length > 0 && ( <>
POS SPIELER TREND PUNKTE
{rest.map((entry, i) => { const isMe = entry.user_id === currentUserId; const prev = rest[i - 1]; return (
{entry.rank}
{initials(entry.full_name)}
{entry.full_name}{isMe ? ' (Ich)' : ''}
{entry.team &&
{entry.team}
} {isMe &&
AUFHOLJAGD!
}
{entry.total_points.toLocaleString('de-DE')}
); })}
)} {/* ── CTA Card ── */} {tippableCount > 0 && (
Punkte sichern!
{tippableCount} Spiel{tippableCount !== 1 ? 'e' : ''} noch ohne Tipp β€” kletter nach oben.
)} )}
); }