@qite/tide-booking-component 1.4.103 → 1.4.105
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 +2782 -1052
- package/build/build-cjs/src/search-results/components/excursions/day-by-day-excursions.d.ts +4 -0
- package/build/build-cjs/src/search-results/components/excursions/excursion-details.d.ts +3 -0
- package/build/build-cjs/src/search-results/components/excursions/excursion-results.d.ts +8 -0
- package/build/build-cjs/src/search-results/components/filters/filters.d.ts +2 -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 +546 -0
- package/build/build-cjs/src/search-results/store/search-results-slice.d.ts +55 -8
- package/build/build-cjs/src/search-results/types.d.ts +40 -2
- package/build/build-cjs/src/search-results/utils/query-utils.d.ts +1 -0
- 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 +4 -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 +3 -0
- package/build/build-cjs/src/shared/utils/tide-api-utils.d.ts +6 -0
- package/build/build-esm/index.js +2735 -1023
- package/build/build-esm/src/search-results/components/excursions/day-by-day-excursions.d.ts +4 -0
- package/build/build-esm/src/search-results/components/excursions/excursion-details.d.ts +3 -0
- package/build/build-esm/src/search-results/components/excursions/excursion-results.d.ts +8 -0
- package/build/build-esm/src/search-results/components/filters/filters.d.ts +2 -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 +546 -0
- package/build/build-esm/src/search-results/store/search-results-slice.d.ts +55 -8
- package/build/build-esm/src/search-results/types.d.ts +40 -2
- package/build/build-esm/src/search-results/utils/query-utils.d.ts +1 -0
- 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 +4 -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 +3 -0
- package/build/build-esm/src/shared/utils/tide-api-utils.d.ts +6 -0
- package/package.json +2 -2
- package/src/booking-wizard/features/flight-options/index.tsx +6 -2
- package/src/search-results/components/excursions/day-by-day-excursions.tsx +169 -0
- package/src/search-results/components/excursions/excursion-details.tsx +340 -0
- package/src/search-results/components/excursions/excursion-results.tsx +186 -0
- package/src/search-results/components/filters/filters.tsx +8 -9
- package/src/search-results/components/hotel/hotel-accommodation-results.tsx +81 -24
- package/src/search-results/components/hotel/hotel-card.tsx +0 -3
- package/src/search-results/components/icon.tsx +1 -4
- package/src/search-results/components/search-results-container/search-results-container.tsx +208 -130
- package/src/search-results/store/search-results-selectors.ts +84 -0
- package/src/search-results/store/search-results-slice.ts +138 -15
- package/src/search-results/types.ts +55 -2
- package/src/search-results/utils/query-utils.ts +1 -0
- package/src/search-results/utils/search-results-utils.ts +310 -58
- package/src/shared/components/flyin/accommodation-flyin.tsx +4 -2
- package/src/shared/components/flyin/flights-flyin.tsx +3 -1
- package/src/shared/components/flyin/flyin.tsx +116 -21
- package/src/shared/components/flyin/group-tour-flyin.tsx +3 -1
- package/src/shared/components/flyin/packaging-flights-flyin.tsx +164 -0
- package/src/shared/translations/ar-SA.json +4 -2
- package/src/shared/translations/da-DK.json +4 -2
- package/src/shared/translations/de-DE.json +4 -2
- package/src/shared/translations/en-GB.json +4 -2
- package/src/shared/translations/es-ES.json +4 -2
- package/src/shared/translations/fr-BE.json +4 -2
- package/src/shared/translations/fr-FR.json +4 -2
- package/src/shared/translations/is-IS.json +4 -2
- package/src/shared/translations/it-IT.json +4 -2
- package/src/shared/translations/ja-JP.json +4 -2
- package/src/shared/translations/nl-BE.json +4 -2
- package/src/shared/translations/nl-NL.json +4 -2
- package/src/shared/translations/no-NO.json +4 -2
- package/src/shared/translations/pl-PL.json +4 -2
- package/src/shared/translations/pt-PT.json +4 -2
- package/src/shared/translations/sv-SE.json +4 -2
- package/src/shared/utils/localization-util.ts +14 -0
- package/src/shared/utils/tide-api-utils.ts +8 -0
- package/styles/components/_flyin.scss +16 -0
- package/styles/components/_search.scss +15 -2
|
@@ -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
|
+
};
|
|
@@ -245,7 +245,9 @@ const AccommodationFlyIn: React.FC<AccommodationFlyInProps> = ({ isLoading, hand
|
|
|
245
245
|
</div>
|
|
246
246
|
|
|
247
247
|
<div className="flyin__footer">
|
|
248
|
-
<div className="flyin__footer__price">
|
|
248
|
+
<div className="flyin__footer__price">
|
|
249
|
+
{translations.SHARED.TOTAL_PRICE}: {calculateTotalPrice()}
|
|
250
|
+
</div>
|
|
249
251
|
|
|
250
252
|
<div className="flyin__button-wrapper">
|
|
251
253
|
<button className="cta cta--select" onClick={handleConfirm}>
|
|
@@ -409,7 +411,7 @@ const AccommodationFlyIn: React.FC<AccommodationFlyInProps> = ({ isLoading, hand
|
|
|
409
411
|
// </div>
|
|
410
412
|
|
|
411
413
|
// <div className="flyin__footer">
|
|
412
|
-
// <div className="flyin__footer__price">
|
|
414
|
+
// <div className="flyin__footer__price">{translations.SHARED.TOTAL_PRICE}: €</div>
|
|
413
415
|
// <div className="flyin__button-wrapper">
|
|
414
416
|
// <button className="cta cta--select" onClick={handleConfirm}>
|
|
415
417
|
// Toevoegen
|
|
@@ -488,7 +488,9 @@ const FlightsFlyIn: React.FC<FlightsFlyInProps> = ({ isOpen, setIsOpen }) => {
|
|
|
488
488
|
)}
|
|
489
489
|
{!flightSearchDetailsLoading && (
|
|
490
490
|
<div className="flyin__footer">
|
|
491
|
-
<div className="flyin__footer__price">
|
|
491
|
+
<div className="flyin__footer__price">
|
|
492
|
+
{translations.SHARED.TOTAL_PRICE}: €{selectedCombinationFlight?.price?.toFixed(2)}
|
|
493
|
+
</div>
|
|
492
494
|
<div className="flyin__button-wrapper">
|
|
493
495
|
<button className="cta cta--select" onClick={handleConfirm}>
|
|
494
496
|
{translations.PRODUCT.BOOK_NOW}
|
|
@@ -1,23 +1,32 @@
|
|
|
1
|
-
import React, { useEffect, useRef } from 'react';
|
|
1
|
+
import React, { useContext, useEffect, useRef } from 'react';
|
|
2
2
|
import Icon from '../icon';
|
|
3
3
|
import { useFlightSearch } from '../../../search-results/components/flight/flight-search-context';
|
|
4
|
-
import { useDispatch } from 'react-redux';
|
|
4
|
+
import { useDispatch, useSelector } from 'react-redux';
|
|
5
5
|
import {
|
|
6
|
-
|
|
6
|
+
resetFilters,
|
|
7
|
+
setFilters,
|
|
8
|
+
setFlyInType,
|
|
7
9
|
setSelectedFlight,
|
|
8
10
|
setSelectedFlightDetails,
|
|
9
|
-
|
|
10
|
-
|
|
11
|
+
setSelectedSearchResult,
|
|
12
|
+
setSortType
|
|
11
13
|
} from '../../../search-results/store/search-results-slice';
|
|
12
14
|
import FlightsFlyIn from './flights-flyin';
|
|
13
15
|
import AccommodationFlyIn from './accommodation-flyin';
|
|
14
16
|
import { PortalQsmType } from '@qite/tide-client';
|
|
15
17
|
import GroupTourFlyIn from './group-tour-flyin';
|
|
16
|
-
import {
|
|
18
|
+
import { FlyInType, SearchSeed, SortByType } from '../../../search-results/types';
|
|
17
19
|
import HotelAccommodationResults from '../../../search-results/components/hotel/hotel-accommodation-results';
|
|
20
|
+
import PackageingFlightsFlyIn from './packaging-flights-flyin';
|
|
21
|
+
import SearchResultsConfigurationContext from '../../../search-results/search-results-configuration-context';
|
|
22
|
+
import { findSortByType, getSortingName, getTranslations } from '../../utils/localization-util';
|
|
23
|
+
import Filters from '../../../search-results/components/filters/filters';
|
|
24
|
+
import { SearchResultsRootState } from '../../../search-results/store/search-results-store';
|
|
25
|
+
import ItemPicker from '../../../search-results/components/item-picker';
|
|
26
|
+
import ExcursionResults from '../../../search-results/components/excursions/excursion-results';
|
|
27
|
+
import ExcursionDetails from '../../../search-results/components/excursions/excursion-details';
|
|
18
28
|
|
|
19
29
|
type FlyInProps = {
|
|
20
|
-
title: string;
|
|
21
30
|
srpType: PortalQsmType;
|
|
22
31
|
isOpen: boolean;
|
|
23
32
|
setIsOpen: (open: boolean) => void;
|
|
@@ -25,23 +34,34 @@ type FlyInProps = {
|
|
|
25
34
|
onPanelRef?: (el: HTMLDivElement | null) => void;
|
|
26
35
|
detailsLoading: boolean;
|
|
27
36
|
handleConfirm?: () => void;
|
|
28
|
-
|
|
37
|
+
flyInType?: FlyInType | null;
|
|
29
38
|
isPackageEditFlow?: boolean;
|
|
39
|
+
sortByTypes?: SortByType[];
|
|
40
|
+
activeSearchSeed?: SearchSeed | null;
|
|
30
41
|
};
|
|
31
42
|
|
|
32
43
|
const FlyIn: React.FC<FlyInProps> = ({
|
|
33
|
-
title,
|
|
34
44
|
srpType,
|
|
35
45
|
isOpen,
|
|
36
46
|
setIsOpen,
|
|
37
47
|
className = '',
|
|
38
48
|
onPanelRef,
|
|
39
49
|
detailsLoading,
|
|
40
|
-
|
|
50
|
+
flyInType,
|
|
41
51
|
isPackageEditFlow,
|
|
42
|
-
handleConfirm
|
|
52
|
+
handleConfirm,
|
|
53
|
+
sortByTypes,
|
|
54
|
+
activeSearchSeed
|
|
43
55
|
}) => {
|
|
44
56
|
const dispatch = useDispatch();
|
|
57
|
+
const context = useContext(SearchResultsConfigurationContext);
|
|
58
|
+
const language = context?.languageCode ?? 'en-GB';
|
|
59
|
+
const translations = getTranslations(language);
|
|
60
|
+
|
|
61
|
+
const { isLoading, initialFilters, filters, filteredPackagingAccoResults, selectedSortType } = useSelector(
|
|
62
|
+
(state: SearchResultsRootState) => state.searchResults
|
|
63
|
+
);
|
|
64
|
+
|
|
45
65
|
const { onCancelSearch } = useFlightSearch();
|
|
46
66
|
const panelRef = useRef<HTMLDivElement>(null);
|
|
47
67
|
|
|
@@ -79,28 +99,58 @@ const FlyIn: React.FC<FlyInProps> = ({
|
|
|
79
99
|
onCancelSearch();
|
|
80
100
|
} else {
|
|
81
101
|
dispatch(setSelectedSearchResult(null));
|
|
82
|
-
dispatch(setSelectedPackagingAccoResult(null));
|
|
83
102
|
}
|
|
84
|
-
dispatch(
|
|
103
|
+
dispatch(setFlyInType('acco-details'));
|
|
85
104
|
setIsOpen(false);
|
|
86
105
|
}
|
|
87
106
|
};
|
|
88
107
|
|
|
89
108
|
const handleGoBack = () => {
|
|
90
|
-
|
|
109
|
+
if (flyInType === 'acco-details') {
|
|
110
|
+
dispatch(setFlyInType('acco-results'));
|
|
111
|
+
} else if (flyInType === 'excursion-details') {
|
|
112
|
+
dispatch(setFlyInType('excursion-results'));
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const handleSortChange = (newSortKey: string, direction?: string) => {
|
|
117
|
+
if (sortByTypes === undefined) return;
|
|
118
|
+
const newSortByType = findSortByType(sortByTypes, newSortKey, direction ?? 'asc');
|
|
119
|
+
if (newSortByType) {
|
|
120
|
+
dispatch(setSortType(newSortByType));
|
|
121
|
+
}
|
|
91
122
|
};
|
|
92
123
|
|
|
93
124
|
return (
|
|
94
|
-
<div
|
|
125
|
+
<div
|
|
126
|
+
className={`flyin ${isOpen ? 'flyin--active' : ''} ${className} ${
|
|
127
|
+
isPackageEditFlow || flyInType === 'flight-outward-results' || flyInType === 'flight-return-results' || flyInType === 'acco-results'
|
|
128
|
+
? 'flyin--large'
|
|
129
|
+
: ''
|
|
130
|
+
}`}>
|
|
95
131
|
<div className={`flyin__panel ${isOpen ? 'flyin__panel--active' : ''}`} ref={panelRef}>
|
|
96
132
|
<div className="flyin__content">
|
|
97
133
|
<div className="flyin__content-title-row">
|
|
98
|
-
<h3 className="flyin__content-title">
|
|
134
|
+
<h3 className="flyin__content-title">
|
|
135
|
+
{srpType === PortalQsmType.Flight && 'Select your fare'}
|
|
136
|
+
{srpType === PortalQsmType.Accommodation ||
|
|
137
|
+
(srpType === PortalQsmType.AccommodationAndFlight &&
|
|
138
|
+
(flyInType === 'acco-results' || flyInType === 'acco-details') &&
|
|
139
|
+
`${translations.SRP.SELECT} ${translations.SRP.ACCOMMODATION}`)}
|
|
140
|
+
{srpType === PortalQsmType.AccommodationAndFlight &&
|
|
141
|
+
flyInType === 'flight-outward-results' &&
|
|
142
|
+
`${translations.SRP.SELECT} ${translations.FLIGHTS_FORM.OUTWARD_FLIGHT}`}
|
|
143
|
+
{srpType === PortalQsmType.AccommodationAndFlight &&
|
|
144
|
+
flyInType === 'flight-return-results' &&
|
|
145
|
+
`${translations.SRP.SELECT} ${translations.FLIGHTS_FORM.RETURN_FLIGHT}`}
|
|
146
|
+
{srpType === PortalQsmType.AccommodationAndFlight && flyInType === 'flight-details' && 'Select your fare'}
|
|
147
|
+
{flyInType === 'excursion-results' || (flyInType === 'excursion-details' && 'Select excursion')}
|
|
148
|
+
</h3>
|
|
99
149
|
<span className="flyin__close" onClick={() => handleClose()}>
|
|
100
150
|
<Icon name="ui-close" width={30} height={30} aria-hidden="true" />
|
|
101
151
|
</span>
|
|
102
152
|
</div>
|
|
103
|
-
{isPackageEditFlow &&
|
|
153
|
+
{((isPackageEditFlow && flyInType === 'acco-details') || flyInType === 'excursion-details') && (
|
|
104
154
|
<div className="flyin__content-title-row">
|
|
105
155
|
<div onClick={handleGoBack} className="flyin__content-title__back">
|
|
106
156
|
<Icon name="ui-chevron" width={14} height={14} aria-hidden="true" />
|
|
@@ -111,16 +161,61 @@ const FlyIn: React.FC<FlyInProps> = ({
|
|
|
111
161
|
</div>
|
|
112
162
|
{srpType === PortalQsmType.Flight && <FlightsFlyIn isOpen={isOpen} setIsOpen={setIsOpen} />}
|
|
113
163
|
|
|
114
|
-
{(srpType === PortalQsmType.Accommodation || srpType === PortalQsmType.AccommodationAndFlight) &&
|
|
115
|
-
<div className="flyin__content">
|
|
116
|
-
<
|
|
164
|
+
{(srpType === PortalQsmType.Accommodation || srpType === PortalQsmType.AccommodationAndFlight) && flyInType === 'acco-results' && (
|
|
165
|
+
<div className="flyin__content flyin__content--columns">
|
|
166
|
+
<Filters
|
|
167
|
+
initialFilters={initialFilters}
|
|
168
|
+
filters={filters}
|
|
169
|
+
isOpen={false}
|
|
170
|
+
handleSetIsOpen={() => {}}
|
|
171
|
+
// handleApplyFilters={() => setSearchTrigger((prev) => prev + 1)}
|
|
172
|
+
isLoading={isLoading}
|
|
173
|
+
setFilters={(filters) => dispatch(setFilters(filters))}
|
|
174
|
+
resetFilters={(filters) => dispatch(resetFilters(filters))}
|
|
175
|
+
/>
|
|
176
|
+
<div className="search__results__wrapper">
|
|
177
|
+
<div className="search__result-row">
|
|
178
|
+
<span className="search__result-row-text">
|
|
179
|
+
{!isLoading && (
|
|
180
|
+
<>
|
|
181
|
+
{filteredPackagingAccoResults?.length && filteredPackagingAccoResults.length}
|
|
182
|
+
{translations.SRP.TOTAL_RESULTS_LABEL}
|
|
183
|
+
</>
|
|
184
|
+
)}
|
|
185
|
+
</span>
|
|
186
|
+
{sortByTypes && sortByTypes.length > 0 && (
|
|
187
|
+
<div className="search__result-row-filter">
|
|
188
|
+
<ItemPicker
|
|
189
|
+
items={sortByTypes}
|
|
190
|
+
selection={selectedSortType?.label || undefined}
|
|
191
|
+
selectedSortByType={selectedSortType}
|
|
192
|
+
label={translations.SRP.SORTBY}
|
|
193
|
+
placeholder={translations.SRP.SORTBY}
|
|
194
|
+
classModifier="travel-class-picker__items"
|
|
195
|
+
valueFormatter={(value, direction) => getSortingName(translations, findSortByType(sortByTypes, value, direction ?? 'asc'))}
|
|
196
|
+
onPick={(newSortKey, direction) => handleSortChange(newSortKey, direction)}
|
|
197
|
+
/>
|
|
198
|
+
</div>
|
|
199
|
+
)}
|
|
200
|
+
</div>
|
|
201
|
+
<HotelAccommodationResults isLoading={detailsLoading} isFlyIn={true} />
|
|
202
|
+
</div>
|
|
117
203
|
</div>
|
|
118
204
|
)}
|
|
119
205
|
|
|
120
|
-
{(srpType === PortalQsmType.Accommodation || srpType === PortalQsmType.AccommodationAndFlight) &&
|
|
206
|
+
{(srpType === PortalQsmType.Accommodation || srpType === PortalQsmType.AccommodationAndFlight) && flyInType === 'acco-details' && (
|
|
121
207
|
<AccommodationFlyIn isLoading={detailsLoading} handleConfirm={handleConfirm!} />
|
|
122
208
|
)}
|
|
123
209
|
|
|
210
|
+
{srpType === PortalQsmType.AccommodationAndFlight && (flyInType === 'flight-outward-results' || flyInType === 'flight-return-results') && (
|
|
211
|
+
<PackageingFlightsFlyIn isOpen={isOpen} setIsOpen={setIsOpen} />
|
|
212
|
+
)}
|
|
213
|
+
|
|
214
|
+
{srpType === PortalQsmType.AccommodationAndFlight && flyInType === 'excursion-results' && (
|
|
215
|
+
<ExcursionResults isFlyIn={true} activeSearchSeed={activeSearchSeed} />
|
|
216
|
+
)}
|
|
217
|
+
{srpType === PortalQsmType.AccommodationAndFlight && flyInType === 'excursion-details' && <ExcursionDetails />}
|
|
218
|
+
|
|
124
219
|
{srpType === PortalQsmType.GroupTour && <GroupTourFlyIn isLoading={detailsLoading} isOpen={isOpen} setIsOpen={setIsOpen} />}
|
|
125
220
|
</div>
|
|
126
221
|
</div>
|
|
@@ -278,7 +278,9 @@ const GroupTourFlyIn: React.FC<GroupTourFlyInProps> = ({ isLoading, isOpen, setI
|
|
|
278
278
|
</div>
|
|
279
279
|
|
|
280
280
|
<div className="flyin__footer">
|
|
281
|
-
<div className="flyin__footer__price">
|
|
281
|
+
<div className="flyin__footer__price">
|
|
282
|
+
{translations.SHARED.TOTAL_PRICE}: {formatPrice(adjustedTotalPrice, bookingPackageDetails.currencyCode)}
|
|
283
|
+
</div>
|
|
282
284
|
|
|
283
285
|
<div className="flyin__button-wrapper">
|
|
284
286
|
<button className="cta cta--select" onClick={handleConfirm}>
|