@qite/tide-booking-component 1.4.68 → 1.4.70
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 +658 -463
- package/build/build-cjs/src/qsm/store/qsm-slice.d.ts +4 -4
- package/build/build-cjs/src/qsm/types.d.ts +2 -3
- package/build/build-cjs/src/search-results/components/filters/filters.d.ts +1 -1
- package/build/build-cjs/src/search-results/components/filters/utility.d.ts +2 -2
- package/build/build-cjs/src/search-results/components/group-tour/group-tour-card.d.ts +8 -0
- package/build/build-cjs/src/search-results/components/group-tour/group-tour-results.d.ts +6 -0
- package/build/build-cjs/src/search-results/components/hotel/hotel-accommodation-results.d.ts +0 -2
- package/build/build-cjs/src/search-results/store/search-results-slice.d.ts +3 -6
- package/build/build-cjs/src/search-results/types.d.ts +7 -2
- package/build/build-cjs/src/search-results/utils/search-results-utils.d.ts +3 -0
- package/build/build-cjs/src/shared/components/flyin.d.ts +1 -1
- package/build/build-cjs/src/shared/types.d.ts +12 -0
- package/build/build-cjs/src/shared/utils/localization-util.d.ts +4 -0
- package/build/build-esm/index.js +656 -462
- package/build/build-esm/src/qsm/store/qsm-slice.d.ts +4 -4
- package/build/build-esm/src/qsm/types.d.ts +2 -3
- package/build/build-esm/src/search-results/components/filters/filters.d.ts +1 -1
- package/build/build-esm/src/search-results/components/filters/utility.d.ts +2 -2
- package/build/build-esm/src/search-results/components/group-tour/group-tour-card.d.ts +8 -0
- package/build/build-esm/src/search-results/components/group-tour/group-tour-results.d.ts +6 -0
- package/build/build-esm/src/search-results/components/hotel/hotel-accommodation-results.d.ts +0 -2
- package/build/build-esm/src/search-results/store/search-results-slice.d.ts +3 -6
- package/build/build-esm/src/search-results/types.d.ts +7 -2
- package/build/build-esm/src/search-results/utils/search-results-utils.d.ts +3 -0
- package/build/build-esm/src/shared/components/flyin.d.ts +1 -1
- package/build/build-esm/src/shared/types.d.ts +12 -0
- package/build/build-esm/src/shared/utils/localization-util.d.ts +4 -0
- package/package.json +1 -1
- package/src/qsm/components/QSMContainer/qsm-container.tsx +18 -7
- package/src/qsm/components/icon.tsx +16 -0
- package/src/qsm/store/qsm-slice.ts +4 -4
- package/src/qsm/types.ts +2 -4
- package/src/search-results/components/filters/filters.tsx +136 -293
- package/src/search-results/components/filters/utility.tsx +61 -2
- package/src/search-results/components/group-tour/group-tour-card.tsx +86 -0
- package/src/search-results/components/group-tour/group-tour-results.tsx +40 -0
- package/src/search-results/components/hotel/hotel-accommodation-results.tsx +13 -16
- package/src/search-results/components/icon.tsx +18 -0
- package/src/search-results/components/search-results-container/search-results-container.tsx +31 -19
- package/src/search-results/store/search-results-slice.ts +8 -2
- package/src/search-results/types.ts +9 -2
- package/src/search-results/utils/search-results-utils.ts +42 -0
- package/src/shared/components/flyin.tsx +2 -1
- package/src/shared/translations/ar-SA.json +4 -2
- package/src/shared/translations/da-DK.json +4 -2
- package/src/shared/translations/de-DE.json +4 -2
- package/src/shared/translations/en-GB.json +4 -2
- package/src/shared/translations/es-ES.json +4 -2
- package/src/shared/translations/fr-BE.json +4 -2
- package/src/shared/translations/fr-FR.json +4 -2
- package/src/shared/translations/is-IS.json +4 -2
- package/src/shared/translations/it-IT.json +4 -2
- package/src/shared/translations/ja-JP.json +4 -2
- package/src/shared/translations/nl-BE.json +4 -2
- package/src/shared/translations/nl-NL.json +4 -2
- package/src/shared/translations/no-NO.json +4 -2
- package/src/shared/translations/pl-PL.json +4 -2
- package/src/shared/translations/pt-PT.json +4 -2
- package/src/shared/translations/sv-SE.json +4 -2
- package/src/shared/types.ts +13 -0
- package/src/shared/utils/localization-util.ts +16 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import React, { useContext } from 'react';
|
|
2
|
+
import Spinner from '../spinner/spinner';
|
|
3
|
+
import SearchResultsConfigurationContext from '../../search-results-configuration-context';
|
|
4
|
+
import { getTranslations } from '../../../shared/utils/localization-util';
|
|
5
|
+
import { SearchResultsRootState } from '../../store/search-results-store';
|
|
6
|
+
import { useSelector } from 'react-redux';
|
|
7
|
+
import { isEmpty } from 'lodash';
|
|
8
|
+
import GroupTourCard from './group-tour-card';
|
|
9
|
+
|
|
10
|
+
interface GroupTourResultsProps {
|
|
11
|
+
isLoading: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const GroupTourResults: React.FC<GroupTourResultsProps> = ({ isLoading }) => {
|
|
15
|
+
const context = useContext(SearchResultsConfigurationContext);
|
|
16
|
+
if (!context) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (isLoading) {
|
|
21
|
+
return <>{context.customSpinner ?? <Spinner />}</>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const translations = getTranslations(context.languageCode ?? 'en-GB');
|
|
25
|
+
const { filteredResults, activeTab } = useSelector((state: SearchResultsRootState) => state.searchResults);
|
|
26
|
+
|
|
27
|
+
if (isEmpty(filteredResults)) {
|
|
28
|
+
return <div className="no-results">{translations.SRP.NO_RESULTS}</div>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<div className="search__results__cards search__results__cards--list">
|
|
33
|
+
{filteredResults.map((result, index) => (
|
|
34
|
+
<GroupTourCard key={index} result={result} />
|
|
35
|
+
))}
|
|
36
|
+
</div>
|
|
37
|
+
);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export default GroupTourResults;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useContext } from 'react';
|
|
2
2
|
import { HotelResult, SearchResultsConfiguration } from '../../types';
|
|
3
3
|
import Spinner from '../spinner/spinner';
|
|
4
4
|
import HotelCard from './hotel-card';
|
|
@@ -6,11 +6,11 @@ import { useSelector } from 'react-redux';
|
|
|
6
6
|
import { SearchResultsRootState } from '../../store/search-results-store';
|
|
7
7
|
import { BookingPackageItem } from '@qite/tide-client/build/types';
|
|
8
8
|
import { format, parseISO } from 'date-fns';
|
|
9
|
-
import { formatPrice, getTranslations } from '../../../shared/utils/localization-util';
|
|
9
|
+
import { calculateNights, formatPrice, getTranslations } from '../../../shared/utils/localization-util';
|
|
10
|
+
import SearchResultsConfigurationContext from '../../search-results-configuration-context';
|
|
10
11
|
|
|
11
12
|
interface HotelAccommodationResultsProps {
|
|
12
13
|
isLoading: boolean;
|
|
13
|
-
context: SearchResultsConfiguration;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
const renderResults = (
|
|
@@ -49,22 +49,19 @@ const mapSearchResult = (searchResult: BookingPackageItem, cmsItem: any, languag
|
|
|
49
49
|
: cmsItem?.parentItem?.name || '',
|
|
50
50
|
price: formatPrice(searchResult.price, searchResult.currencyCode, languageCode),
|
|
51
51
|
ctaText: translations?.SRP.VIEW_DETAILS,
|
|
52
|
-
days: calculateNights(searchResult.stayFromDate, searchResult.stayToDate
|
|
52
|
+
days: `${calculateNights(searchResult.stayFromDate, searchResult.stayToDate)} ${translations?.SRP.NIGHTS}`,
|
|
53
53
|
accommodation: searchResult.accommodationName,
|
|
54
54
|
regime: searchResult.regimeName,
|
|
55
55
|
stars: cmsItem?.content?.general?.stars || searchResult.hotelStars
|
|
56
56
|
};
|
|
57
57
|
};
|
|
58
58
|
|
|
59
|
-
const
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
return `${diffDays} ${translations?.SRP.NIGHTS}`;
|
|
65
|
-
};
|
|
59
|
+
const HotelAccommodationResults: React.FC<HotelAccommodationResultsProps> = ({ isLoading }) => {
|
|
60
|
+
const context = useContext(SearchResultsConfigurationContext);
|
|
61
|
+
if (!context) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
66
64
|
|
|
67
|
-
const HotelAccommodationResults: React.FC<HotelAccommodationResultsProps> = ({ isLoading, context }) => {
|
|
68
65
|
if (context.showMockup) {
|
|
69
66
|
return showMocukups(context);
|
|
70
67
|
}
|
|
@@ -75,9 +72,9 @@ const HotelAccommodationResults: React.FC<HotelAccommodationResultsProps> = ({ i
|
|
|
75
72
|
return <>{context?.customSpinner ?? <Spinner />}</>;
|
|
76
73
|
}
|
|
77
74
|
|
|
78
|
-
const {
|
|
75
|
+
const { filteredResults, activeTab } = useSelector((state: SearchResultsRootState) => state.searchResults);
|
|
79
76
|
|
|
80
|
-
if (!
|
|
77
|
+
if (!filteredResults.length) {
|
|
81
78
|
return <div className="no-results">{translations.SRP.NO_RESULTS}</div>;
|
|
82
79
|
}
|
|
83
80
|
|
|
@@ -90,7 +87,7 @@ const HotelAccommodationResults: React.FC<HotelAccommodationResultsProps> = ({ i
|
|
|
90
87
|
return map;
|
|
91
88
|
}, [context.cmsHotelData]);
|
|
92
89
|
|
|
93
|
-
const firstResult =
|
|
90
|
+
const firstResult = filteredResults?.[0];
|
|
94
91
|
|
|
95
92
|
const firstResultDay = firstResult?.fromDate ? format(parseISO(firstResult.fromDate), 'd') : null;
|
|
96
93
|
|
|
@@ -109,7 +106,7 @@ const HotelAccommodationResults: React.FC<HotelAccommodationResultsProps> = ({ i
|
|
|
109
106
|
</h3>
|
|
110
107
|
</div>
|
|
111
108
|
</div>
|
|
112
|
-
{renderResults(
|
|
109
|
+
{renderResults(filteredResults, context, cmsMap, activeTab, translations)}
|
|
113
110
|
</>
|
|
114
111
|
);
|
|
115
112
|
};
|
|
@@ -654,6 +654,24 @@ const Icon: React.FC<IconProps> = ({ name, className, title, width, height, fill
|
|
|
654
654
|
</svg>
|
|
655
655
|
);
|
|
656
656
|
|
|
657
|
+
case 'ui-other':
|
|
658
|
+
return (
|
|
659
|
+
<svg
|
|
660
|
+
className={['icon', `icon--${name}`, className].filter((className) => !isEmpty(className)).join(' ')}
|
|
661
|
+
width={width}
|
|
662
|
+
height={height}
|
|
663
|
+
viewBox="0 0 640 512"
|
|
664
|
+
fill={fill ?? 'currentColor'}>
|
|
665
|
+
<HTMLComment text="!Font Awesome Free 6.7.2 - mars-and-venus" />
|
|
666
|
+
{title && <title>{title}</title>}
|
|
667
|
+
|
|
668
|
+
<path
|
|
669
|
+
d="M320 32c0-17.7 14.3-32 32-32l128 0c17.7 0 32 14.3 32 32l0 128c0 17.7-14.3 32-32 32s-32-14.3-32-32l0-50.7-64.2 64.2c19.5 28.4 31 62.7 31 99.8 0 97.2-78.8 176-176 176S32 401.2 32 304s78.8-176 176-176c37.1 0 71.4 11.5 99.8 31l64.2-64.2L320 96c-17.7 0-32-14.3-32-32zM208 416a112 112 0 1 0 0-224 112 112 0 1 0 0 224z"
|
|
670
|
+
fill="currentColor"
|
|
671
|
+
/>
|
|
672
|
+
</svg>
|
|
673
|
+
);
|
|
674
|
+
|
|
657
675
|
default:
|
|
658
676
|
return null;
|
|
659
677
|
}
|
|
@@ -10,7 +10,8 @@ import {
|
|
|
10
10
|
setSelectedHotel,
|
|
11
11
|
setBookingPackageDetails,
|
|
12
12
|
setEntry,
|
|
13
|
-
setFlyInIsOpen
|
|
13
|
+
setFlyInIsOpen,
|
|
14
|
+
setFilteredResults
|
|
14
15
|
} from '../../store/search-results-slice';
|
|
15
16
|
import { Filter, SortByType } from '../../types';
|
|
16
17
|
import useMediaQuery from '../../../shared/utils/use-media-query-util';
|
|
@@ -41,6 +42,8 @@ import { getTranslations } from '../../../shared/utils/localization-util';
|
|
|
41
42
|
import { FlightSearchProvider } from '../flight/flight-search-context';
|
|
42
43
|
import FlightResultsContainer from './flight-search-results';
|
|
43
44
|
import Filters from '../filters/filters';
|
|
45
|
+
import GroupTourResults from '../group-tour/group-tour-results';
|
|
46
|
+
import { applyFilters } from '../../utils/search-results-utils';
|
|
44
47
|
|
|
45
48
|
const SearchResultsContainer: React.FC = () => {
|
|
46
49
|
const dispatch = useDispatch();
|
|
@@ -48,16 +51,16 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
48
51
|
const context = useContext(SearchResultsConfigurationContext);
|
|
49
52
|
const translations = getTranslations(context?.languageCode ?? 'en-GB');
|
|
50
53
|
|
|
51
|
-
const { results, bookingPackageDetails, entry, isLoading, filters, sortKey, selectedHotelId, flyInIsOpen } = useSelector(
|
|
54
|
+
const { results, filteredResults, bookingPackageDetails, entry, isLoading, filters, sortKey, selectedHotelId, flyInIsOpen } = useSelector(
|
|
52
55
|
(state: SearchResultsRootState) => state.searchResults
|
|
53
56
|
);
|
|
54
57
|
|
|
55
58
|
const isMobile = useMediaQuery('(max-width: 1200px)');
|
|
56
59
|
|
|
57
|
-
const [searchTrigger, setSearchTrigger] = useState(0);
|
|
58
60
|
const [initialFiltersSet, setInitialFiltersSet] = useState(false);
|
|
59
61
|
const [initialFilters, setInitialFilters] = useState<Filter[]>([]);
|
|
60
62
|
const [filtersOpen, setFiltersOpen] = useState(false);
|
|
63
|
+
|
|
61
64
|
const [itineraryOpen, setItineraryOpen] = useState(false);
|
|
62
65
|
|
|
63
66
|
const panelRef = useRef<HTMLDivElement | null>(null);
|
|
@@ -155,10 +158,8 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
155
158
|
let hotel = getNumberFromParams(params, 'hotel');
|
|
156
159
|
let tagId = getNumberFromParams(params, 'tagId');
|
|
157
160
|
|
|
158
|
-
// temp hardcoded params
|
|
159
161
|
if (!from || !to) {
|
|
160
162
|
console.error('Missing fromDate or toDate in query params, using default values');
|
|
161
|
-
|
|
162
163
|
return null;
|
|
163
164
|
}
|
|
164
165
|
|
|
@@ -190,8 +191,9 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
190
191
|
officeId: 1,
|
|
191
192
|
payload: {
|
|
192
193
|
catalogueIds: context!.tideConnection.catalogueIds ?? [],
|
|
193
|
-
serviceType:
|
|
194
|
-
|
|
194
|
+
serviceType:
|
|
195
|
+
context!.type === 'hotel' || context!.type === 'hotel-flight' ? 3 : context!.type === 'flight' ? 7 : context!.type === 'roundTrip' ? 1 : undefined,
|
|
196
|
+
searchType: context!.type === 'groupTour' ? 1 : 0,
|
|
195
197
|
destination: {
|
|
196
198
|
id: Number(destinationId),
|
|
197
199
|
isCountry: destinationIsCountry,
|
|
@@ -212,7 +214,7 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
212
214
|
// .flatMap((o) => o.value.toString()) || [],
|
|
213
215
|
// minPrice: filters.find((f) => f.property === 'price')?.selectedMin,
|
|
214
216
|
// maxPrice: filters.find((f) => f.property === 'price')?.selectedMax,
|
|
215
|
-
useExactDates: true,
|
|
217
|
+
useExactDates: context?.type === 'groupTour' ? false : true,
|
|
216
218
|
onlyCachedResults: false,
|
|
217
219
|
includeAllAllotments: true,
|
|
218
220
|
productIds: hotel ? [hotel] : [],
|
|
@@ -288,7 +290,7 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
288
290
|
|
|
289
291
|
// seperate Search
|
|
290
292
|
useEffect(() => {
|
|
291
|
-
const
|
|
293
|
+
const runSearch = async () => {
|
|
292
294
|
dispatch(setIsLoading(true));
|
|
293
295
|
try {
|
|
294
296
|
if (!context) {
|
|
@@ -314,7 +316,6 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
314
316
|
searchRequest = buildSearchFromEntry(entryLight);
|
|
315
317
|
} else {
|
|
316
318
|
const rq = buildSearchFromQueryParams(params);
|
|
317
|
-
|
|
318
319
|
if (!rq) {
|
|
319
320
|
throw new Error('Invalid search parameters');
|
|
320
321
|
}
|
|
@@ -326,14 +327,17 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
326
327
|
|
|
327
328
|
console.log('Search results', packageSearchResults);
|
|
328
329
|
|
|
329
|
-
const enrichedFilters = enrichFiltersWithResults(packageSearchResults, context.filters);
|
|
330
|
+
const enrichedFilters = enrichFiltersWithResults(packageSearchResults, context.filters, context.tags ?? []);
|
|
330
331
|
if (!initialFiltersSet) {
|
|
331
332
|
dispatch(resetFilters(enrichedFilters));
|
|
332
333
|
setInitialFilters(enrichedFilters);
|
|
333
334
|
setInitialFiltersSet(true);
|
|
334
335
|
}
|
|
335
336
|
|
|
336
|
-
dispatch(setResults(
|
|
337
|
+
dispatch(setResults(packageSearchResults));
|
|
338
|
+
const initialFilteredResults = applyFilters(packageSearchResults, filters);
|
|
339
|
+
dispatch(setFilteredResults(initialFilteredResults));
|
|
340
|
+
|
|
337
341
|
if (packageSearchResults?.length > 0) {
|
|
338
342
|
if (entryId) {
|
|
339
343
|
const matching = packageSearchResults.find((r) => r.productId === entry?.id);
|
|
@@ -354,11 +358,11 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
354
358
|
};
|
|
355
359
|
|
|
356
360
|
if (!context?.showMockup) {
|
|
357
|
-
if (context?.type === 'hotel-flight' || context?.type === 'hotel') {
|
|
358
|
-
|
|
361
|
+
if (context?.type === 'hotel-flight' || context?.type === 'hotel' || context?.type === 'groupTour') {
|
|
362
|
+
runSearch();
|
|
359
363
|
}
|
|
360
364
|
}
|
|
361
|
-
}, [location.search
|
|
365
|
+
}, [location.search]);
|
|
362
366
|
|
|
363
367
|
// Seperate detailsCall
|
|
364
368
|
useEffect(() => {
|
|
@@ -427,6 +431,11 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
427
431
|
fetchPackageDetails();
|
|
428
432
|
}, [selectedHotelId]);
|
|
429
433
|
|
|
434
|
+
useEffect(() => {
|
|
435
|
+
const filteredResults = applyFilters(results, filters);
|
|
436
|
+
dispatch(setFilteredResults(filteredResults));
|
|
437
|
+
}, [filters, results]);
|
|
438
|
+
|
|
430
439
|
return (
|
|
431
440
|
<div id="tide-booking" className="search__bg">
|
|
432
441
|
{context && (
|
|
@@ -438,14 +447,15 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
438
447
|
<FlyIn srpType={context.type} isOpen={flyInIsOpen} setIsOpen={handleFlyInToggle} onPanelRef={(el) => (panelRef.current = el)} />
|
|
439
448
|
</FlightSearchProvider>
|
|
440
449
|
)}
|
|
441
|
-
{(context.type === 'hotel-flight' || context.type === 'hotel' || context.type === 'roundTrip') && (
|
|
450
|
+
{(context.type === 'hotel-flight' || context.type === 'hotel' || context.type === 'groupTour' || context.type === 'roundTrip') && (
|
|
442
451
|
<>
|
|
443
452
|
{context.type != 'hotel-flight' && context.showFilters && (
|
|
444
453
|
<Filters
|
|
454
|
+
initialFilters={initialFilters}
|
|
445
455
|
filters={filters}
|
|
446
456
|
isOpen={filtersOpen}
|
|
447
457
|
handleSetIsOpen={() => setFiltersOpen(!filtersOpen)}
|
|
448
|
-
handleApplyFilters={() => setSearchTrigger((prev) => prev + 1)}
|
|
458
|
+
// handleApplyFilters={() => setSearchTrigger((prev) => prev + 1)}
|
|
449
459
|
isLoading={isLoading}
|
|
450
460
|
/>
|
|
451
461
|
)}
|
|
@@ -486,7 +496,7 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
486
496
|
<span className="search__result-row-text">
|
|
487
497
|
{!isLoading && (
|
|
488
498
|
<>
|
|
489
|
-
{
|
|
499
|
+
{filteredResults?.length && filteredResults.length} {translations.SRP.TOTAL_RESULTS_LABEL}
|
|
490
500
|
</>
|
|
491
501
|
)}
|
|
492
502
|
</span>
|
|
@@ -509,11 +519,13 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
509
519
|
|
|
510
520
|
{context.showRoundTripResults && context.showMockup && <RoundTripResults />}
|
|
511
521
|
|
|
522
|
+
{context.type === 'groupTour' && <GroupTourResults isLoading={isLoading} />}
|
|
523
|
+
|
|
512
524
|
{context.type === 'hotel-flight' && context.showFlightResults && bookingPackageDetails?.outwardFlights && (
|
|
513
525
|
<FlightResults flights={bookingPackageDetails?.outwardFlights} isDeparture={true} />
|
|
514
526
|
)}
|
|
515
527
|
|
|
516
|
-
{context.showHotelAccommodationResults && <HotelAccommodationResults isLoading={isLoading}
|
|
528
|
+
{context.showHotelAccommodationResults && <HotelAccommodationResults isLoading={isLoading} />}
|
|
517
529
|
|
|
518
530
|
{context.type === 'hotel-flight' && context.showFlightResults && bookingPackageDetails?.returnFlights && (
|
|
519
531
|
<FlightResults flights={bookingPackageDetails?.returnFlights} isDeparture={false} />
|
|
@@ -4,6 +4,7 @@ import { BookingPackage, BookingPackageItem, EntryLight } from '@qite/tide-clien
|
|
|
4
4
|
|
|
5
5
|
export interface SearchResultsState {
|
|
6
6
|
results: BookingPackageItem[];
|
|
7
|
+
filteredResults: BookingPackageItem[];
|
|
7
8
|
selectedHotelId: number | null;
|
|
8
9
|
selectedFlight: ExtendedFlightSearchResponseItem | null;
|
|
9
10
|
selectedFlightDetails: ExtendedFlightSearchResponseItem | null;
|
|
@@ -19,6 +20,7 @@ export interface SearchResultsState {
|
|
|
19
20
|
|
|
20
21
|
const initialState: SearchResultsState = {
|
|
21
22
|
results: [],
|
|
23
|
+
filteredResults: [],
|
|
22
24
|
selectedHotelId: null,
|
|
23
25
|
selectedFlight: null,
|
|
24
26
|
selectedFlightDetails: null,
|
|
@@ -36,8 +38,11 @@ const searchResultsSlice = createSlice({
|
|
|
36
38
|
name: 'searchResults',
|
|
37
39
|
initialState,
|
|
38
40
|
reducers: {
|
|
39
|
-
setResults(state, action: PayloadAction<
|
|
40
|
-
state.results = action.payload
|
|
41
|
+
setResults(state, action: PayloadAction<BookingPackageItem[]>) {
|
|
42
|
+
state.results = action.payload;
|
|
43
|
+
},
|
|
44
|
+
setFilteredResults(state, action: PayloadAction<BookingPackageItem[]>) {
|
|
45
|
+
state.filteredResults = action.payload;
|
|
41
46
|
},
|
|
42
47
|
setSelectedHotel(state, action: PayloadAction<number | null>) {
|
|
43
48
|
state.selectedHotelId = action.payload;
|
|
@@ -109,6 +114,7 @@ const searchResultsSlice = createSlice({
|
|
|
109
114
|
|
|
110
115
|
export const {
|
|
111
116
|
setResults,
|
|
117
|
+
setFilteredResults,
|
|
112
118
|
setSelectedHotel,
|
|
113
119
|
setSelectedFlight,
|
|
114
120
|
setSelectedFlightDetails,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { FlightSearchResponseItem } from '@qite/tide-client';
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
|
+
import { SRPType } from '../shared/types';
|
|
3
4
|
|
|
4
|
-
export type SRPType = 'hotel' | 'flight' | 'hotel-flight' | 'roundTrip';
|
|
5
5
|
export type FlightSelectionMode = 'paired' | 'independent';
|
|
6
6
|
|
|
7
7
|
export interface SearchResultsConfiguration {
|
|
@@ -46,6 +46,8 @@ export interface SearchResultsConfiguration {
|
|
|
46
46
|
customSpinner?: ReactNode;
|
|
47
47
|
|
|
48
48
|
cmsHotelData?: any[];
|
|
49
|
+
tags?: TideTag[];
|
|
50
|
+
|
|
49
51
|
languageCode?: string;
|
|
50
52
|
|
|
51
53
|
destinationImage?: { url: string; alt: string };
|
|
@@ -53,7 +55,7 @@ export interface SearchResultsConfiguration {
|
|
|
53
55
|
|
|
54
56
|
export type FilterType = 'checkbox' | 'toggle' | 'slider' | 'star-rating';
|
|
55
57
|
|
|
56
|
-
export type FilterProperty = 'regime' | 'max-duration' | 'price' | 'rating' | 'theme';
|
|
58
|
+
export type FilterProperty = 'regime' | 'accommodation' | 'max-duration' | 'price' | 'rating' | 'theme';
|
|
57
59
|
|
|
58
60
|
export interface FilterOption {
|
|
59
61
|
label: string;
|
|
@@ -168,3 +170,8 @@ export interface SortByType {
|
|
|
168
170
|
label: string;
|
|
169
171
|
icon?: ReactNode;
|
|
170
172
|
}
|
|
173
|
+
|
|
174
|
+
export interface TideTag {
|
|
175
|
+
id: number;
|
|
176
|
+
name: string;
|
|
177
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { BookingPackageItem } from '@qite/tide-client';
|
|
2
|
+
import { Filter } from '../types';
|
|
3
|
+
|
|
4
|
+
export const applyFilters = (results: BookingPackageItem[], filters: Filter[]) => {
|
|
5
|
+
return results.filter((r) => {
|
|
6
|
+
return filters.every((filter) => {
|
|
7
|
+
if (!filter.isFrontendFilter) return true;
|
|
8
|
+
|
|
9
|
+
// ACCOMMODATION
|
|
10
|
+
if (filter.property === 'accommodation') {
|
|
11
|
+
const selected = filter.options?.filter((o) => o.isChecked).map((o) => o.value);
|
|
12
|
+
if (!selected || selected.length === 0) return true;
|
|
13
|
+
return selected.includes(r.accommodationCode);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// REGIME
|
|
17
|
+
if (filter.property === 'regime') {
|
|
18
|
+
const selected = filter.options?.filter((o) => o.isChecked).map((o) => o.value);
|
|
19
|
+
if (!selected || selected.length === 0) return true;
|
|
20
|
+
if (!r.regimeCode) return false;
|
|
21
|
+
return selected.includes(r.regimeCode);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// PRICE
|
|
25
|
+
if (filter.property === 'price') {
|
|
26
|
+
if (filter.selectedMin != null && r.price < filter.selectedMin) return false;
|
|
27
|
+
if (filter.selectedMax != null && r.price > filter.selectedMax) return false;
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// THEME
|
|
32
|
+
if (filter.property === 'theme') {
|
|
33
|
+
const selected = filter.options?.filter((o) => o.isChecked).map((o) => o.value);
|
|
34
|
+
if (!selected || selected.length === 0) return true;
|
|
35
|
+
|
|
36
|
+
return r.tagIds?.some((tagId) => selected.includes(tagId));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return true;
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useContext, useEffect, useRef, useState } from 'react';
|
|
2
2
|
import Icon from './icon';
|
|
3
|
-
import { ExtendedFlightSearchResponseItem
|
|
3
|
+
import { ExtendedFlightSearchResponseItem } from '../../search-results/types';
|
|
4
4
|
import { useFlightSearch } from '../../search-results/components/flight/flight-search-context';
|
|
5
5
|
import { useDispatch, useSelector } from 'react-redux';
|
|
6
6
|
import { setSelectedFlight, setSelectedFlightDetails } from '../../search-results/store/search-results-slice';
|
|
@@ -10,6 +10,7 @@ import SearchResultsConfigurationContext from '../../search-results/search-resul
|
|
|
10
10
|
import { durationTicksInHoursString, getTranslations, timeFromDateTime } from '../utils/localization-util';
|
|
11
11
|
import { getArrivalSegment, getDepartureSegment, getNumberOfStopsLabel } from '../../search-results/utils/flight-utils';
|
|
12
12
|
import { first, isEmpty } from 'lodash';
|
|
13
|
+
import { SRPType } from '../types';
|
|
13
14
|
|
|
14
15
|
type FlyInProps = {
|
|
15
16
|
srpType?: SRPType;
|
|
@@ -323,7 +323,8 @@
|
|
|
323
323
|
"DEPARTURE_DATE": "تاريخ المغادرة",
|
|
324
324
|
"RETURN_DATE": "تاريخ العودة",
|
|
325
325
|
"CONFIRM": "تأكيد",
|
|
326
|
-
"TRAVELERS": "المسافرون"
|
|
326
|
+
"TRAVELERS": "المسافرون",
|
|
327
|
+
"GROUP_TOUR": "جولة جماعية"
|
|
327
328
|
},
|
|
328
329
|
"SRP": {
|
|
329
330
|
"NO_RESULTS": "لم يتم العثور على نتائج.",
|
|
@@ -371,6 +372,7 @@
|
|
|
371
372
|
"DEPARTURE_TIME_ASC": "وقت المغادرة تصاعدياً",
|
|
372
373
|
"DEPARTURE_TIME_DESC": "وقت المغادرة تنازلياً",
|
|
373
374
|
"DURATION_ASC": "المدة تصاعدياً",
|
|
374
|
-
"DURATION_DESC": "المدة تنازلياً"
|
|
375
|
+
"DURATION_DESC": "المدة تنازلياً",
|
|
376
|
+
"TRAVEL_GROUP": "مجموعة المسافرين"
|
|
375
377
|
}
|
|
376
378
|
}
|
|
@@ -323,7 +323,8 @@
|
|
|
323
323
|
"DEPARTURE_DATE": "Afrejsedato",
|
|
324
324
|
"RETURN_DATE": "Hjemrejsedato",
|
|
325
325
|
"CONFIRM": "Bekræft",
|
|
326
|
-
"TRAVELERS": "Rejsende"
|
|
326
|
+
"TRAVELERS": "Rejsende",
|
|
327
|
+
"GROUP_TOUR": "Grupperejse"
|
|
327
328
|
},
|
|
328
329
|
"SRP": {
|
|
329
330
|
"NO_RESULTS": "Ingen resultater fundet.",
|
|
@@ -371,6 +372,7 @@
|
|
|
371
372
|
"DEPARTURE_TIME_ASC": "Afgangstid stigende",
|
|
372
373
|
"DEPARTURE_TIME_DESC": "Afgangstid faldende",
|
|
373
374
|
"DURATION_ASC": "Varighed stigende",
|
|
374
|
-
"DURATION_DESC": "Varighed faldende"
|
|
375
|
+
"DURATION_DESC": "Varighed faldende",
|
|
376
|
+
"TRAVEL_GROUP": "Rejseselskab"
|
|
375
377
|
}
|
|
376
378
|
}
|
|
@@ -323,7 +323,8 @@
|
|
|
323
323
|
"DEPARTURE_DATE": "Abreisedatum",
|
|
324
324
|
"RETURN_DATE": "Rückreisedatum",
|
|
325
325
|
"CONFIRM": "Bestätigen",
|
|
326
|
-
"TRAVELERS": "Reisende"
|
|
326
|
+
"TRAVELERS": "Reisende",
|
|
327
|
+
"GROUP_TOUR": "Gruppentour"
|
|
327
328
|
},
|
|
328
329
|
"SRP": {
|
|
329
330
|
"NO_RESULTS": "Keine Ergebnisse gefunden.",
|
|
@@ -371,6 +372,7 @@
|
|
|
371
372
|
"DEPARTURE_RANGE": "Abflugzeitraum",
|
|
372
373
|
"DEPARTURE_AIRPORTS": "Abflughäfen",
|
|
373
374
|
"ARRIVAL_AIRPORTS": "Ankunftsflughäfen",
|
|
374
|
-
"PRICE": "Preis"
|
|
375
|
+
"PRICE": "Preis",
|
|
376
|
+
"TRAVEL_GROUP": "Reisegruppe"
|
|
375
377
|
}
|
|
376
378
|
}
|
|
@@ -327,7 +327,8 @@
|
|
|
327
327
|
"DEPARTURE_DATE": "Departure date",
|
|
328
328
|
"RETURN_DATE": "Return date",
|
|
329
329
|
"CONFIRM": "Confirm",
|
|
330
|
-
"TRAVELERS": "Travelers"
|
|
330
|
+
"TRAVELERS": "Travelers",
|
|
331
|
+
"GROUP_TOUR": "Group tour"
|
|
331
332
|
},
|
|
332
333
|
"SRP": {
|
|
333
334
|
"NO_RESULTS": "No results found.",
|
|
@@ -375,6 +376,7 @@
|
|
|
375
376
|
"NIGHT_RANGE": "Night",
|
|
376
377
|
"DEPARTURE_RANGE": "Departure range",
|
|
377
378
|
"DEPARTURE_AIRPORTS": "Departure airports",
|
|
378
|
-
"ARRIVAL_AIRPORTS": "Arrival airports"
|
|
379
|
+
"ARRIVAL_AIRPORTS": "Arrival airports",
|
|
380
|
+
"TRAVEL_GROUP": "Travel group"
|
|
379
381
|
}
|
|
380
382
|
}
|
|
@@ -323,7 +323,8 @@
|
|
|
323
323
|
"DEPARTURE_DATE": "Fecha de salida",
|
|
324
324
|
"RETURN_DATE": "Fecha de regreso",
|
|
325
325
|
"CONFIRM": "Confirmar",
|
|
326
|
-
"TRAVELERS": "Viajeros"
|
|
326
|
+
"TRAVELERS": "Viajeros",
|
|
327
|
+
"GROUP_TOUR": "Tour grupal"
|
|
327
328
|
},
|
|
328
329
|
"SRP": {
|
|
329
330
|
"NO_RESULTS": "No se han encontrado resultados.",
|
|
@@ -371,6 +372,7 @@
|
|
|
371
372
|
"DEPARTURE_RANGE": "Franja de salida",
|
|
372
373
|
"DEPARTURE_AIRPORTS": "Aeropuertos de salida",
|
|
373
374
|
"ARRIVAL_AIRPORTS": "Aeropuertos de llegada",
|
|
374
|
-
"PRICE": "Precio"
|
|
375
|
+
"PRICE": "Precio",
|
|
376
|
+
"TRAVEL_GROUP": "Grupo de viaje"
|
|
375
377
|
}
|
|
376
378
|
}
|
|
@@ -327,7 +327,8 @@
|
|
|
327
327
|
"DEPARTURE_DATE": "Date de départ",
|
|
328
328
|
"RETURN_DATE": "Date de retour",
|
|
329
329
|
"CONFIRM": "Confirmer",
|
|
330
|
-
"TRAVELERS": "Voyageurs"
|
|
330
|
+
"TRAVELERS": "Voyageurs",
|
|
331
|
+
"GROUP_TOUR": "Tour en groupe"
|
|
331
332
|
},
|
|
332
333
|
"SRP": {
|
|
333
334
|
"NO_RESULTS": "Aucun résultat trouvé.",
|
|
@@ -375,6 +376,7 @@
|
|
|
375
376
|
"DEPARTURE_RANGE": "Plage de départ",
|
|
376
377
|
"DEPARTURE_AIRPORTS": "Aéroports de départ",
|
|
377
378
|
"ARRIVAL_AIRPORTS": "Aéroports d’arrivée",
|
|
378
|
-
"PRICE": "Prix"
|
|
379
|
+
"PRICE": "Prix",
|
|
380
|
+
"TRAVEL_GROUP": "Groupe de voyageurs"
|
|
379
381
|
}
|
|
380
382
|
}
|
|
@@ -323,7 +323,8 @@
|
|
|
323
323
|
"DEPARTURE_DATE": "Date de départ",
|
|
324
324
|
"RETURN_DATE": "Date de retour",
|
|
325
325
|
"CONFIRM": "Confirmer",
|
|
326
|
-
"TRAVELERS": "Voyageurs"
|
|
326
|
+
"TRAVELERS": "Voyageurs",
|
|
327
|
+
"GROUP_TOUR": "Tour en groupe"
|
|
327
328
|
},
|
|
328
329
|
"SRP": {
|
|
329
330
|
"NO_RESULTS": "Aucun résultat trouvé.",
|
|
@@ -371,6 +372,7 @@
|
|
|
371
372
|
"DEPARTURE_RANGE": "Plage de départ",
|
|
372
373
|
"DEPARTURE_AIRPORTS": "Aéroports de départ",
|
|
373
374
|
"ARRIVAL_AIRPORTS": "Aéroports d’arrivée",
|
|
374
|
-
"PRICE": "Prix"
|
|
375
|
+
"PRICE": "Prix",
|
|
376
|
+
"TRAVEL_GROUP": "Groupe de voyageurs"
|
|
375
377
|
}
|
|
376
378
|
}
|
|
@@ -323,7 +323,8 @@
|
|
|
323
323
|
"DEPARTURE_DATE": "Brottfarardagur",
|
|
324
324
|
"RETURN_DATE": "Heimkomudagur",
|
|
325
325
|
"CONFIRM": "Staðfesta",
|
|
326
|
-
"TRAVELERS": "Ferðalangar"
|
|
326
|
+
"TRAVELERS": "Ferðalangar",
|
|
327
|
+
"GROUP_TOUR": "Ferð í hóp"
|
|
327
328
|
},
|
|
328
329
|
"SRP": {
|
|
329
330
|
"NO_RESULTS": "Engar niðurstöður fundust.",
|
|
@@ -371,6 +372,7 @@
|
|
|
371
372
|
"DEPARTURE_RANGE": "Brottfarartímabil",
|
|
372
373
|
"DEPARTURE_AIRPORTS": "Brottfararflugvellir",
|
|
373
374
|
"ARRIVAL_AIRPORTS": "Komuflugvellir",
|
|
374
|
-
"PRICE": "Verð"
|
|
375
|
+
"PRICE": "Verð",
|
|
376
|
+
"TRAVEL_GROUP": "Ferðahópur"
|
|
375
377
|
}
|
|
376
378
|
}
|
|
@@ -323,7 +323,8 @@
|
|
|
323
323
|
"DEPARTURE_DATE": "Data di partenza",
|
|
324
324
|
"RETURN_DATE": "Data di ritorno",
|
|
325
325
|
"CONFIRM": "Conferma",
|
|
326
|
-
"TRAVELERS": "Viaggiatori"
|
|
326
|
+
"TRAVELERS": "Viaggiatori",
|
|
327
|
+
"GROUP_TOUR": "Tour di gruppo"
|
|
327
328
|
},
|
|
328
329
|
"SRP": {
|
|
329
330
|
"NO_RESULTS": "Nessun risultato trovato.",
|
|
@@ -371,6 +372,7 @@
|
|
|
371
372
|
"DEPARTURE_RANGE": "Fascia di partenza",
|
|
372
373
|
"DEPARTURE_AIRPORTS": "Aeroporti di partenza",
|
|
373
374
|
"ARRIVAL_AIRPORTS": "Aeroporti di arrivo",
|
|
374
|
-
"PRICE": "Prezzo"
|
|
375
|
+
"PRICE": "Prezzo",
|
|
376
|
+
"TRAVEL_GROUP": "Gruppo di viaggio"
|
|
375
377
|
}
|
|
376
378
|
}
|
|
@@ -323,7 +323,8 @@
|
|
|
323
323
|
"DEPARTURE_DATE": "出発日",
|
|
324
324
|
"RETURN_DATE": "帰着日",
|
|
325
325
|
"CONFIRM": "確認",
|
|
326
|
-
"TRAVELERS": "旅行者"
|
|
326
|
+
"TRAVELERS": "旅行者",
|
|
327
|
+
"GROUP_TOUR": "グループツアー"
|
|
327
328
|
},
|
|
328
329
|
"SRP": {
|
|
329
330
|
"NO_RESULTS": "結果が見つかりませんでした。",
|
|
@@ -371,6 +372,7 @@
|
|
|
371
372
|
"DEPARTURE_RANGE": "出発時間帯",
|
|
372
373
|
"DEPARTURE_AIRPORTS": "出発空港",
|
|
373
374
|
"ARRIVAL_AIRPORTS": "到着空港",
|
|
374
|
-
"PRICE": "価格"
|
|
375
|
+
"PRICE": "価格",
|
|
376
|
+
"TRAVEL_GROUP": "旅行グループ"
|
|
375
377
|
}
|
|
376
378
|
}
|