@qite/tide-booking-component
Version:
React Booking wizard & Booking product component for Tide
539 lines (452 loc) • 17.1 kB
text/typescript
import { BookingPackageItem, PackagingAccommodationResponse, PackagingFlightResponse } from '@qite/tide-client';
import { Filter, SortByType, TideTag } from '../types';
import { filter, flatMap, orderBy } from 'lodash';
import { getArrivalSegment, getDepartureRangeName, getDepartureSegment, getNumberOfStops } from './flight-utils';
import { DepartureRange } from '../../shared/types';
import { durationInTicksInMinutes, rangeFromDateTimeInMinutes } from '../../shared/utils/localization-util';
export const enrichFiltersWithResults = (results: BookingPackageItem[], filters: Filter[] | undefined, tags: TideTag[]): Filter[] => {
if (!results || results.length === 0 || !filters) {
return filters ?? [];
}
return filters.map((filter) => {
let updatedFilter = { ...filter };
if (filter.property === 'price' && (filter.min == null || filter.max == null)) {
const prices = results.map((r) => r.price ?? 0).filter((p) => p > 0);
if (prices.length > 0) {
updatedFilter.min = Math.floor(Math.min(...prices));
updatedFilter.max = Math.ceil(Math.max(...prices));
}
}
if (filter.property === 'accommodation') {
const map = new Map<string, { name?: string; code: string }>();
results.forEach((r) => {
if (r.accommodationCode) {
map.set(r.accommodationCode, {
name: r.accommodationName,
code: r.accommodationCode
});
}
});
updatedFilter.options = Array.from(map.values()).map((accommodation) => ({
label: accommodation.name ?? accommodation.code,
value: accommodation.code,
isChecked: false
}));
}
if (filter.property === 'regime') {
const map = new Map<string, { name?: string; code: string }>();
results.forEach((r) => {
if (r.regimeCode) {
map.set(r.regimeCode, {
name: r.regimeName,
code: r.regimeCode
});
}
});
updatedFilter.options = Array.from(map.values()).map((regime) => ({
label: regime.name ?? regime.code,
value: regime.code,
isChecked: false
}));
}
if (filter.property === 'theme') {
const map = new Map<number, { name: string; id: number }>();
results.forEach((r) => {
r.tagIds?.forEach((tagId) => {
const tag = tags.find((t) => t.id === tagId);
if (tag && tag.id != null && tag.name != null) {
map.set(tag.id, { name: tag.name, id: tag.id });
}
});
});
updatedFilter.options = Array.from(map.values()).map((theme) => ({
label: theme.name,
value: theme.id,
isChecked: false
}));
}
return updatedFilter;
});
};
export const enrichFiltersWithPackageAccoResults = (results: PackagingAccommodationResponse[], tags: TideTag[]): Filter[] => {
const filters: Filter[] = [];
if (!results || results.length === 0) {
return filters;
}
const regimeFilter: Filter = {
property: 'regime',
label: 'Regime',
type: 'checkbox',
options: [],
isFrontendFilter: true
};
const map = new Map<string, { name?: string; code: string }>();
results.forEach((r) => {
const rooms = flatMap(r.rooms);
if (rooms) {
rooms.map((room) => {
room.options.map((option) => {
if (option.regimeCode) {
map.set(option.regimeCode, {
name: option.regimeName,
code: option.regimeCode
});
}
});
});
}
});
regimeFilter.options = Array.from(map.values()).map((regime) => ({
label: regime.name ?? regime.code,
value: regime.code,
isChecked: false
}));
filters.push(regimeFilter);
const priceFilter: Filter = {
property: 'price',
label: 'Prijs',
type: 'slider',
isFrontendFilter: true
};
const prices = results.map((r) => r.price ?? 0).filter((p) => p > 0);
priceFilter.min = Math.floor(Math.min(...prices));
priceFilter.max = Math.ceil(Math.max(...prices));
filters.push(priceFilter);
const starsFilter: Filter = {
property: 'rating',
label: 'Stars',
type: 'star-rating',
options: [],
isFrontendFilter: true
};
const ratingMap = new Map<string, { name?: string; value: number }>();
results.forEach((r) => {
const stars = r.stars;
if (stars) {
ratingMap.set(stars.toString(), {
name: stars.toString(),
value: stars
});
}
});
starsFilter.options = Array.from(ratingMap.values()).map((rating) => ({
label: rating.name ?? rating.value.toString(),
value: rating.value,
isChecked: false
}));
filters.push(starsFilter);
return filters;
};
export const enrichFiltersWithPackageFlightResults = (results: PackagingFlightResponse[], tags: TideTag[], translations: any): Filter[] => {
const filters: Filter[] = [];
if (!results || results.length === 0) {
return filters;
}
// Airlines
const airlinesFilter: Filter = {
label: 'Airlines',
property: 'airline',
type: 'checkbox',
isFrontendFilter: true,
options: []
};
const airlinesFilterMap = new Map<string, { name?: string; code: string }>();
results.map((r) => {
const airlineCode = r.airlineCode;
const airlineName = r.airlineName;
if (airlineCode && airlineName) {
airlinesFilterMap.set(airlineCode, {
name: airlineName,
code: airlineCode
});
}
});
airlinesFilter.options = Array.from(airlinesFilterMap.values()).map((airline) => ({
label: airline.name ?? airline.code,
value: airline.code,
isChecked: false
}));
filters.push(airlinesFilter);
// Number of stops
const stopsFilter: Filter = {
label: 'Number of Stops',
property: 'numberOfStops',
type: 'checkbox',
isFrontendFilter: true,
options: []
};
const stopsMap = new Map<number, { numberOfStops: number }>();
results.map((result) => {
let numberOfStops = getNumberOfStops(result.outward);
if (!stopsMap.has(numberOfStops)) {
stopsMap.set(numberOfStops, { numberOfStops });
}
});
stopsFilter.options = Array.from(stopsMap.values()).map((stop) => ({
label: `${stop.numberOfStops == 0 ? 'direct' : stop.numberOfStops + ` Stop${stop.numberOfStops !== 1 ? 's' : ''}`}`,
value: stop.numberOfStops,
isChecked: false
}));
filters.push(stopsFilter);
// Departure range
const departureRangeFilter: Filter = {
label: 'Departure Range',
property: 'departureRange',
type: 'checkbox',
isFrontendFilter: true,
options: []
};
const departureRangeMap = new Map<DepartureRange, DepartureRange>();
results.map((result) => {
const departureRange = rangeFromDateTimeInMinutes(getDepartureSegment(result.outward)?.departureDateTime);
if (!departureRangeMap.has(departureRange)) {
departureRangeMap.set(departureRange, departureRange);
}
});
departureRangeFilter.options = orderBy(Array.from(departureRangeMap.values()), ['id'], ['asc']).map((range) => ({
label: getDepartureRangeName(translations, range),
value: range,
isChecked: false
}));
filters.push(departureRangeFilter);
// Departure Airport
const departureAirportFilter: Filter = {
label: 'Departure Airport',
property: 'departureAirport',
type: 'checkbox',
isFrontendFilter: true,
options: []
};
const departureAirportsMap = new Map<string, { name?: string; code: string }>();
results.map((result) => {
const departureSegment = getDepartureSegment(result.outward);
const departureAirport = departureSegment?.departureAirportCode;
if (departureAirport && !departureAirportsMap.has(departureAirport)) {
departureAirportsMap.set(departureAirport, {
name: departureSegment?.departureAirportName + ' (' + departureAirport + ')',
code: departureAirport
});
}
});
departureAirportFilter.options = Array.from(departureAirportsMap.values()).map((airport) => ({
label: airport.name ?? airport.code,
value: airport.code,
isChecked: false
}));
filters.push(departureAirportFilter);
// Arrival Airport
const arrivalAirportFilter: Filter = {
label: 'Arrival Airport',
property: 'arrivalAirport',
type: 'checkbox',
isFrontendFilter: true,
options: []
};
const arrivalAirportsMap = new Map<string, { name?: string; code: string }>();
results.map((result) => {
const arrivalSegment = getArrivalSegment(result.outward);
const arrivalAirport = arrivalSegment?.arrivalAirportCode;
if (arrivalAirport && !arrivalAirportsMap.has(arrivalAirport)) {
arrivalAirportsMap.set(arrivalAirport, {
name: arrivalSegment?.arrivalAirportName + ' (' + arrivalAirport + ')',
code: arrivalAirport
});
}
});
arrivalAirportFilter.options = Array.from(arrivalAirportsMap.values()).map((airport) => ({
label: airport.name ?? airport.code,
value: airport.code,
isChecked: false
}));
filters.push(arrivalAirportFilter);
// Price
const priceFilter: Filter = {
label: 'Price',
property: 'price',
type: 'slider',
isFrontendFilter: true
};
const prices = results.map((r) => r.price ?? 0).filter((p) => p > 0);
if (prices.length > 0) {
priceFilter.min = Math.floor(Math.min(...prices));
priceFilter.max = Math.ceil(Math.max(...prices));
}
filters.push(priceFilter);
// Travel duration
const travelDurationFilter: Filter = {
label: 'Travel Duration',
property: 'travelDuration',
type: 'slider',
isFrontendFilter: true
};
const minTravelTimeDuration = Math.min(...results.map((result) => result.outward.durationInTicks));
const maxTravelTimeDuration = Math.max(...results.map((result) => result.outward.durationInTicks));
const minTravelTimeValue = durationInTicksInMinutes(minTravelTimeDuration);
const maxTravelTimeValue = durationInTicksInMinutes(maxTravelTimeDuration);
if (minTravelTimeValue != null && maxTravelTimeValue != null) {
travelDurationFilter.min = minTravelTimeValue;
travelDurationFilter.max = maxTravelTimeValue;
}
filters.push(travelDurationFilter);
return filters;
};
export const applyFilters = (results: BookingPackageItem[], filters: Filter[], sortBy: SortByType | null) => {
const filtered = results.filter((r) => {
return filters.every((filter) => {
if (!filter.isFrontendFilter) return true;
// ACCOMMODATION
if (filter.property === 'accommodation') {
const selected = filter.options?.filter((o) => o.isChecked).map((o) => o.value);
if (!selected || selected.length === 0) return true;
return selected.includes(r.accommodationCode);
}
// REGIME
if (filter.property === 'regime') {
const selected = filter.options?.filter((o) => o.isChecked).map((o) => o.value);
if (!selected || selected.length === 0) return true;
if (!r.regimeCode) return false;
return selected.includes(r.regimeCode);
}
// PRICE
if (filter.property === 'price') {
if (filter.selectedMin != null && r.price < filter.selectedMin) return false;
if (filter.selectedMax != null && r.price > filter.selectedMax) return false;
return true;
}
// THEME
if (filter.property === 'theme') {
const selected = filter.options?.filter((o) => o.isChecked).map((o) => o.value);
if (!selected || selected.length === 0) return true;
return r.tagIds?.some((tagId) => selected.includes(tagId));
}
return true;
});
});
// SORTING
if (!sortBy || sortBy.label === 'default') {
return filtered;
}
return filtered.sort((a, b) => {
if (sortBy.label === 'price') {
return sortBy.direction === 'asc' ? a.price - b.price : b.price - a.price;
}
return 0;
});
};
export const applyFiltersToPackageAccoResults = (results: PackagingAccommodationResponse[], filters: Filter[], sortBy: SortByType | null) => {
const filtered = results.filter((r) => {
return filters.every((filter) => {
if (!filter.isFrontendFilter) return true;
// ACCOMMODATION
if (filter.property === 'accommodation') {
const selected = filter.options?.filter((o) => o.isChecked).map((o) => o.value);
if (!selected || selected.length === 0) return true;
const roomOptions = r.rooms.flatMap((room) => room.options);
return roomOptions.some((option) => selected.includes(option.accommodationCode));
}
// REGIME
if (filter.property === 'regime') {
const selected = filter.options?.filter((o) => o.isChecked).map((o) => o.value);
if (!selected || selected.length === 0) return true;
const roomOptions = r.rooms.flatMap((room) => room.options);
return roomOptions.some((option) => selected.includes(option.regimeCode));
}
// PRICE
if (filter.property === 'price') {
if (filter.selectedMin != null && r.price < filter.selectedMin) return false;
if (filter.selectedMax != null && r.price > filter.selectedMax) return false;
return true;
}
// RATING
if (filter.property === 'rating') {
if (r.stars == null) return false;
if (filter.selectedRating != null && r.stars < filter.selectedRating) return false;
return true;
}
return true;
});
});
// SORTING
if (!sortBy || sortBy.label === 'default') {
return filtered;
}
return filtered.sort((a, b) => {
if (sortBy.label === 'price') {
return sortBy.direction === 'asc' ? a.price - b.price : b.price - a.price;
}
return 0;
});
};
export const applyFiltersToPackageFlightResults = (results: PackagingFlightResponse[], filters: Filter[], sortBy: SortByType | null) => {
const filtered = results.filter((result) => {
return filters.every((filter) => {
if (!filter.isFrontendFilter) return true;
// Airline
if (filter.property === 'airline') {
const selected = filter.options?.filter((o) => o.isChecked).map((o) => o.value);
if (!selected || selected.length === 0) return true;
return selected.includes(result.airlineCode);
}
// Stops
if (filter.property === 'numberOfStops') {
const selected = filter.options?.filter((o) => o.isChecked).map((o) => o.value);
if (!selected || selected.length === 0) return true;
return selected.includes(getNumberOfStops(result.outward)) && selected.includes(getNumberOfStops(result.return));
}
// Departure range
if (filter.property === 'departureRange') {
const selected = filter.options?.filter((o) => o.isChecked).map((o) => o.value);
if (!selected || selected.length === 0) return true;
return selected.includes(rangeFromDateTimeInMinutes(getDepartureSegment(result.outward)?.departureDateTime));
}
// Departure airport
if (filter.property === 'departureAirport') {
const selected = filter.options?.filter((o) => o.isChecked).map((o) => o.value);
if (!selected || selected.length === 0) return true;
const departureAirportCode = getDepartureSegment(result.outward)?.departureAirportCode;
if (!departureAirportCode) return false;
return selected.includes(departureAirportCode);
}
// Arrival airport
if (filter.property === 'arrivalAirport') {
const selected = filter.options?.filter((o) => o.isChecked).map((o) => o.value);
if (!selected || selected.length === 0) return true;
const arrivalAirportCode = getArrivalSegment(result.outward)?.arrivalAirportCode;
if (!arrivalAirportCode) return false;
return selected.includes(arrivalAirportCode);
}
// PRICE
if (filter.property === 'price') {
if (filter.selectedMin != null && result.price < filter.selectedMin) return false;
if (filter.selectedMax != null && result.price > filter.selectedMax) return false;
return true;
}
// Travel times
if (filter.property === 'travelDuration') {
if (filter.selectedMin != null && durationInTicksInMinutes(result.outward?.durationInTicks) < filter.selectedMin) return false;
if (filter.selectedMax != null && durationInTicksInMinutes(result.outward?.durationInTicks) > filter.selectedMax) return false;
return true;
}
return true;
});
});
// SORTING
if (!sortBy || sortBy.label === 'default') {
return filtered;
}
if (sortBy.label === 'departureTime') {
return orderBy(
results,
[(result) => getDepartureSegment(result.outward)?.departureDateTime],
[sortBy.direction] // or "desc"
);
} else if (sortBy.label === 'durationInTicks') {
return orderBy(
results,
[(result) => durationInTicksInMinutes(result.outward?.durationInTicks ?? 0)],
[sortBy.direction] // or "desc"
);
} else {
return orderBy(results, [sortBy.label], [sortBy.direction]);
}
};