From 122dbc6b20fcb5cc179c3287b3740ddda5492c49 Mon Sep 17 00:00:00 2001 From: Oscar Grande Date: Tue, 15 Sep 2026 13:08:03 +0200 Subject: [PATCH 1/2] implemented the SearchAndFilter component, and the logic for simple search and team filtering --- src/App.tsx | 68 +++++++++++++++++++++--------- src/components/SearchAndFilter.tsx | 36 ++++++++++++++++ 2 files changed, 84 insertions(+), 20 deletions(-) create mode 100644 src/components/SearchAndFilter.tsx diff --git a/src/App.tsx b/src/App.tsx index 44fe85b..72e9afa 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,42 +1,70 @@ -import './App.css' -import api, { type Driver } from './api/api' +import "./App.css"; +import api, { type Driver } from "./api/api"; import { useEffect, useState } from "react"; -import DriverCard from './components/DriverCard' +import DriverCard from "./components/DriverCard"; +import SearchAndFilter from "./components/SearchAndFilter"; function App() { - const { getAllDrivers } = api() - const [drivers, setDrivers] = useState([]) + const { getAllDrivers } = api(); + const [drivers, setDrivers] = useState([]); + const [search, setSearch] = useState( + () => sessionStorage.getItem("search") ?? "", + ); + const [team, setTeam] = useState( + () => sessionStorage.getItem("team") ?? "all", + ); useEffect(() => { async function loadDrivers() { - const allDrivers = await getAllDrivers() - setDrivers(allDrivers) - console.log(allDrivers) + const allDrivers = await getAllDrivers(); + setDrivers(allDrivers); + console.log(allDrivers); } - loadDrivers() - }, []) + loadDrivers(); + }, []); - return ( - <> + useEffect(() => { + sessionStorage.setItem("search", search); + }, [search]); + + useEffect(() => { + sessionStorage.setItem("team", team); + }, [team]); + const filteredDrivers = drivers.filter((driver) => { + const matchesSearch = driver.full_name + .toLowerCase() + .includes(search.toLowerCase()); + const matchesTeam = team === "all" || driver.team_name === team; + return matchesSearch && matchesTeam; + }); + return ( + <> + d.team_name)))} + />

F1 The Website

