update request model rewiring
All checks were successful
Deploy to Cloudflare Pages / deploy (push) Successful in 3m46s

This commit is contained in:
2026-03-01 16:45:30 +01:00
parent 3004545400
commit 5a116eabe7
7 changed files with 176 additions and 135 deletions

View File

@@ -41,7 +41,12 @@ export default function ClientPage({ initialAppData }: ClientPageProps) {
<section className="lg:col-span-1">
<h2 className="text-2xl font-bold text-white mb-4">Points Table:</h2>
<PointsKey pointsTable={appData.pointsTable} flPoints={appData.flPoints} />
<PointsKey
pointsTable={appData.pointsTable}
flPoints={appData.flPoints}
flColor={appData.flColor}
flTextColor={appData.flTextColor}
/>
</section>
</div>
</main>

View File

@@ -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) {
<table className="w-full text-center font-bold">
<tbody>
{sortedPoints.map((style) => (
<tr key={style.points}>
<tr key={style.id || `pt-${style.points}`}>
<td
className="px-3 py-2"
style={{
@@ -33,9 +35,15 @@ export default function PointsKey({ pointsTable, flPoints }: PointsKeyProps) {
</tr>
))}
<tr>
<td className="px-3 py-2 points-fl">
<td
className="px-3 py-2 points-fl"
style={{
backgroundColor: flColor,
color: flTextColor,
}}
>
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', flexDirection: 'row' }}>
FL <span className="fl-bg-text" style={{ color: flPoints.textColor }}>(+{flPoints.points})</span>
FL <span className="fl-bg-text" style={{ color: flTextColor }}>(+{flPoints})</span>
</div>
</td>
</tr>

View File

@@ -36,93 +36,97 @@ export default function RaceResultsTable({ appData }: RaceResultsTableProps) {
</tr>
</thead>
<tbody id="race-table-body" className="divide-y divide-gray-700">
{appData.tracks.map((track, trackIndex) => (
<tr key={trackIndex}>
<td className="font-semibold py-3 px-4 whitespace-nowrap min-w-[200px]">
<span>{track.name}</span>
</td>
<td className="py-3 px-4 font-mono min-w-[100px]">
{track.fastestLapInitials || ""}
</td>
{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 (
<tr key={trackIndex}>
<td className="font-semibold py-3 px-4 whitespace-nowrap min-w-[200px]">
<span>{track.name}</span>
</td>
<td className="py-3 px-4 font-mono min-w-[100px]">
{flDriver?.initials || ""}
</td>
{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 (
<td
key={driverIndex}
className="py-3 px-4 font-mono"
style={cellStyles}
>
{hasFL ? (
<>
<span style={{ flexGrow: 1, textAlign: "left" }}>
{result}
</span>
<span
className="fl-text-only"
style={{ color: appData.flPoints.textColor }}
>
(+{appData.flPoints.points})
</span>
</>
) : (
<span>{result}</span>
)}
</td>
);
})}
</tr>
))}
return (
<td
key={driver.id}
className="py-3 px-4 font-mono"
style={cellStyles}
>
{hasFL ? (
<>
<span style={{ flexGrow: 1, textAlign: "left" }}>
{result}
</span>
<span
className="fl-text-only"
style={{ color: appData.flTextColor }}
>
(+{appData.flPoints})
</span>
</>
) : (
<span>{result}</span>
)}
</td>
);
})}
</tr>
);
})}
</tbody>
</table>
</div>

11
src/app/not-found.tsx Normal file
View File

@@ -0,0 +1,11 @@
export default function NotFound() {
return (
<div className="container mx-auto max-w-7xl space-y-12">
<header className="text-center">
<h1 className="text-4xl md:text-5xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-blue-400 to-purple-500 mb-4">
404 - Page Not Found
</h1>
</header>
</div>
);
}

View File

@@ -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: "" }
]
};

View File

@@ -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;

View File

@@ -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[];
}