@qite/tide-booking-component 1.4.103 → 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 +2167 -1400
- package/build/build-cjs/src/search-results/components/filters/filters.d.ts +2 -0
- package/build/build-cjs/src/search-results/components/hotel/hotel-accommodation-results.d.ts +1 -0
- package/build/build-cjs/src/search-results/store/search-results-selectors.d.ts +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-esm/index.js +2152 -1395
- package/build/build-esm/src/search-results/components/filters/filters.d.ts +2 -0
- package/build/build-esm/src/search-results/components/hotel/hotel-accommodation-results.d.ts +1 -0
- package/build/build-esm/src/search-results/store/search-results-selectors.d.ts +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/package.json +1 -1
- 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/hotel/hotel-accommodation-results.tsx +81 -24
- package/src/search-results/components/search-results-container/search-results-container.tsx +118 -102
- 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/styles/components/_flyin.scss +16 -0
- package/styles/components/_search.scss +4 -1
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import React, { useContext,
|
|
1
|
+
import React, { useContext, useState } from 'react';
|
|
2
2
|
import { Filter, FilterOption } from '../../types';
|
|
3
3
|
import MultiRangeFilter from '../multi-range-filter';
|
|
4
4
|
import SearchResultsConfigurationContext from '../../search-results-configuration-context';
|
|
5
|
-
import { resetFilters, setFilters } from '../../store/search-results-slice';
|
|
6
|
-
import { useDispatch } from 'react-redux';
|
|
7
5
|
import Spinner from '../spinner/spinner';
|
|
8
6
|
import Icon from '../icon';
|
|
9
7
|
import { getTranslations } from '../../../shared/utils/localization-util';
|
|
@@ -14,10 +12,13 @@ interface FiltersProps {
|
|
|
14
12
|
isOpen: boolean;
|
|
15
13
|
handleSetIsOpen: () => void;
|
|
16
14
|
isLoading?: boolean;
|
|
15
|
+
setFilters: (filters: Filter[]) => void;
|
|
16
|
+
resetFilters: (filters: Filter[]) => void;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
const Filters: React.FC<FiltersProps> = ({ initialFilters, filters, isOpen, handleSetIsOpen, isLoading }) => {
|
|
19
|
+
const Filters: React.FC<FiltersProps> = ({ initialFilters, filters, isOpen, handleSetIsOpen, isLoading, setFilters, resetFilters }) => {
|
|
20
20
|
const context = useContext(SearchResultsConfigurationContext);
|
|
21
|
+
|
|
21
22
|
if (!context || !context.showFilters) {
|
|
22
23
|
return null;
|
|
23
24
|
}
|
|
@@ -25,8 +26,6 @@ const Filters: React.FC<FiltersProps> = ({ initialFilters, filters, isOpen, hand
|
|
|
25
26
|
const translations = getTranslations(context?.languageCode ?? 'en-GB');
|
|
26
27
|
const [visibleFilters, setVisibleFilters] = useState<Record<string, boolean>>({});
|
|
27
28
|
|
|
28
|
-
const dispatch = useDispatch();
|
|
29
|
-
|
|
30
29
|
const toggleFilterVisibility = (filterId: string) => {
|
|
31
30
|
setVisibleFilters((prev) => ({
|
|
32
31
|
...prev,
|
|
@@ -44,7 +43,7 @@ const Filters: React.FC<FiltersProps> = ({ initialFilters, filters, isOpen, hand
|
|
|
44
43
|
};
|
|
45
44
|
});
|
|
46
45
|
|
|
47
|
-
|
|
46
|
+
setFilters(updated);
|
|
48
47
|
};
|
|
49
48
|
|
|
50
49
|
const handleSliderChange = (filter: Filter, newMin: number, newMax: number) => {
|
|
@@ -58,12 +57,12 @@ const Filters: React.FC<FiltersProps> = ({ initialFilters, filters, isOpen, hand
|
|
|
58
57
|
};
|
|
59
58
|
});
|
|
60
59
|
|
|
61
|
-
|
|
60
|
+
setFilters(updated);
|
|
62
61
|
};
|
|
63
62
|
|
|
64
63
|
const handleFullReset = () => {
|
|
65
64
|
if (!isLoading) {
|
|
66
|
-
|
|
65
|
+
resetFilters(initialFilters);
|
|
67
66
|
}
|
|
68
67
|
};
|
|
69
68
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import React, { useContext } from 'react';
|
|
2
|
-
import { HotelResult, SearchResultsConfiguration } from '../../types';
|
|
1
|
+
import React, { useContext, useState } from 'react';
|
|
2
|
+
import { FlyInType, HotelResult, SearchResultsConfiguration } from '../../types';
|
|
3
3
|
import Spinner from '../spinner/spinner';
|
|
4
4
|
import HotelCard from './hotel-card';
|
|
5
|
-
import { useSelector } from 'react-redux';
|
|
5
|
+
import { useDispatch, useSelector } from 'react-redux';
|
|
6
6
|
import { SearchResultsRootState } from '../../store/search-results-store';
|
|
7
7
|
import { BookingPackageItem, PackagingAccommodationResponse, PortalQsmType } from '@qite/tide-client';
|
|
8
8
|
import { format, parseISO } from 'date-fns';
|
|
@@ -10,9 +10,11 @@ import { calculateNights, formatPrice, getTranslations } from '../../../shared/u
|
|
|
10
10
|
import SearchResultsConfigurationContext from '../../search-results-configuration-context';
|
|
11
11
|
import { first } from 'lodash';
|
|
12
12
|
import Icon from '../icon';
|
|
13
|
+
import { setFlyInIsOpen, setFlyInType } from '../../store/search-results-slice';
|
|
13
14
|
|
|
14
15
|
interface HotelAccommodationResultsProps {
|
|
15
16
|
isLoading: boolean;
|
|
17
|
+
isFlyIn?: boolean;
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
const getLocation = (locationName?: string, regionName?: string, countryName?: string, fallback?: string): string => {
|
|
@@ -71,7 +73,14 @@ const mapPackagingAccoResult = (searchResult: PackagingAccommodationResponse, cm
|
|
|
71
73
|
};
|
|
72
74
|
};
|
|
73
75
|
|
|
74
|
-
const renderHotelResults = (
|
|
76
|
+
const renderHotelResults = (
|
|
77
|
+
results: HotelResult[],
|
|
78
|
+
context: SearchResultsConfiguration,
|
|
79
|
+
activeTab: string | null,
|
|
80
|
+
translations: any,
|
|
81
|
+
selectedPackagingAccoResult?: HotelResult,
|
|
82
|
+
isFlyIn?: boolean
|
|
83
|
+
) => {
|
|
75
84
|
const renderedResults = results.map((result, index) => {
|
|
76
85
|
const key = `${result.id ?? result.code}-${index}`;
|
|
77
86
|
|
|
@@ -86,19 +95,28 @@ const renderHotelResults = (results: HotelResult[], context: SearchResultsConfig
|
|
|
86
95
|
return <HotelCard key={key} result={result} translations={translations} />;
|
|
87
96
|
});
|
|
88
97
|
|
|
89
|
-
return
|
|
98
|
+
return (
|
|
99
|
+
<div className={`search__results__cards ${activeTab ? `search__results__cards--${activeTab}` : ''}`}>
|
|
100
|
+
{selectedPackagingAccoResult && !isFlyIn && (
|
|
101
|
+
<HotelCard key={selectedPackagingAccoResult.code} result={selectedPackagingAccoResult} translations={translations} />
|
|
102
|
+
)}
|
|
103
|
+
{renderedResults}
|
|
104
|
+
</div>
|
|
105
|
+
);
|
|
90
106
|
};
|
|
91
107
|
|
|
92
|
-
const HotelAccommodationResults: React.FC<HotelAccommodationResultsProps> = ({ isLoading }) => {
|
|
108
|
+
const HotelAccommodationResults: React.FC<HotelAccommodationResultsProps> = ({ isLoading, isFlyIn }) => {
|
|
93
109
|
const context = useContext(SearchResultsConfigurationContext);
|
|
94
|
-
|
|
110
|
+
const dispatch = useDispatch();
|
|
95
111
|
if (!context) {
|
|
96
112
|
return null;
|
|
97
113
|
}
|
|
98
114
|
|
|
99
115
|
const translations = getTranslations(context.languageCode ?? 'en-GB');
|
|
100
116
|
|
|
101
|
-
const { filteredResults, filteredPackagingAccoResults, activeTab } = useSelector(
|
|
117
|
+
const { filteredResults, filteredPackagingAccoResults, packagingAccoResults, activeTab, flyInIsOpen, selectedPackagingAccoResultCode } = useSelector(
|
|
118
|
+
(state: SearchResultsRootState) => state.searchResults
|
|
119
|
+
);
|
|
102
120
|
|
|
103
121
|
const cmsMap = React.useMemo(() => {
|
|
104
122
|
const map = new Map();
|
|
@@ -147,26 +165,65 @@ const HotelAccommodationResults: React.FC<HotelAccommodationResultsProps> = ({ i
|
|
|
147
165
|
const firstResultDay = firstResultDate ? format(parseISO(firstResultDate), 'd') : null;
|
|
148
166
|
const firstResultMonth = firstResultDate ? format(parseISO(firstResultDate), 'MMM') : null;
|
|
149
167
|
|
|
168
|
+
const selectedPackagingAccoResult = React.useMemo(() => {
|
|
169
|
+
const selectedResult = packagingAccoResults.find((result) => result.code === selectedPackagingAccoResultCode);
|
|
170
|
+
if (selectedResult) {
|
|
171
|
+
return mapPackagingAccoResult(selectedResult, cmsMap.get(selectedResult.code), context.languageCode, translations);
|
|
172
|
+
}
|
|
173
|
+
}, [packagingAccoResults, selectedPackagingAccoResultCode, cmsMap, context.languageCode, translations]);
|
|
174
|
+
|
|
175
|
+
const visibleResults = React.useMemo(() => {
|
|
176
|
+
const shouldShowAll = context?.searchConfiguration.qsmType !== PortalQsmType.AccommodationAndFlight || isFlyIn;
|
|
177
|
+
|
|
178
|
+
if (shouldShowAll) {
|
|
179
|
+
return mappedResults;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (selectedPackagingAccoResult) {
|
|
183
|
+
return mappedResults.filter((result) => result.code !== selectedPackagingAccoResult.code).slice(0, 2);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return mappedResults.slice(0, 3);
|
|
187
|
+
}, [context?.searchConfiguration.qsmType, mappedResults, isFlyIn, selectedPackagingAccoResult]);
|
|
188
|
+
|
|
189
|
+
const handleShowMoreHotels = (flyInType: FlyInType) => {
|
|
190
|
+
dispatch(setFlyInType(flyInType));
|
|
191
|
+
dispatch(setFlyInIsOpen(true));
|
|
192
|
+
};
|
|
193
|
+
|
|
150
194
|
return (
|
|
151
195
|
<>
|
|
152
|
-
|
|
153
|
-
<div className="
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
196
|
+
{!isFlyIn && (
|
|
197
|
+
<div className="search__results__label search__results__label--secondary">
|
|
198
|
+
<div className="search__results__label__date">
|
|
199
|
+
{firstResultDay && firstResultMonth ? (
|
|
200
|
+
<>
|
|
201
|
+
<p className="search__results__label__date-date">{firstResultDay}</p>
|
|
202
|
+
<p>{firstResultMonth}</p>
|
|
203
|
+
</>
|
|
204
|
+
) : (
|
|
205
|
+
<Icon name="ui-bed" height={16} fill="white" />
|
|
206
|
+
)}
|
|
207
|
+
</div>
|
|
208
|
+
<div className="search__results__label__text">
|
|
209
|
+
<h3>
|
|
210
|
+
{translations.SRP.SELECT} <strong>{translations.SRP.ACCOMMODATION}</strong>
|
|
211
|
+
</h3>
|
|
212
|
+
</div>
|
|
162
213
|
</div>
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
214
|
+
)}
|
|
215
|
+
{isLoading ? (
|
|
216
|
+
<>{context.customSpinner ?? <Spinner />}</>
|
|
217
|
+
) : (
|
|
218
|
+
renderHotelResults(visibleResults, context, activeTab, translations, selectedPackagingAccoResult, isFlyIn)
|
|
219
|
+
)}
|
|
220
|
+
{packagingAccoResults.length > 3 && !isFlyIn && (
|
|
221
|
+
<div className="search__results__cards__actions">
|
|
222
|
+
<button className="cta cta--secondary" onClick={() => handleShowMoreHotels('acco-results')}>
|
|
223
|
+
{translations.SRP.SHOW_MORE}
|
|
224
|
+
</button>
|
|
167
225
|
</div>
|
|
168
|
-
|
|
169
|
-
{isLoading ? <>{context.customSpinner ?? <Spinner />}</> : renderHotelResults(mappedResults, context, activeTab, translations)}
|
|
226
|
+
)}
|
|
170
227
|
{mappedResults.length === 0 && !isLoading && <div className="no-results">{translations.SRP.NO_RESULTS}</div>}
|
|
171
228
|
</>
|
|
172
229
|
);
|
|
@@ -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 {
|
|
@@ -91,6 +100,15 @@ import { getFlightKey } from '../../utils/flight-utils';
|
|
|
91
100
|
import IndependentFlightOption from '../flight/flight-selection/independent-flight-option';
|
|
92
101
|
import { Spinner } from '../../..';
|
|
93
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';
|
|
94
112
|
|
|
95
113
|
type BuildPackagingEntryPartialArgs = {
|
|
96
114
|
sourceEntry: PackagingEntry | null | undefined;
|
|
@@ -115,18 +133,20 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
115
133
|
filteredResults,
|
|
116
134
|
packagingAccoResults,
|
|
117
135
|
filteredPackagingAccoResults,
|
|
118
|
-
bookingPackageDetails,
|
|
119
136
|
isLoading,
|
|
120
137
|
flightsLoading,
|
|
138
|
+
initialFilters,
|
|
121
139
|
filters,
|
|
140
|
+
flightFilters,
|
|
122
141
|
selectedSortType,
|
|
142
|
+
selectedFlightSortType,
|
|
123
143
|
selectedSearchResult,
|
|
124
144
|
selectedPackagingAccoResultCode,
|
|
125
145
|
flyInIsOpen,
|
|
126
146
|
packagingAccoSearchDetails,
|
|
127
147
|
editablePackagingEntry,
|
|
128
148
|
transactionId,
|
|
129
|
-
|
|
149
|
+
flyInType,
|
|
130
150
|
itinerary,
|
|
131
151
|
packagingFlightResults
|
|
132
152
|
} = useSelector((state: SearchResultsRootState) => state.searchResults);
|
|
@@ -134,7 +154,7 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
134
154
|
const isMobile = useMediaQuery('(max-width: 1200px)');
|
|
135
155
|
|
|
136
156
|
const [initialFiltersSet, setInitialFiltersSet] = useState(false);
|
|
137
|
-
const [
|
|
157
|
+
const [initialFlightFiltersSet, setInitialFlightFiltersSet] = useState(false);
|
|
138
158
|
const [filtersOpen, setFiltersOpen] = useState(false);
|
|
139
159
|
|
|
140
160
|
const [detailsIsLoading, setDetailsIsLoading] = useState(false);
|
|
@@ -147,8 +167,6 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
147
167
|
|
|
148
168
|
const skipInitialPackagingAccoDetailsRef = useRef(false);
|
|
149
169
|
|
|
150
|
-
const [showAllOutwardFlights, setShowAllOutwardFlights] = useState(false);
|
|
151
|
-
|
|
152
170
|
const panelRef = useRef<HTMLDivElement | null>(null);
|
|
153
171
|
|
|
154
172
|
const sortByTypes: SortByType[] = [
|
|
@@ -439,7 +457,7 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
439
457
|
setDetailsIsLoading(true);
|
|
440
458
|
|
|
441
459
|
setSelectedAccommodationSeed(seed);
|
|
442
|
-
dispatch(
|
|
460
|
+
dispatch(setFlyInType('acco-results'));
|
|
443
461
|
handleFlyInToggle(true);
|
|
444
462
|
const currentTransactionId = await getOrCreateTransactionId();
|
|
445
463
|
await runAccommodationFlow(seed, currentTransactionId ?? '');
|
|
@@ -578,7 +596,7 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
578
596
|
const enrichedFilters = enrichFiltersWithResults(packageSearchResults, context.filters, context.tags ?? []);
|
|
579
597
|
if (!initialFiltersSet) {
|
|
580
598
|
dispatch(resetFilters(enrichedFilters));
|
|
581
|
-
setInitialFilters(enrichedFilters);
|
|
599
|
+
dispatch(setInitialFilters(enrichedFilters));
|
|
582
600
|
setInitialFiltersSet(true);
|
|
583
601
|
}
|
|
584
602
|
|
|
@@ -637,7 +655,7 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
637
655
|
|
|
638
656
|
const packageAccoSearchResults = await searchPackagingAccommodations(config, searchRequest);
|
|
639
657
|
|
|
640
|
-
const enrichedFilters = enrichFiltersWithPackageAccoResults(packageAccoSearchResults, context.
|
|
658
|
+
const enrichedFilters = enrichFiltersWithPackageAccoResults(packageAccoSearchResults, context.tags ?? []);
|
|
641
659
|
if (!initialFiltersSet) {
|
|
642
660
|
dispatch(resetFilters(enrichedFilters));
|
|
643
661
|
setInitialFilters(enrichedFilters);
|
|
@@ -680,11 +698,24 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
680
698
|
|
|
681
699
|
const packageFlightSearchResults = await searchPackagingFlights(config, searchRequest);
|
|
682
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
|
+
|
|
683
708
|
dispatch(setPackagingFlightResults(packageFlightSearchResults));
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
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
|
+
}
|
|
688
719
|
}
|
|
689
720
|
|
|
690
721
|
dispatch(setFlightsLoading(false));
|
|
@@ -839,6 +870,7 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
839
870
|
context?.searchConfiguration.qsmType === PortalQsmType.GroupTour
|
|
840
871
|
) {
|
|
841
872
|
handleFlyInToggle(true);
|
|
873
|
+
dispatch(setFlyInType('acco-details'));
|
|
842
874
|
}
|
|
843
875
|
|
|
844
876
|
try {
|
|
@@ -923,11 +955,10 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
923
955
|
if (selectedPackagingAccoResultCode) {
|
|
924
956
|
fetchPackagingAccoSearchDetails();
|
|
925
957
|
}
|
|
926
|
-
dispatch(setAccommodationFlyInStep('details'));
|
|
927
958
|
}, [selectedSearchResult, selectedPackagingAccoResultCode]);
|
|
928
959
|
|
|
929
960
|
useEffect(() => {
|
|
930
|
-
if (context?.searchConfiguration.qsmType === PortalQsmType.Accommodation) {
|
|
961
|
+
if (context?.searchConfiguration.qsmType === PortalQsmType.Accommodation || context?.searchConfiguration.qsmType === PortalQsmType.AccommodationAndFlight) {
|
|
931
962
|
const filteredPackageAccoResults = applyFiltersToPackageAccoResults(packagingAccoResults, filters, selectedSortType);
|
|
932
963
|
dispatch(setFilteredPackagingAccoResults(filteredPackageAccoResults));
|
|
933
964
|
} else {
|
|
@@ -936,6 +967,13 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
936
967
|
}
|
|
937
968
|
}, [filters, results, packagingAccoResults, selectedSortType]);
|
|
938
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
|
+
|
|
939
977
|
useEffect(() => {
|
|
940
978
|
setInitialFiltersSet(false);
|
|
941
979
|
}, [activeSearchSeed]);
|
|
@@ -985,7 +1023,6 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
985
1023
|
} as PackagingRequestBase<PackagingEntry>;
|
|
986
1024
|
|
|
987
1025
|
const itinerary = await getItinerary(config, request);
|
|
988
|
-
console.log('Fetched itinerary', itinerary);
|
|
989
1026
|
dispatch(setItinerary(itinerary));
|
|
990
1027
|
setItineraryIsLoading(false);
|
|
991
1028
|
} catch (err) {
|
|
@@ -999,35 +1036,25 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
999
1036
|
}, [editablePackagingEntry]);
|
|
1000
1037
|
|
|
1001
1038
|
// Flight selection
|
|
1002
|
-
const [selectedOutwardKey, setSelectedOutwardKey] = useState<string | null>(null);
|
|
1003
|
-
const [selectedReturnKey, setSelectedReturnKey] = useState<string | null>(null);
|
|
1004
|
-
|
|
1005
|
-
const
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
if (!map.has(key)) {
|
|
1012
|
-
map.set(key, flight);
|
|
1013
|
-
}
|
|
1014
|
-
});
|
|
1015
|
-
|
|
1016
|
-
return Array.from(map.values());
|
|
1017
|
-
}, [packagingFlightResults]);
|
|
1018
|
-
|
|
1019
|
-
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);
|
|
1020
1048
|
|
|
1021
1049
|
useEffect(() => {
|
|
1022
1050
|
if (!selectedOutwardKey) {
|
|
1023
|
-
|
|
1024
|
-
setSelectedReturnKey(null);
|
|
1051
|
+
dispatch(setSelectedReturnKey(null));
|
|
1025
1052
|
return;
|
|
1026
1053
|
}
|
|
1027
1054
|
|
|
1028
|
-
// Filter combinations that match selected outward fare
|
|
1029
1055
|
const matchingCombinations = packagingFlightResults.filter((flight) => getFlightKey(flight.outward.segments) === selectedOutwardKey);
|
|
1030
|
-
|
|
1056
|
+
|
|
1057
|
+
const returnMap = new Map<string, PackagingFlightResponse>();
|
|
1031
1058
|
|
|
1032
1059
|
matchingCombinations.forEach((flight) => {
|
|
1033
1060
|
const key = getFlightKey(flight.return.segments);
|
|
@@ -1038,34 +1065,18 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
1038
1065
|
});
|
|
1039
1066
|
|
|
1040
1067
|
const returns = Array.from(returnMap.values());
|
|
1068
|
+
const segments = first(returns)?.return.segments;
|
|
1069
|
+
const firstReturnKey = returns.length > 0 && segments ? getFlightKey(segments) : null;
|
|
1041
1070
|
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
if (firstReturnKey) {
|
|
1046
|
-
setSelectedReturnKey(firstReturnKey);
|
|
1071
|
+
if (!returns.some((x) => getFlightKey(x.return.segments) === selectedReturnKey)) {
|
|
1072
|
+
dispatch(setSelectedReturnKey(firstReturnKey));
|
|
1047
1073
|
}
|
|
1048
|
-
}, [selectedOutwardKey, packagingFlightResults]);
|
|
1049
|
-
|
|
1050
|
-
const selectedOutward = React.useMemo(() => {
|
|
1051
|
-
if (!selectedOutwardKey) return null;
|
|
1052
|
-
|
|
1053
|
-
return packagingFlightResults.find((flight) => getFlightKey(flight.outward.segments) === selectedOutwardKey) || null;
|
|
1054
|
-
}, [packagingFlightResults, selectedOutwardKey]);
|
|
1055
|
-
|
|
1056
|
-
const selectedReturn = React.useMemo(() => {
|
|
1057
|
-
if (!selectedReturnKey) return null;
|
|
1074
|
+
}, [selectedOutwardKey, packagingFlightResults, selectedReturnKey, dispatch]);
|
|
1058
1075
|
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
if (!selectedOutwardKey || !selectedReturnKey) return undefined;
|
|
1064
|
-
|
|
1065
|
-
return packagingFlightResults.find(
|
|
1066
|
-
(flight) => getFlightKey(flight.outward.segments) === selectedOutwardKey && getFlightKey(flight.return.segments) === selectedReturnKey
|
|
1067
|
-
);
|
|
1068
|
-
}, [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]);
|
|
1069
1080
|
|
|
1070
1081
|
// TODO: get details for selected combination flight and show in fly-in
|
|
1071
1082
|
// useEffect(() => {
|
|
@@ -1076,6 +1087,7 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
1076
1087
|
// dispatch(setFlyInIsOpen(true));
|
|
1077
1088
|
// }, [selectedCombinationFlight, dispatch]);
|
|
1078
1089
|
|
|
1090
|
+
// Build packagingEntry
|
|
1079
1091
|
useEffect(() => {
|
|
1080
1092
|
if (!context) return;
|
|
1081
1093
|
|
|
@@ -1367,9 +1379,10 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
1367
1379
|
} as PackagingEntry;
|
|
1368
1380
|
};
|
|
1369
1381
|
|
|
1370
|
-
const
|
|
1371
|
-
|
|
1372
|
-
|
|
1382
|
+
const handleShowMoreFlights = (flyInType: FlyInType) => {
|
|
1383
|
+
dispatch(setFlyInType(flyInType));
|
|
1384
|
+
dispatch(setFlyInIsOpen(true));
|
|
1385
|
+
};
|
|
1373
1386
|
|
|
1374
1387
|
return (
|
|
1375
1388
|
<div id="tide-booking" className="search__bg">
|
|
@@ -1380,7 +1393,6 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
1380
1393
|
<FlightSearchProvider tideConnection={context.tideConnection}>
|
|
1381
1394
|
<FlightResultsContainer isMobile={isMobile} />
|
|
1382
1395
|
<FlyIn
|
|
1383
|
-
title="Select your fare"
|
|
1384
1396
|
srpType={context.searchConfiguration.qsmType}
|
|
1385
1397
|
isOpen={flyInIsOpen}
|
|
1386
1398
|
setIsOpen={handleFlyInToggle}
|
|
@@ -1402,6 +1414,8 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
1402
1414
|
handleSetIsOpen={() => setFiltersOpen(!filtersOpen)}
|
|
1403
1415
|
// handleApplyFilters={() => setSearchTrigger((prev) => prev + 1)}
|
|
1404
1416
|
isLoading={isLoading}
|
|
1417
|
+
setFilters={(filters) => dispatch(setFilters(filters))}
|
|
1418
|
+
resetFilters={(filters) => dispatch(resetFilters(filters))}
|
|
1405
1419
|
/>
|
|
1406
1420
|
)}
|
|
1407
1421
|
{context.searchConfiguration.qsmType === PortalQsmType.AccommodationAndFlight && (
|
|
@@ -1444,33 +1458,35 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
1444
1458
|
)}
|
|
1445
1459
|
</div>
|
|
1446
1460
|
)}
|
|
1447
|
-
|
|
1448
|
-
<
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
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>
|
|
1457
1487
|
)}
|
|
1458
|
-
</
|
|
1459
|
-
|
|
1460
|
-
<div className="search__result-row-filter">
|
|
1461
|
-
<ItemPicker
|
|
1462
|
-
items={sortByTypes}
|
|
1463
|
-
selection={selectedSortType?.label || undefined}
|
|
1464
|
-
selectedSortByType={selectedSortType}
|
|
1465
|
-
label={translations.SRP.SORTBY}
|
|
1466
|
-
placeholder={translations.SRP.SORTBY}
|
|
1467
|
-
classModifier="travel-class-picker__items"
|
|
1468
|
-
valueFormatter={(value, direction) => getSortingName(translations, findSortByType(sortByTypes, value, direction ?? 'asc'))}
|
|
1469
|
-
onPick={(newSortKey, direction) => handleSortChange(newSortKey, direction)}
|
|
1470
|
-
/>
|
|
1471
|
-
</div>
|
|
1472
|
-
)}
|
|
1473
|
-
</div>
|
|
1488
|
+
</div>
|
|
1489
|
+
)}
|
|
1474
1490
|
|
|
1475
1491
|
<div className="search__results__wrapper">
|
|
1476
1492
|
{context.showTabViews &&
|
|
@@ -1506,7 +1522,7 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
1506
1522
|
key={`flight-${selectedOutwardKey}`}
|
|
1507
1523
|
item={selectedOutward.outward}
|
|
1508
1524
|
guid={selectedOutward.outwardGuid}
|
|
1509
|
-
onSelect={() => setSelectedOutwardKey(null)}
|
|
1525
|
+
onSelect={() => dispatch(setSelectedOutwardKey(null))}
|
|
1510
1526
|
selectedGuid={selectedOutward.outwardGuid}
|
|
1511
1527
|
isOutward={true}
|
|
1512
1528
|
showSelectedState={true}
|
|
@@ -1517,7 +1533,7 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
1517
1533
|
<IndependentFlightOption
|
|
1518
1534
|
key={`flight-${result.outwardGuid}`}
|
|
1519
1535
|
item={result.outward}
|
|
1520
|
-
onSelect={() => setSelectedOutwardKey(getFlightKey(result.outward.segments))}
|
|
1536
|
+
onSelect={() => dispatch(setSelectedOutwardKey(getFlightKey(result.outward.segments)))}
|
|
1521
1537
|
guid={result.outwardGuid}
|
|
1522
1538
|
isOutward={true}
|
|
1523
1539
|
price={result.price}
|
|
@@ -1527,8 +1543,8 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
1527
1543
|
</div>
|
|
1528
1544
|
{uniqueOutwardFlights && uniqueOutwardFlights.length > 3 && (
|
|
1529
1545
|
<div className="search__results__cards__actions">
|
|
1530
|
-
<button className="cta cta--secondary" onClick={() =>
|
|
1531
|
-
{
|
|
1546
|
+
<button className="cta cta--secondary" onClick={() => handleShowMoreFlights('flight-outward-results')}>
|
|
1547
|
+
{translations.SRP.SHOW_MORE}
|
|
1532
1548
|
</button>
|
|
1533
1549
|
</div>
|
|
1534
1550
|
)}
|
|
@@ -1570,7 +1586,7 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
1570
1586
|
<IndependentFlightOption
|
|
1571
1587
|
key={`flight-${result.outwardGuid}`}
|
|
1572
1588
|
item={result.return}
|
|
1573
|
-
onSelect={() => setSelectedReturnKey(getFlightKey(result.return.segments))}
|
|
1589
|
+
onSelect={() => dispatch(setSelectedReturnKey(getFlightKey(result.return.segments)))}
|
|
1574
1590
|
guid={result.outwardGuid}
|
|
1575
1591
|
isOutward={false}
|
|
1576
1592
|
currentSelectedPrice={selectedReturn?.price}
|
|
@@ -1588,15 +1604,15 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
1588
1604
|
</div>
|
|
1589
1605
|
{/* <button onClick={() => handleFlyInToggle(!flyInIsOpen)}>Toggle FlyIn</button> */}
|
|
1590
1606
|
<FlyIn
|
|
1591
|
-
title={`${translations.SRP.SELECT} ${translations.SRP.ACCOMMODATION}`}
|
|
1592
1607
|
srpType={context.searchConfiguration.qsmType}
|
|
1593
1608
|
isOpen={flyInIsOpen}
|
|
1594
1609
|
setIsOpen={handleFlyInToggle}
|
|
1595
1610
|
handleConfirm={() => handleConfirmHotelSwap()}
|
|
1596
1611
|
onPanelRef={(el) => (panelRef.current = el)}
|
|
1597
1612
|
detailsLoading={detailsIsLoading}
|
|
1598
|
-
|
|
1613
|
+
flyInType={flyInType}
|
|
1599
1614
|
isPackageEditFlow={!!context.packagingEntry}
|
|
1615
|
+
sortByTypes={sortByTypes}
|
|
1600
1616
|
/>
|
|
1601
1617
|
</>
|
|
1602
1618
|
)}
|