@qite/tide-booking-component 1.4.102 → 1.4.104
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/build-cjs/index.js +1773 -877
- package/build/build-cjs/src/search-results/components/filters/filters.d.ts +2 -0
- package/build/build-cjs/src/search-results/components/flight/flight-selection/independent-flight-option.d.ts +3 -0
- package/build/build-cjs/src/search-results/components/hotel/hotel-accommodation-results.d.ts +1 -0
- package/build/build-cjs/src/search-results/store/search-results-selectors.d.ts +424 -0
- package/build/build-cjs/src/search-results/store/search-results-slice.d.ts +27 -8
- package/build/build-cjs/src/search-results/types.d.ts +14 -2
- package/build/build-cjs/src/search-results/utils/search-results-utils.d.ts +8 -6
- package/build/build-cjs/src/shared/components/flyin/flyin.d.ts +3 -3
- package/build/build-cjs/src/shared/components/flyin/packaging-flights-flyin.d.ts +7 -0
- package/build/build-cjs/src/shared/utils/localization-util.d.ts +2 -0
- package/build/build-esm/index.js +1747 -861
- package/build/build-esm/src/search-results/components/filters/filters.d.ts +2 -0
- package/build/build-esm/src/search-results/components/flight/flight-selection/independent-flight-option.d.ts +3 -0
- package/build/build-esm/src/search-results/components/hotel/hotel-accommodation-results.d.ts +1 -0
- package/build/build-esm/src/search-results/store/search-results-selectors.d.ts +424 -0
- package/build/build-esm/src/search-results/store/search-results-slice.d.ts +27 -8
- package/build/build-esm/src/search-results/types.d.ts +14 -2
- package/build/build-esm/src/search-results/utils/search-results-utils.d.ts +8 -6
- package/build/build-esm/src/shared/components/flyin/flyin.d.ts +3 -3
- package/build/build-esm/src/shared/components/flyin/packaging-flights-flyin.d.ts +7 -0
- package/build/build-esm/src/shared/utils/localization-util.d.ts +2 -0
- package/package.json +2 -2
- package/src/booking-wizard/features/flight-options/index.tsx +6 -2
- package/src/search-results/components/filters/filters.tsx +8 -9
- package/src/search-results/components/flight/flight-selection/independent-flight-option.tsx +31 -4
- package/src/search-results/components/hotel/hotel-accommodation-results.tsx +81 -25
- package/src/search-results/components/icon.tsx +1 -1
- package/src/search-results/components/search-results-container/search-results-container.tsx +194 -130
- package/src/search-results/store/search-results-selectors.ts +73 -0
- package/src/search-results/store/search-results-slice.ts +94 -14
- package/src/search-results/types.ts +14 -2
- package/src/search-results/utils/search-results-utils.ts +310 -58
- package/src/shared/components/flyin/flyin.tsx +102 -19
- package/src/shared/components/flyin/packaging-flights-flyin.tsx +164 -0
- package/src/shared/translations/ar-SA.json +2 -0
- package/src/shared/translations/da-DK.json +2 -0
- package/src/shared/translations/de-DE.json +2 -0
- package/src/shared/translations/en-GB.json +2 -0
- package/src/shared/translations/es-ES.json +2 -0
- package/src/shared/translations/fr-BE.json +2 -0
- package/src/shared/translations/fr-FR.json +2 -0
- package/src/shared/translations/is-IS.json +2 -0
- package/src/shared/translations/it-IT.json +2 -0
- package/src/shared/translations/ja-JP.json +2 -0
- package/src/shared/translations/nl-BE.json +2 -0
- package/src/shared/translations/nl-NL.json +2 -0
- package/src/shared/translations/no-NO.json +2 -0
- package/src/shared/translations/pl-PL.json +2 -0
- package/src/shared/translations/pt-PT.json +2 -0
- package/src/shared/translations/sv-SE.json +2 -0
- package/src/shared/utils/localization-util.ts +5 -2
- package/styles/components/_flight-option.scss +14 -1
- package/styles/components/_flyin.scss +16 -0
- package/styles/components/_search.scss +9 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
|
2
|
-
import {
|
|
2
|
+
import { ExtendedFlightSearchResponseItem, Filter, FlyInType, SortByType } from '../types';
|
|
3
3
|
import {
|
|
4
4
|
BookingPackage,
|
|
5
5
|
BookingPackageItem,
|
|
@@ -14,53 +14,87 @@ export interface SearchResultsState {
|
|
|
14
14
|
results: BookingPackageItem[];
|
|
15
15
|
filteredResults: BookingPackageItem[];
|
|
16
16
|
selectedSearchResult: BookingPackageItem | null;
|
|
17
|
+
|
|
17
18
|
packagingAccoResults: PackagingAccommodationResponse[];
|
|
18
19
|
filteredPackagingAccoResults: PackagingAccommodationResponse[];
|
|
19
20
|
packagingAccoSearchDetails: PackagingAccommodationResponse[];
|
|
20
21
|
selectedPackagingAccoResultCode: string | null;
|
|
22
|
+
|
|
21
23
|
packagingFlightResults: PackagingFlightResponse[];
|
|
24
|
+
filteredPackagingFlightResults: PackagingFlightResponse[];
|
|
22
25
|
selectedPackagingFlight: PackagingFlightResponse | null;
|
|
26
|
+
selectedOutwardKey: string | null;
|
|
27
|
+
selectedReturnKey: string | null;
|
|
28
|
+
|
|
23
29
|
selectedFlight: ExtendedFlightSearchResponseItem | null;
|
|
24
30
|
selectedFlightDetails: ExtendedFlightSearchResponseItem | null;
|
|
31
|
+
|
|
25
32
|
bookingPackageDetails: BookingPackage | null;
|
|
33
|
+
priceDetails: BookingPriceDetails | null;
|
|
34
|
+
|
|
26
35
|
isLoading: boolean;
|
|
27
36
|
flightsLoading: boolean;
|
|
28
|
-
|
|
37
|
+
|
|
29
38
|
selectedSortType: SortByType | null;
|
|
39
|
+
selectedFlightSortType: SortByType | null;
|
|
40
|
+
filters: Filter[];
|
|
41
|
+
initialFilters: Filter[];
|
|
42
|
+
flightFilters: Filter[];
|
|
43
|
+
initialFlightFilters: Filter[];
|
|
44
|
+
|
|
30
45
|
activeTab: string | null;
|
|
31
46
|
currentPage: number;
|
|
32
|
-
|
|
33
|
-
editablePackagingEntry: PackagingEntry | null;
|
|
47
|
+
|
|
34
48
|
transactionId: string | null;
|
|
35
|
-
|
|
36
|
-
|
|
49
|
+
|
|
50
|
+
flyInIsOpen: boolean;
|
|
51
|
+
flyInType: FlyInType | null;
|
|
52
|
+
|
|
37
53
|
itinerary: ClientPortalItinerary | null;
|
|
54
|
+
editablePackagingEntry: PackagingEntry | null;
|
|
38
55
|
}
|
|
39
56
|
|
|
40
57
|
const initialState: SearchResultsState = {
|
|
41
58
|
results: [],
|
|
42
59
|
filteredResults: [],
|
|
43
60
|
selectedSearchResult: null,
|
|
61
|
+
|
|
44
62
|
packagingAccoResults: [],
|
|
45
63
|
filteredPackagingAccoResults: [],
|
|
46
64
|
packagingAccoSearchDetails: [],
|
|
47
65
|
selectedPackagingAccoResultCode: null,
|
|
66
|
+
|
|
48
67
|
packagingFlightResults: [],
|
|
68
|
+
filteredPackagingFlightResults: [],
|
|
49
69
|
selectedPackagingFlight: null,
|
|
70
|
+
selectedOutwardKey: null,
|
|
71
|
+
selectedReturnKey: null,
|
|
72
|
+
|
|
50
73
|
selectedFlight: null,
|
|
51
74
|
selectedFlightDetails: null,
|
|
75
|
+
|
|
52
76
|
bookingPackageDetails: null,
|
|
77
|
+
priceDetails: null,
|
|
78
|
+
|
|
53
79
|
isLoading: false,
|
|
54
80
|
flightsLoading: false,
|
|
55
|
-
|
|
81
|
+
|
|
56
82
|
selectedSortType: null,
|
|
83
|
+
selectedFlightSortType: null,
|
|
84
|
+
initialFilters: [],
|
|
85
|
+
filters: [],
|
|
86
|
+
initialFlightFilters: [],
|
|
87
|
+
flightFilters: [],
|
|
88
|
+
|
|
57
89
|
activeTab: 'compact',
|
|
58
90
|
currentPage: 1,
|
|
91
|
+
|
|
92
|
+
transactionId: null,
|
|
93
|
+
|
|
59
94
|
flyInIsOpen: false,
|
|
95
|
+
flyInType: null,
|
|
96
|
+
|
|
60
97
|
editablePackagingEntry: null,
|
|
61
|
-
transactionId: null,
|
|
62
|
-
accommodationFlyInStep: 'details',
|
|
63
|
-
priceDetails: null,
|
|
64
98
|
itinerary: null
|
|
65
99
|
};
|
|
66
100
|
|
|
@@ -92,6 +126,9 @@ const searchResultsSlice = createSlice({
|
|
|
92
126
|
setPackagingFlightResults(state, action: PayloadAction<PackagingFlightResponse[]>) {
|
|
93
127
|
state.packagingFlightResults = action.payload;
|
|
94
128
|
},
|
|
129
|
+
setFilteredPackagingFlightResults(state, action: PayloadAction<PackagingFlightResponse[]>) {
|
|
130
|
+
state.filteredPackagingFlightResults = action.payload;
|
|
131
|
+
},
|
|
95
132
|
setSelectedPackagingFlight(state, action: PayloadAction<PackagingFlightResponse | null>) {
|
|
96
133
|
state.selectedPackagingFlight = action.payload;
|
|
97
134
|
},
|
|
@@ -122,6 +159,9 @@ const searchResultsSlice = createSlice({
|
|
|
122
159
|
setFlightsLoading(state, action: PayloadAction<boolean>) {
|
|
123
160
|
state.flightsLoading = action.payload;
|
|
124
161
|
},
|
|
162
|
+
setInitialFilters(state, action: PayloadAction<Filter[]>) {
|
|
163
|
+
state.initialFilters = action.payload;
|
|
164
|
+
},
|
|
125
165
|
setFilters(state, action: PayloadAction<Filter[]>) {
|
|
126
166
|
const updatedFilters = action.payload;
|
|
127
167
|
|
|
@@ -137,9 +177,30 @@ const searchResultsSlice = createSlice({
|
|
|
137
177
|
resetFilters(state, action: PayloadAction<Filter[]>) {
|
|
138
178
|
state.filters = action.payload;
|
|
139
179
|
},
|
|
180
|
+
setInitialFlightFilters(state, action: PayloadAction<Filter[]>) {
|
|
181
|
+
state.initialFlightFilters = action.payload;
|
|
182
|
+
},
|
|
183
|
+
setFlightFilters(state, action: PayloadAction<Filter[]>) {
|
|
184
|
+
const updatedFilters = action.payload;
|
|
185
|
+
|
|
186
|
+
updatedFilters.forEach((updatedFilter) => {
|
|
187
|
+
const existingIndex = state.flightFilters.findIndex((f) => f.property === updatedFilter.property);
|
|
188
|
+
if (existingIndex !== -1) {
|
|
189
|
+
state.flightFilters[existingIndex] = updatedFilter;
|
|
190
|
+
} else {
|
|
191
|
+
state.flightFilters.push(updatedFilter); // Optional: Add new filters if not present
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
},
|
|
195
|
+
resetFlightFilters(state, action: PayloadAction<Filter[]>) {
|
|
196
|
+
state.flightFilters = action.payload;
|
|
197
|
+
},
|
|
140
198
|
setSortType(state, action: PayloadAction<SortByType | null>) {
|
|
141
199
|
state.selectedSortType = action.payload;
|
|
142
200
|
},
|
|
201
|
+
setFlightSortType(state, action: PayloadAction<SortByType | null>) {
|
|
202
|
+
state.selectedFlightSortType = action.payload;
|
|
203
|
+
},
|
|
143
204
|
setActiveTab(state, action: PayloadAction<string | null>) {
|
|
144
205
|
state.activeTab = action.payload;
|
|
145
206
|
},
|
|
@@ -163,14 +224,24 @@ const searchResultsSlice = createSlice({
|
|
|
163
224
|
setTransactionId(state, action: PayloadAction<string | null>) {
|
|
164
225
|
state.transactionId = action.payload;
|
|
165
226
|
},
|
|
166
|
-
|
|
167
|
-
state.
|
|
227
|
+
setFlyInType(state, action: PayloadAction<FlyInType | null>) {
|
|
228
|
+
state.flyInType = action.payload;
|
|
168
229
|
},
|
|
169
230
|
setPriceDetails(state, action: PayloadAction<BookingPriceDetails | null>) {
|
|
170
231
|
state.priceDetails = action.payload;
|
|
171
232
|
},
|
|
172
233
|
setItinerary(state, action: PayloadAction<ClientPortalItinerary | null>) {
|
|
173
234
|
state.itinerary = action.payload;
|
|
235
|
+
},
|
|
236
|
+
setSelectedOutwardKey: (state, action: PayloadAction<string | null>) => {
|
|
237
|
+
state.selectedOutwardKey = action.payload;
|
|
238
|
+
},
|
|
239
|
+
setSelectedReturnKey: (state, action: PayloadAction<string | null>) => {
|
|
240
|
+
state.selectedReturnKey = action.payload;
|
|
241
|
+
},
|
|
242
|
+
resetFlightSelection: (state) => {
|
|
243
|
+
state.selectedOutwardKey = null;
|
|
244
|
+
state.selectedReturnKey = null;
|
|
174
245
|
}
|
|
175
246
|
}
|
|
176
247
|
});
|
|
@@ -181,6 +252,7 @@ export const {
|
|
|
181
252
|
setSelectedSearchResult,
|
|
182
253
|
setPackagingAccoResults,
|
|
183
254
|
setFilteredPackagingAccoResults,
|
|
255
|
+
setFilteredPackagingFlightResults,
|
|
184
256
|
setPackagingAccoSearchDetails,
|
|
185
257
|
setSelectedPackagingAccoResult,
|
|
186
258
|
setPackagingFlightResults,
|
|
@@ -191,18 +263,26 @@ export const {
|
|
|
191
263
|
selectFlight,
|
|
192
264
|
setIsLoading,
|
|
193
265
|
setFlightsLoading,
|
|
266
|
+
setInitialFilters,
|
|
194
267
|
setFilters,
|
|
195
268
|
resetFilters,
|
|
269
|
+
setInitialFlightFilters,
|
|
270
|
+
setFlightFilters,
|
|
271
|
+
resetFlightFilters,
|
|
196
272
|
setSortType,
|
|
273
|
+
setFlightSortType,
|
|
197
274
|
setActiveTab,
|
|
198
275
|
setCurrentPage,
|
|
199
276
|
resetSearchState,
|
|
200
277
|
setFlyInIsOpen,
|
|
201
278
|
setEditablePackagingEntry,
|
|
202
279
|
setTransactionId,
|
|
203
|
-
|
|
280
|
+
setFlyInType,
|
|
204
281
|
setPriceDetails,
|
|
205
|
-
setItinerary
|
|
282
|
+
setItinerary,
|
|
283
|
+
setSelectedOutwardKey,
|
|
284
|
+
setSelectedReturnKey,
|
|
285
|
+
resetFlightSelection
|
|
206
286
|
} = searchResultsSlice.actions;
|
|
207
287
|
|
|
208
288
|
export default searchResultsSlice.reducer;
|
|
@@ -64,7 +64,19 @@ export interface SearchResultsConfiguration {
|
|
|
64
64
|
|
|
65
65
|
export type FilterType = 'checkbox' | 'toggle' | 'slider' | 'star-rating';
|
|
66
66
|
|
|
67
|
-
export type FilterProperty =
|
|
67
|
+
export type FilterProperty =
|
|
68
|
+
| 'regime'
|
|
69
|
+
| 'accommodation'
|
|
70
|
+
| 'max-duration'
|
|
71
|
+
| 'price'
|
|
72
|
+
| 'rating'
|
|
73
|
+
| 'theme'
|
|
74
|
+
| 'airline'
|
|
75
|
+
| 'numberOfStops'
|
|
76
|
+
| 'departureRange'
|
|
77
|
+
| 'departureAirport'
|
|
78
|
+
| 'arrivalAirport'
|
|
79
|
+
| 'travelDuration';
|
|
68
80
|
|
|
69
81
|
export interface FilterOption {
|
|
70
82
|
label: string;
|
|
@@ -205,4 +217,4 @@ export type SearchSeed = {
|
|
|
205
217
|
rooms: BookingPackageRequestRoom[];
|
|
206
218
|
};
|
|
207
219
|
|
|
208
|
-
export type
|
|
220
|
+
export type FlyInType = 'flight-outward-results' | 'flight-return-results' | 'flight-details' | 'acco-results' | 'acco-details';
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import { BookingPackageItem, PackagingAccommodationResponse } from '@qite/tide-client';
|
|
1
|
+
import { BookingPackageItem, PackagingAccommodationResponse, PackagingFlightResponse } from '@qite/tide-client';
|
|
2
2
|
import { Filter, SortByType, TideTag } from '../types';
|
|
3
|
-
import {
|
|
3
|
+
import { filter, flatMap, orderBy } from 'lodash';
|
|
4
|
+
import { getArrivalSegment, getDepartureRangeName, getDepartureSegment, getNumberOfStops } from './flight-utils';
|
|
5
|
+
import { DepartureRange } from '../../shared/types';
|
|
6
|
+
import { durationInTicksInMinutes, rangeFromDateTimeInMinutes } from '../../shared/utils/localization-util';
|
|
4
7
|
|
|
5
8
|
export const enrichFiltersWithResults = (results: BookingPackageItem[], filters: Filter[] | undefined, tags: TideTag[]): Filter[] => {
|
|
6
9
|
if (!results || results.length === 0 || !filters) {
|
|
@@ -79,76 +82,237 @@ export const enrichFiltersWithResults = (results: BookingPackageItem[], filters:
|
|
|
79
82
|
});
|
|
80
83
|
};
|
|
81
84
|
|
|
82
|
-
export const enrichFiltersWithPackageAccoResults = (results: PackagingAccommodationResponse[],
|
|
83
|
-
|
|
84
|
-
|
|
85
|
+
export const enrichFiltersWithPackageAccoResults = (results: PackagingAccommodationResponse[], tags: TideTag[]): Filter[] => {
|
|
86
|
+
const filters: Filter[] = [];
|
|
87
|
+
if (!results || results.length === 0) {
|
|
88
|
+
return filters;
|
|
85
89
|
}
|
|
86
90
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
91
|
+
const regimeFilter: Filter = {
|
|
92
|
+
property: 'regime',
|
|
93
|
+
label: 'Regime',
|
|
94
|
+
type: 'checkbox',
|
|
95
|
+
options: [],
|
|
96
|
+
isFrontendFilter: true
|
|
97
|
+
};
|
|
98
|
+
const map = new Map<string, { name?: string; code: string }>();
|
|
99
|
+
|
|
100
|
+
results.forEach((r) => {
|
|
101
|
+
const rooms = flatMap(r.rooms);
|
|
102
|
+
if (rooms) {
|
|
103
|
+
rooms.map((room) => {
|
|
104
|
+
room.options.map((option) => {
|
|
105
|
+
if (option.regimeCode) {
|
|
106
|
+
map.set(option.regimeCode, {
|
|
107
|
+
name: option.regimeName,
|
|
108
|
+
code: option.regimeCode
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
});
|
|
96
113
|
}
|
|
114
|
+
});
|
|
97
115
|
|
|
98
|
-
|
|
99
|
-
|
|
116
|
+
regimeFilter.options = Array.from(map.values()).map((regime) => ({
|
|
117
|
+
label: regime.name ?? regime.code,
|
|
118
|
+
value: regime.code,
|
|
119
|
+
isChecked: false
|
|
120
|
+
}));
|
|
121
|
+
filters.push(regimeFilter);
|
|
122
|
+
|
|
123
|
+
const priceFilter: Filter = {
|
|
124
|
+
property: 'price',
|
|
125
|
+
label: 'Prijs',
|
|
126
|
+
type: 'slider',
|
|
127
|
+
isFrontendFilter: true
|
|
128
|
+
};
|
|
129
|
+
const prices = results.map((r) => r.price ?? 0).filter((p) => p > 0);
|
|
130
|
+
priceFilter.min = Math.floor(Math.min(...prices));
|
|
131
|
+
priceFilter.max = Math.ceil(Math.max(...prices));
|
|
132
|
+
|
|
133
|
+
filters.push(priceFilter);
|
|
134
|
+
|
|
135
|
+
return filters;
|
|
136
|
+
};
|
|
100
137
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
138
|
+
export const enrichFiltersWithPackageFlightResults = (results: PackagingFlightResponse[], tags: TideTag[], translations: any): Filter[] => {
|
|
139
|
+
const filters: Filter[] = [];
|
|
140
|
+
if (!results || results.length === 0) {
|
|
141
|
+
return filters;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Airlines
|
|
145
|
+
const airlinesFilter: Filter = {
|
|
146
|
+
label: 'Airlines',
|
|
147
|
+
property: 'airline',
|
|
148
|
+
type: 'checkbox',
|
|
149
|
+
isFrontendFilter: true,
|
|
150
|
+
options: []
|
|
151
|
+
};
|
|
152
|
+
const airlinesFilterMap = new Map<string, { name?: string; code: string }>();
|
|
153
|
+
|
|
154
|
+
results.map((r) => {
|
|
155
|
+
const airlineCode = r.airlineCode;
|
|
156
|
+
const airlineName = r.airlineName;
|
|
157
|
+
if (airlineCode && airlineName) {
|
|
158
|
+
airlinesFilterMap.set(airlineCode, {
|
|
159
|
+
name: airlineName,
|
|
160
|
+
code: airlineCode
|
|
115
161
|
});
|
|
162
|
+
}
|
|
163
|
+
});
|
|
116
164
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
165
|
+
airlinesFilter.options = Array.from(airlinesFilterMap.values()).map((airline) => ({
|
|
166
|
+
label: airline.name ?? airline.code,
|
|
167
|
+
value: airline.code,
|
|
168
|
+
isChecked: false
|
|
169
|
+
}));
|
|
170
|
+
|
|
171
|
+
filters.push(airlinesFilter);
|
|
172
|
+
|
|
173
|
+
// Number of stops
|
|
174
|
+
const stopsFilter: Filter = {
|
|
175
|
+
label: 'Number of Stops',
|
|
176
|
+
property: 'numberOfStops',
|
|
177
|
+
type: 'checkbox',
|
|
178
|
+
isFrontendFilter: true,
|
|
179
|
+
options: []
|
|
180
|
+
};
|
|
181
|
+
const stopsMap = new Map<number, { numberOfStops: number }>();
|
|
182
|
+
|
|
183
|
+
results.map((result) => {
|
|
184
|
+
let numberOfStops = getNumberOfStops(result.outward);
|
|
185
|
+
if (!stopsMap.has(numberOfStops)) {
|
|
186
|
+
stopsMap.set(numberOfStops, { numberOfStops });
|
|
122
187
|
}
|
|
188
|
+
});
|
|
123
189
|
|
|
124
|
-
|
|
125
|
-
|
|
190
|
+
stopsFilter.options = Array.from(stopsMap.values()).map((stop) => ({
|
|
191
|
+
label: `${stop.numberOfStops == 0 ? 'direct' : stop.numberOfStops + ` Stop${stop.numberOfStops !== 1 ? 's' : ''}`}`,
|
|
192
|
+
value: stop.numberOfStops,
|
|
193
|
+
isChecked: false
|
|
194
|
+
}));
|
|
195
|
+
|
|
196
|
+
filters.push(stopsFilter);
|
|
197
|
+
|
|
198
|
+
// Departure range
|
|
199
|
+
const departureRangeFilter: Filter = {
|
|
200
|
+
label: 'Departure Range',
|
|
201
|
+
property: 'departureRange',
|
|
202
|
+
type: 'checkbox',
|
|
203
|
+
isFrontendFilter: true,
|
|
204
|
+
options: []
|
|
205
|
+
};
|
|
206
|
+
const departureRangeMap = new Map<DepartureRange, DepartureRange>();
|
|
207
|
+
results.map((result) => {
|
|
208
|
+
const departureRange = rangeFromDateTimeInMinutes(getDepartureSegment(result.outward)?.departureDateTime);
|
|
209
|
+
if (!departureRangeMap.has(departureRange)) {
|
|
210
|
+
departureRangeMap.set(departureRange, departureRange);
|
|
211
|
+
}
|
|
212
|
+
});
|
|
126
213
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
214
|
+
departureRangeFilter.options = orderBy(Array.from(departureRangeMap.values()), ['id'], ['asc']).map((range) => ({
|
|
215
|
+
label: getDepartureRangeName(translations, range),
|
|
216
|
+
value: range,
|
|
217
|
+
isChecked: false
|
|
218
|
+
}));
|
|
219
|
+
|
|
220
|
+
filters.push(departureRangeFilter);
|
|
221
|
+
|
|
222
|
+
// Departure Airport
|
|
223
|
+
const departureAirportFilter: Filter = {
|
|
224
|
+
label: 'Departure Airport',
|
|
225
|
+
property: 'departureAirport',
|
|
226
|
+
type: 'checkbox',
|
|
227
|
+
isFrontendFilter: true,
|
|
228
|
+
options: []
|
|
229
|
+
};
|
|
230
|
+
const departureAirportsMap = new Map<string, { name?: string; code: string }>();
|
|
231
|
+
|
|
232
|
+
results.map((result) => {
|
|
233
|
+
const departureSegment = getDepartureSegment(result.outward);
|
|
234
|
+
const departureAirport = departureSegment?.departureAirportCode;
|
|
235
|
+
if (departureAirport && !departureAirportsMap.has(departureAirport)) {
|
|
236
|
+
departureAirportsMap.set(departureAirport, {
|
|
237
|
+
name: departureSegment?.departureAirportName + ' (' + departureAirport + ')',
|
|
238
|
+
code: departureAirport
|
|
141
239
|
});
|
|
142
|
-
|
|
143
|
-
updatedFilter.options = Array.from(map.values()).map((regime) => ({
|
|
144
|
-
label: regime.name ?? regime.code,
|
|
145
|
-
value: regime.code,
|
|
146
|
-
isChecked: false
|
|
147
|
-
}));
|
|
148
240
|
}
|
|
241
|
+
});
|
|
149
242
|
|
|
150
|
-
|
|
243
|
+
departureAirportFilter.options = Array.from(departureAirportsMap.values()).map((airport) => ({
|
|
244
|
+
label: airport.name ?? airport.code,
|
|
245
|
+
value: airport.code,
|
|
246
|
+
isChecked: false
|
|
247
|
+
}));
|
|
248
|
+
|
|
249
|
+
filters.push(departureAirportFilter);
|
|
250
|
+
|
|
251
|
+
// Arrival Airport
|
|
252
|
+
const arrivalAirportFilter: Filter = {
|
|
253
|
+
label: 'Arrival Airport',
|
|
254
|
+
property: 'arrivalAirport',
|
|
255
|
+
type: 'checkbox',
|
|
256
|
+
isFrontendFilter: true,
|
|
257
|
+
options: []
|
|
258
|
+
};
|
|
259
|
+
const arrivalAirportsMap = new Map<string, { name?: string; code: string }>();
|
|
260
|
+
|
|
261
|
+
results.map((result) => {
|
|
262
|
+
const arrivalSegment = getArrivalSegment(result.outward);
|
|
263
|
+
const arrivalAirport = arrivalSegment?.arrivalAirportCode;
|
|
264
|
+
if (arrivalAirport && !arrivalAirportsMap.has(arrivalAirport)) {
|
|
265
|
+
arrivalAirportsMap.set(arrivalAirport, {
|
|
266
|
+
name: arrivalSegment?.arrivalAirportName + ' (' + arrivalAirport + ')',
|
|
267
|
+
code: arrivalAirport
|
|
268
|
+
});
|
|
269
|
+
}
|
|
151
270
|
});
|
|
271
|
+
|
|
272
|
+
arrivalAirportFilter.options = Array.from(arrivalAirportsMap.values()).map((airport) => ({
|
|
273
|
+
label: airport.name ?? airport.code,
|
|
274
|
+
value: airport.code,
|
|
275
|
+
isChecked: false
|
|
276
|
+
}));
|
|
277
|
+
|
|
278
|
+
filters.push(arrivalAirportFilter);
|
|
279
|
+
|
|
280
|
+
// Price
|
|
281
|
+
const priceFilter: Filter = {
|
|
282
|
+
label: 'Price',
|
|
283
|
+
property: 'price',
|
|
284
|
+
type: 'slider',
|
|
285
|
+
isFrontendFilter: true
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
const prices = results.map((r) => r.price ?? 0).filter((p) => p > 0);
|
|
289
|
+
if (prices.length > 0) {
|
|
290
|
+
priceFilter.min = Math.floor(Math.min(...prices));
|
|
291
|
+
priceFilter.max = Math.ceil(Math.max(...prices));
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
filters.push(priceFilter);
|
|
295
|
+
|
|
296
|
+
// Travel duration
|
|
297
|
+
const travelDurationFilter: Filter = {
|
|
298
|
+
label: 'Travel Duration',
|
|
299
|
+
property: 'travelDuration',
|
|
300
|
+
type: 'slider',
|
|
301
|
+
isFrontendFilter: true
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
const minTravelTimeDuration = Math.min(...results.map((result) => result.outward.durationInTicks));
|
|
305
|
+
const maxTravelTimeDuration = Math.max(...results.map((result) => result.outward.durationInTicks));
|
|
306
|
+
const minTravelTimeValue = durationInTicksInMinutes(minTravelTimeDuration);
|
|
307
|
+
const maxTravelTimeValue = durationInTicksInMinutes(maxTravelTimeDuration);
|
|
308
|
+
if (minTravelTimeValue != null && maxTravelTimeValue != null) {
|
|
309
|
+
travelDurationFilter.min = minTravelTimeValue;
|
|
310
|
+
travelDurationFilter.max = maxTravelTimeValue;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
filters.push(travelDurationFilter);
|
|
314
|
+
|
|
315
|
+
return filters;
|
|
152
316
|
};
|
|
153
317
|
|
|
154
318
|
export const applyFilters = (results: BookingPackageItem[], filters: Filter[], sortBy: SortByType | null) => {
|
|
@@ -249,3 +413,91 @@ export const applyFiltersToPackageAccoResults = (results: PackagingAccommodation
|
|
|
249
413
|
return 0;
|
|
250
414
|
});
|
|
251
415
|
};
|
|
416
|
+
|
|
417
|
+
export const applyFiltersToPackageFlightResults = (results: PackagingFlightResponse[], filters: Filter[], sortBy: SortByType | null) => {
|
|
418
|
+
const filtered = results.filter((result) => {
|
|
419
|
+
return filters.every((filter) => {
|
|
420
|
+
if (!filter.isFrontendFilter) return true;
|
|
421
|
+
|
|
422
|
+
// Airline
|
|
423
|
+
if (filter.property === 'airline') {
|
|
424
|
+
const selected = filter.options?.filter((o) => o.isChecked).map((o) => o.value);
|
|
425
|
+
if (!selected || selected.length === 0) return true;
|
|
426
|
+
return selected.includes(result.airlineCode);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// Stops
|
|
430
|
+
if (filter.property === 'numberOfStops') {
|
|
431
|
+
const selected = filter.options?.filter((o) => o.isChecked).map((o) => o.value);
|
|
432
|
+
if (!selected || selected.length === 0) return true;
|
|
433
|
+
return selected.includes(getNumberOfStops(result.outward)) && selected.includes(getNumberOfStops(result.return));
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Departure range
|
|
437
|
+
if (filter.property === 'departureRange') {
|
|
438
|
+
const selected = filter.options?.filter((o) => o.isChecked).map((o) => o.value);
|
|
439
|
+
if (!selected || selected.length === 0) return true;
|
|
440
|
+
return selected.includes(rangeFromDateTimeInMinutes(getDepartureSegment(result.outward)?.departureDateTime));
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// Departure airport
|
|
444
|
+
if (filter.property === 'departureAirport') {
|
|
445
|
+
const selected = filter.options?.filter((o) => o.isChecked).map((o) => o.value);
|
|
446
|
+
if (!selected || selected.length === 0) return true;
|
|
447
|
+
|
|
448
|
+
const departureAirportCode = getDepartureSegment(result.outward)?.departureAirportCode;
|
|
449
|
+
if (!departureAirportCode) return false;
|
|
450
|
+
|
|
451
|
+
return selected.includes(departureAirportCode);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Arrival airport
|
|
455
|
+
if (filter.property === 'arrivalAirport') {
|
|
456
|
+
const selected = filter.options?.filter((o) => o.isChecked).map((o) => o.value);
|
|
457
|
+
if (!selected || selected.length === 0) return true;
|
|
458
|
+
|
|
459
|
+
const arrivalAirportCode = getArrivalSegment(result.outward)?.arrivalAirportCode;
|
|
460
|
+
if (!arrivalAirportCode) return false;
|
|
461
|
+
|
|
462
|
+
return selected.includes(arrivalAirportCode);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// PRICE
|
|
466
|
+
if (filter.property === 'price') {
|
|
467
|
+
if (filter.selectedMin != null && result.price < filter.selectedMin) return false;
|
|
468
|
+
if (filter.selectedMax != null && result.price > filter.selectedMax) return false;
|
|
469
|
+
return true;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// Travel times
|
|
473
|
+
if (filter.property === 'travelDuration') {
|
|
474
|
+
if (filter.selectedMin != null && durationInTicksInMinutes(result.outward?.durationInTicks) < filter.selectedMin) return false;
|
|
475
|
+
if (filter.selectedMax != null && durationInTicksInMinutes(result.outward?.durationInTicks) > filter.selectedMax) return false;
|
|
476
|
+
return true;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
return true;
|
|
480
|
+
});
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
// SORTING
|
|
484
|
+
if (!sortBy || sortBy.label === 'default') {
|
|
485
|
+
return filtered;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
if (sortBy.label === 'departureTime') {
|
|
489
|
+
return orderBy(
|
|
490
|
+
results,
|
|
491
|
+
[(result) => getDepartureSegment(result.outward)?.departureDateTime],
|
|
492
|
+
[sortBy.direction] // or "desc"
|
|
493
|
+
);
|
|
494
|
+
} else if (sortBy.label === 'durationInTicks') {
|
|
495
|
+
return orderBy(
|
|
496
|
+
results,
|
|
497
|
+
[(result) => durationInTicksInMinutes(result.outward?.durationInTicks ?? 0)],
|
|
498
|
+
[sortBy.direction] // or "desc"
|
|
499
|
+
);
|
|
500
|
+
} else {
|
|
501
|
+
return orderBy(results, [sortBy.label], [sortBy.direction]);
|
|
502
|
+
}
|
|
503
|
+
};
|