@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.
Files changed (70) hide show
  1. package/build/build-cjs/index.js +2782 -1052
  2. package/build/build-cjs/src/search-results/components/excursions/day-by-day-excursions.d.ts +4 -0
  3. package/build/build-cjs/src/search-results/components/excursions/excursion-details.d.ts +3 -0
  4. package/build/build-cjs/src/search-results/components/excursions/excursion-results.d.ts +8 -0
  5. package/build/build-cjs/src/search-results/components/filters/filters.d.ts +2 -0
  6. package/build/build-cjs/src/search-results/components/hotel/hotel-accommodation-results.d.ts +1 -0
  7. package/build/build-cjs/src/search-results/store/search-results-selectors.d.ts +546 -0
  8. package/build/build-cjs/src/search-results/store/search-results-slice.d.ts +55 -8
  9. package/build/build-cjs/src/search-results/types.d.ts +40 -2
  10. package/build/build-cjs/src/search-results/utils/query-utils.d.ts +1 -0
  11. package/build/build-cjs/src/search-results/utils/search-results-utils.d.ts +8 -6
  12. package/build/build-cjs/src/shared/components/flyin/flyin.d.ts +4 -3
  13. package/build/build-cjs/src/shared/components/flyin/packaging-flights-flyin.d.ts +7 -0
  14. package/build/build-cjs/src/shared/utils/localization-util.d.ts +3 -0
  15. package/build/build-cjs/src/shared/utils/tide-api-utils.d.ts +6 -0
  16. package/build/build-esm/index.js +2735 -1023
  17. package/build/build-esm/src/search-results/components/excursions/day-by-day-excursions.d.ts +4 -0
  18. package/build/build-esm/src/search-results/components/excursions/excursion-details.d.ts +3 -0
  19. package/build/build-esm/src/search-results/components/excursions/excursion-results.d.ts +8 -0
  20. package/build/build-esm/src/search-results/components/filters/filters.d.ts +2 -0
  21. package/build/build-esm/src/search-results/components/hotel/hotel-accommodation-results.d.ts +1 -0
  22. package/build/build-esm/src/search-results/store/search-results-selectors.d.ts +546 -0
  23. package/build/build-esm/src/search-results/store/search-results-slice.d.ts +55 -8
  24. package/build/build-esm/src/search-results/types.d.ts +40 -2
  25. package/build/build-esm/src/search-results/utils/query-utils.d.ts +1 -0
  26. package/build/build-esm/src/search-results/utils/search-results-utils.d.ts +8 -6
  27. package/build/build-esm/src/shared/components/flyin/flyin.d.ts +4 -3
  28. package/build/build-esm/src/shared/components/flyin/packaging-flights-flyin.d.ts +7 -0
  29. package/build/build-esm/src/shared/utils/localization-util.d.ts +3 -0
  30. package/build/build-esm/src/shared/utils/tide-api-utils.d.ts +6 -0
  31. package/package.json +2 -2
  32. package/src/booking-wizard/features/flight-options/index.tsx +6 -2
  33. package/src/search-results/components/excursions/day-by-day-excursions.tsx +169 -0
  34. package/src/search-results/components/excursions/excursion-details.tsx +340 -0
  35. package/src/search-results/components/excursions/excursion-results.tsx +186 -0
  36. package/src/search-results/components/filters/filters.tsx +8 -9
  37. package/src/search-results/components/hotel/hotel-accommodation-results.tsx +81 -24
  38. package/src/search-results/components/hotel/hotel-card.tsx +0 -3
  39. package/src/search-results/components/icon.tsx +1 -4
  40. package/src/search-results/components/search-results-container/search-results-container.tsx +208 -130
  41. package/src/search-results/store/search-results-selectors.ts +84 -0
  42. package/src/search-results/store/search-results-slice.ts +138 -15
  43. package/src/search-results/types.ts +55 -2
  44. package/src/search-results/utils/query-utils.ts +1 -0
  45. package/src/search-results/utils/search-results-utils.ts +310 -58
  46. package/src/shared/components/flyin/accommodation-flyin.tsx +4 -2
  47. package/src/shared/components/flyin/flights-flyin.tsx +3 -1
  48. package/src/shared/components/flyin/flyin.tsx +116 -21
  49. package/src/shared/components/flyin/group-tour-flyin.tsx +3 -1
  50. package/src/shared/components/flyin/packaging-flights-flyin.tsx +164 -0
  51. package/src/shared/translations/ar-SA.json +4 -2
  52. package/src/shared/translations/da-DK.json +4 -2
  53. package/src/shared/translations/de-DE.json +4 -2
  54. package/src/shared/translations/en-GB.json +4 -2
  55. package/src/shared/translations/es-ES.json +4 -2
  56. package/src/shared/translations/fr-BE.json +4 -2
  57. package/src/shared/translations/fr-FR.json +4 -2
  58. package/src/shared/translations/is-IS.json +4 -2
  59. package/src/shared/translations/it-IT.json +4 -2
  60. package/src/shared/translations/ja-JP.json +4 -2
  61. package/src/shared/translations/nl-BE.json +4 -2
  62. package/src/shared/translations/nl-NL.json +4 -2
  63. package/src/shared/translations/no-NO.json +4 -2
  64. package/src/shared/translations/pl-PL.json +4 -2
  65. package/src/shared/translations/pt-PT.json +4 -2
  66. package/src/shared/translations/sv-SE.json +4 -2
  67. package/src/shared/utils/localization-util.ts +14 -0
  68. package/src/shared/utils/tide-api-utils.ts +8 -0
  69. package/styles/components/_flyin.scss +16 -0
  70. 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 { first, flatMap } from 'lodash';
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[], filters: Filter[] | undefined, tags: TideTag[]): Filter[] => {
83
- if (!results || results.length === 0 || !filters) {
84
- return filters ?? [];
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
- return filters.map((filter) => {
88
- let updatedFilter = { ...filter };
89
-
90
- if (filter.property === 'price' && (filter.min == null || filter.max == null)) {
91
- const prices = results.map((r) => r.price ?? 0).filter((p) => p > 0);
92
- if (prices.length > 0) {
93
- updatedFilter.min = Math.floor(Math.min(...prices));
94
- updatedFilter.max = Math.ceil(Math.max(...prices));
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
- if (filter.property === 'accommodation') {
99
- const map = new Map<string, { name?: string; code: string }>();
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
- results.forEach((r) => {
102
- const rooms = flatMap(r.rooms);
103
- if (rooms) {
104
- rooms.map((room) => {
105
- room.options.map((option) => {
106
- if (option.accommodationCode) {
107
- map.set(option.accommodationCode, {
108
- name: option.accommodationName,
109
- code: option.accommodationCode
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
- updatedFilter.options = Array.from(map.values()).map((accommodation) => ({
118
- label: accommodation.name ?? accommodation.code,
119
- value: accommodation.code,
120
- isChecked: false
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
- if (filter.property === 'regime') {
125
- const map = new Map<string, { name?: string; code: string }>();
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
- results.forEach((r) => {
128
- const rooms = flatMap(r.rooms);
129
- if (rooms) {
130
- rooms.map((room) => {
131
- room.options.map((option) => {
132
- if (option.regimeCode) {
133
- map.set(option.regimeCode, {
134
- name: option.regimeName,
135
- code: option.regimeCode
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
- return updatedFilter;
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">Total price: {calculateTotalPrice()}</div>
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">Total price: €</div>
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">Total price: €{selectedCombinationFlight?.price?.toFixed(2)}</div>
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
- setAccommodationFlyInStep,
6
+ resetFilters,
7
+ setFilters,
8
+ setFlyInType,
7
9
  setSelectedFlight,
8
10
  setSelectedFlightDetails,
9
- setSelectedPackagingAccoResult,
10
- setSelectedSearchResult
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 { AccommodationFlyInStep } from '../../../search-results/types';
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
- accommodationStep?: AccommodationFlyInStep;
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
- accommodationStep,
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(setAccommodationFlyInStep('details'));
103
+ dispatch(setFlyInType('acco-details'));
85
104
  setIsOpen(false);
86
105
  }
87
106
  };
88
107
 
89
108
  const handleGoBack = () => {
90
- dispatch(setAccommodationFlyInStep('results'));
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 className={`flyin ${isOpen ? 'flyin--active' : ''} ${className} ${isPackageEditFlow ? 'flyin--large' : ''}`}>
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">{title}</h3>
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 && accommodationStep === 'details' && (
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) && accommodationStep === 'results' && (
115
- <div className="flyin__content">
116
- <HotelAccommodationResults isLoading={detailsLoading} />
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
+ &nbsp;{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) && accommodationStep === 'details' && (
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">Total price: {formatPrice(adjustedTotalPrice, bookingPackageDetails.currencyCode)}</div>
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}>