diff --git a/src/App.css b/src/App.css index 9fd856e..1de9cb7 100644 --- a/src/App.css +++ b/src/App.css @@ -20,6 +20,12 @@ body { padding: 10px; margin: 9px; + + cursor: pointer; +} + +.driver-card-selected { + box-shadow: 0 0 0 3px white; } .driver-card img { @@ -35,10 +41,11 @@ body { font-weight: 400; } -.standing-drivers { +.content-row { display: flex; - flex-direction: column; align-items: flex-start; + gap: 2rem; + flex-wrap: wrap; } .standing-drivers { @@ -52,6 +59,106 @@ body { align-self: center; } +.driver-detail { + background-color: #15151e; + border-radius: 30px; + + color: white; + font-family: "Titillium Web", sans-serif; + + width: 360px; + padding: 2rem; + margin: 10px; + + display: flex; + flex-direction: column; + align-items: center; + gap: 1rem; + + position: sticky; + top: 1rem; +} + +.driver-detail-nav { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; +} + +.driver-detail-nav-button { + background-color: transparent; + border: 1px solid grey; + border-radius: 10px; + color: white; + font-size: 1rem; + cursor: pointer; +} + +.driver-detail-nav-button:disabled { + color: grey; + border-color: grey; + cursor: not-allowed; +} + +.driver-detail-photo { + height: 240px; + width: auto; + max-width: 100%; + border-radius: 25px; +} + +.driver-detail-heading { + display: flex; + align-items: center; + justify-content: center; + flex-wrap: wrap; + gap: 1rem; +} + +.driver-detail-name { + font-size: 22px; + font-weight: 400; + margin: 0; + text-align: center; +} + +.driver-detail-number { + font-size: 50px; + font-weight: 700; +} + +.driver-detail-stats { + display: flex; + flex-direction: column; + width: 100%; + margin-top: 0.5rem; +} + +.driver-detail-stat-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; + padding: 0.65rem 0; +} + +.driver-detail-stat-label { + color: grey; + font-size: 1rem; +} + +.driver-detail-stat-value { + font-size: 1.25rem; + font-weight: 600; +} + +.driver-detail-divider { + width: 100%; + margin: 0 auto; + border-top: 1px solid grey; +} + .meeting-list { width: 100%; max-width: 760px; diff --git a/src/App.tsx b/src/App.tsx index e44d72b..62bbe91 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,9 +1,11 @@ import './App.css' +import { useState } from 'react' import api from './api/api' import type { Session, SessionResult } from './api/api' import { useQuery } from '@tanstack/react-query' import DriverCard from './components/DriverCard' +import DriverDetail from './components/DriverDetail' const sessionDate = new Intl.DateTimeFormat('en-GB', { dateStyle: 'medium', timeZone: 'UTC' }) @@ -15,6 +17,15 @@ function App() { queryFn: getDriversWithStandings }) + const [selectedIndex, setSelectedIndex] = useState(0) + const clampedIndex = driversWithStandings.length ? Math.min(selectedIndex, driversWithStandings.length - 1) : 0 + const selectedDriver = driversWithStandings[clampedIndex] + + function selectDriverByNumber(driverNumber: number) { + const index = driversWithStandings.findIndex((driver) => driver.driver_number === driverNumber) + if (index !== -1) { setSelectedIndex(index) } + } + const {data: teamStandings = [], isPending: teamStandingsPending, isError: teamStandingsError} = useQuery({ queryKey: ['teamStandings'], queryFn: getAllTeamStandings, @@ -61,25 +72,50 @@ function App() { return ( <> -
{driver.points_current}
{driver.position_current}
diff --git a/src/components/DriverDetail.tsx b/src/components/DriverDetail.tsx new file mode 100644 index 0000000..79e5fcd --- /dev/null +++ b/src/components/DriverDetail.tsx @@ -0,0 +1,110 @@ +import type { TeamStandings } from '../api/api' +import type { DriverProps } from './DriverCard' + +// Default "avatar" used when api does not return a driver photo +const DEFAULT_AVATAR_URL = 'https://canvas.ntnu.no/images/messages/avatar-50.png' + +interface DriverDetailProps { + driver?: DriverProps; + teamStanding?: TeamStandings; + teamStandingsPending: boolean; + teamStandingsError: boolean; + isLoading: boolean; + isError: boolean; + hasPrev: boolean; + hasNext: boolean; + onPrev: () => void; + onNext: () => void; +} + +export default function DriverDetail({ + driver, + teamStanding, + teamStandingsPending, + teamStandingsError, + isLoading, + isError, + hasPrev, + hasNext, + onPrev, + onNext, +}: DriverDetailProps) { + + return ( +
Loading driver...
+ ) : isError ? ( +Could not load driver details.
+ ) : !driver ? ( +No driver selected.
+ ) : ( + <> +