From 1b390e4bb91da190b9483291d7e8dbda0c4997a8 Mon Sep 17 00:00:00 2001 From: CarloBu Date: Sat, 21 Dec 2024 03:30:02 +0200 Subject: [PATCH] responsive cards layout --- README.md | 29 ++++++-- src/components/react/FlightResults.tsx | 35 ++++----- src/components/react/FlightSearch.tsx | 2 +- .../{multi-combobox.tsx => MultiComboBox.tsx} | 74 +++++++++++-------- 4 files changed, 82 insertions(+), 58 deletions(-) rename src/components/react/modals/{multi-combobox.tsx => MultiComboBox.tsx} (85%) diff --git a/README.md b/README.md index fe58c89..64bd13f 100644 --- a/README.md +++ b/README.md @@ -87,12 +87,29 @@ The main endpoint `/api/search-flights` accepts the following parameters: #### Response Format -The API streams flight results in real-time using Server-Sent Events (SSE). Each event contains: - -- Flight details (outbound and inbound) -- Airport codes and full names -- Departure times -- Total price for all passengers +The API streams flight results in real-time using Server-Sent Events (SSE). Each event is prefixed with "data: " and contains a JSON object with: + +```json +{ + "outbound": { + "origin": "IATA code", + "originFull": "City, Country", + "destination": "IATA code", + "destinationFull": "City, Country", + "departureTime": "ISO 8601 datetime" + }, + "inbound": { + "origin": "IATA code", + "originFull": "City, Country", + "destination": "IATA code", + "destinationFull": "City, Country", + "departureTime": "ISO 8601 datetime" + }, + "totalPrice": number +} +``` + +The stream ends with a "data: END" message. ## Development diff --git a/src/components/react/FlightResults.tsx b/src/components/react/FlightResults.tsx index 2b0eff2..4969581 100644 --- a/src/components/react/FlightResults.tsx +++ b/src/components/react/FlightResults.tsx @@ -209,18 +209,18 @@ function MiniCityCard({ className="absolute left-0 top-0 flex h-full w-full cursor-pointer flex-col justify-between px-6 py-3" >
{city}
- + {cityData.flights.length} flight {cityData.flights.length !== 1 ? "s" : ""} €{Math.round(cityData.minPrice)} @@ -317,7 +317,7 @@ function DetailedFlightCard({ id={`flight-${flightId}`} onMouseEnter={() => setIsHovered(true)} onMouseLeave={() => setIsHovered(false)} - className={`group relative mx-0 my-4 rounded-3xl transition-all duration-200 sm:mx-2 md:mx-8 md:my-8 ${ + className={`group relative mt-3 rounded-3xl transition-all duration-200 ${ isHighlighted ? "ring-2 ring-gray-500 dark:ring-gray-200" : "" }`} > @@ -533,17 +533,17 @@ function CityGroup({ return (
-
+
{city} @@ -564,13 +564,13 @@ function CityGroup({ {/* Mini Cards - Show when collapsed */} {!isExpanded && ( -
+
{cityData.flights .sort((a, b) => a.totalPrice - b.totalPrice) .map((flight, index) => (
+
{cityData.flights .sort((a, b) => a.totalPrice - b.totalPrice) .map((flight, index) => { @@ -879,18 +879,18 @@ export function FlightResults({
-
+
toggleCountry(country)} > -
+
{country} @@ -910,16 +910,13 @@ export function FlightResults({ {/* Mini City Cards - Show when collapsed */} {!openCountries[country] && ( -
+
{Object.entries(countryData.cities) .sort(([, cityDataA], [, cityDataB]) => { return cityDataA.minPrice - cityDataB.minPrice; }) .map(([city, cityData]) => ( -
+
)} + {/* Selected Items */} + {filterOptions + .filter((opt) => selectedValues.includes(opt.code)) + .map((option) => ( + handleSelection(option.code)} + className="flex w-full items-center justify-between rounded-full px-4 py-3 text-left text-base text-gray-900 transition-all data-[selected=true]:bg-selected-color dark:text-white dark:hover:bg-gray-700 dark:data-[selected=true]:bg-gray-700" + > +
+ + {getDisplayText(option)} + +
+ +
+ ))} + + {/* City Groups */} {groupedOptions.map((group) => ( )} - {group.airports.map((airport) => ( - handleSelection(airport.code)} - className="relative ml-4 flex w-[calc(100%-1rem)] items-center justify-between rounded-full px-4 py-3 text-left text-base text-gray-900 transition-all data-[selected=true]:bg-selected-color dark:text-white dark:hover:bg-gray-700 dark:data-[selected=true]:bg-gray-700" - > -
- - {getDisplayText(airport)} - -
- {selectedValues.includes(airport.code) && ( - - )} -
- ))} + {group.airports + .filter((airport) => !selectedValues.includes(airport.code)) + .map((airport) => ( + handleSelection(airport.code)} + className="relative ml-4 flex w-[calc(100%-1rem)] items-center justify-between rounded-full px-4 py-3 text-left text-base text-gray-900 transition-all data-[selected=true]:bg-selected-color dark:text-white dark:hover:bg-gray-700 dark:data-[selected=true]:bg-gray-700" + > +
+ + {getDisplayText(airport)} + +
+ {selectedValues.includes(airport.code) && ( + + )} +
+ ))}
))} + {/* Remaining Items */} {filterOptions .filter( (opt) => + !selectedValues.includes(opt.code) && !groupedOptions.some((group) => group.airports.some( (airport) => airport.code === opt.code, @@ -413,14 +430,7 @@ export function MultiCombobox({ className="flex w-full items-center justify-between rounded-full px-4 py-3 text-left text-base text-gray-900 transition-all data-[selected=true]:bg-selected-color dark:text-white dark:hover:bg-gray-700 dark:data-[selected=true]:bg-gray-700" >
- + {getDisplayText(option)}