- {drivers.map((driver) => ( - - ( + + team_colour: driver.team_colour, + }} + /> ))}
- - ) + ); } -export default App +export default App; diff --git a/src/components/SearchAndFilter.tsx b/src/components/SearchAndFilter.tsx new file mode 100644 index 0000000..1866eae --- /dev/null +++ b/src/components/SearchAndFilter.tsx @@ -0,0 +1,36 @@ +interface SearchAndFilterProps { + search: string; + onSearchChange: (value: string) => void; + team: string; + onTeamChange: (value: string) => void; + teams: string[]; +} + +function SearchAndFilter({ + search, + onSearchChange, + team, + onTeamChange, + teams, +}: SearchAndFilterProps) { + return ( +
+ onSearchChange(e.target.value)} + /> + +
+ ); +} + +export default SearchAndFilter; From 5cd7de69e159a4637f7a9262f28656ddad47c691 Mon Sep 17 00:00:00 2001 From: Oscar Grande Date: Tue, 15 Sep 2026 14:02:35 +0200 Subject: [PATCH 2/2] implemented sorting function on position based on the new params from API fetching. --- src/App.tsx | 282 +++++++++++++++++++---------- src/components/SearchAndFilter.tsx | 14 ++ 2 files changed, 205 insertions(+), 91 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 28c0f2f..c332979 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,81 +1,145 @@ -import './App.css' -import api from './api/api' -import type { Session, SessionResult } from './api/api' -import { useQuery } from '@tanstack/react-query' -import { useEffect, useState } from 'react' +import "./App.css"; +import api from "./api/api"; +import type { Session, SessionResult } from "./api/api"; +import { useQuery } from "@tanstack/react-query"; +import { useEffect, useState } from "react"; -import DriverCard from './components/DriverCard' -import SearchAndFilter from './components/SearchAndFilter' +import DriverCard from "./components/DriverCard"; +import SearchAndFilter from "./components/SearchAndFilter"; -const sessionDate = new Intl.DateTimeFormat('en-GB', { dateStyle: 'medium', timeZone: 'UTC' }) +const sessionDate = new Intl.DateTimeFormat("en-GB", { + dateStyle: "medium", + timeZone: "UTC", +}); + +type SortOrder = "none" | "asc" | "desc"; function App() { - const [search, setSearch] = useState(() => sessionStorage.getItem('search') ?? '') - const [team, setTeam] = useState(() => sessionStorage.getItem('team') ?? 'all') + const [search, setSearch] = useState( + () => sessionStorage.getItem("search") ?? "", + ); + const [team, setTeam] = useState( + () => sessionStorage.getItem("team") ?? "all", + ); + + const [sortOrder, setSortOrder] = useState( + () => (sessionStorage.getItem("sortOrder") as SortOrder) ?? "none", + ); useEffect(() => { - sessionStorage.setItem('search', search) - }, [search]) + sessionStorage.setItem("team", team); + }, [team]); useEffect(() => { - sessionStorage.setItem('team', team) - }, [team]) + sessionStorage.setItem("sortOrder", sortOrder); + }, [sortOrder]); - const { getDriversWithStandings, getAllTeamStandings, getAllSessionResults, getAllSessions, getAllMeetings } = api() + const { + getDriversWithStandings, + getAllTeamStandings, + getAllSessionResults, + getAllSessions, + getAllMeetings, + } = api(); - const {data: driversWithStandings = [], isPending: driversWithStandingsPending, isError: driversWithStandingsError} = useQuery({ - queryKey: ['driversWithStandings'], - queryFn: getDriversWithStandings - }) + const { + data: driversWithStandings = [], + isPending: driversWithStandingsPending, + isError: driversWithStandingsError, + } = useQuery({ + queryKey: ["driversWithStandings"], + queryFn: getDriversWithStandings, + }); - const {data: teamStandings = [], isPending: teamStandingsPending, isError: teamStandingsError} = useQuery({ - queryKey: ['teamStandings'], + const { + data: teamStandings = [], + isPending: teamStandingsPending, + isError: teamStandingsError, + } = useQuery({ + queryKey: ["teamStandings"], queryFn: getAllTeamStandings, - enabled: !driversWithStandingsPending - }) + enabled: !driversWithStandingsPending, + }); - const {data: allSessions = [], isPending: sessionsPending, isError: sessionsError} = useQuery({ - queryKey: ['sessions', 2026], + const { + data: allSessions = [], + isPending: sessionsPending, + isError: sessionsError, + } = useQuery({ + queryKey: ["sessions", 2026], queryFn: getAllSessions, - enabled: !driversWithStandingsPending - }) + enabled: !driversWithStandingsPending, + }); - const {data: allSessionResults = [], isPending: sessionResultsPending, isError: sessionResultsError} = useQuery({ - queryKey: ['sessionResults', 2026, allSessions.map((session) => session.session_key)], + const { + data: allSessionResults = [], + isPending: sessionResultsPending, + isError: sessionResultsError, + } = useQuery({ + queryKey: [ + "sessionResults", + 2026, + allSessions.map((session) => session.session_key), + ], queryFn: () => getAllSessionResults(allSessions), - enabled: !sessionsPending && !sessionsError - }) + enabled: !sessionsPending && !sessionsError, + }); - const {data: allMeetings = [], isPending: meetingsPending, isError: meetingsError} = useQuery({ - queryKey: ['meetings', 2026], + const { + data: allMeetings = [], + isPending: meetingsPending, + isError: meetingsError, + } = useQuery({ + queryKey: ["meetings", 2026], queryFn: getAllMeetings, - enabled: !driversWithStandingsPending - }) - - const filteredDrivers = driversWithStandings.filter((driver) => { - const matchesSearch = driver.full_name.toLowerCase().includes(search.toLowerCase()) - const matchesTeam = team === 'all' || driver.team_name === team - return matchesSearch && matchesTeam - }) - - const meetingsByKey = new Map(allMeetings.map((meeting) => [meeting.meeting_key, meeting])) - const sessionsByMeeting = new Map() - const orderedSessions = [...allSessions].sort((a, b) => Date.parse(a.date_start) - Date.parse(b.date_start)) + enabled: !driversWithStandingsPending, + }); + + const filteredDrivers = driversWithStandings + .filter((driver) => { + const matchesSearch = driver.full_name + .toLowerCase() + .includes(search.toLowerCase()); + const matchesTeam = team === "all" || driver.team_name === team; + return matchesSearch && matchesTeam; + }) + .sort((a, b) => { + if (sortOrder === "asc") { + return b.position_current - a.position_current; + } else if (sortOrder === "desc") { + return a.position_current - b.position_current; + } else { + return 0; // No sorting, maintain original order + } + }); + + const meetingsByKey = new Map( + allMeetings.map((meeting) => [meeting.meeting_key, meeting]), + ); + const sessionsByMeeting = new Map(); + const orderedSessions = [...allSessions].sort( + (a, b) => Date.parse(a.date_start) - Date.parse(b.date_start), + ); for (const session of orderedSessions) { - const sessions = sessionsByMeeting.get(session.meeting_key) ?? [] - sessions.push(session) - sessionsByMeeting.set(session.meeting_key, sessions) + const sessions = sessionsByMeeting.get(session.meeting_key) ?? []; + sessions.push(session); + sessionsByMeeting.set(session.meeting_key, sessions); } - const driverNames = new Map(driversWithStandings.map((driver) => [driver.driver_number, driver.full_name])) - const resultsBySession = new Map() + const driverNames = new Map( + driversWithStandings.map((driver) => [ + driver.driver_number, + driver.full_name, + ]), + ); + const resultsBySession = new Map(); for (const result of allSessionResults) { - const results = resultsBySession.get(result.session_key) ?? [] - results.push(result) - resultsBySession.set(result.session_key, results) + const results = resultsBySession.get(result.session_key) ?? []; + results.push(result); + resultsBySession.set(result.session_key, results); } for (const results of resultsBySession.values()) { - results.sort((a, b) => (a.position ?? Infinity) - (b.position ?? Infinity)) + results.sort((a, b) => (a.position ?? Infinity) - (b.position ?? Infinity)); } return ( @@ -85,28 +149,39 @@ function App() { onSearchChange={setSearch} team={team} onTeamChange={setTeam} - teams={Array.from(new Set(driversWithStandings.map((driver) => driver.team_name)))} + sortOrder={sortOrder} + onSortOrderChange={setSortOrder} + teams={Array.from( + new Set(driversWithStandings.map((driver) => driver.team_name)), + )} />

F1 The Website

- { driversWithStandingsPending ? "loading drivers..." : driversWithStandingsError ? "Could not load drivers." : - filteredDrivers.length === 0 ? 'No drivers match your search and team filter.' : - filteredDrivers.map((driver) => ( - team.team_name === driver.team_name)} - teamStandingsPending={teamStandingsPending} - teamStandingsError={teamStandingsError} - driver={{ - name: driver.full_name, - number: driver.driver_number, - headshot_url: driver.headshot_url, - team_colour: driver.team_colour, - team_name: driver.team_name, - points_current: driver.points_current, - position_current: driver.position_current - }} /> - )) - } + {driversWithStandingsPending + ? "loading drivers..." + : driversWithStandingsError + ? "Could not load drivers." + : filteredDrivers.length === 0 + ? "No drivers match your search and team filter." + : filteredDrivers.map((driver) => ( + team.team_name === driver.team_name, + )} + teamStandingsPending={teamStandingsPending} + teamStandingsError={teamStandingsError} + driver={{ + name: driver.full_name, + number: driver.driver_number, + headshot_url: driver.headshot_url, + team_colour: driver.team_colour, + team_name: driver.team_name, + points_current: driver.points_current, + position_current: driver.position_current, + }} + /> + ))}
{sessionsError || sessionResultsError ? ( @@ -118,28 +193,44 @@ function App() { ) : (

2026 meetings

- {meetingsError &&

Meeting names could not be loaded. Showing circuits and dates.

} + {meetingsError && ( +

+ Meeting names could not be loaded. Showing circuits and dates. +

+ )} {Array.from(sessionsByMeeting, ([meetingKey, sessions]) => { - const meeting = meetingsByKey.get(meetingKey) - const firstSession = sessions[0] - const lastSession = sessions[sessions.length - 1] + const meeting = meetingsByKey.get(meetingKey); + const firstSession = sessions[0]; + const lastSession = sessions[sessions.length - 1]; return (
- {meeting?.meeting_name ?? firstSession.circuit_short_name} + + {meeting?.meeting_name ?? firstSession.circuit_short_name} + - {firstSession.country_name} · {firstSession.circuit_short_name} · {' '} - {sessionDate.format(new Date(meeting?.date_start ?? firstSession.date_start))} – {' '} - {sessionDate.format(new Date(meeting?.date_end ?? lastSession.date_end))} + {firstSession.country_name} ·{" "} + {firstSession.circuit_short_name} ·{" "} + {sessionDate.format( + new Date(meeting?.date_start ?? firstSession.date_start), + )}{" "} + –{" "} + {sessionDate.format( + new Date(meeting?.date_end ?? lastSession.date_end), + )}
{sessions.map((session) => { - const results = resultsBySession.get(session.session_key) ?? [] + const results = + resultsBySession.get(session.session_key) ?? []; return ( -
+
{session.session_name} {results.length === 0 ? ( -

{session.is_cancelled ? 'Session cancelled.' : 'Results not available yet.'}

+

+ {session.is_cancelled + ? "Session cancelled." + : "Results not available yet."} +

) : (
    {results.map((result) => ( -
  • - Pos {result.position ?? '—'}: - {driverNames.get(result.driver_number) ?? `Driver ${result.driver_number}`} +
  • + Pos {result.position ?? "—"}: + + {driverNames.get(result.driver_number) ?? + `Driver ${result.driver_number}`} +
  • ))}
)}
- ) + ); })}
- ) + ); })}
)} - ) + ); } -export default App +export default App; diff --git a/src/components/SearchAndFilter.tsx b/src/components/SearchAndFilter.tsx index 1866eae..c6e8461 100644 --- a/src/components/SearchAndFilter.tsx +++ b/src/components/SearchAndFilter.tsx @@ -4,6 +4,8 @@ interface SearchAndFilterProps { team: string; onTeamChange: (value: string) => void; teams: string[]; + sortOrder: "none" | "asc" | "desc"; + onSortOrderChange: (value: "none" | "asc" | "desc") => void; } function SearchAndFilter({ @@ -12,6 +14,8 @@ function SearchAndFilter({ team, onTeamChange, teams, + sortOrder, + onSortOrderChange, }: SearchAndFilterProps) { return (
@@ -29,6 +33,16 @@ function SearchAndFilter({ ))} +
); }