diff --git a/src/app/ClientPage.tsx b/src/app/ClientPage.tsx index 0d1cbf6..4a3f59b 100644 --- a/src/app/ClientPage.tsx +++ b/src/app/ClientPage.tsx @@ -41,7 +41,12 @@ export default function ClientPage({ initialAppData }: ClientPageProps) {

Points Table:

- +
diff --git a/src/app/components/PointsKey.tsx b/src/app/components/PointsKey.tsx index 8f2f6ac..a4a6702 100644 --- a/src/app/components/PointsKey.tsx +++ b/src/app/components/PointsKey.tsx @@ -2,10 +2,12 @@ import { PointStyle } from '@/lib/types'; interface PointsKeyProps { pointsTable: PointStyle[]; - flPoints: PointStyle; + flPoints: number; + flColor: string; + flTextColor: string; } -export default function PointsKey({ pointsTable, flPoints }: PointsKeyProps) { +export default function PointsKey({ pointsTable, flPoints, flColor, flTextColor }: PointsKeyProps) { const sortedPoints = [...pointsTable].sort((a, b) => (a.priority || 99) - (b.priority || 99)); return ( @@ -13,7 +15,7 @@ export default function PointsKey({ pointsTable, flPoints }: PointsKeyProps) { {sortedPoints.map((style) => ( - + - diff --git a/src/app/components/RaceResultsTable.tsx b/src/app/components/RaceResultsTable.tsx index 716729c..a1a8259 100644 --- a/src/app/components/RaceResultsTable.tsx +++ b/src/app/components/RaceResultsTable.tsx @@ -36,93 +36,97 @@ export default function RaceResultsTable({ appData }: RaceResultsTableProps) { - {appData.tracks.map((track, trackIndex) => ( - - - - {appData.drivers.map((driver, driverIndex) => { - const driverInitials = driver.initials; - let result = track.results[driverIndex]; - // DNS fallback - if (result === undefined || result === null || result === "") { - result = "DNS"; - } - const pointsNum = parsePoints(result); - const style = findStyleByPoints(appData, pointsNum); + {appData.tracks.map((track, trackIndex) => { + // Find driver name for fastest lap + const flDriver = appData.drivers.find( + (d) => d.id === track.fastestLapDriverId + ); + return ( + + + + {appData.drivers.map((driver) => { + const raceResult = track.results.find( + (r) => r.driverId === driver.id + ); + let result = raceResult?.score; + // DNS fallback + if (result === undefined || result === null || result === "") { + result = "DNS"; + } + const pointsNum = parsePoints(result); + const style = findStyleByPoints(appData, pointsNum); - let cellStyles: React.CSSProperties = { - backgroundColor: style.color, - color: style.textColor, - }; + let cellStyles: React.CSSProperties = { + backgroundColor: style.color, + color: style.textColor, + }; - let hasFL = false; - if ( - track.fastestLapInitials && - track.fastestLapInitials === driverInitials && - appData.flPoints.points - ) { - hasFL = true; - const flColor = appData.flPoints.color; - if (pointsNum === 0) { + let hasFL = false; + if (track.fastestLapDriverId === driver.id && appData.flPoints) { + hasFL = true; + const flColor = appData.flColor; + if (pointsNum === 0) { + cellStyles = { + backgroundColor: flColor, + color: appData.flTextColor, + }; + } else { + cellStyles = { + background: `linear-gradient(135deg, ${style.color} 50%, ${flColor} 50%)`, + color: style.textColor, + }; + } + } + + if (hasFL) { cellStyles = { - backgroundColor: flColor, - color: appData.flPoints.textColor, + ...cellStyles, + display: "flex", + justifyContent: "space-between", + alignItems: "center", + flexDirection: "row", + paddingLeft: "0.75rem", + paddingRight: "0.75rem", }; } else { cellStyles = { - background: `linear-gradient(135deg, ${style.color} 50%, ${flColor} 50%)`, - color: style.textColor, + ...cellStyles, + textAlign: "center", }; } - } - if (hasFL) { - cellStyles = { - ...cellStyles, - display: "flex", - justifyContent: "space-between", - alignItems: "center", - flexDirection: "row", - paddingLeft: "0.75rem", - paddingRight: "0.75rem", - }; - } else { - cellStyles = { - ...cellStyles, - textAlign: "center", - }; - } - - return ( - - ); - })} - - ))} + return ( + + ); + })} + + ); + })}
))}
+
- FL (+{flPoints.points}) + FL (+{flPoints})
- {track.name} - - {track.fastestLapInitials || ""} -
+ {track.name} + + {flDriver?.initials || ""} + - {hasFL ? ( - <> - - {result} - - - (+{appData.flPoints.points}) - - - ) : ( - {result} - )} -
+ {hasFL ? ( + <> + + {result} + + + (+{appData.flPoints}) + + + ) : ( + {result} + )} +
diff --git a/src/app/not-found.tsx b/src/app/not-found.tsx new file mode 100644 index 0000000..9d99690 --- /dev/null +++ b/src/app/not-found.tsx @@ -0,0 +1,11 @@ +export default function NotFound() { + return ( +
+
+

+ 404 - Page Not Found +

+
+
+ ); +} diff --git a/src/lib/defaultData.ts b/src/lib/defaultData.ts index eca969b..11652f3 100644 --- a/src/lib/defaultData.ts +++ b/src/lib/defaultData.ts @@ -7,50 +7,52 @@ const defaultColors = [ export const defaultAppData: AppData = { drivers: [ - { name: "Filip Makarijevski", color: defaultColors[0], initials: "F.M." }, - { name: "Karlo Steinbruckner", color: defaultColors[1], initials: "K.S." }, - { name: "Fran Jurmanović", color: defaultColors[2], initials: "F.J." }, - { name: "Borna Bevanda", color: defaultColors[3], initials: "B.B." }, - { name: "Matej Jugović", color: defaultColors[4], initials: "M.J." }, - { name: "Lovo Bravić", color: defaultColors[5], initials: "L.B." }, - { name: "Dominik Dejanović", color: defaultColors[6], initials: "D.D." }, - { name: "Josip Kompanović", color: defaultColors[7], initials: "J.K." }, - { name: "Tin Vidmar", color: defaultColors[8], initials: "T.V." }, - { name: "Ivan Ivezić", color: defaultColors[9], initials: "I.I." }, - { name: "Tomislav Glavaš", color: defaultColors[10], initials: "T.G." } + { id: "550e8400-e29b-41d4-a716-446655440000", name: "Filip Makarijevski", color: defaultColors[0], initials: "F.M." }, + { id: "550e8400-e29b-41d4-a716-446655440001", name: "Karlo Steinbruckner", color: defaultColors[1], initials: "K.S." }, + { id: "550e8400-e29b-41d4-a716-446655440002", name: "Fran Jurmanović", color: defaultColors[2], initials: "F.J." }, + { id: "550e8400-e29b-41d4-a716-446655440003", name: "Borna Bevanda", color: defaultColors[3], initials: "B.B." }, + { id: "550e8400-e29b-41d4-a716-446655440004", name: "Matej Jugović", color: defaultColors[4], initials: "M.J." }, + { id: "550e8400-e29b-41d4-a716-446655440005", name: "Lovo Bravić", color: defaultColors[5], initials: "L.B." }, + { id: "550e8400-e29b-41d4-a716-446655440006", name: "Dominik Dejanović", color: defaultColors[6], initials: "D.D." }, + { id: "550e8400-e29b-41d4-a716-446655440007", name: "Josip Kompanović", color: defaultColors[7], initials: "J.K." }, + { id: "550e8400-e29b-41d4-a716-446655440008", name: "Tin Vidmar", color: defaultColors[8], initials: "T.V." }, + { id: "550e8400-e29b-41d4-a716-446655440009", name: "Ivan Ivezić", color: defaultColors[9], initials: "I.I." }, + { id: "550e8400-e29b-41d4-a716-44665544000a", name: "Tomislav Glavaš", color: defaultColors[10], initials: "T.G." } ], pointsTable: [ - { points: 10, label: "1st", color: "#fde047", textColor: "#422006", priority: 1 }, - { points: 7, label: "2nd", color: "#9ca3af", textColor: "#1f2937", priority: 2 }, - { points: 5, label: "3rd", color: "#f0c0a0", textColor: "#431407", priority: 3 }, - { points: 2, label: "4th", color: "#81c784", textColor: "#1F2937", priority: 4 }, - { points: 1, label: "5th", color: "#a7d9ef", textColor: "#1F2937", priority: 5 }, + { id: "pt-1st", points: 10, label: "1st", color: "#fde047", textColor: "#422006", priority: 1 }, + { id: "pt-2nd", points: 7, label: "2nd", color: "#9ca3af", textColor: "#1f2937", priority: 2 }, + { id: "pt-3rd", points: 5, label: "3rd", color: "#f0c0a0", textColor: "#431407", priority: 3 }, + { id: "pt-4th", points: 2, label: "4th", color: "#81c784", textColor: "#1F2937", priority: 4 }, + { id: "pt-5th", points: 1, label: "5th", color: "#a7d9ef", textColor: "#1F2937", priority: 5 }, ], - flPoints: { points: 1, label: "FL +1", color: "#8b5cf6", textColor: "#000000", priority: 6 }, + flPoints: 1, + flColor: "#8b5cf6", + flTextColor: "#000000", tracks: [ - { name: "Hungaroring", results: [], fastestLapInitials: "" }, - { name: "Watkins Glen", results: [], fastestLapInitials: "" }, - { name: "Paul Ricard", results: [], fastestLapInitials: "" }, - { name: "Silverstone", results: [], fastestLapInitials: "" }, - { name: "Zandvoort", results: [], fastestLapInitials: "" }, - { name: "Brands Hatch", results: [], fastestLapInitials: "" }, - { name: "Circuit Ricardo Tormo (Valencia)", results: [], fastestLapInitials: "" }, - { name: "Mount Panorama Circuit", results: [], fastestLapInitials: "" }, - { name: "Autodromo Internazionale Enzo e Dino Ferrari - Imola", results: [], fastestLapInitials: "" }, - { name: "Barcelona", results: [null, 0, 0], fastestLapInitials: "" }, - { name: "Indianapolis", results: [], fastestLapInitials: "" }, - { name: "WeatherTech Raceway Laguna Seca", results: [], fastestLapInitials: "" }, - { name: "Zolder", results: [], fastestLapInitials: "" }, - { name: "Suzuka Circuit", results: [], fastestLapInitials: "" }, - { name: "Snetterton", results: [], fastestLapInitials: "" }, - { name: "Spa-Francochamps", results: [], fastestLapInitials: "" }, - { name: "Circuit of the Americas (COTA)", results: [], fastestLapInitials: "" }, - { name: "Donington Park", results: [], fastestLapInitials: "" }, - { name: "Monza", results: [], fastestLapInitials: "" }, - { name: "Misano", results: [], fastestLapInitials: "" }, - { name: "Kyalami Grand Prix Circuit", results: [], fastestLapInitials: "" }, - { name: "Oulton Park", results: [], fastestLapInitials: "" }, - { name: "Red Bull Ring", results: [], fastestLapInitials: "" }, - { name: "Nürburgring", results: [], fastestLapInitials: "" } + { id: "track-hungaroring", name: "Hungaroring", results: [], fastestLapDriverId: "" }, + { id: "track-watkins", name: "Watkins Glen", results: [], fastestLapDriverId: "" }, + { id: "track-paulricard", name: "Paul Ricard", results: [], fastestLapDriverId: "" }, + { id: "track-silverstone", name: "Silverstone", results: [], fastestLapDriverId: "" }, + { id: "track-zandvoort", name: "Zandvoort", results: [], fastestLapDriverId: "" }, + { id: "track-brands", name: "Brands Hatch", results: [], fastestLapDriverId: "" }, + { id: "track-valencia", name: "Circuit Ricardo Tormo (Valencia)", results: [], fastestLapDriverId: "" }, + { id: "track-bathurst", name: "Mount Panorama Circuit", results: [], fastestLapDriverId: "" }, + { id: "track-imola", name: "Autodromo Internazionale Enzo e Dino Ferrari - Imola", results: [], fastestLapDriverId: "" }, + { id: "track-barcelona", name: "Barcelona", results: [], fastestLapDriverId: "" }, + { id: "track-indianapolis", name: "Indianapolis", results: [], fastestLapDriverId: "" }, + { id: "track-laguna", name: "WeatherTech Raceway Laguna Seca", results: [], fastestLapDriverId: "" }, + { id: "track-zolder", name: "Zolder", results: [], fastestLapDriverId: "" }, + { id: "track-suzuka", name: "Suzuka Circuit", results: [], fastestLapDriverId: "" }, + { id: "track-snetterton", name: "Snetterton", results: [], fastestLapDriverId: "" }, + { id: "track-spa", name: "Spa-Francochamps", results: [], fastestLapDriverId: "" }, + { id: "track-cota", name: "Circuit of the Americas (COTA)", results: [], fastestLapDriverId: "" }, + { id: "track-donington", name: "Donington Park", results: [], fastestLapDriverId: "" }, + { id: "track-monza", name: "Monza", results: [], fastestLapDriverId: "" }, + { id: "track-misano", name: "Misano", results: [], fastestLapDriverId: "" }, + { id: "track-kyalami", name: "Kyalami Grand Prix Circuit", results: [], fastestLapDriverId: "" }, + { id: "track-oulton", name: "Oulton Park", results: [], fastestLapDriverId: "" }, + { id: "track-redbull", name: "Red Bull Ring", results: [], fastestLapDriverId: "" }, + { id: "track-nurburgring", name: "Nürburgring", results: [], fastestLapDriverId: "" } ] }; diff --git a/src/lib/leaderboard.ts b/src/lib/leaderboard.ts index 4e6ce95..f22b291 100644 --- a/src/lib/leaderboard.ts +++ b/src/lib/leaderboard.ts @@ -11,6 +11,7 @@ export function findStyleByPoints( ): PointStyle { return ( appData.pointsTable.find((p) => p.points === points) || { + id: "unknown", points: 0, label: "???", color: "#374151", @@ -25,20 +26,18 @@ export function calculateStandingsData(appData: AppData): DriverStanding[] { let totalPoints = 0; let cumulativePoints = 0; const raceHistory: RaceHistoryEntry[] = []; - const driverInitials = driver.initials; appData.tracks.forEach((track) => { - let result = track.results[driverIndex]; + // Find result for this driver by driverId + const raceResult = track.results.find((r) => r.driverId === driver.id); + let result = raceResult?.score; // DNS fallback: if result is undefined, null, or empty, treat as DNS if (result === undefined || result === null || result === "") { result = "DNS"; } let racePoints = parsePoints(result); - if ( - track.fastestLapInitials && - track.fastestLapInitials === driverInitials - ) { - racePoints += appData.flPoints.points ?? 1; + if (track.fastestLapDriverId === driver.id) { + racePoints += appData.flPoints ?? 1; } totalPoints += racePoints; cumulativePoints += racePoints; diff --git a/src/lib/types.ts b/src/lib/types.ts index 92d39a4..11635a7 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -1,10 +1,12 @@ export interface Driver { + id: string; name: string; color: string; initials: string; } export interface PointStyle { + id?: string; points: number; label: string; color: string; @@ -12,10 +14,16 @@ export interface PointStyle { priority: number; } +export interface RaceResult { + driverId: string; + score: number | string | null | undefined; +} + export interface Track { + id: string; name: string; - results: (number | string | null | undefined)[]; - fastestLapInitials: string; + results: RaceResult[]; + fastestLapDriverId: string; } export interface RaceHistoryEntry { @@ -34,8 +42,12 @@ export interface DriverStanding { } export interface AppData { + id?: string; + serverId?: string; drivers: Driver[]; pointsTable: PointStyle[]; - flPoints: PointStyle; + flPoints: number; + flColor: string; + flTextColor: string; tracks: Track[]; }