@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.
Files changed (55) hide show
  1. package/build/build-cjs/index.js +1773 -877
  2. package/build/build-cjs/src/search-results/components/filters/filters.d.ts +2 -0
  3. package/build/build-cjs/src/search-results/components/flight/flight-selection/independent-flight-option.d.ts +3 -0
  4. package/build/build-cjs/src/search-results/components/hotel/hotel-accommodation-results.d.ts +1 -0
  5. package/build/build-cjs/src/search-results/store/search-results-selectors.d.ts +424 -0
  6. package/build/build-cjs/src/search-results/store/search-results-slice.d.ts +27 -8
  7. package/build/build-cjs/src/search-results/types.d.ts +14 -2
  8. package/build/build-cjs/src/search-results/utils/search-results-utils.d.ts +8 -6
  9. package/build/build-cjs/src/shared/components/flyin/flyin.d.ts +3 -3
  10. package/build/build-cjs/src/shared/components/flyin/packaging-flights-flyin.d.ts +7 -0
  11. package/build/build-cjs/src/shared/utils/localization-util.d.ts +2 -0
  12. package/build/build-esm/index.js +1747 -861
  13. package/build/build-esm/src/search-results/components/filters/filters.d.ts +2 -0
  14. package/build/build-esm/src/search-results/components/flight/flight-selection/independent-flight-option.d.ts +3 -0
  15. package/build/build-esm/src/search-results/components/hotel/hotel-accommodation-results.d.ts +1 -0
  16. package/build/build-esm/src/search-results/store/search-results-selectors.d.ts +424 -0
  17. package/build/build-esm/src/search-results/store/search-results-slice.d.ts +27 -8
  18. package/build/build-esm/src/search-results/types.d.ts +14 -2
  19. package/build/build-esm/src/search-results/utils/search-results-utils.d.ts +8 -6
  20. package/build/build-esm/src/shared/components/flyin/flyin.d.ts +3 -3
  21. package/build/build-esm/src/shared/components/flyin/packaging-flights-flyin.d.ts +7 -0
  22. package/build/build-esm/src/shared/utils/localization-util.d.ts +2 -0
  23. package/package.json +2 -2
  24. package/src/booking-wizard/features/flight-options/index.tsx +6 -2
  25. package/src/search-results/components/filters/filters.tsx +8 -9
  26. package/src/search-results/components/flight/flight-selection/independent-flight-option.tsx +31 -4
  27. package/src/search-results/components/hotel/hotel-accommodation-results.tsx +81 -25
  28. package/src/search-results/components/icon.tsx +1 -1
  29. package/src/search-results/components/search-results-container/search-results-container.tsx +194 -130
  30. package/src/search-results/store/search-results-selectors.ts +73 -0
  31. package/src/search-results/store/search-results-slice.ts +94 -14
  32. package/src/search-results/types.ts +14 -2
  33. package/src/search-results/utils/search-results-utils.ts +310 -58
  34. package/src/shared/components/flyin/flyin.tsx +102 -19
  35. package/src/shared/components/flyin/packaging-flights-flyin.tsx +164 -0
  36. package/src/shared/translations/ar-SA.json +2 -0
  37. package/src/shared/translations/da-DK.json +2 -0
  38. package/src/shared/translations/de-DE.json +2 -0
  39. package/src/shared/translations/en-GB.json +2 -0
  40. package/src/shared/translations/es-ES.json +2 -0
  41. package/src/shared/translations/fr-BE.json +2 -0
  42. package/src/shared/translations/fr-FR.json +2 -0
  43. package/src/shared/translations/is-IS.json +2 -0
  44. package/src/shared/translations/it-IT.json +2 -0
  45. package/src/shared/translations/ja-JP.json +2 -0
  46. package/src/shared/translations/nl-BE.json +2 -0
  47. package/src/shared/translations/nl-NL.json +2 -0
  48. package/src/shared/translations/no-NO.json +2 -0
  49. package/src/shared/translations/pl-PL.json +2 -0
  50. package/src/shared/translations/pt-PT.json +2 -0
  51. package/src/shared/translations/sv-SE.json +2 -0
  52. package/src/shared/utils/localization-util.ts +5 -2
  53. package/styles/components/_flight-option.scss +14 -1
  54. package/styles/components/_flyin.scss +16 -0
  55. package/styles/components/_search.scss +9 -1
@@ -16,15 +16,22 @@ import {
16
16
  setPackagingAccoSearchDetails,
17
17
  setEditablePackagingEntry,
18
18
  setTransactionId,
19
- setAccommodationFlyInStep,
19
+ setFlyInType,
20
20
  setPriceDetails,
21
21
  setItinerary,
22
22
  setFlightsLoading,
23
23
  setPackagingFlightResults,
24
24
  setSelectedPackagingFlight,
25
- setSelectedPackagingAccoResult
25
+ setSelectedPackagingAccoResult,
26
+ setSelectedOutwardKey,
27
+ setSelectedReturnKey,
28
+ setFilteredPackagingFlightResults,
29
+ setInitialFlightFilters,
30
+ resetFlightFilters,
31
+ setFilters,
32
+ setInitialFilters
26
33
  } from '../../store/search-results-slice';
