4 Commits

Author SHA1 Message Date
3ba68ac973 add 0 fallaback 2026-03-01 15:04:09 +01:00
67a72f9a92 fix leaderboard 2026-03-01 14:33:21 +01:00
51c45ad4a9 fix build errors 2026-03-01 12:23:38 +01:00
61f0b69176 Merge branch 'feature/leaderboard' 2026-03-01 12:17:13 +01:00
3 changed files with 20 additions and 11 deletions

View File

@@ -40,13 +40,17 @@ export function LeaderboardManager({ serverId, initialData }: LeaderboardManager
const handleDriversChange = (newDrivers: LeaderboardDriver[]) => { const handleDriversChange = (newDrivers: LeaderboardDriver[]) => {
const oldDrivers = draft.drivers; const oldDrivers = draft.drivers;
// Build index map: for each position in newDrivers, what was the old index? // Build index map by matching drivers by initials (their stable identifier)
const indexMap = newDrivers.map((d) => oldDrivers.indexOf(d)); // For each position in newDrivers, find where that driver was in oldDrivers
const indexMap = newDrivers.map((d) => {
const oldIndex = oldDrivers.findIndex((old) => old.initials === d.initials);
return oldIndex; // -1 if new driver, otherwise the old index
});
// If a driver was added (indexOf returns -1), it's new // If a driver was added (findIndex returns -1), it's new, start with DNS
const newTracks = draft.tracks.map((t) => ({ const newTracks = draft.tracks.map((t) => ({
...t, ...t,
results: indexMap.map((i) => (i === -1 ? 0 : (t.results[i] ?? 0))) results: indexMap.map((i) => (i === -1 ? 'DNS' : (t.results[i] ?? 'DNS')))
})); }));
setDraft({ ...draft, drivers: newDrivers, tracks: newTracks }); setDraft({ ...draft, drivers: newDrivers, tracks: newTracks });

View File

@@ -16,7 +16,7 @@ export function LeaderboardStandings({ leaderboard }: LeaderboardStandingsProps)
(t) => t.fastestLapInitials === driver.initials (t) => t.fastestLapInitials === driver.initials
).length; ).length;
const totalPoints = const totalPoints =
trackResults.reduce((sum, r) => sum + (typeof r === 'number' ? r : 0), 0) + trackResults.reduce((sum: number, r) => sum + (typeof r === 'number' ? r : 0), 0) +
fastestLapCount * leaderboard.flPoints.points; fastestLapCount * leaderboard.flPoints.points;
return { driver, driverIndex: idx, totalPoints, trackResults, fastestLapCount }; return { driver, driverIndex: idx, totalPoints, trackResults, fastestLapCount };
}) })
@@ -100,9 +100,7 @@ export function LeaderboardStandings({ leaderboard }: LeaderboardStandingsProps)
<td className="px-2 py-2 text-center text-xs text-purple-400"> <td className="px-2 py-2 text-center text-xs text-purple-400">
{row.fastestLapCount > 0 ? `×${row.fastestLapCount}` : '—'} {row.fastestLapCount > 0 ? `×${row.fastestLapCount}` : '—'}
</td> </td>
<td className="px-2 py-2 text-center font-bold text-yellow-400"> <td className="px-2 py-2 text-center font-bold text-yellow-400">{row.totalPoints}</td>
{row.totalPoints}
</td>
</tr> </tr>
))} ))}
</tbody> </tbody>

View File

@@ -10,20 +10,27 @@ interface ResultInputProps {
function ResultInput({ value, pointsTable, onChange }: ResultInputProps) { function ResultInput({ value, pointsTable, onChange }: ResultInputProps) {
const knownPoints = Array.from(new Set(pointsTable.map((e) => e.points))).sort((a, b) => b - a); const knownPoints = Array.from(new Set(pointsTable.map((e) => e.points))).sort((a, b) => b - a);
// Fallback 0 to DNS if 0 is not a valid points option
const displayValue = value === 0 && !knownPoints.includes(0) ? 'DNS' : value;
// Ensure the current numeric value is always included in options
const allPoints = typeof displayValue === 'number'
? Array.from(new Set([...knownPoints, displayValue])).sort((a, b) => b - a)
: knownPoints;
const handleChange = (raw: string) => { const handleChange = (raw: string) => {
if (raw === 'DNF') return onChange('DNF'); if (raw === 'DNF') return onChange('DNF');
if (raw === 'DNS') return onChange('DNS'); if (raw === 'DNS') return onChange('DNS');
onChange(parseInt(raw) || 0); const parsed = parseInt(raw);
onChange(isNaN(parsed) ? 0 : parsed);
}; };
return ( return (
<select <select
value={String(value)} value={String(displayValue)}
onChange={(e) => handleChange(e.target.value)} onChange={(e) => handleChange(e.target.value)}
className="w-full rounded bg-gray-600 px-1 py-1 text-xs text-white" className="w-full rounded bg-gray-600 px-1 py-1 text-xs text-white"
> >
{knownPoints.map((p) => ( {allPoints.map((p) => (
<option key={p} value={String(p)}> <option key={p} value={String(p)}>
{p} {p}
</option> </option>