This commit is contained in:
2025-12-04 23:31:55 +01:00
parent 9fa21a488a
commit fb509607c1
96 changed files with 5839 additions and 1609 deletions

View File

@@ -1,19 +1,18 @@
'use client';
import Link from 'next/link';
import { Standing } from '@gridpilot/racing/domain/entities/Standing';
import { Driver } from '@gridpilot/racing/domain/entities/Driver';
import DriverIdentity from '@/components/drivers/DriverIdentity';
import type { DriverDTO } from '@gridpilot/racing/application/dto/DriverDTO';
import type { LeagueDriverSeasonStatsDTO } from '@gridpilot/racing/application/dto/LeagueDriverSeasonStatsDTO';
interface StandingsTableProps {
standings: Standing[];
drivers: Driver[];
standings: LeagueDriverSeasonStatsDTO[];
drivers: DriverDTO[];
leagueId: string;
}
export default function StandingsTable({ standings, drivers, leagueId }: StandingsTableProps) {
const getDriverName = (driverId: string): string => {
const driver = drivers.find((d) => d.id === driverId);
return driver?.name || 'Unknown Driver';
const getDriver = (driverId: string): DriverDTO | undefined => {
return drivers.find((d) => d.id === driverId);
};
if (standings.length === 0) {
@@ -26,50 +25,121 @@ export default function StandingsTable({ standings, drivers, leagueId }: Standin
return (
<div className="overflow-x-auto">
<table className="w-full">
<table className="w-full text-sm">
<thead>
<tr className="border-b border-charcoal-outline">
<th className="text-left py-3 px-4 text-sm font-semibold text-gray-400">Pos</th>
<th className="text-left py-3 px-4 text-sm font-semibold text-gray-400">Driver</th>
<th className="text-left py-3 px-4 text-sm font-semibold text-gray-400">Points</th>
<th className="text-left py-3 px-4 text-sm font-semibold text-gray-400">Wins</th>
<th className="text-left py-3 px-4 text-sm font-semibold text-gray-400">Races</th>
<th className="text-left py-3 px-4 font-semibold text-gray-400">Pos</th>
<th className="text-left py-3 px-4 font-semibold text-gray-400">Driver</th>
<th className="text-left py-3 px-4 font-semibold text-gray-400">Team</th>
<th className="text-left py-3 px-4 font-semibold text-gray-400">Total Pts</th>
<th className="text-left py-3 px-4 font-semibold text-gray-400">Pts / Race</th>
<th className="text-left py-3 px-4 font-semibold text-gray-400">Started</th>
<th className="text-left py-3 px-4 font-semibold text-gray-400">Finished</th>
<th className="text-left py-3 px-4 font-semibold text-gray-400">DNF</th>
<th className="text-left py-3 px-4 font-semibold text-gray-400">NoShows</th>
<th className="text-left py-3 px-4 font-semibold text-gray-400">Penalty</th>
<th className="text-left py-3 px-4 font-semibold text-gray-400">Bonus</th>
<th className="text-left py-3 px-4 font-semibold text-gray-400">Avg Finish</th>
<th className="text-left py-3 px-4 font-semibold text-gray-400">Rating Δ</th>
</tr>
</thead>
<tbody>
{standings.map((standing) => {
const isLeader = standing.position === 1;
{standings.map((row) => {
const isLeader = row.position === 1;
const driver = getDriver(row.driverId);
const totalPointsLine =
row.penaltyPoints > 0
? `Total Points: ${row.totalPoints} (-${row.penaltyPoints} penalty)`
: `Total Points: ${row.totalPoints}`;
const ratingDelta =
row.ratingChange === null || row.ratingChange === 0
? '—'
: row.ratingChange > 0
? `+${row.ratingChange}`
: `${row.ratingChange}`;
return (
<tr
key={`${standing.leagueId}-${standing.driverId}`}
key={`${row.leagueId}-${row.driverId}`}
className="border-b border-charcoal-outline/50 hover:bg-iron-gray/20 transition-colors"
>
<td className="py-3 px-4">
<span className={`font-semibold ${isLeader ? 'text-yellow-500' : 'text-white'}`}>
{standing.position}
{row.position}
</span>
</td>
<td className="py-3 px-4">
<Link
href={`/drivers/${standing.driverId}?from=league&leagueId=${leagueId}`}
className={
isLeader
? 'text-white font-semibold hover:text-primary-blue transition-colors'
: 'text-white hover:text-primary-blue transition-colors'
}
>
{getDriverName(standing.driverId)}
</Link>
{driver ? (
<DriverIdentity
driver={driver}
href={`/drivers/${row.driverId}?from=league-standings&leagueId=${leagueId}`}
contextLabel={`P${row.position}`}
size="sm"
meta={totalPointsLine}
/>
) : (
<span className="text-white">Unknown Driver</span>
)}
</td>
<td className="py-3 px-4">
<span className="text-white font-medium">{standing.points}</span>
<span className="text-gray-300">
{row.teamName ?? '—'}
</span>
</td>
<td className="py-3 px-4">
<span className="text-white">{standing.wins}</span>
<div className="flex flex-col">
<span className="text-white font-medium">{row.totalPoints}</span>
{row.penaltyPoints > 0 || row.bonusPoints !== 0 ? (
<span className="text-xs text-gray-400">
base {row.basePoints}
{row.penaltyPoints > 0 && (
<span className="text-red-400"> {row.penaltyPoints}</span>
)}
{row.bonusPoints !== 0 && (
<span className="text-green-400"> +{row.bonusPoints}</span>
)}
</span>
) : null}
</div>
</td>
<td className="py-3 px-4">
<span className="text-white">{standing.racesCompleted}</span>
<span className="text-white">
{row.pointsPerRace.toFixed(2)}
</span>
</td>
<td className="py-3 px-4">
<span className="text-white">{row.racesStarted}</span>
</td>
<td className="py-3 px-4">
<span className="text-white">{row.racesFinished}</span>
</td>
<td className="py-3 px-4">
<span className="text-white">{row.dnfs}</span>
</td>
<td className="py-3 px-4">
<span className="text-white">{row.noShows}</span>
</td>
<td className="py-3 px-4">
<span className={row.penaltyPoints > 0 ? 'text-red-400' : 'text-gray-300'}>
{row.penaltyPoints > 0 ? `-${row.penaltyPoints}` : '—'}
</span>
</td>
<td className="py-3 px-4">
<span className={row.bonusPoints !== 0 ? 'text-green-400' : 'text-gray-300'}>
{row.bonusPoints !== 0 ? `+${row.bonusPoints}` : '—'}
</span>
</td>
<td className="py-3 px-4">
<span className="text-white">
{row.avgFinish !== null ? row.avgFinish.toFixed(2) : '—'}
</span>
</td>
<td className="py-3 px-4">
<span className={row.ratingChange && row.ratingChange > 0 ? 'text-green-400' : row.ratingChange && row.ratingChange < 0 ? 'text-red-400' : 'text-gray-300'}>
{ratingDelta}
</span>
</td>
</tr>
);