import React, { useContext, useEffect, useState } from 'react'; import Icon from '../icon'; import { ExtendedFlightSearchResponseItem } from '../../../search-results/types'; import { useFlightSearch } from '../../../search-results/components/flight/flight-search-context'; import { useDispatch, useSelector } from 'react-redux'; import { setSelectedFlightDetails } from '../../../search-results/store/search-results-slice'; import { SearchResultsRootState } from '../../../search-results/store/search-results-store'; import Spinner from '../../../search-results/components/spinner/spinner'; import SearchResultsConfigurationContext from '../../../search-results/search-results-configuration-context'; import { durationTicksInHoursString, getTranslations, timeFromDateTime } from '../../utils/localization-util'; import { getArrivalSegment, getDepartureSegment, getNumberOfStopsLabel } from '../../../search-results/utils/flight-utils'; import { first, isEmpty } from 'lodash'; type FlightsFlyInProps = { isOpen: boolean; setIsOpen: (open: boolean) => void; }; const FlightsFlyIn: React.FC = ({ isOpen, setIsOpen }) => { const context = useContext(SearchResultsConfigurationContext); const language = context?.languageCode ?? 'en-GB'; const translations = getTranslations(language); const dispatch = useDispatch(); const { flightSearchDetailsLoading, flightDetailsSearchResults, onCancelSearch, numberOfTravellers } = useFlightSearch(); const { selectedFlight } = useSelector((state: SearchResultsRootState) => state.searchResults); const [flights, setFlights] = useState([]); const [flight, setFlight] = useState(undefined); const [uniqueOutwardFlights, setUniqueOutwardFlights] = useState([]); const [selectedOutwardFareCode, setSelectedOutwardFareCode] = useState(null); const [uniqueReturnFlights, setUniqueReturnFlights] = useState([]); const [selectedReturnFareCode, setSelectedReturnFareCode] = useState(null); useEffect(() => { if (flightDetailsSearchResults.length > 0 && selectedFlight) { const routeFlights = flightDetailsSearchResults.filter((r) => r.flightRouteId === selectedFlight.flightRouteId); setFlights(routeFlights); setFlight(first(routeFlights)); // Deduplicate by outward fareCode const uniqueMap = new Map(); routeFlights.forEach((flight) => { const fareCode = flight.outward.segments?.[0]?.metaData?.fareCode; if (fareCode && !uniqueMap.has(fareCode)) { uniqueMap.set(fareCode, flight); } }); const values = Array.from(uniqueMap.values()); setUniqueOutwardFlights(values); const fareCode = first(values)?.outward.segments?.[0]?.metaData?.fareCode; setSelectedOutwardFareCode(fareCode ?? null); } }, [flightDetailsSearchResults, flightSearchDetailsLoading]); useEffect(() => { if (!selectedOutwardFareCode) { setUniqueReturnFlights([]); setSelectedReturnFareCode(null); return; } // Filter combinations that match selected outward fare const matchingCombinations = flights.filter((flight) => flight.outward.segments?.[0]?.metaData?.fareCode === selectedOutwardFareCode); const returnMap = new Map(); // Deduplicate return flights by return fareCode matchingCombinations.forEach((flight) => { const returnFareCode = flight.return.segments?.[0]?.metaData?.fareCode; if (returnFareCode && !returnMap.has(returnFareCode)) { returnMap.set(returnFareCode, flight); } }); const returns = Array.from(returnMap.values()); setUniqueReturnFlights(returns); // keep current return if still valid based on the selected outward, otherwise select the first available const stillValid = returns.some((r) => r.return.segments?.[0]?.metaData?.fareCode === selectedReturnFareCode); if (!stillValid) { const defaultFareCode = returns[0]?.return.segments?.[0]?.metaData?.fareCode ?? null; setSelectedReturnFareCode(defaultFareCode); } }, [selectedOutwardFareCode, flights]); const selectedCombinationFlight = React.useMemo(() => { if (!selectedOutwardFareCode || !selectedReturnFareCode) return undefined; return flights.find( (flight) => flight.outward.segments?.[0]?.metaData?.fareCode === selectedOutwardFareCode && flight.return.segments?.[0]?.metaData?.fareCode === selectedReturnFareCode ); }, [flights, selectedOutwardFareCode, selectedReturnFareCode]); useEffect(() => { if (!selectedCombinationFlight) return; dispatch(setSelectedFlightDetails(selectedCombinationFlight)); }, [selectedCombinationFlight, dispatch]); const getOutwardPriceDiff = (outwardFareCode: string) => { if (!selectedReturnFareCode || !selectedCombinationFlight) return 0; const combo = flights.find( (flight) => flight.outward.segments?.[0]?.metaData?.fareCode === outwardFareCode && flight.return.segments?.[0]?.metaData?.fareCode === selectedReturnFareCode ) ?? // it means that outward flight cannot be paired with the selected return fallback to first combo with that outward fare flights.find((flight) => flight.outward.segments?.[0]?.metaData?.fareCode === outwardFareCode); if (!combo) return 0; return Math.round(combo.price - selectedCombinationFlight.price); }; const getReturnPriceDiff = (returnFareCode: string) => { if (!selectedOutwardFareCode || !selectedCombinationFlight) return 0; const combo = flights.find( (flight) => flight.outward.segments?.[0]?.metaData?.fareCode === selectedOutwardFareCode && flight.return.segments?.[0]?.metaData?.fareCode === returnFareCode ) ?? // it means that return flight cannot be paired with the selected outward fallback to first combo with that return fare flights.find((flight) => flight.return.segments?.[0]?.metaData?.fareCode === returnFareCode); if (!combo) return 0; return Math.round(combo.price - selectedCombinationFlight.price); }; // TODO: go to booking page? const handleConfirm = () => { if (isOpen) { onCancelSearch(); setIsOpen(false); } }; return ( <>
{flightSearchDetailsLoading || isEmpty(flights) ? ( ) : ( flight && (
{translations.SRP.DEPARTURE} {getDepartureSegment(flight?.outward)?.departureAirportCode} -{' '} {getArrivalSegment(flight?.outward)?.arrivalAirportCode} {timeFromDateTime(getDepartureSegment(flight?.outward)?.departureDateTime)} -{' '} {timeFromDateTime(getArrivalSegment(flight?.outward)?.arrivalDateTime)} ({durationTicksInHoursString(flight.outward.durationInTicks)},{' '} {getNumberOfStopsLabel(flight.outward, translations.SRP.DIRECT, translations.SRP.STOPS, translations.SRP.STOP)}), {numberOfTravellers}{' '} travellers
) )}
{!flightSearchDetailsLoading && flight && ( <>
{uniqueOutwardFlights.map((flightOption, index) => { const firstSegment = first(flightOption.outward.segments); if (!firstSegment) return null; const diff = getOutwardPriceDiff(firstSegment.metaData.fareCode); return (
{firstSegment.metaData.fareMarketingName} {diff !== null && diff != 0 && ( 0 ? 'flyin__content-card-top-price--increase' : diff < 0 ? 'flyin__content-card-top-price--decrease' : '' }`}> {diff > 0 ? `+€${diff}` : `-€${Math.abs(diff)}`} )}
Number of travellers {numberOfTravellers}
Travel class {firstSegment.metaData.fareMarketingName}
Booking class {firstSegment.bookingClassCode}
Fare basis {firstSegment.metaData.fareCode}
{firstSegment.metaData.luggageCarryOn && (
Carry-on luggage
{firstSegment.metaData.luggageCarryOn.text}
)} {firstSegment.metaData.luggageChecked && (
Checked luggage
{firstSegment.metaData.luggageChecked.text}
)} {firstSegment.metaData.seatSelection && (
Seat selection
{firstSegment.metaData.seatSelection.text}
)} {firstSegment.metaData.cancel && (
Refund
{firstSegment.metaData.cancel.text}
)} {firstSegment.metaData.other && (
Other
    {firstSegment.metaData.other.map((other, index) => (
  • {other.text}
  • ))}
)}
{ const fareCode = flightOption.outward.segments?.[0]?.metaData?.fareCode; setSelectedOutwardFareCode(fareCode ?? null); }}>
{' '} {selectedOutwardFareCode === firstSegment.metaData.fareCode ? 'Selected' : 'Select'}
); })}
{translations.SRP.RETURN} {getDepartureSegment(flight?.return)?.departureAirportCode} -{' '} {getArrivalSegment(flight?.return)?.arrivalAirportCode} {timeFromDateTime(getDepartureSegment(flight?.return)?.departureDateTime)} -{' '} {timeFromDateTime(getArrivalSegment(flight?.return)?.arrivalDateTime)} ({durationTicksInHoursString(flight.return.durationInTicks)},{' '} {getNumberOfStopsLabel(flight.return, translations.SRP.DIRECT, translations.SRP.STOPS, translations.SRP.STOP)}), {numberOfTravellers}{' '} travellers
{uniqueReturnFlights.map((flightOption, index) => { const firstSegment = first(flightOption.return.segments); if (!firstSegment) return null; const diff = getReturnPriceDiff(firstSegment.metaData.fareCode); return (
{firstSegment.metaData.fareMarketingName} {diff !== null && diff != 0 && ( 0 ? 'flyin__content-card-top-price--increase' : diff < 0 ? 'flyin__content-card-top-price--decrease' : '' }`}> {diff > 0 ? `+€${diff}` : `-€${Math.abs(diff)}`} )}
Number of travellers {numberOfTravellers}
Travel class {firstSegment.metaData.fareMarketingName}
Booking class {firstSegment.bookingClassCode}
Fare basis {firstSegment.metaData.fareCode}
{firstSegment.metaData.luggageCarryOn && (
Carry-on luggage
{firstSegment.metaData.luggageCarryOn.text}
)} {firstSegment.metaData.luggageChecked && (
Checked luggage
{firstSegment.metaData.luggageChecked.text}
)} {firstSegment.metaData.seatSelection && (
Seat selection
{firstSegment.metaData.seatSelection.text}
)} {firstSegment.metaData.cancel && (
Refund
{firstSegment.metaData.cancel.text}
)} {firstSegment.metaData.other && (
Other
    {firstSegment.metaData.other.map((other, index) => (
  • {other.text}
  • ))}
)}
{ const fareCode = flightOption.return.segments?.[0]?.metaData?.fareCode; setSelectedReturnFareCode(fareCode ?? null); }}>
{' '} {selectedReturnFareCode === firstSegment.metaData.fareCode ? 'Selected' : 'Select'}
); })}
)} {!flightSearchDetailsLoading && (
{translations.SHARED.TOTAL_PRICE}: €{selectedCombinationFlight?.price?.toFixed(2)}
)} ); }; export default FlightsFlyIn;