FODASSE QUE BORRAÇO

This commit is contained in:
2025-10-25 23:16:38 +01:00
parent 2f641217ce
commit ede5003530
21 changed files with 2745 additions and 143 deletions
+326
View File
@@ -0,0 +1,326 @@
// app/rankings/page.tsx
// Rankings with pagination and adjustable page size
import { query } from '@/lib/db';
import { TrophyIcon } from '@/components/ui/icons';
import Link from 'next/link';
interface RankingDriver {
driver_guid: string;
driver_name: string;
user_rank: number;
laps_completed: number;
created_at: Date;
}
const PAGE_SIZE_OPTIONS = [25, 50, 100];
async function getRankings(page: number = 1, pageSize: number = 50): Promise<{ drivers: RankingDriver[]; totalCount: number }> {
const offset = (page - 1) * pageSize;
// Get total count
const countResult = await query(`SELECT COUNT(*) as count FROM users`);
const totalCount = countResult[0]?.count || 0;
// Get paginated results
const sql = `
SELECT
driver_guid,
driver_name,
user_rank,
laps_completed,
created_at
FROM users
ORDER BY user_rank DESC
LIMIT ${pageSize} OFFSET ${offset}
`;
const rows = await query(sql);
return {
drivers: rows as RankingDriver[],
totalCount: totalCount
};
}
function getDriverClass(rank: number): { name: string; color: string } {
if (rank >= 5000) return { name: 'S CLASS', color: 'text-white' };
if (rank >= 2500) return { name: 'A CLASS', color: 'text-white' };
if (rank >= 1750) return { name: 'B CLASS', color: 'text-white/80' };
if (rank >= 1250) return { name: 'C CLASS', color: 'text-white/60' };
return { name: 'D CLASS', color: 'text-white/40' };
}
export default async function RankingsPage({
searchParams,
}: {
searchParams: Promise<{ page?: string; pageSize?: string }>;
}) {
// Await searchParams in Next.js 15+
const params = await searchParams;
const currentPage = parseInt(params.page || '1');
const pageSize = parseInt(params.pageSize || '50');
const { drivers, totalCount } = await getRankings(currentPage, pageSize);
const totalPages = Math.ceil(totalCount / pageSize);
const startRank = (currentPage - 1) * pageSize + 1;
return (
<>
{/* Hero */}
<div className="relative border-b border-white/10 grid-overlay">
<div className="max-w-7xl mx-auto px-6 py-16">
<div className="space-y-4">
<div className="inline-flex items-center space-x-2 px-3 py-1 border border-white/20 bg-black">
<TrophyIcon className="w-4 h-4" />
<span className="text-xs font-medium tracking-wider">DRIVER RANKINGS</span>
</div>
<h1 className="text-6xl font-bold tracking-tight">
LEADERBOARD
</h1>
<p className="text-white/60 text-lg max-w-3xl">
Global driver rankings based on performance, consistency, and clean racing
</p>
<p className="text-white/40 text-sm">
{totalCount.toLocaleString()} total drivers
</p>
</div>
{/* Class Explanation */}
<div className="mt-12 grid grid-cols-1 md:grid-cols-4 gap-4">
<ClassCard
name="D CLASS"
range="0 - 1250"
description="Entry level — learning the fundamentals"
/>
<ClassCard
name="C CLASS"
range="1250 - 1750"
description="Intermediate — consistent performance"
/>
<ClassCard
name="B CLASS"
range="1750 - 2500"
description="Advanced — competitive racing"
/>
<ClassCard
name="A CLASS"
range="2500+"
description="Elite — top-tier drivers"
/>
</div>
</div>
</div>
{/* Rankings Table */}
<div className="max-w-7xl mx-auto px-6 py-12">
{/* Page Size Selector */}
<div className="mb-6 flex items-center justify-between border border-white/10 p-4 bg-black">
<div className="text-sm text-white/60">
Items per page:
</div>
<div className="flex items-center space-x-2">
{PAGE_SIZE_OPTIONS.map((size) => (
<Link
key={size}
href={`/rankings?page=1&pageSize=${size}`}
className={`px-4 py-2 border text-sm transition-colors bg-black ${
pageSize === size
? 'border-white text-white'
: 'border-white/20 text-white/60 hover:border-white/40 hover:text-white'
}`}
>
{size}
</Link>
))}
</div>
</div>
<div className="border border-white/10 bg-black">
<table className="w-full">
<thead>
<tr className="border-b border-white/10 bg-white/5">
<th className="px-6 py-4 text-left text-xs font-bold tracking-wider text-white/60">RANK</th>
<th className="px-6 py-4 text-left text-xs font-bold tracking-wider text-white/60">DRIVER</th>
<th className="px-6 py-4 text-left text-xs font-bold tracking-wider text-white/60">RATING</th>
<th className="px-6 py-4 text-left text-xs font-bold tracking-wider text-white/60">CLASS</th>
<th className="px-6 py-4 text-left text-xs font-bold tracking-wider text-white/60">LAPS</th>
<th className="px-6 py-4 text-left text-xs font-bold tracking-wider text-white/60">STEAM ID</th>
</tr>
</thead>
<tbody>
{drivers.map((driver, index) => {
const driverClass = getDriverClass(driver.user_rank);
const globalRank = startRank + index;
const isPodium = globalRank <= 3;
return (
<tr
key={driver.driver_guid}
className={`
border-b border-white/5 hover:bg-white/5 transition-colors
${isPodium ? 'bg-white/[0.02]' : ''}
`}
>
<td className="px-6 py-4">
<span className={`
text-base font-bold tracking-tight
${globalRank === 1 ? 'text-white' : ''}
${globalRank === 2 ? 'text-white/90' : ''}
${globalRank === 3 ? 'text-white/80' : ''}
${globalRank > 3 ? 'text-white/60' : ''}
`}>
{String(globalRank).padStart(2, '0')}
</span>
{isPodium && (
<span className="ml-2 text-xs text-white/40">
{globalRank === 1 ? '█' : globalRank === 2 ? '▓' : '▒'}
</span>
)}
</td>
<td className="px-6 py-4">
<span className="font-semibold tracking-tight text-base">
{driver.driver_name}
</span>
</td>
<td className="px-6 py-4">
<span className="font-mono text-base">
{driver.user_rank.toLocaleString()}
</span>
</td>
<td className="px-6 py-4">
<span className={`inline-block px-3 py-1 border border-white/20 text-xs font-bold tracking-wider bg-black ${driverClass.color}`}>
{driverClass.name}
</span>
</td>
<td className="px-6 py-4">
<span className="text-white/70 font-mono text-sm">
{driver.laps_completed.toLocaleString()}
</span>
</td>
<td className="px-6 py-4">
<span className="text-white/40 font-mono text-xs">
{driver.driver_guid}
</span>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
{/* Pagination */}
{totalPages > 1 && (
<div className="mt-8 flex items-center justify-between border border-white/10 p-4 bg-black">
<div className="text-sm text-white/60">
Page {currentPage} of {totalPages} Showing {startRank}-{Math.min(startRank + pageSize - 1, totalCount)} of {totalCount.toLocaleString()}
</div>
<div className="flex items-center space-x-2">
{/* First Page */}
{currentPage > 1 && (
<Link
href={`/rankings?page=1&pageSize=${pageSize}`}
className="px-3 py-2 border border-white/20 hover:border-white/40 transition-colors text-sm bg-black"
>
««
</Link>
)}
{/* Previous Page */}
{currentPage > 1 && (
<Link
href={`/rankings?page=${currentPage - 1}&pageSize=${pageSize}`}
className="px-3 py-2 border border-white/20 hover:border-white/40 transition-colors text-sm bg-black"
>
PREV
</Link>
)}
{/* Page Numbers */}
{Array.from({ length: Math.min(5, totalPages) }, (_, i) => {
let pageNum;
if (totalPages <= 5) {
pageNum = i + 1;
} else if (currentPage <= 3) {
pageNum = i + 1;
} else if (currentPage >= totalPages - 2) {
pageNum = totalPages - 4 + i;
} else {
pageNum = currentPage - 2 + i;
}
return (
<Link
key={pageNum}
href={`/rankings?page=${pageNum}&pageSize=${pageSize}`}
className={`px-4 py-2 border text-sm transition-colors bg-black ${
currentPage === pageNum
? 'border-white text-white'
: 'border-white/20 text-white/60 hover:border-white/40 hover:text-white'
}`}
>
{pageNum}
</Link>
);
})}
{/* Next Page */}
{currentPage < totalPages && (
<Link
href={`/rankings?page=${currentPage + 1}&pageSize=${pageSize}`}
className="px-3 py-2 border border-white/20 hover:border-white/40 transition-colors text-sm bg-black"
>
NEXT
</Link>
)}
{/* Last Page */}
{currentPage < totalPages && (
<Link
href={`/rankings?page=${totalPages}&pageSize=${pageSize}`}
className="px-3 py-2 border border-white/20 hover:border-white/40 transition-colors text-sm bg-black"
>
»»
</Link>
)}
</div>
</div>
)}
{/* Info Box */}
<div className="mt-8 border border-white/10 p-6 topo-lines-dense bg-black">
<h3 className="text-sm font-bold tracking-wider text-white/60 mb-4">RANKING SYSTEM</h3>
<div className="space-y-2 text-sm text-white/60">
<p> Rating increases with <span className="text-white">clean racing</span> and <span className="text-white">fast lap times</span></p>
<p> Penalties applied for <span className="text-white/40">track cuts</span> and <span className="text-white/40">collisions</span></p>
<p> Class promotion based on consistent performance over multiple sessions</p>
</div>
</div>
</div>
</>
);
}
function ClassCard({
name,
range,
description
}: {
name: string;
range: string;
description: string;
}) {
return (
<div className="border border-white/10 p-4 sharp-border">
<div className="text-xs font-bold tracking-wider text-white/60 mb-2">
{name}
</div>
<div className="text-2xl font-bold tracking-tight mb-1">
{range}
</div>
<div className="text-sm text-white/50">
{description}
</div>
</div>
);
}