import { BookingPackageFlight, BookingPackageFlightMetaDataLine } from '@qite/tide-client/build/types'; import { differenceInMinutes, isEqual, parseISO } from 'date-fns'; import { FlightDirectionFilter, FlightFilterOption, FlightFilterOptions, GroupedFlightDetails, GroupedFlights } from '../../types'; /*interface FlightGroup { code: string; startDate: Date, endDate: Date; options: BookingPackageFlight[]; }*/ export const buildGroupedFlights = (outwardFlights: BookingPackageFlight[] | undefined, returnFlights: BookingPackageFlight[] | undefined) => { if (!outwardFlights || !returnFlights) return [] as GroupedFlights[]; // let outwardGroups = groupFlights(outwardFlights); // let returnGroups = groupFlights(returnFlights); const pairs: { outward: BookingPackageFlight; return: BookingPackageFlight; }[] = []; outwardFlights.forEach((outwardFlight) => { if (outwardFlight.externalGuid) { const returnFlight = returnFlights.find((x) => x.externalGuid === outwardFlight.externalGuid); if (!returnFlight) return; pairs.push({ outward: outwardFlight, return: returnFlight }); } else { const outwardCode = outwardFlight.code.substring(0, 7); const returnCode = outwardCode.split(' ').reduce((a, b) => `${b} ${a}`); returnFlights .filter((x) => x.code.startsWith(returnCode)) .forEach((returnFlight) => { pairs.push({ outward: outwardFlight, return: returnFlight }); }); } }); const results: GroupedFlights[] = []; pairs.forEach((x) => { const outwardFlightDetails = getFlightDetails(x.outward); const returnFlightDetails = getFlightDetails(x.return); if (!outwardFlightDetails || !returnFlightDetails) return; results.push({ isSelected: x.outward.isSelected && x.return.isSelected, price: x.outward.price + x.return.price, outward: outwardFlightDetails, return: returnFlightDetails, selectedOutward: x.outward, selectedReturn: x.return } as GroupedFlights); }); return results; }; export const buildFilterOptions = ( outwardFlights: BookingPackageFlight[] | undefined, returnFlights: BookingPackageFlight[] | undefined, translations: any ) => { if (!outwardFlights || !returnFlights) return undefined; const airports: FlightFilterOption[] = []; const airlines: FlightFilterOption[] = []; const numberOfStops: FlightFilterOption[] = []; const outwardDeparturePeriods: FlightFilterOption[] = []; const returnDeparturePeriods: FlightFilterOption[] = []; let lowestDepartureTravelDuration = 9999; let highestDepartureTravelDuration = 0; let lowestDepartureChangeDuration = 9999; let highestDepartureChangeDuration = 0; outwardFlights.forEach((flight) => { const airlineCode = flight.code.split('/')[1]; if (flight.flightMetaData.flightLines?.length) { const firstLine = flight.flightMetaData.flightLines[0]; if (!airports.some((x) => x.value === firstLine.departureAirport)) { airports.push({ value: firstLine?.departureAirport, label: firstLine.departureAirportDescription, count: 0, isSelected: false }); } } if (!airlines.some((x) => x.value === airlineCode)) { airlines.push({ value: airlineCode, label: flight.airlineDescription, count: 0, isSelected: false }); } const stopCount = flight.flightMetaData.flightLines.length - 1; if (!numberOfStops.some((x) => x.value === stopCount + '')) { numberOfStops.push({ value: stopCount + '', label: stopCount === 0 ? translations.FLIGHTS_FORM.DIRECT_FLIGHT : stopCount == 1 ? `${stopCount} ${translations.FLIGHTS_FORM.STOP}` : `${stopCount} ${translations.FLIGHTS_FORM.STOPS}`, count: 0, isSelected: false }); } const departureTime = flight.flightMetaData.flightLines[0].departureTime; const timeBracket = determineTimeBracket(departureTime); if (!outwardDeparturePeriods.some((x) => x.value === timeBracket)) { outwardDeparturePeriods.push({ value: timeBracket, label: getBracketTranslation(timeBracket, translations), count: 0, isSelected: false }); } const travelDurationInMinutes = minutesFromTicks(flight.flightMetaData.durationInTicks); if (travelDurationInMinutes > highestDepartureTravelDuration) highestDepartureTravelDuration = travelDurationInMinutes; if (travelDurationInMinutes < lowestDepartureTravelDuration) lowestDepartureTravelDuration = travelDurationInMinutes; const changeDurationInMinutes = getTotalChangeDuration(flight); if (changeDurationInMinutes > highestDepartureChangeDuration) highestDepartureChangeDuration = changeDurationInMinutes; if (changeDurationInMinutes < lowestDepartureChangeDuration) lowestDepartureChangeDuration = changeDurationInMinutes; }); let lowestReturnTravelDuration = 9999; let highestReturnTravelDuration = 0; let lowestReturnChangeDuration = 9999; let highestReturnChangeDuration = 0; returnFlights.forEach((flight) => { const durationInMinutes = minutesFromTicks(flight.flightMetaData.durationInTicks); if (durationInMinutes > highestReturnTravelDuration) highestReturnTravelDuration = durationInMinutes; if (durationInMinutes < lowestReturnTravelDuration) lowestReturnTravelDuration = durationInMinutes; const changeDurationInMinutes = getTotalChangeDuration(flight); if (changeDurationInMinutes > highestReturnChangeDuration) highestReturnChangeDuration = changeDurationInMinutes; if (changeDurationInMinutes < lowestReturnChangeDuration) lowestReturnChangeDuration = changeDurationInMinutes; const departureTime = flight.flightMetaData.flightLines[0].departureTime; const timeBracket = determineTimeBracket(departureTime); if (!returnDeparturePeriods.some((x) => x.value === timeBracket)) { returnDeparturePeriods.push({ value: timeBracket, label: getBracketTranslation(timeBracket, translations), count: 0, isSelected: false }); } }); return { airports: airports, airlines: airlines, numberOfStops: numberOfStops, outward: { departurePeriod: outwardDeparturePeriods, travelDuration: { min: lowestDepartureTravelDuration, max: highestDepartureTravelDuration, selectedMin: lowestDepartureTravelDuration, selectedMax: highestDepartureTravelDuration }, changeDuration: { min: lowestDepartureChangeDuration, max: highestDepartureChangeDuration, selectedMin: lowestDepartureChangeDuration, selectedMax: highestDepartureChangeDuration } }, return: { departurePeriod: returnDeparturePeriods, travelDuration: { min: lowestReturnTravelDuration, max: highestReturnTravelDuration, selectedMin: lowestReturnTravelDuration, selectedMax: highestReturnTravelDuration }, changeDuration: { min: lowestReturnChangeDuration, max: highestReturnChangeDuration, selectedMin: lowestReturnChangeDuration, selectedMax: highestReturnChangeDuration } } } as FlightFilterOptions; }; export const filterGroupedFlights = (groups: GroupedFlights[], filterOptions: FlightFilterOptions | undefined) => { if (!groups.length || !filterOptions) return []; let filteredGroups = groups; if (filterOptions.airlines.some((x) => x.isSelected)) { const selectedAirlineCodes = filterOptions.airlines.filter((x) => x.isSelected); filteredGroups = filteredGroups.filter((x) => selectedAirlineCodes.some((y) => y.value === x.outward.airlineCode)); } if (filterOptions.airports.some((x) => x.isSelected)) { const selectedAirlineCodes = filterOptions.airports.filter((x) => x.isSelected); filteredGroups = filteredGroups.filter((x) => selectedAirlineCodes.some((y) => y.value === x.outward.departureAirportCode)); } if (filterOptions.numberOfStops.some((x) => x.isSelected)) { const selectedNumberOfStops = filterOptions.numberOfStops.filter((x) => x.isSelected); filteredGroups = filteredGroups.filter((x) => selectedNumberOfStops.some((y) => parseInt(y.value) === x.outward.flightLines.length - 1)); } filteredGroups = filterGroupedFlightByDirection(filteredGroups, true, filterOptions.outward); filteredGroups = filterGroupedFlightByDirection(filteredGroups, false, filterOptions.return); return filteredGroups; }; const filterGroupedFlightByDirection = (groups: GroupedFlights[], isOutward: boolean, directionFilter: FlightDirectionFilter) => { let filteredGroups = groups; if (directionFilter.departurePeriod.some((x) => x.isSelected)) { const selectedDeparturePeriods = directionFilter.departurePeriod.filter((x) => x.isSelected); filteredGroups = filteredGroups.filter((x) => selectedDeparturePeriods.some((y) => y.value === determineTimeBracket((isOutward ? x.outward : x.return).departureTime)) ); } filteredGroups = filteredGroups.filter( (x) => directionFilter.travelDuration.selectedMin <= (isOutward ? x.outward : x.return).travelDurationMinutes && (isOutward ? x.outward : x.return).travelDurationMinutes <= directionFilter.travelDuration.selectedMax ); return filteredGroups.filter( (x) => directionFilter.changeDuration.selectedMin <= (isOutward ? x.outward : x.return).changeDurationMinutes && (isOutward ? x.outward : x.return).changeDurationMinutes <= directionFilter.changeDuration.selectedMax ); }; export const formatMinutes = (minutes: number) => { var hh = Math.floor(minutes / 60); var mm = Math.floor(minutes % 60); return pad(hh, 2) + ':' + pad(mm, 2); }; const getFlightDetails = (flight: BookingPackageFlight) => { if (flight.flightMetaData == null) return; const firstLine = flight.flightMetaData.flightLines[0]; const lastLine = flight.flightMetaData.flightLines[flight.flightMetaData.flightLines.length - 1]; const airlineCode = flight.code.split('/')[1]; const waitDurations = getWaitDurations(flight.flightMetaData.flightLines); return { airline: flight.airlineDescription, airlineCode: airlineCode, departureDate: firstLine.departureDate, departureTime: firstLine.departureTime, departureAirportCode: firstLine.departureAirport, departureAirport: firstLine.departureAirportDescription, arrivalDate: lastLine.arrivalDate, arrivalTime: lastLine.arrivalTime, arrivalAirport: lastLine.arrivalAirportDescription, travelDuration: formatDuration(flight.flightMetaData.durationInTicks), travelDurationMinutes: minutesFromTicks(flight.flightMetaData.durationInTicks), changeDurationMinutes: getTotalChangeDuration(flight), numberOfStops: flight.flightMetaData.flightLines.length - 1, isNextDay: isNextDay(firstLine.departureDate, lastLine.arrivalDate), travelClass: firstLine.travelClass, flightLines: flight.flightMetaData.flightLines.map((x, i) => ({ airline: x.operatingAirlineDescription, departureDate: x.departureDate, departureTime: x.departureTime, departureAirport: x.departureAirportDescription, arrivalDate: x.arrivalDate, arrivalTime: x.arrivalTime, arrivalAirport: x.arrivalAirportDescription, number: `${x.airlineCode} ${x.number}`, travelDuration: formatDuration(x.durationInTicks), waitDuration: waitDurations.length - 1 <= i ? waitDurations[i] : undefined })) } as GroupedFlightDetails; }; const isNextDay = (startDateString: string, endDateString: string) => { const startDate = parseISO(startDateString); const endDate = parseISO(endDateString); return !isEqual(startDate, endDate); }; const getWaitDurations = (lines: BookingPackageFlightMetaDataLine[]) => { if (lines.length <= 1) return []; let arrivalDate = lines[0].arrivalDate; let arrivalTime = lines[0].arrivalTime; const waitDurations: string[] = []; for (var i = 1; i < lines.length; i++) { const line = lines[i]; const waitDuration = getWaitDuration(arrivalDate, arrivalTime, line.departureDate, line.departureTime); waitDurations.push(waitDuration); arrivalDate = line.arrivalDate; arrivalTime = line.arrivalTime; } return waitDurations; }; const getWaitDuration = (arrivalDateString: string, arrivalTime: string, departureDateString: string, departureTime: string) => { const minutes = getWaitDurationInMinutes(arrivalDateString, arrivalTime, departureDateString, departureTime); var hh = Math.floor(minutes / 60); var mm = Math.floor(minutes % 60); return pad(hh, 2) + ':' + pad(mm, 2); }; const getWaitDurationInMinutes = (arrivalDateString: string, arrivalTime: string, departureDateString: string, departureTime: string) => { const arrivalDate = parseISO(arrivalDateString); const arrivalTimeParts = arrivalTime.split(':'); arrivalDate.setHours(parseInt(arrivalTimeParts[0])); arrivalDate.setMinutes(parseInt(arrivalTimeParts[1])); const departureDate = parseISO(departureDateString); const departureTimeParts = departureTime.split(':'); departureDate.setHours(parseInt(departureTimeParts[0])); departureDate.setMinutes(parseInt(departureTimeParts[1])); return differenceInMinutes(departureDate, arrivalDate); }; /*const groupFlights = (flights: BookingPackageFlight[]) => { let flightsPool = [...flights]; let groups = [] as FlightGroup[]; for (var i = 0; i < flightsPool.length; i++) { const flight = flightsPool[i]; const relatedFlights = flightsPool.filter(x => x != flight && x.code === flight.code && isDateEqual(x.startDateTime, flight.startDateTime) && isDateEqual(x.endDateTime, flight.endDateTime) ); flightsPool = flightsPool.filter(x => x != flight && relatedFlights.some(y => y != x)); groups.push({ code: flight.code, startDate: parseISO(flight.startDateTime), endDate: parseISO(flight.endDateTime), options: [flight, ...relatedFlights] }); } } const isDateEqual = (first: string, second: string) => { const firstDate = parseISO(first); const secondDate = parseISO(second); return isEqual(firstDate, secondDate); }*/ const minutesFromTicks = (ticks: number) => { const totalSeconds = ticks / 10_000_000; return Math.floor(totalSeconds / 60); }; const formatDuration = (ticks: number) => { if (!ticks) return ''; const totalSeconds = ticks / 10_000_000; var hh = Math.floor(totalSeconds / 3600); var mm = Math.floor((totalSeconds % 3600) / 60); return pad(hh, 2) + ':' + pad(mm, 2); }; const pad = (input: number, width: number) => { const n = input + ''; return n.length >= width ? n : new Array(width - n.length + 1).join('0') + n; }; const determineTimeBracket = (input: string) => { const time = parseInt(input.replace(':', '')); if (time <= 500) return '0000-0500'; if (time > 500 && time <= 1200) return '0500-1200'; if (time > 1200 && time <= 1800) return '1201-1800'; return '1800-2400'; }; const getBracketTranslation = (input: string, translations: any) => { if (input === '0000-0500') return translations.FLIGHTS_FORM.NIGHT_DEPARTURE; if (input === '0500-1200') return translations.FLIGHTS_FORM.MORNING_DEPARTURE; if (input === '1200-1800') return translations.FLIGHTS_FORM.AFTERNOON_DEPARTURE; return translations.FLIGHTS_FORM.EVENING_DEPARTURE; }; const getTotalChangeDuration = (flight: BookingPackageFlight) => { const lines = flight.flightMetaData.flightLines; if (lines.length <= 1) return 0; let arrivalDate = lines[0].arrivalDate; let arrivalTime = lines[0].arrivalTime; let waitDuration = 0; for (var i = 1; i < lines.length; i++) { const line = lines[i]; waitDuration += getWaitDurationInMinutes(arrivalDate, arrivalTime, line.departureDate, line.departureTime); } return waitDuration; };