Port to Next.js with static export for Cloudflare Pages
- Create Next.js app with TypeScript, Tailwind, and Postcss - Extract API logic, types, and utilities to lib/ - Build React components for Race Results, Driver Standings, Points Key, and D3 Chart - Use environment variables for API credentials (NEXT_PUBLIC_*) - Static export (output: 'export') for Cloudflare Pages deployment - D3 chart rendered client-side with dynamic import - Update deploy workflow to build and deploy ./out directory - Remove original public/index.html
This commit is contained in:
98
src/app/components/RaceResultsTable.tsx
Normal file
98
src/app/components/RaceResultsTable.tsx
Normal file
@@ -0,0 +1,98 @@
|
||||
import { AppData } from '@/lib/types';
|
||||
import { parsePoints, findStyleByPoints } from '@/lib/leaderboard';
|
||||
|
||||
interface RaceResultsTableProps {
|
||||
appData: AppData;
|
||||
}
|
||||
|
||||
export default function RaceResultsTable({ appData }: RaceResultsTableProps) {
|
||||
return (
|
||||
<div id="table-container" className="bg-gray-800/70 p-4 rounded-2xl shadow-2xl backdrop-blur-sm border border-gray-700/50 overflow-x-auto">
|
||||
<table id="race-results-table" className="w-full min-w-[1200px] border-collapse text-center">
|
||||
<thead id="race-table-head">
|
||||
<tr>
|
||||
<th scope="col" className="py-3 px-4 min-w-[200px]">Track</th>
|
||||
<th scope="col" className="py-3 px-4 min-w-[100px]">FL</th>
|
||||
{appData.drivers.map((driver) => (
|
||||
<th key={driver.initials} scope="col" className="py-3 px-4 min-w-[170px]" style={{ textAlign: 'center' }}>
|
||||
<span>{driver.name}</span>
|
||||
</th>
|
||||
))}
|
||||
</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);
|
||||
|
||||
let cellStyles: React.CSSProperties = {
|
||||
backgroundColor: style.color,
|
||||
color: style.textColor,
|
||||
};
|
||||
|
||||
let hasFL = false;
|
||||
if (track.fastestLapInitials && track.fastestLapInitials === driverInitials) {
|
||||
hasFL = true;
|
||||
const flColor = appData.flPoints.color;
|
||||
if (pointsNum === 0) {
|
||||
cellStyles = {
|
||||
backgroundColor: flColor,
|
||||
color: appData.flPoints.textColor,
|
||||
};
|
||||
} else {
|
||||
cellStyles = {
|
||||
background: `linear-gradient(135deg, ${style.color} 50%, ${flColor} 50%)`,
|
||||
color: style.textColor,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
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 }}>(+1)</span>
|
||||
</>
|
||||
) : (
|
||||
<span>{result}</span>
|
||||
)}
|
||||
</td>
|
||||
);
|
||||
})}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user