151 lines
6.4 KiB
TypeScript
151 lines
6.4 KiB
TypeScript
'use client';
|
||
|
||
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: LeagueDriverSeasonStatsDTO[];
|
||
drivers: DriverDTO[];
|
||
leagueId: string;
|
||
}
|
||
|
||
export default function StandingsTable({ standings, drivers, leagueId }: StandingsTableProps) {
|
||
const getDriver = (driverId: string): DriverDTO | undefined => {
|
||
return drivers.find((d) => d.id === driverId);
|
||
};
|
||
|
||
if (standings.length === 0) {
|
||
return (
|
||
<div className="text-center py-8 text-gray-400">
|
||
No standings available
|
||
</div>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<div className="overflow-x-auto">
|
||
<table className="w-full text-sm">
|
||
<thead>
|
||
<tr className="border-b border-charcoal-outline">
|
||
<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">No‑Shows</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((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={`${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'}`}>
|
||
{row.position}
|
||
</span>
|
||
</td>
|
||
<td className="py-3 px-4">
|
||
{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-gray-300">
|
||
{row.teamName ?? '—'}
|
||
</span>
|
||
</td>
|
||
<td className="py-3 px-4">
|
||
<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">
|
||
{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>
|
||
);
|
||
})}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
);
|
||
} |