Files
euchre_camp/src/app/rankings/RankingsClient.tsx
T
david ce13bae949
Pull Request / unit-tests (pull_request) Successful in 1m5s
Release / release (push) Failing after 16s
Pull Request / e2e-tests (pull_request) Failing after 4m7s
Pull Request / analyze-bump-type (pull_request) Has been skipped
fix: prevent content overflow on right side of screen
Fixes #23

- Added overflow-x-hidden to body in root layout as defensive measure
- Changed table containers from overflow-hidden to overflow-x-auto
  in admin/players page and RankingsClient (3 tables)
- Added min-w-0 and overflow-hidden to Navigation flex containers
  to prevent links from pushing content off screen
- Added flex-shrink-0 to EuchreCamp wordmark link
2026-04-27 03:12:53 +00:00

242 lines
10 KiB
TypeScript

"use client"
import { useState } from "react"
import Link from "next/link"
// Define the Player type with all rating relationships
interface PlayerWithRatings {
id: number
name: string
currentElo: number
gamesPlayed: number
wins: number
losses: number
eloRating: { rating: number; gamesPlayed: number; wins: number; losses: number } | null
glicko2Rating: { rating: number; gamesPlayed: number; wins: number; losses: number } | null
openSkillRating: { rating: number; gamesPlayed: number; wins: number; losses: number } | null
}
export default function RankingsClient({ players }: { players: PlayerWithRatings[] }) {
const [activeTab, setActiveTab] = useState<"elo" | "openskill" | "glicko2">("elo")
return (
<>
{/* Tab Navigation */}
<div className="border-b border-gray-200 mb-6">
<nav className="-mb-px flex space-x-8">
<button
onClick={() => setActiveTab("elo")}
className={`${
activeTab === "elo"
? "border-green-500 text-green-600"
: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300"
} whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm`}
>
Elo Rating
</button>
<button
onClick={() => setActiveTab("openskill")}
className={`${
activeTab === "openskill"
? "border-green-500 text-green-600"
: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300"
} whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm`}
>
OpenSkill
</button>
<button
onClick={() => setActiveTab("glicko2")}
className={`${
activeTab === "glicko2"
? "border-green-500 text-green-600"
: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300"
} whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm`}
>
Glicko2
</button>
</nav>
</div>
{/* Elo Rating Tab */}
{activeTab === "elo" && (
<div className="bg-white shadow overflow-x-auto sm:rounded-lg">
<h2 className="text-xl font-semibold text-gray-900 px-6 py-4 border-b">
Elo Rating Rankings
</h2>
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Rank
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Player
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Elo Rating
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Games Played
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Win Rate
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{players.map((player, index) => (
<tr key={player.id} className="hover:bg-gray-50">
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
{index + 1}
</td>
<td className="px-6 py-4 whitespace-nowrap">
<Link
href={`/players/${player.id}/profile`}
className="text-green-600 hover:text-green-900"
>
{player.name}
</Link>
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
{player.eloRating?.rating ?? player.currentElo}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{player.gamesPlayed}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{player.gamesPlayed > 0
? `${((player.wins / player.gamesPlayed) * 100).toFixed(1)}%`
: "N/A"}
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
{/* OpenSkill Rating Tab */}
{activeTab === "openskill" && (
<div className="bg-white shadow overflow-x-auto sm:rounded-lg">
<h2 className="text-xl font-semibold text-gray-900 px-6 py-4 border-b">
OpenSkill Rating Rankings
</h2>
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Rank
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Player
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
OpenSkill Rating
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Games Played
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Win Rate
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{players
.filter(p => p.openSkillRating)
.sort((a, b) => (b.openSkillRating?.rating ?? 0) - (a.openSkillRating?.rating ?? 0))
.map((player, index) => (
<tr key={player.id} className="hover:bg-gray-50">
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
{index + 1}
</td>
<td className="px-6 py-4 whitespace-nowrap">
<Link
href={`/players/${player.id}/profile`}
className="text-green-600 hover:text-green-900"
>
{player.name}
</Link>
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
{player.openSkillRating?.rating?.toFixed(1) ?? 'N/A'}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{player.openSkillRating?.gamesPlayed ?? 0}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{player.openSkillRating && player.openSkillRating.gamesPlayed > 0
? `${((player.openSkillRating.wins / player.openSkillRating.gamesPlayed) * 100).toFixed(1)}%`
: "N/A"}
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
{/* Glicko2 Rating Tab */}
{activeTab === "glicko2" && (
<div className="bg-white shadow overflow-x-auto sm:rounded-lg">
<h2 className="text-xl font-semibold text-gray-900 px-6 py-4 border-b">
Glicko2 Rating Rankings
</h2>
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Rank
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Player
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Glicko2 Rating
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Games Played
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Win Rate
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{players
.filter(p => p.glicko2Rating)
.sort((a, b) => (b.glicko2Rating?.rating ?? 0) - (a.glicko2Rating?.rating ?? 0))
.map((player, index) => (
<tr key={player.id} className="hover:bg-gray-50">
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
{index + 1}
</td>
<td className="px-6 py-4 whitespace-nowrap">
<Link
href={`/players/${player.id}/profile`}
className="text-green-600 hover:text-green-900"
>
{player.name}
</Link>
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
{player.glicko2Rating?.rating?.toFixed(1) ?? 'N/A'}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{player.glicko2Rating?.gamesPlayed ?? 0}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{player.glicko2Rating && player.glicko2Rating.gamesPlayed > 0
? `${((player.glicko2Rating.wins / player.glicko2Rating.gamesPlayed) * 100).toFixed(1)}%`
: "N/A"}
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</>
)
}