@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.
Files changed (62) hide show
  1. package/build/build-cjs/index.js +658 -463
  2. package/build/build-cjs/src/qsm/store/qsm-slice.d.ts +4 -4
  3. package/build/build-cjs/src/qsm/types.d.ts +2 -3
  4. package/build/build-cjs/src/search-results/components/filters/filters.d.ts +1 -1
  5. package/build/build-cjs/src/search-results/components/filters/utility.d.ts +2 -2
  6. package/build/build-cjs/src/search-results/components/group-tour/group-tour-card.d.ts +8 -0
  7. package/build/build-cjs/src/search-results/components/group-tour/group-tour-results.d.ts +6 -0
  8. package/build/build-cjs/src/search-results/components/hotel/hotel-accommodation-results.d.ts +0 -2
  9. package/build/build-cjs/src/search-results/store/search-results-slice.d.ts +3 -6
  10. package/build/build-cjs/src/search-results/types.d.ts +7 -2
  11. package/build/build-cjs/src/search-results/utils/search-results-utils.d.ts +3 -0
  12. package/build/build-cjs/src/shared/components/flyin.d.ts +1 -1
  13. package/build/build-cjs/src/shared/types.d.ts +12 -0
  14. package/build/build-cjs/src/shared/utils/localization-util.d.ts +4 -0
  15. package/build/build-esm/index.js +656 -462
  16. package/build/build-esm/src/qsm/store/qsm-slice.d.ts +4 -4
  17. package/build/build-esm/src/qsm/types.d.ts +2 -3
  18. package/build/build-esm/src/search-results/components/filters/filters.d.ts +1 -1
  19. package/build/build-esm/src/search-results/components/filters/utility.d.ts +2 -2
  20. package/build/build-esm/src/search-results/components/group-tour/group-tour-card.d.ts +8 -0
  21. package/build/build-esm/src/search-results/components/group-tour/group-tour-results.d.ts +6 -0
  22. package/build/build-esm/src/search-results/components/hotel/hotel-accommodation-results.d.ts +0 -2
  23. package/build/build-esm/src/search-results/store/search-results-slice.d.ts +3 -6
  24. package/build/build-esm/src/search-results/types.d.ts +7 -2
  25. package/build/build-esm/src/search-results/utils/search-results-utils.d.ts +3 -0
  26. package/build/build-esm/src/shared/components/flyin.d.ts +1 -1
  27. package/build/build-esm/src/shared/types.d.ts +12 -0
  28. package/build/build-esm/src/shared/utils/localization-util.d.ts +4 -0
  29. package/package.json +1 -1
  30. package/src/qsm/components/QSMContainer/qsm-container.tsx +18 -7
  31. package/src/qsm/components/icon.tsx +16 -0
  32. package/src/qsm/store/qsm-slice.ts +4 -4
  33. package/src/qsm/types.ts +2 -4
  34. package/src/search-results/components/filters/filters.tsx +136 -293
  35. package/src/search-results/components/filters/utility.tsx +61 -2
  36. package/src/search-results/components/group-tour/group-tour-card.tsx +86 -0
  37. package/src/search-results/components/group-tour/group-tour-results.tsx +40 -0
  38. package/src/search-results/components/hotel/hotel-accommodation-results.tsx +13 -16
  39. package/src/search-results/components/icon.tsx +18 -0
  40. package/src/search-results/components/search-results-container/search-results-container.tsx +31 -19
  41. package/src/search-results/store/search-results-slice.ts +8 -2
  42. package/src/search-results/types.ts +9 -2
  43. package/src/search-results/utils/search-results-utils.ts +42 -0
  44. package/src/shared/components/flyin.tsx +2 -1
  45. package/src/shared/translations/ar-SA.json +4 -2
  46. package/src/shared/translations/da-DK.json +4 -2
  47. package/src/shared/translations/de-DE.json +4 -2
  48. package/src/shared/translations/en-GB.json +4 -2
  49. package/src/shared/translations/es-ES.json +4 -2
  50. package/src/shared/translations/fr-BE.json +4 -2
  51. package/src/shared/translations/fr-FR.json +4 -2
  52. package/src/shared/translations/is-IS.json +4 -2
  53. package/src/shared/translations/it-IT.json +4 -2
  54. package/src/shared/translations/ja-JP.json +4 -2
  55. package/src/shared/translations/nl-BE.json +4 -2
  56. package/src/shared/translations/nl-NL.json +4 -2
  57. package/src/shared/translations/no-NO.json +4 -2
  58. package/src/shared/translations/pl-PL.json +4 -2
  59. package/src/shared/translations/pt-PT.json +4 -2
  60. package/src/shared/translations/sv-SE.json +4 -2
  61. package/src/shared/types.ts +13 -0
  62. 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, translations),
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 calculateNights = (fromDate: Date, toDate: Date, translations?: any): string => {
60
- const from = new Date(fromDate).getTime(); // returns a number
61
- const to = new Date(toDate).getTime(); // returns a number
62
- const diffTime = Math.abs(to - from);
63
- const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
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 { results, activeTab } = useSelector((state: SearchResultsRootState) => state.searchResults);
75
+ const { filteredResults, activeTab } = useSelector((state: SearchResultsRootState) => state.searchResults);
79
76
 
80
- if (!results.length) {
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 = results?.[0];
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(results, context, cmsMap, activeTab, translations)}
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: context!.type === 'hotel' || context!.type === 'hotel-flight' ? 3 : context!.type === 'flight' ? 7 : context!.type === 'roundTrip' ? 1 : 0,
194
- searchType: 0,
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 runHotelSearch = async () => {
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({ results: packageSearchResults }));
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
- runHotelSearch();
361
+ if (context?.type === 'hotel-flight' || context?.type === 'hotel' || context?.type === 'groupTour') {
362
+ runSearch();
359
363
  }
360
364
  }
361
- }, [location.search, searchTrigger]);
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
- {results?.length && results.length} {translations.SRP.TOTAL_RESULTS_LABEL}
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} context={context} />}
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<{ results: BookingPackageItem[] }>) {
40
- state.results = action.payload.results;
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, SRPType } from '../../search-results/types';
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
  }