@qite/tide-booking-component 1.4.102 → 1.4.104
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/build-cjs/index.js +1773 -877
- package/build/build-cjs/src/search-results/components/filters/filters.d.ts +2 -0
- package/build/build-cjs/src/search-results/components/flight/flight-selection/independent-flight-option.d.ts +3 -0
- package/build/build-cjs/src/search-results/components/hotel/hotel-accommodation-results.d.ts +1 -0
- package/build/build-cjs/src/search-results/store/search-results-selectors.d.ts +424 -0
- package/build/build-cjs/src/search-results/store/search-results-slice.d.ts +27 -8
- package/build/build-cjs/src/search-results/types.d.ts +14 -2
- package/build/build-cjs/src/search-results/utils/search-results-utils.d.ts +8 -6
- package/build/build-cjs/src/shared/components/flyin/flyin.d.ts +3 -3
- package/build/build-cjs/src/shared/components/flyin/packaging-flights-flyin.d.ts +7 -0
- package/build/build-cjs/src/shared/utils/localization-util.d.ts +2 -0
- package/build/build-esm/index.js +1747 -861
- package/build/build-esm/src/search-results/components/filters/filters.d.ts +2 -0
- package/build/build-esm/src/search-results/components/flight/flight-selection/independent-flight-option.d.ts +3 -0
- package/build/build-esm/src/search-results/components/hotel/hotel-accommodation-results.d.ts +1 -0
- package/build/build-esm/src/search-results/store/search-results-selectors.d.ts +424 -0
- package/build/build-esm/src/search-results/store/search-results-slice.d.ts +27 -8
- package/build/build-esm/src/search-results/types.d.ts +14 -2
- package/build/build-esm/src/search-results/utils/search-results-utils.d.ts +8 -6
- package/build/build-esm/src/shared/components/flyin/flyin.d.ts +3 -3
- package/build/build-esm/src/shared/components/flyin/packaging-flights-flyin.d.ts +7 -0
- package/build/build-esm/src/shared/utils/localization-util.d.ts +2 -0
- package/package.json +2 -2
- package/src/booking-wizard/features/flight-options/index.tsx +6 -2
- package/src/search-results/components/filters/filters.tsx +8 -9
- package/src/search-results/components/flight/flight-selection/independent-flight-option.tsx +31 -4
- package/src/search-results/components/hotel/hotel-accommodation-results.tsx +81 -25
- package/src/search-results/components/icon.tsx +1 -1
- package/src/search-results/components/search-results-container/search-results-container.tsx +194 -130
- package/src/search-results/store/search-results-selectors.ts +73 -0
- package/src/search-results/store/search-results-slice.ts +94 -14
- package/src/search-results/types.ts +14 -2
- package/src/search-results/utils/search-results-utils.ts +310 -58
- package/src/shared/components/flyin/flyin.tsx +102 -19
- package/src/shared/components/flyin/packaging-flights-flyin.tsx +164 -0
- package/src/shared/translations/ar-SA.json +2 -0
- package/src/shared/translations/da-DK.json +2 -0
- package/src/shared/translations/de-DE.json +2 -0
- package/src/shared/translations/en-GB.json +2 -0
- package/src/shared/translations/es-ES.json +2 -0
- package/src/shared/translations/fr-BE.json +2 -0
- package/src/shared/translations/fr-FR.json +2 -0
- package/src/shared/translations/is-IS.json +2 -0
- package/src/shared/translations/it-IT.json +2 -0
- package/src/shared/translations/ja-JP.json +2 -0
- package/src/shared/translations/nl-BE.json +2 -0
- package/src/shared/translations/nl-NL.json +2 -0
- package/src/shared/translations/no-NO.json +2 -0
- package/src/shared/translations/pl-PL.json +2 -0
- package/src/shared/translations/pt-PT.json +2 -0
- package/src/shared/translations/sv-SE.json +2 -0
- package/src/shared/utils/localization-util.ts +5 -2
- package/styles/components/_flight-option.scss +14 -1
- package/styles/components/_flyin.scss +16 -0
- package/styles/components/_search.scss +9 -1
|
@@ -16,15 +16,22 @@ import {
|
|
|
16
16
|
setPackagingAccoSearchDetails,
|
|
17
17
|
setEditablePackagingEntry,
|
|
18
18
|
setTransactionId,
|
|
19
|
-
|
|
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 {
|
|
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
|
-
|
|
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 [
|
|
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(
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
|
963
|
-
|
|
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
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
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
|
-
|
|
1036
|
-
|
|
1037
|
-
);
|
|
1038
|
-
}, [
|
|
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
|
-
|
|
1419
|
-
<
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
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
|
+
{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
|
-
</
|
|
1430
|
-
|
|
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
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
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
|
-
|
|
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
|
+
);
|