27
- import { AccommodationFlyInStep, Filter, SearchSeed, SortByType } from '../../types';
34
+ import { FlyInType, Filter, SearchSeed, SortByType } from '../../types';
28
35
  import useMediaQuery from '../../../shared/utils/use-media-query-util';
29
36
  import ItemPicker from '../item-picker';
30
37
  import {
@@ -71,7 +78,9 @@ import GroupTourResults from '../group-tour/group-tour-results';
71
78
  import {
72
79
  applyFilters,
73
80
  applyFiltersToPackageAccoResults,
81
+ applyFiltersToPackageFlightResults,
74
82
  enrichFiltersWithPackageAccoResults,
83
+ enrichFiltersWithPackageFlightResults,
75
84
  enrichFiltersWithResults
76
85
  } from '../../utils/search-results-utils';
77
86
  import {
@@ -89,6 +98,17 @@ import { getRequestRoomsFromPackagingSegments, getRoomIndexFromLine, getSelected
89
98
  import FullItinerary from '../itinerary/full-itinerary';
90
99
  import { getFlightKey } from '../../utils/flight-utils';
91
100
  import IndependentFlightOption from '../flight/flight-selection/independent-flight-option';
101
+ import { Spinner } from '../../..';
102
+ import { PackagingRequestBase } from '@qite/tide-client/build/types/booking-v2/request/packaging/packaging-request-base';
103
+ import {
104
+ selectSelectedCombinationFlight,
105
+ selectSelectedOutward,
106
+ selectSelectedOutwardKey,
107
+ selectSelectedReturn,
108
+ selectSelectedReturnKey,
109
+ selectUniqueOutwardFlights,
110
+ selectUniqueReturnFlights
111
+ } from '../../store/search-results-selectors';
92
112
 
93
113
  type BuildPackagingEntryPartialArgs = {
94
114
  sourceEntry: PackagingEntry | null | undefined;
@@ -113,17 +133,20 @@ const SearchResultsContainer: React.FC = () => {
113
133
  filteredResults,
114
134
  packagingAccoResults,
115
135
  filteredPackagingAccoResults,
116
- bookingPackageDetails,
117
136
  isLoading,
137
+ flightsLoading,
138
+ initialFilters,
118
139
  filters,
140
+ flightFilters,
119
141
  selectedSortType,
142
+ selectedFlightSortType,
120
143
  selectedSearchResult,
121
144
  selectedPackagingAccoResultCode,
122
145
  flyInIsOpen,
123
146
  packagingAccoSearchDetails,
124
147
  editablePackagingEntry,
125
148
  transactionId,
126
- accommodationFlyInStep,
149
+ flyInType,
127
150
  itinerary,
128
151
  packagingFlightResults
129
152
  } = useSelector((state: SearchResultsRootState) => state.searchResults);
@@ -131,7 +154,7 @@ const SearchResultsContainer: React.FC = () => {
131
154
  const isMobile = useMediaQuery('(max-width: 1200px)');
132
155
 
133
156
  const [initialFiltersSet, setInitialFiltersSet] = useState(false);
134
- const [initialFilters, setInitialFilters] = useState<Filter[]>([]);
157
+ const [initialFlightFiltersSet, setInitialFlightFiltersSet] = useState(false);
135
158
  const [filtersOpen, setFiltersOpen] = useState(false);
136
159
 
137
160
  const [detailsIsLoading, setDetailsIsLoading] = useState(false);
@@ -434,7 +457,7 @@ const SearchResultsContainer: React.FC = () => {
434
457
  setDetailsIsLoading(true);
435
458
 
436
459
  setSelectedAccommodationSeed(seed);
437
- dispatch(setAccommodationFlyInStep('results'));
460
+ dispatch(setFlyInType('acco-results'));
438
461
  handleFlyInToggle(true);
439
462
  const currentTransactionId = await getOrCreateTransactionId();
440
463
  await runAccommodationFlow(seed, currentTransactionId ?? '');
@@ -573,7 +596,7 @@ const SearchResultsContainer: React.FC = () => {
573
596
  const enrichedFilters = enrichFiltersWithResults(packageSearchResults, context.filters, context.tags ?? []);
574
597
  if (!initialFiltersSet) {
575
598
  dispatch(resetFilters(enrichedFilters));
576
- setInitialFilters(enrichedFilters);
599
+ dispatch(setInitialFilters(enrichedFilters));
577
600
  setInitialFiltersSet(true);
578
601
  }
579
602
 
@@ -597,19 +620,21 @@ const SearchResultsContainer: React.FC = () => {
597
620
  try {
598
621
  if (!context) return null;
599
622
 
623
+ dispatch(setIsLoading(true));
624
+
600
625
  const config: TideClientConfig = {
601
626
  host: context.tideConnection.host,
602
627
  apiKey: context.tideConnection.apiKey
603
628
  };
604
629
 
605
630
  const transaction = await startTransaction(config);
606
- console.log('Transaction started', transaction);
607
631
 
608
632
  dispatch(setTransactionId(transaction.transactionId));
609
-
633
+ dispatch(setIsLoading(false));
610
634
  return transaction.transactionId;
611
635
  } catch (err) {
612
636
  console.error('Transaction failed', err);
637
+ dispatch(setIsLoading(false));
613
638
  return null;
614
639
  }
615
640
  };
@@ -630,7 +655,7 @@ const SearchResultsContainer: React.FC = () => {
630
655
 
631
656
  const packageAccoSearchResults = await searchPackagingAccommodations(config, searchRequest);
632
657
 
633
- const enrichedFilters = enrichFiltersWithPackageAccoResults(packageAccoSearchResults, context.filters, context.tags ?? []);
658
+ const enrichedFilters = enrichFiltersWithPackageAccoResults(packageAccoSearchResults, context.tags ?? []);
634
659
  if (!initialFiltersSet) {
635
660
  dispatch(resetFilters(enrichedFilters));
636
661
  setInitialFilters(enrichedFilters);
@@ -673,8 +698,26 @@ const SearchResultsContainer: React.FC = () => {
673
698
 
674
699
  const packageFlightSearchResults = await searchPackagingFlights(config, searchRequest);
675
700
 
701
+ const enrichedFilters = enrichFiltersWithPackageFlightResults(packageFlightSearchResults, context.tags ?? [], translations);
702
+ if (!initialFlightFiltersSet) {
703
+ dispatch(resetFlightFilters(enrichedFilters));
704
+ dispatch(setInitialFlightFilters(enrichedFilters));
705
+ setInitialFlightFiltersSet(true);
706
+ }
707
+
676
708
  dispatch(setPackagingFlightResults(packageFlightSearchResults));
677
709
 
710
+ const initialFilteredResults = applyFiltersToPackageFlightResults(packageFlightSearchResults, filters, null);
711
+ dispatch(setFilteredPackagingFlightResults(initialFilteredResults));
712
+
713
+ if (initialFilteredResults.length > 0) {
714
+ const firstResult = first(packageFlightSearchResults);
715
+ if (firstResult) {
716
+ dispatch(setSelectedOutwardKey(getFlightKey(firstResult.outward.segments)));
717
+ dispatch(setSelectedReturnKey(getFlightKey(firstResult.return.segments)));
718
+ }
719
+ }
720
+
678
721
  dispatch(setFlightsLoading(false));
679
722
  } catch (err) {
680
723
  console.error('FlightSearch failed', err);
@@ -746,9 +789,7 @@ const SearchResultsContainer: React.FC = () => {
746
789
  ]);
747
790
 
748
791
  useEffect(() => {
749
- console.log('packaging entry from context', context?.packagingEntry);
750
792
  if (context?.packagingEntry) {
751
- console.log('original packaging entry from context', context.packagingEntry);
752
793
  dispatch(setEditablePackagingEntry(structuredClone(context.packagingEntry)));
753
794
  dispatch(setTransactionId(context.packagingEntry.transactionId));
754
795
  }
@@ -829,6 +870,7 @@ const SearchResultsContainer: React.FC = () => {
829
870
  context?.searchConfiguration.qsmType === PortalQsmType.GroupTour
830
871
  ) {
831
872
  handleFlyInToggle(true);
873
+ dispatch(setFlyInType('acco-details'));
832
874
  }
833
875
 
834
876
  try {
@@ -913,11 +955,10 @@ const SearchResultsContainer: React.FC = () => {
913
955
  if (selectedPackagingAccoResultCode) {
914
956
  fetchPackagingAccoSearchDetails();
915
957
  }
916
- dispatch(setAccommodationFlyInStep('details'));
917
958
  }, [selectedSearchResult, selectedPackagingAccoResultCode]);
918
959
 
919
960
  useEffect(() => {
920
- if (context?.searchConfiguration.qsmType === PortalQsmType.Accommodation) {
961
+ if (context?.searchConfiguration.qsmType === PortalQsmType.Accommodation || context?.searchConfiguration.qsmType === PortalQsmType.AccommodationAndFlight) {
921
962
  const filteredPackageAccoResults = applyFiltersToPackageAccoResults(packagingAccoResults, filters, selectedSortType);
922
963
  dispatch(setFilteredPackagingAccoResults(filteredPackageAccoResults));
923
964
  } else {
@@ -926,6 +967,13 @@ const SearchResultsContainer: React.FC = () => {
926
967
  }
927
968
  }, [filters, results, packagingAccoResults, selectedSortType]);
928
969
 
970
+ useEffect(() => {
971
+ if (context?.searchConfiguration.qsmType === PortalQsmType.AccommodationAndFlight) {
972
+ const filteredPackageFlightResults = applyFiltersToPackageFlightResults(packagingFlightResults, flightFilters, selectedFlightSortType);
973
+ dispatch(setFilteredPackagingFlightResults(filteredPackageFlightResults));
974
+ }
975
+ }, [flightFilters, packagingFlightResults, selectedFlightSortType]);
976
+
929
977
  useEffect(() => {
930
978
  setInitialFiltersSet(false);
931
979
  }, [activeSearchSeed]);
@@ -940,7 +988,15 @@ const SearchResultsContainer: React.FC = () => {
940
988
  apiKey: context.tideConnection.apiKey
941
989
  };
942
990
 
943
- const priceDetails = await getPriceDetails(config, editablePackagingEntry);
991
+ const request = {
992
+ language: context.languageCode ?? 'en-GB',
993
+ officeId: context.tideConnection.officeId,
994
+ catalogueId: context.searchConfiguration.defaultCatalogueId ?? 0,
995
+ agentId: context.agentId,
996
+ payload: editablePackagingEntry
997
+ } as PackagingRequestBase<PackagingEntry>;
998
+
999
+ const priceDetails = await getPriceDetails(config, request);
944
1000
  dispatch(setPriceDetails(priceDetails));
945
1001
  setPricesAreLoading(false);
946
1002
  } catch (err) {
@@ -950,8 +1006,7 @@ const SearchResultsContainer: React.FC = () => {
950
1006
  };
951
1007
 
952
1008
  const fetchItinerary = async () => {
953
- console.log('Fetching itinerary for entry', editablePackagingEntry);
954
- if (!context || !editablePackagingEntry || isEmpty(editablePackagingEntry.lines)) return;
1009
+ if (!context || !context.packagingEntry || !editablePackagingEntry || isEmpty(editablePackagingEntry.lines)) return;
955
1010
  setItineraryIsLoading(true);
956
1011
  try {
957
1012
  const config: TideClientConfig = {
@@ -959,8 +1014,15 @@ const SearchResultsContainer: React.FC = () => {
959
1014
  apiKey: context.tideConnection.apiKey
960
1015
  };
961
1016
 
962
- const itinerary = await getItinerary(config, editablePackagingEntry);
963
- console.log('Fetched itinerary', itinerary);
1017
+ const request = {
1018
+ language: context.languageCode ?? 'en-GB',
1019
+ officeId: context.tideConnection.officeId,
1020
+ catalogueId: context.searchConfiguration.defaultCatalogueId ?? 0,
1021
+ agentId: context.agentId,
1022
+ payload: editablePackagingEntry
1023
+ } as PackagingRequestBase<PackagingEntry>;
1024
+
1025
+ const itinerary = await getItinerary(config, request);
964
1026
  dispatch(setItinerary(itinerary));
965
1027
  setItineraryIsLoading(false);
966
1028
  } catch (err) {
@@ -974,35 +1036,25 @@ const SearchResultsContainer: React.FC = () => {
974
1036
  }, [editablePackagingEntry]);
975
1037
 
976
1038
  // Flight selection
977
- const [selectedOutwardKey, setSelectedOutwardKey] = useState<string | null>(null);
978
- const [selectedReturnKey, setSelectedReturnKey] = useState<string | null>(null);
979
-
980
- const uniqueOutwardFlights: PackagingFlightResponse[] = React.useMemo(() => {
981
- const map = new Map();
982
-
983
- packagingFlightResults.forEach((flight) => {
984
- const key = getFlightKey(flight.outward.segments);
985
-
986
- if (!map.has(key)) {
987
- map.set(key, flight);
988
- }
989
- });
990
-
991
- return Array.from(map.values());
992
- }, [packagingFlightResults]);
993
-
994
- const [uniqueReturnFlights, setUniqueReturnFlights] = useState<PackagingFlightResponse[]>([]);
1039
+ // const [selectedOutwardKey, setSelectedOutwardKey] = useState<string | null>(null);
1040
+ // const [selectedReturnKey, setSelectedReturnKey] = useState<string | null>(null);
1041
+ const selectedOutwardKey = useSelector(selectSelectedOutwardKey);
1042
+ const selectedReturnKey = useSelector(selectSelectedReturnKey);
1043
+ const uniqueOutwardFlights = useSelector(selectUniqueOutwardFlights);
1044
+ const uniqueReturnFlights = useSelector(selectUniqueReturnFlights);
1045
+ const selectedOutward = useSelector(selectSelectedOutward);
1046
+ const selectedReturn = useSelector(selectSelectedReturn);
1047
+ const selectedCombinationFlight = useSelector(selectSelectedCombinationFlight);
995
1048
 
996
1049
  useEffect(() => {
997
1050
  if (!selectedOutwardKey) {
998
- setUniqueReturnFlights([]);
999
- setSelectedReturnKey(null);
1051
+ dispatch(setSelectedReturnKey(null));
1000
1052
  return;
1001
1053
  }
1002
1054
 
1003
- // Filter combinations that match selected outward fare
1004
1055
  const matchingCombinations = packagingFlightResults.filter((flight) => getFlightKey(flight.outward.segments) === selectedOutwardKey);
1005
- const returnMap = new Map();
1056
+
1057
+ const returnMap = new Map<string, PackagingFlightResponse>();
1006
1058
 
1007
1059
  matchingCombinations.forEach((flight) => {
1008
1060
  const key = getFlightKey(flight.return.segments);
@@ -1013,29 +1065,18 @@ const SearchResultsContainer: React.FC = () => {
1013
1065
  });
1014
1066
 
1015
1067
  const returns = Array.from(returnMap.values());
1068
+ const segments = first(returns)?.return.segments;
1069
+ const firstReturnKey = returns.length > 0 && segments ? getFlightKey(segments) : null;
1016
1070
 
1017
- setUniqueReturnFlights(returns);
1018
- }, [selectedOutwardKey, packagingFlightResults]);
1019
-
1020
- const selectedOutward = React.useMemo(() => {
1021
- if (!selectedOutwardKey) return null;
1022
-
1023
- return packagingFlightResults.find((flight) => getFlightKey(flight.outward.segments) === selectedOutwardKey) || null;
1024
- }, [packagingFlightResults, selectedOutwardKey]);
1025
-
1026
- const selectedReturn = React.useMemo(() => {
1027
- if (!selectedReturnKey) return null;
1028
-
1029
- return packagingFlightResults.find((flight) => getFlightKey(flight.return.segments) === selectedReturnKey) || null;
1030
- }, [packagingFlightResults, selectedReturnKey]);
1031
-
1032
- const selectedCombinationFlight = React.useMemo(() => {
1033
- if (!selectedOutwardKey || !selectedReturnKey) return undefined;
1071
+ if (!returns.some((x) => getFlightKey(x.return.segments) === selectedReturnKey)) {
1072
+ dispatch(setSelectedReturnKey(firstReturnKey));
1073
+ }
1074
+ }, [selectedOutwardKey, packagingFlightResults, selectedReturnKey, dispatch]);
1034
1075
 
1035
- return packagingFlightResults.find(
1036
- (flight) => getFlightKey(flight.outward.segments) === selectedOutwardKey && getFlightKey(flight.return.segments) === selectedReturnKey
1037
- );
1038
- }, [packagingFlightResults, selectedOutwardKey, selectedReturnKey]);
1076
+ const visibleOutwardFlights = React.useMemo(() => {
1077
+ const withoutSelected = uniqueOutwardFlights.filter((x) => getFlightKey(x.outward.segments) !== selectedOutwardKey);
1078
+ return withoutSelected.slice(0, 3);
1079
+ }, [uniqueOutwardFlights, selectedOutwardKey]);
1039
1080
 
1040
1081
  // TODO: get details for selected combination flight and show in fly-in
1041
1082
  // useEffect(() => {
@@ -1046,6 +1087,7 @@ const SearchResultsContainer: React.FC = () => {
1046
1087
  // dispatch(setFlyInIsOpen(true));
1047
1088
  // }, [selectedCombinationFlight, dispatch]);
1048
1089
 
1090
+ // Build packagingEntry
1049
1091
  useEffect(() => {
1050
1092
  if (!context) return;
1051
1093
 
@@ -1062,7 +1104,6 @@ const SearchResultsContainer: React.FC = () => {
1062
1104
  language: context.languageCode ?? 'en-GB'
1063
1105
  });
1064
1106
 
1065
- console.log('Built next packaging entry', nextEntry);
1066
1107
  if (!nextEntry) return;
1067
1108
 
1068
1109
  dispatch(setEditablePackagingEntry(nextEntry));
@@ -1269,8 +1310,6 @@ const SearchResultsContainer: React.FC = () => {
1269
1310
 
1270
1311
  const selectedHotel = selectedHotelCode ? accommodationResults.find((r) => r.code === selectedHotelCode) : null;
1271
1312
 
1272
- console.log('Selected hotel for packaging entry', selectedHotel);
1273
-
1274
1313
  // Update accommodation only when enough data exists
1275
1314
  if (selectedHotel) {
1276
1315
  const accommodationLines = buildAccommodationLinesFromSelection(selectedHotel, seed);
@@ -1284,8 +1323,6 @@ const SearchResultsContainer: React.FC = () => {
1284
1323
  // Update flights only when full selected combination exists
1285
1324
  if (selectedFlight) {
1286
1325
  const flightLines = buildFlightLinesFromSelection(selectedFlight);
1287
- console.log('selectedFlight', selectedFlight);
1288
- console.log('Built flight lines from selection', flightLines);
1289
1326
  if (flightLines.length) {
1290
1327
  nextLines = removeFlightLines(nextLines);
1291
1328
  nextLines = [...nextLines, ...flightLines];
@@ -1342,6 +1379,11 @@ const SearchResultsContainer: React.FC = () => {
1342
1379
  } as PackagingEntry;
1343
1380
  };
1344
1381
 
1382
+ const handleShowMoreFlights = (flyInType: FlyInType) => {
1383
+ dispatch(setFlyInType(flyInType));
1384
+ dispatch(setFlyInIsOpen(true));
1385
+ };
1386
+
1345
1387
  return (
1346
1388
  <div id="tide-booking" className="search__bg">
1347
1389
  {context && (
@@ -1351,7 +1393,6 @@ const SearchResultsContainer: React.FC = () => {
1351
1393
  <FlightSearchProvider tideConnection={context.tideConnection}>
1352
1394
  <FlightResultsContainer isMobile={isMobile} />
1353
1395
  <FlyIn
1354
- title="Select your fare"
1355
1396
  srpType={context.searchConfiguration.qsmType}
1356
1397
  isOpen={flyInIsOpen}
1357
1398
  setIsOpen={handleFlyInToggle}
@@ -1373,6 +1414,8 @@ const SearchResultsContainer: React.FC = () => {
1373
1414
  handleSetIsOpen={() => setFiltersOpen(!filtersOpen)}
1374
1415
  // handleApplyFilters={() => setSearchTrigger((prev) => prev + 1)}
1375
1416
  isLoading={isLoading}
1417
+ setFilters={(filters) => dispatch(setFilters(filters))}
1418
+ resetFilters={(filters) => dispatch(resetFilters(filters))}
1376
1419
  />
1377
1420
  )}
1378
1421
  {context.searchConfiguration.qsmType === PortalQsmType.AccommodationAndFlight && (
@@ -1415,33 +1458,35 @@ const SearchResultsContainer: React.FC = () => {
1415
1458
  )}
1416
1459
  </div>
1417
1460
  )}
1418
- <div className="search__result-row">
1419
- <span className="search__result-row-text">
1420
- {!isLoading && context.searchConfiguration.qsmType !== PortalQsmType.AccommodationAndFlight && (
1421
- <>
1422
- {context.searchConfiguration.qsmType === PortalQsmType.Accommodation &&
1423
- filteredPackagingAccoResults?.length &&
1424
- filteredPackagingAccoResults?.length}
1425
- {context.searchConfiguration.qsmType !== PortalQsmType.Accommodation && filteredResults?.length && filteredResults.length}
1426
- &nbsp;{translations.SRP.TOTAL_RESULTS_LABEL}
1427
- </>
1461
+ {context.searchConfiguration.qsmType !== PortalQsmType.AccommodationAndFlight && (
1462
+ <div className="search__result-row">
1463
+ <span className="search__result-row-text">
1464
+ {!isLoading && (
1465
+ <>
1466
+ {context.searchConfiguration.qsmType === PortalQsmType.Accommodation &&
1467
+ filteredPackagingAccoResults?.length &&
1468
+ filteredPackagingAccoResults?.length}
1469
+ {context.searchConfiguration.qsmType !== PortalQsmType.Accommodation && filteredResults?.length && filteredResults.length}
1470
+ &nbsp;{translations.SRP.TOTAL_RESULTS_LABEL}
1471
+ </>
1472
+ )}
1473
+ </span>
1474
+ {!context.packagingEntry && !isMobile && sortByTypes && sortByTypes.length > 0 && (
1475
+ <div className="search__result-row-filter">
1476
+ <ItemPicker
1477
+ items={sortByTypes}
1478
+ selection={selectedSortType?.label || undefined}
1479
+ selectedSortByType={selectedSortType}
1480
+ label={translations.SRP.SORTBY}
1481
+ placeholder={translations.SRP.SORTBY}
1482
+ classModifier="travel-class-picker__items"
1483
+ valueFormatter={(value, direction) => getSortingName(translations, findSortByType(sortByTypes, value, direction ?? 'asc'))}
1484
+ onPick={(newSortKey, direction) => handleSortChange(newSortKey, direction)}
1485
+ />
1486
+ </div>
1428
1487
  )}
1429
- </span>
1430
- {!context.packagingEntry && !isMobile && sortByTypes && sortByTypes.length > 0 && (
1431
- <div className="search__result-row-filter">
1432
- <ItemPicker
1433
- items={sortByTypes}
1434
- selection={selectedSortType?.label || undefined}
1435
- selectedSortByType={selectedSortType}
1436
- label={translations.SRP.SORTBY}
1437
- placeholder={translations.SRP.SORTBY}
1438
- classModifier="travel-class-picker__items"
1439
- valueFormatter={(value, direction) => getSortingName(translations, findSortByType(sortByTypes, value, direction ?? 'asc'))}
1440
- onPick={(newSortKey, direction) => handleSortChange(newSortKey, direction)}
1441
- />
1442
- </div>
1443
- )}
1444
- </div>
1488
+ </div>
1489
+ )}
1445
1490
 
1446
1491
  <div className="search__results__wrapper">
1447
1492
  {context.showTabViews &&
@@ -1467,28 +1512,44 @@ const SearchResultsContainer: React.FC = () => {
1467
1512
  </div>
1468
1513
  </div>
1469
1514
 
1470
- <div className="search__results__cards search__results__cards--extended">
1471
- {selectedOutwardKey && selectedOutward ? (
1472
- <IndependentFlightOption
1473
- key={`flight-${selectedOutwardKey}`}
1474
- item={selectedOutward.outward}
1475
- guid={selectedOutward.outwardGuid}
1476
- onSelect={() => setSelectedOutwardKey(null)}
1477
- selectedGuid={selectedOutward.outwardGuid}
1478
- isOutward={true}
1479
- />
1480
- ) : (
1481
- uniqueOutwardFlights.map((result) => (
1482
- <IndependentFlightOption
1483
- key={`flight-${result.outwardGuid}`}
1484
- item={result.outward}
1485
- onSelect={() => setSelectedOutwardKey(getFlightKey(result.outward.segments))}
1486
- guid={result.outwardGuid}
1487
- isOutward={true}
1488
- />
1489
- ))
1490
- )}
1491
- </div>
1515
+ {flightsLoading ? (
1516
+ <Spinner />
1517
+ ) : (
1518
+ <>
1519
+ <div className="search__results__cards search__results__cards--extended">
1520
+ {selectedOutwardKey && selectedOutward && (
1521
+ <IndependentFlightOption
1522
+ key={`flight-${selectedOutwardKey}`}
1523
+ item={selectedOutward.outward}
1524
+ guid={selectedOutward.outwardGuid}
1525
+ onSelect={() => dispatch(setSelectedOutwardKey(null))}
1526
+ selectedGuid={selectedOutward.outwardGuid}
1527
+ isOutward={true}
1528
+ showSelectedState={true}
1529
+ price={selectedOutward.price}
1530
+ />
1531
+ )}
1532
+ {visibleOutwardFlights.map((result) => (
1533
+ <IndependentFlightOption
1534
+ key={`flight-${result.outwardGuid}`}
1535
+ item={result.outward}
1536
+ onSelect={() => dispatch(setSelectedOutwardKey(getFlightKey(result.outward.segments)))}
1537
+ guid={result.outwardGuid}
1538
+ isOutward={true}
1539
+ price={result.price}
1540
+ currentSelectedPrice={selectedOutward?.price}
1541
+ />
1542
+ ))}
1543
+ </div>
1544
+ {uniqueOutwardFlights && uniqueOutwardFlights.length > 3 && (
1545
+ <div className="search__results__cards__actions">
1546
+ <button className="cta cta--secondary" onClick={() => handleShowMoreFlights('flight-outward-results')}>
1547
+ {translations.SRP.SHOW_MORE}
1548
+ </button>
1549
+ </div>
1550
+ )}
1551
+ </>
1552
+ )}
1492
1553
  </>
1493
1554
  )}
1494
1555
 
@@ -1510,25 +1571,28 @@ const SearchResultsContainer: React.FC = () => {
1510
1571
  </div>
1511
1572
 
1512
1573
  <div className="search__results__cards search__results__cards--extended">
1513
- {selectedReturnKey && selectedReturn ? (
1574
+ {selectedReturnKey && selectedReturn && (
1514
1575
  <IndependentFlightOption
1515
1576
  key={`flight-${selectedReturnKey}`}
1516
1577
  item={selectedReturn.return}
1517
1578
  guid={selectedReturn.outwardGuid}
1518
1579
  selectedGuid={selectedReturn.outwardGuid}
1519
1580
  isOutward={false}
1581
+ showSelectedState={true}
1582
+ price={selectedReturn.price}
1520
1583
  />
1521
- ) : (
1522
- uniqueReturnFlights.map((result) => (
1523
- <IndependentFlightOption
1524
- key={`flight-${result.outwardGuid}`}
1525
- item={result.return}
1526
- onSelect={() => setSelectedReturnKey(getFlightKey(result.return.segments))}
1527
- guid={result.outwardGuid}
1528
- isOutward={false}
1529
- />
1530
- ))
1531
1584
  )}
1585
+ {uniqueReturnFlights.map((result) => (
1586
+ <IndependentFlightOption
1587
+ key={`flight-${result.outwardGuid}`}
1588
+ item={result.return}
1589
+ onSelect={() => dispatch(setSelectedReturnKey(getFlightKey(result.return.segments)))}
1590
+ guid={result.outwardGuid}
1591
+ isOutward={false}
1592
+ currentSelectedPrice={selectedReturn?.price}
1593
+ price={result.price}
1594
+ />
1595
+ ))}
1532
1596
  </div>
1533
1597
  </>
1534
1598
  )}
@@ -1540,15 +1604,15 @@ const SearchResultsContainer: React.FC = () => {
1540
1604
  </div>
1541
1605
  {/* <button onClick={() => handleFlyInToggle(!flyInIsOpen)}>Toggle FlyIn</button> */}
1542
1606
  <FlyIn
1543
- title={`${translations.SRP.SELECT} ${translations.SRP.ACCOMMODATION}`}
1544
1607
  srpType={context.searchConfiguration.qsmType}
1545
1608
  isOpen={flyInIsOpen}
1546
1609
  setIsOpen={handleFlyInToggle}
1547
1610
  handleConfirm={() => handleConfirmHotelSwap()}
1548
1611
  onPanelRef={(el) => (panelRef.current = el)}
1549
1612
  detailsLoading={detailsIsLoading}
1550
- accommodationStep={accommodationFlyInStep}
1613
+ flyInType={flyInType}
1551
1614
  isPackageEditFlow={!!context.packagingEntry}
1615
+ sortByTypes={sortByTypes}
1552
1616
  />
1553
1617
  </>
1554
1618
  )}
@@ -0,0 +1,73 @@
1
+ import { createSelector } from '@reduxjs/toolkit';
2
+ import { SearchResultsRootState } from './search-results-store';
3
+ import { getFlightKey } from '../utils/flight-utils';
4
+ import { PackagingFlightResponse } from '@qite/tide-client';
5
+
6
+ const selectSearchResultsState = (state: SearchResultsRootState) => state.searchResults;
7
+
8
+ export const selectPackagingFlightResults = createSelector([selectSearchResultsState], (state) => state.packagingFlightResults);
9
+ export const selectFilteredPackagingFlightResults = createSelector([selectSearchResultsState], (state) => state.filteredPackagingFlightResults);
10
+
11
+ export const selectSelectedOutwardKey = createSelector([selectSearchResultsState], (state) => state.selectedOutwardKey);
12
+
13
+ export const selectSelectedReturnKey = createSelector([selectSearchResultsState], (state) => state.selectedReturnKey);
14
+
15
+ export const selectUniqueOutwardFlights = createSelector([selectFilteredPackagingFlightResults], (packagingFlightResults): PackagingFlightResponse[] => {
16
+ const map = new Map<string, PackagingFlightResponse>();
17
+
18
+ packagingFlightResults.forEach((flight) => {
19
+ const key = getFlightKey(flight.outward.segments);
20
+
21
+ if (!map.has(key)) {
22
+ map.set(key, flight);
23
+ }
24
+ });
25
+
26
+ return Array.from(map.values());
27
+ });
28
+
29
+ export const selectUniqueReturnFlights = createSelector(
30
+ [selectFilteredPackagingFlightResults, selectSelectedOutwardKey],
31
+ (packagingFlightResults, selectedOutwardKey): PackagingFlightResponse[] => {
32
+ if (!selectedOutwardKey) return [];
33
+
34
+ const matchingCombinations = packagingFlightResults.filter((flight) => getFlightKey(flight.outward.segments) === selectedOutwardKey);
35
+
36
+ const map = new Map<string, PackagingFlightResponse>();
37
+
38
+ matchingCombinations.forEach((flight) => {
39
+ const key = getFlightKey(flight.return.segments);
40
+
41
+ if (!map.has(key)) {
42
+ map.set(key, flight);
43
+ }
44
+ });
45
+
46
+ return Array.from(map.values());
47
+ }
48
+ );
49
+
50
+ export const selectSelectedOutward = createSelector([selectPackagingFlightResults, selectSelectedOutwardKey], (packagingFlightResults, selectedOutwardKey) => {
51
+ if (!selectedOutwardKey) return null;
52
+
53
+ return packagingFlightResults.find((flight) => getFlightKey(flight.outward.segments) === selectedOutwardKey) ?? null;
54
+ });
55
+
56
+ export const selectSelectedReturn = createSelector([selectPackagingFlightResults, selectSelectedReturnKey], (packagingFlightResults, selectedReturnKey) => {
57
+ if (!selectedReturnKey) return null;
58
+
59
+ return packagingFlightResults.find((flight) => getFlightKey(flight.return.segments) === selectedReturnKey) ?? null;
60
+ });
61
+
62
+ export const selectSelectedCombinationFlight = createSelector(
63
+ [selectPackagingFlightResults, selectSelectedOutwardKey, selectSelectedReturnKey],
64
+ (packagingFlightResults, selectedOutwardKey, selectedReturnKey) => {
65
+ if (!selectedOutwardKey || !selectedReturnKey) return null;
66
+
67
+ return (
68
+ packagingFlightResults.find(
69
+ (flight) => getFlightKey(flight.outward.segments) === selectedOutwardKey && getFlightKey(flight.return.segments) === selectedReturnKey
70
+ ) ?? null
71
+ );
72
+ }
73
+ );