feat: filters in Ranking page
This commit is contained in:
parent
81b497517e
commit
8e8d59c335
@ -1,5 +1,5 @@
|
||||
// app/rankings/page.tsx
|
||||
// Rankings with pagination and adjustable page size
|
||||
// Rankings with pagination, adjustable page size, and filters
|
||||
|
||||
import { query } from '@/lib/db';
|
||||
import { TrophyIcon } from '@/components/ui/icons';
|
||||
@ -9,18 +9,97 @@ export const dynamic = "force-dynamic";
|
||||
interface RankingDriver {
|
||||
driver_guid: string;
|
||||
driver_name: string;
|
||||
driver_team: string | null;
|
||||
car_model: string | null;
|
||||
user_rank: number;
|
||||
laps_completed: number;
|
||||
cuts_alltime: number;
|
||||
contacts_alltime: number;
|
||||
created_at: Date;
|
||||
}
|
||||
|
||||
interface FilterOptions {
|
||||
teams: string[];
|
||||
carModels: string[];
|
||||
}
|
||||
|
||||
const PAGE_SIZE_OPTIONS = [25, 50, 100];
|
||||
|
||||
async function getRankings(page: number = 1, pageSize: number = 50): Promise<{ drivers: RankingDriver[]; totalCount: number }> {
|
||||
async function getFilterOptions(): Promise<FilterOptions> {
|
||||
const teamsQuery = `
|
||||
SELECT DISTINCT driver_team
|
||||
FROM users
|
||||
WHERE driver_team IS NOT NULL AND driver_team != ''
|
||||
ORDER BY driver_team
|
||||
`;
|
||||
|
||||
const carsQuery = `
|
||||
SELECT DISTINCT car_model
|
||||
FROM users
|
||||
WHERE car_model IS NOT NULL AND car_model != ''
|
||||
ORDER BY car_model
|
||||
`;
|
||||
|
||||
const teams = await query(teamsQuery);
|
||||
const cars = await query(carsQuery);
|
||||
|
||||
return {
|
||||
teams: teams.map((t: any) => t.driver_team),
|
||||
carModels: cars.map((c: any) => c.car_model)
|
||||
};
|
||||
}
|
||||
|
||||
async function getRankings(
|
||||
page: number = 1,
|
||||
pageSize: number = 50,
|
||||
filters: {
|
||||
search?: string;
|
||||
class?: string;
|
||||
team?: string;
|
||||
carModel?: string;
|
||||
} = {}
|
||||
): Promise<{ drivers: RankingDriver[]; totalCount: number }> {
|
||||
const offset = (page - 1) * pageSize;
|
||||
|
||||
// Build WHERE clause
|
||||
const conditions: string[] = [];
|
||||
|
||||
if (filters.search) {
|
||||
conditions.push(`LOWER(driver_name) LIKE LOWER('%${filters.search}%')`);
|
||||
}
|
||||
|
||||
if (filters.class) {
|
||||
switch (filters.class) {
|
||||
case 'S':
|
||||
conditions.push('user_rank >= 5000');
|
||||
break;
|
||||
case 'A':
|
||||
conditions.push('user_rank >= 2500 AND user_rank < 5000');
|
||||
break;
|
||||
case 'B':
|
||||
conditions.push('user_rank >= 1750 AND user_rank < 2500');
|
||||
break;
|
||||
case 'C':
|
||||
conditions.push('user_rank >= 1250 AND user_rank < 1750');
|
||||
break;
|
||||
case 'D':
|
||||
conditions.push('user_rank < 1250');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (filters.team) {
|
||||
conditions.push(`driver_team = '${filters.team}'`);
|
||||
}
|
||||
|
||||
if (filters.carModel) {
|
||||
conditions.push(`car_model = '${filters.carModel}'`);
|
||||
}
|
||||
|
||||
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
|
||||
|
||||
// Get total count
|
||||
const countResult = await query(`SELECT COUNT(*) as count FROM users`);
|
||||
const countResult = await query(`SELECT COUNT(*) as count FROM users ${whereClause}`);
|
||||
const totalCount = countResult[0]?.count || 0;
|
||||
|
||||
// Get paginated results
|
||||
@ -28,10 +107,15 @@ async function getRankings(page: number = 1, pageSize: number = 50): Promise<{ d
|
||||
SELECT
|
||||
driver_guid,
|
||||
driver_name,
|
||||
driver_team,
|
||||
car_model,
|
||||
user_rank,
|
||||
laps_completed,
|
||||
cuts_alltime,
|
||||
contacts_alltime,
|
||||
created_at
|
||||
FROM users
|
||||
${whereClause}
|
||||
ORDER BY user_rank DESC
|
||||
LIMIT ${pageSize} OFFSET ${offset}
|
||||
`;
|
||||
@ -43,27 +127,55 @@ async function getRankings(page: number = 1, pageSize: number = 50): Promise<{ d
|
||||
};
|
||||
}
|
||||
|
||||
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' };
|
||||
function getDriverClass(rank: number): { name: string; color: string; code: string } {
|
||||
if (rank >= 5000) return { name: 'S CLASS', color: 'text-white', code: 'S' };
|
||||
if (rank >= 2500) return { name: 'A CLASS', color: 'text-white', code: 'A' };
|
||||
if (rank >= 1750) return { name: 'B CLASS', color: 'text-white/80', code: 'B' };
|
||||
if (rank >= 1250) return { name: 'C CLASS', color: 'text-white/60', code: 'C' };
|
||||
return { name: 'D CLASS', color: 'text-white/40', code: 'D' };
|
||||
}
|
||||
|
||||
function buildQueryString(params: Record<string, string | undefined>): string {
|
||||
const filtered = Object.entries(params)
|
||||
.filter(([_, value]) => value !== undefined && value !== '')
|
||||
.map(([key, value]) => `${key}=${encodeURIComponent(value!)}`)
|
||||
.join('&');
|
||||
return filtered ? `?${filtered}` : '';
|
||||
}
|
||||
|
||||
export default async function RankingsPage({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: Promise<{ page?: string; pageSize?: string }>;
|
||||
searchParams: Promise<{
|
||||
page?: string;
|
||||
pageSize?: string;
|
||||
search?: string;
|
||||
class?: string;
|
||||
team?: string;
|
||||
carModel?: string;
|
||||
}>;
|
||||
}) {
|
||||
// Await searchParams in Next.js 15+
|
||||
const params = await searchParams;
|
||||
const currentPage = parseInt(params.page || '1');
|
||||
const pageSize = parseInt(params.pageSize || '50');
|
||||
const searchQuery = params.search || '';
|
||||
const classFilter = params.class || '';
|
||||
const teamFilter = params.team || '';
|
||||
const carModelFilter = params.carModel || '';
|
||||
|
||||
const { drivers, totalCount } = await getRankings(currentPage, pageSize, {
|
||||
search: searchQuery,
|
||||
class: classFilter,
|
||||
team: teamFilter,
|
||||
carModel: carModelFilter
|
||||
});
|
||||
|
||||
const filterOptions = await getFilterOptions();
|
||||
|
||||
const { drivers, totalCount } = await getRankings(currentPage, pageSize);
|
||||
const totalPages = Math.ceil(totalCount / pageSize);
|
||||
const startRank = (currentPage - 1) * pageSize + 1;
|
||||
|
||||
const hasActiveFilters = searchQuery || classFilter || teamFilter || carModelFilter;
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -82,7 +194,7 @@ export default async function RankingsPage({
|
||||
Global driver rankings based on performance, consistency, and clean racing
|
||||
</p>
|
||||
<p className="text-white/40 text-sm">
|
||||
{totalCount.toLocaleString()} total drivers
|
||||
{totalCount.toLocaleString()} {hasActiveFilters ? 'filtered' : 'total'} drivers
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@ -112,100 +224,256 @@ export default async function RankingsPage({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Rankings Table */}
|
||||
<div className="max-w-7xl mx-auto px-6 py-12">
|
||||
{/* Filters & Controls */}
|
||||
<div className="max-w-7xl mx-auto px-6 py-8">
|
||||
<div className="border border-white/10 bg-black p-6 space-y-6">
|
||||
{/* Single Filter Form */}
|
||||
<form action="/rankings" method="get" className="space-y-6">
|
||||
<input type="hidden" name="page" value="1" />
|
||||
<input type="hidden" name="pageSize" value={pageSize} />
|
||||
|
||||
{/* Search Bar */}
|
||||
<div>
|
||||
<label className="block text-xs font-bold tracking-wider text-white/60 mb-2">
|
||||
SEARCH DRIVER
|
||||
</label>
|
||||
<div className="flex gap-2">
|
||||
<input
|
||||
type="text"
|
||||
name="search"
|
||||
defaultValue={searchQuery}
|
||||
placeholder="Enter driver name..."
|
||||
className="flex-1 px-4 py-3 bg-black border border-white/20 text-white placeholder-white/40 focus:border-white focus:outline-none"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Filter Grid */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
{/* Class Filter */}
|
||||
<div>
|
||||
<label className="block text-xs font-bold tracking-wider text-white/60 mb-2">
|
||||
CLASS
|
||||
</label>
|
||||
<select
|
||||
name="class"
|
||||
className="w-full px-4 py-3 bg-black border border-white/20 text-white focus:border-white focus:outline-none"
|
||||
defaultValue={classFilter}
|
||||
>
|
||||
<option value="">All Classes</option>
|
||||
<option value="S">S CLASS (5000+)</option>
|
||||
<option value="A">A CLASS (2500-4999)</option>
|
||||
<option value="B">B CLASS (1750-2499)</option>
|
||||
<option value="C">C CLASS (1250-1749)</option>
|
||||
<option value="D">D CLASS (0-1249)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Team Filter */}
|
||||
<div>
|
||||
<label className="block text-xs font-bold tracking-wider text-white/60 mb-2">
|
||||
TEAM
|
||||
</label>
|
||||
<select
|
||||
name="team"
|
||||
className="w-full px-4 py-3 bg-black border border-white/20 text-white focus:border-white focus:outline-none"
|
||||
defaultValue={teamFilter}
|
||||
>
|
||||
<option value="">All Teams</option>
|
||||
{filterOptions.teams.map(team => (
|
||||
<option key={team} value={team}>{team}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Car Model Filter */}
|
||||
<div>
|
||||
<label className="block text-xs font-bold tracking-wider text-white/60 mb-2">
|
||||
CAR MODEL
|
||||
</label>
|
||||
<select
|
||||
name="carModel"
|
||||
className="w-full px-4 py-3 bg-black border border-white/20 text-white focus:border-white focus:outline-none"
|
||||
defaultValue={carModelFilter}
|
||||
>
|
||||
<option value="">All Cars</option>
|
||||
{filterOptions.carModels.map(car => (
|
||||
<option key={car} value={car}>{car}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Submit Button */}
|
||||
<div className="flex justify-end">
|
||||
<button
|
||||
type="submit"
|
||||
className="px-8 py-3 border border-white/20 hover:border-white bg-black text-white transition-colors"
|
||||
>
|
||||
APPLY FILTERS
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{/* Active Filters & Clear */}
|
||||
{hasActiveFilters && (
|
||||
<div className="flex items-center justify-between pt-4 border-t border-white/10">
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<span className="text-xs text-white/40">ACTIVE FILTERS:</span>
|
||||
{searchQuery && (
|
||||
<span className="px-2 py-1 bg-white/10 text-xs border border-white/20">
|
||||
Search: {searchQuery}
|
||||
</span>
|
||||
)}
|
||||
{classFilter && (
|
||||
<span className="px-2 py-1 bg-white/10 text-xs border border-white/20">
|
||||
Class: {classFilter}
|
||||
</span>
|
||||
)}
|
||||
{teamFilter && (
|
||||
<span className="px-2 py-1 bg-white/10 text-xs border border-white/20">
|
||||
Team: {teamFilter}
|
||||
</span>
|
||||
)}
|
||||
{carModelFilter && (
|
||||
<span className="px-2 py-1 bg-white/10 text-xs border border-white/20">
|
||||
Car: {carModelFilter}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<Link
|
||||
href={`/rankings?pageSize=${pageSize}`}
|
||||
className="px-4 py-2 border border-white/20 hover:border-white text-xs transition-colors"
|
||||
>
|
||||
CLEAR ALL
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Page Size Selector */}
|
||||
<div className="mb-6 flex items-center justify-between border border-white/10 p-4 bg-black">
|
||||
<div className="mt-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>
|
||||
))}
|
||||
{PAGE_SIZE_OPTIONS.map((size) => {
|
||||
const newParams = buildQueryString({
|
||||
page: '1',
|
||||
pageSize: size.toString(),
|
||||
search: searchQuery,
|
||||
class: classFilter,
|
||||
team: teamFilter,
|
||||
carModel: carModelFilter
|
||||
});
|
||||
return (
|
||||
<Link
|
||||
key={size}
|
||||
href={`/rankings${newParams}`}
|
||||
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">
|
||||
{/* Rankings Table */}
|
||||
<div className="mt-6 border border-white/10 bg-black overflow-x-auto">
|
||||
<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">TEAM</th>
|
||||
<th className="px-6 py-4 text-left text-xs font-bold tracking-wider text-white/60">CAR</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>
|
||||
<th className="px-6 py-4 text-left text-xs font-bold tracking-wider text-white/60">CUTS</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 ? '▓' : '▒'}
|
||||
{drivers.length === 0 ? (
|
||||
<tr>
|
||||
<td colSpan={8} className="px-6 py-12 text-center text-white/40">
|
||||
No drivers found matching your filters
|
||||
</td>
|
||||
</tr>
|
||||
) : (
|
||||
drivers.map((driver, index) => {
|
||||
const driverClass = getDriverClass(driver.user_rank);
|
||||
const globalRank = startRank + index;
|
||||
const isPodium = globalRank <= 3 && !hasActiveFilters;
|
||||
|
||||
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 && !hasActiveFilters ? 'text-white' : ''}
|
||||
${globalRank === 2 && !hasActiveFilters ? 'text-white/90' : ''}
|
||||
${globalRank === 3 && !hasActiveFilters ? 'text-white/80' : ''}
|
||||
${(globalRank > 3 || hasActiveFilters) ? 'text-white/60' : ''}
|
||||
`}>
|
||||
{String(globalRank).padStart(2, '0')}
|
||||
</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="px-6 py-4">
|
||||
<span className="tracking-tight text-base" style={{ letterSpacing: "0.04em" }}>
|
||||
{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>
|
||||
);
|
||||
})}
|
||||
{isPodium && (
|
||||
<span className="ml-2 text-xs text-white/40">
|
||||
{globalRank === 1 ? '█' : globalRank === 2 ? '▓' : '▒'}
|
||||
</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="px-6 py-4">
|
||||
<span className="tracking-tight text-base" style={{ letterSpacing: "0.04em" }}>
|
||||
{driver.driver_name}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-6 py-4">
|
||||
<span className="text-white/60 text-sm">
|
||||
{driver.driver_team || '—'}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-6 py-4">
|
||||
<span className="text-white/60 text-sm">
|
||||
{driver.car_model || '—'}
|
||||
</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/50 font-mono text-sm">
|
||||
{driver.cuts_alltime.toLocaleString()}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@ -220,7 +488,14 @@ export default async function RankingsPage({
|
||||
{/* First Page */}
|
||||
{currentPage > 1 && (
|
||||
<Link
|
||||
href={`/rankings?page=1&pageSize=${pageSize}`}
|
||||
href={`/rankings${buildQueryString({
|
||||
page: '1',
|
||||
pageSize: pageSize.toString(),
|
||||
search: searchQuery,
|
||||
class: classFilter,
|
||||
team: teamFilter,
|
||||
carModel: carModelFilter
|
||||
})}`}
|
||||
className="px-3 py-2 border border-white/20 hover:border-white/40 transition-colors text-sm bg-black"
|
||||
>
|
||||
««
|
||||
@ -230,7 +505,14 @@ export default async function RankingsPage({
|
||||
{/* Previous Page */}
|
||||
{currentPage > 1 && (
|
||||
<Link
|
||||
href={`/rankings?page=${currentPage - 1}&pageSize=${pageSize}`}
|
||||
href={`/rankings${buildQueryString({
|
||||
page: (currentPage - 1).toString(),
|
||||
pageSize: pageSize.toString(),
|
||||
search: searchQuery,
|
||||
class: classFilter,
|
||||
team: teamFilter,
|
||||
carModel: carModelFilter
|
||||
})}`}
|
||||
className="px-3 py-2 border border-white/20 hover:border-white/40 transition-colors text-sm bg-black"
|
||||
>
|
||||
‹ PREV
|
||||
@ -253,7 +535,14 @@ export default async function RankingsPage({
|
||||
return (
|
||||
<Link
|
||||
key={pageNum}
|
||||
href={`/rankings?page=${pageNum}&pageSize=${pageSize}`}
|
||||
href={`/rankings${buildQueryString({
|
||||
page: pageNum.toString(),
|
||||
pageSize: pageSize.toString(),
|
||||
search: searchQuery,
|
||||
class: classFilter,
|
||||
team: teamFilter,
|
||||
carModel: carModelFilter
|
||||
})}`}
|
||||
className={`px-4 py-2 border text-sm transition-colors bg-black ${
|
||||
currentPage === pageNum
|
||||
? 'border-white text-white'
|
||||
@ -268,7 +557,14 @@ export default async function RankingsPage({
|
||||
{/* Next Page */}
|
||||
{currentPage < totalPages && (
|
||||
<Link
|
||||
href={`/rankings?page=${currentPage + 1}&pageSize=${pageSize}`}
|
||||
href={`/rankings${buildQueryString({
|
||||
page: (currentPage + 1).toString(),
|
||||
pageSize: pageSize.toString(),
|
||||
search: searchQuery,
|
||||
class: classFilter,
|
||||
team: teamFilter,
|
||||
carModel: carModelFilter
|
||||
})}`}
|
||||
className="px-3 py-2 border border-white/20 hover:border-white/40 transition-colors text-sm bg-black"
|
||||
>
|
||||
NEXT ›
|
||||
@ -278,7 +574,14 @@ export default async function RankingsPage({
|
||||
{/* Last Page */}
|
||||
{currentPage < totalPages && (
|
||||
<Link
|
||||
href={`/rankings?page=${totalPages}&pageSize=${pageSize}`}
|
||||
href={`/rankings${buildQueryString({
|
||||
page: totalPages.toString(),
|
||||
pageSize: pageSize.toString(),
|
||||
search: searchQuery,
|
||||
class: classFilter,
|
||||
team: teamFilter,
|
||||
carModel: carModelFilter
|
||||
})}`}
|
||||
className="px-3 py-2 border border-white/20 hover:border-white/40 transition-colors text-sm bg-black"
|
||||
>
|
||||
»»
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user