This commit is contained in:
2025-12-09 10:32:59 +01:00
parent 35f988f885
commit a780139692
26 changed files with 2224 additions and 344 deletions

View File

@@ -1,21 +1,55 @@
'use client';
import Link from 'next/link';
import { Result } from '@gridpilot/racing/domain/entities/Result';
import { Driver } from '@gridpilot/racing/domain/entities/Driver';
import type { PenaltyType } from '@gridpilot/racing/domain/entities/Penalty';
import { AlertTriangle, ExternalLink } from 'lucide-react';
/**
* Penalty data for display (can be domain Penalty or RacePenaltyDTO)
*/
interface PenaltyData {
driverId: string;
type: PenaltyType;
value?: number;
}
interface ResultsTableProps {
results: Result[];
drivers: Driver[];
pointsSystem: Record<number, number>;
fastestLapTime?: number;
penalties?: PenaltyData[];
currentDriverId?: string;
}
export default function ResultsTable({ results, drivers, pointsSystem, fastestLapTime }: ResultsTableProps) {
export default function ResultsTable({ results, drivers, pointsSystem, fastestLapTime, penalties = [], currentDriverId }: ResultsTableProps) {
const getDriver = (driverId: string): Driver | undefined => {
return drivers.find(d => d.id === driverId);
};
const getDriverName = (driverId: string): string => {
const driver = drivers.find(d => d.id === driverId);
const driver = getDriver(driverId);
return driver?.name || 'Unknown Driver';
};
const getDriverPenalties = (driverId: string): PenaltyData[] => {
return penalties.filter(p => p.driverId === driverId);
};
const getPenaltyDescription = (penalty: PenaltyData): string => {
const descriptions: Record<string, string> = {
time_penalty: `+${penalty.value}s time penalty`,
grid_penalty: `${penalty.value} place grid penalty`,
points_deduction: `-${penalty.value} points`,
disqualification: 'Disqualified',
warning: 'Warning',
license_points: `${penalty.value} license points`,
};
return descriptions[penalty.type] || penalty.type;
};
const formatLapTime = (seconds: number): string => {
const minutes = Math.floor(seconds / 60);
const secs = (seconds % 60).toFixed(3);
@@ -57,23 +91,70 @@ export default function ResultsTable({ results, drivers, pointsSystem, fastestLa
<th className="text-left py-3 px-4 text-sm font-semibold text-gray-400">Incidents</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">+/-</th>
<th className="text-left py-3 px-4 text-sm font-semibold text-gray-400">Penalties</th>
</tr>
</thead>
<tbody>
{results.map((result) => {
const positionChange = result.getPositionChange();
const isFastestLap = fastestLapTime && result.fastestLap === fastestLapTime;
const driverPenalties = getDriverPenalties(result.driverId);
const driver = getDriver(result.driverId);
const isCurrentUser = currentDriverId === result.driverId;
const isPodium = result.position <= 3;
return (
<tr
<tr
key={result.id}
className="border-b border-charcoal-outline/50 hover:bg-iron-gray/20 transition-colors"
className={`
border-b border-charcoal-outline/50 transition-colors
${isCurrentUser
? 'bg-gradient-to-r from-primary-blue/20 via-primary-blue/10 to-transparent hover:from-primary-blue/30'
: 'hover:bg-iron-gray/20'}
`}
>
<td className="py-3 px-4">
<span className="text-white font-semibold">{result.position}</span>
<div className={`
inline-flex items-center justify-center w-8 h-8 rounded-lg font-bold text-sm
${result.position === 1 ? 'bg-yellow-500/20 text-yellow-400' :
result.position === 2 ? 'bg-gray-400/20 text-gray-300' :
result.position === 3 ? 'bg-amber-600/20 text-amber-500' :
'text-white'}
`}>
{result.position}
</div>
</td>
<td className="py-3 px-4">
<span className="text-white">{getDriverName(result.driverId)}</span>
<div className="flex items-center gap-3">
{driver ? (
<>
<div className={`
w-8 h-8 rounded-full flex items-center justify-center text-sm font-bold flex-shrink-0
${isCurrentUser ? 'bg-primary-blue/30 text-primary-blue ring-2 ring-primary-blue/50' : 'bg-iron-gray text-gray-400'}
`}>
{driver.name.charAt(0)}
</div>
<Link
href={`/drivers/${driver.id}`}
className={`
flex items-center gap-1.5 group
${isCurrentUser ? 'text-primary-blue font-semibold' : 'text-white hover:text-primary-blue'}
transition-colors
`}
>
<span className="group-hover:underline">{driver.name}</span>
{isCurrentUser && (
<span className="px-1.5 py-0.5 text-[10px] font-bold bg-primary-blue text-white rounded-full uppercase">
You
</span>
)}
<ExternalLink className="w-3 h-3 opacity-0 group-hover:opacity-100 transition-opacity" />
</Link>
</>
) : (
<span className="text-white">{getDriverName(result.driverId)}</span>
)}
</div>
</td>
<td className="py-3 px-4">
<span className={isFastestLap ? 'text-performance-green font-medium' : 'text-white'}>
@@ -93,6 +174,23 @@ export default function ResultsTable({ results, drivers, pointsSystem, fastestLa
{getPositionChangeText(positionChange)}
</span>
</td>
<td className="py-3 px-4">
{driverPenalties.length > 0 ? (
<div className="flex flex-col gap-1">
{driverPenalties.map((penalty, idx) => (
<div
key={idx}
className="flex items-center gap-1.5 text-xs text-red-400"
>
<AlertTriangle className="w-3 h-3" />
<span>{getPenaltyDescription(penalty)}</span>
</div>
))}
</div>
) : (
<span className="text-gray-500"></span>
)}
</td>
</tr>
);
})}