@qite/tide-booking-component 1.4.103 → 1.4.105

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 (70) hide show
  1. package/build/build-cjs/index.js +2782 -1052
  2. package/build/build-cjs/src/search-results/components/excursions/day-by-day-excursions.d.ts +4 -0
  3. package/build/build-cjs/src/search-results/components/excursions/excursion-details.d.ts +3 -0
  4. package/build/build-cjs/src/search-results/components/excursions/excursion-results.d.ts +8 -0
  5. package/build/build-cjs/src/search-results/components/filters/filters.d.ts +2 -0
  6. package/build/build-cjs/src/search-results/components/hotel/hotel-accommodation-results.d.ts +1 -0
  7. package/build/build-cjs/src/search-results/store/search-results-selectors.d.ts +546 -0
  8. package/build/build-cjs/src/search-results/store/search-results-slice.d.ts +55 -8
  9. package/build/build-cjs/src/search-results/types.d.ts +40 -2
  10. package/build/build-cjs/src/search-results/utils/query-utils.d.ts +1 -0
  11. package/build/build-cjs/src/search-results/utils/search-results-utils.d.ts +8 -6
  12. package/build/build-cjs/src/shared/components/flyin/flyin.d.ts +4 -3
  13. package/build/build-cjs/src/shared/components/flyin/packaging-flights-flyin.d.ts +7 -0
  14. package/build/build-cjs/src/shared/utils/localization-util.d.ts +3 -0
  15. package/build/build-cjs/src/shared/utils/tide-api-utils.d.ts +6 -0
  16. package/build/build-esm/index.js +2735 -1023
  17. package/build/build-esm/src/search-results/components/excursions/day-by-day-excursions.d.ts +4 -0
  18. package/build/build-esm/src/search-results/components/excursions/excursion-details.d.ts +3 -0
  19. package/build/build-esm/src/search-results/components/excursions/excursion-results.d.ts +8 -0
  20. package/build/build-esm/src/search-results/components/filters/filters.d.ts +2 -0
  21. package/build/build-esm/src/search-results/components/hotel/hotel-accommodation-results.d.ts +1 -0
  22. package/build/build-esm/src/search-results/store/search-results-selectors.d.ts +546 -0
  23. package/build/build-esm/src/search-results/store/search-results-slice.d.ts +55 -8
  24. package/build/build-esm/src/search-results/types.d.ts +40 -2
  25. package/build/build-esm/src/search-results/utils/query-utils.d.ts +1 -0
  26. package/build/build-esm/src/search-results/utils/search-results-utils.d.ts +8 -6
  27. package/build/build-esm/src/shared/components/flyin/flyin.d.ts +4 -3
  28. package/build/build-esm/src/shared/components/flyin/packaging-flights-flyin.d.ts +7 -0
  29. package/build/build-esm/src/shared/utils/localization-util.d.ts +3 -0
  30. package/build/build-esm/src/shared/utils/tide-api-utils.d.ts +6 -0
  31. package/package.json +2 -2
  32. package/src/booking-wizard/features/flight-options/index.tsx +6 -2
  33. package/src/search-results/components/excursions/day-by-day-excursions.tsx +169 -0
  34. package/src/search-results/components/excursions/excursion-details.tsx +340 -0
  35. package/src/search-results/components/excursions/excursion-results.tsx +186 -0
  36. package/src/search-results/components/filters/filters.tsx +8 -9
  37. package/src/search-results/components/hotel/hotel-accommodation-results.tsx +81 -24
  38. package/src/search-results/components/hotel/hotel-card.tsx +0 -3
  39. package/src/search-results/components/icon.tsx +1 -4
  40. package/src/search-results/components/search-results-container/search-results-container.tsx +208 -130
  41. package/src/search-results/store/search-results-selectors.ts +84 -0
  42. package/src/search-results/store/search-results-slice.ts +138 -15
  43. package/src/search-results/types.ts +55 -2
  44. package/src/search-results/utils/query-utils.ts +1 -0
  45. package/src/search-results/utils/search-results-utils.ts +310 -58
  46. package/src/shared/components/flyin/accommodation-flyin.tsx +4 -2
  47. package/src/shared/components/flyin/flights-flyin.tsx +3 -1
  48. package/src/shared/components/flyin/flyin.tsx +116 -21
  49. package/src/shared/components/flyin/group-tour-flyin.tsx +3 -1
  50. package/src/shared/components/flyin/packaging-flights-flyin.tsx +164 -0
  51. package/src/shared/translations/ar-SA.json +4 -2
  52. package/src/shared/translations/da-DK.json +4 -2
  53. package/src/shared/translations/de-DE.json +4 -2
  54. package/src/shared/translations/en-GB.json +4 -2
  55. package/src/shared/translations/es-ES.json +4 -2
  56. package/src/shared/translations/fr-BE.json +4 -2
  57. package/src/shared/translations/fr-FR.json +4 -2
  58. package/src/shared/translations/is-IS.json +4 -2
  59. package/src/shared/translations/it-IT.json +4 -2
  60. package/src/shared/translations/ja-JP.json +4 -2
  61. package/src/shared/translations/nl-BE.json +4 -2
  62. package/src/shared/translations/nl-NL.json +4 -2
  63. package/src/shared/translations/no-NO.json +4 -2
  64. package/src/shared/translations/pl-PL.json +4 -2
  65. package/src/shared/translations/pt-PT.json +4 -2
  66. package/src/shared/translations/sv-SE.json +4 -2
  67. package/src/shared/utils/localization-util.ts +14 -0
  68. package/src/shared/utils/tide-api-utils.ts +8 -0
  69. package/styles/components/_flyin.scss +16 -0
  70. package/styles/components/_search.scss +15 -2
@@ -0,0 +1,340 @@
1
+ import React, { useContext, useMemo } from 'react';
2
+ import { useDispatch, useSelector } from 'react-redux';
3
+ import { first } from 'lodash';
4
+ import { SearchResultsRootState } from '../../store/search-results-store';
5
+ import { getTranslations } from '../../../shared/utils/localization-util';
6
+ import SearchResultsConfigurationContext from '../../search-results-configuration-context';
7
+ import { PackagingAccommodationResponse } from '@qite/tide-client';
8
+ import { confirmExcursionForDay, setFlyInIsOpen, setSelectedExcursionSearchResult } from '../../store/search-results-slice';
9
+ import { format, parseISO } from 'date-fns';
10
+
11
+ type ExcursionOption = PackagingAccommodationResponse['rooms'][number]['options'][number];
12
+
13
+ type GroupedExcursion = {
14
+ accommodationCode: string;
15
+ accommodationName: string;
16
+ options: ExcursionOption[];
17
+ };
18
+
19
+ const formatPrice = (price?: number, currencyCode?: string | null) => {
20
+ if (typeof price !== 'number') return '';
21
+
22
+ return new Intl.NumberFormat('nl-BE', {
23
+ style: 'currency',
24
+ currency: currencyCode ?? 'EUR'
25
+ }).format(price);
26
+ };
27
+
28
+ const getExcursionDayKey = (date: string | Date) => {
29
+ const parsed = typeof date === 'string' ? parseISO(date) : date;
30
+ return format(parsed, 'yyyy-MM-dd');
31
+ };
32
+
33
+ const getOptionPaxIds = (option: ExcursionOption): number[] => {
34
+ return Array.isArray(option.paxIds) ? Array.from(new Set(option.paxIds)).sort((a, b) => a - b) : [];
35
+ };
36
+
37
+ const optionAppliesToPax = (option: ExcursionOption, paxId: number) => {
38
+ return getOptionPaxIds(option).includes(paxId);
39
+ };
40
+
41
+ const optionAppliesToAllTravellers = (option: ExcursionOption, travellerCount: number) => {
42
+ const paxIds = getOptionPaxIds(option);
43
+ const expected = Array.from({ length: travellerCount }, (_, i) => i);
44
+
45
+ return paxIds.length === expected.length && paxIds.every((id, index) => id === expected[index]);
46
+ };
47
+
48
+ const groupOptionsByExcursion = (options: ExcursionOption[]): GroupedExcursion[] => {
49
+ const groupedMap = new Map<string, GroupedExcursion>();
50
+
51
+ options.forEach((option) => {
52
+ const key = option.accommodationCode;
53
+
54
+ if (!groupedMap.has(key)) {
55
+ groupedMap.set(key, {
56
+ accommodationCode: option.accommodationCode,
57
+ accommodationName: option.accommodationName,
58
+ options: []
59
+ });
60
+ }
61
+
62
+ groupedMap.get(key)!.options.push(option);
63
+ });
64
+
65
+ return Array.from(groupedMap.values());
66
+ };
67
+
68
+ const ExcursionDetails: React.FC = () => {
69
+ const context = useContext(SearchResultsConfigurationContext);
70
+ const dispatch = useDispatch();
71
+
72
+ const { selectedExcursionSearchResult, editablePackagingEntry, excursionSearchParams } = useSelector((state: SearchResultsRootState) => state.searchResults);
73
+
74
+ if (!context || !selectedExcursionSearchResult || !editablePackagingEntry || !excursionSearchParams?.date) {
75
+ return null;
76
+ }
77
+
78
+ const translations = getTranslations(context.languageCode ?? 'en-GB');
79
+ const travellerCount = editablePackagingEntry.pax.length;
80
+
81
+ const allOptions = useMemo(() => {
82
+ return selectedExcursionSearchResult.rooms.flatMap((room) => room.options ?? []);
83
+ }, [selectedExcursionSearchResult]);
84
+
85
+ const sharedOptions = useMemo(() => {
86
+ return allOptions.filter((option) => optionAppliesToAllTravellers(option, travellerCount));
87
+ }, [allOptions, travellerCount]);
88
+
89
+ const sharedExcursions = useMemo(() => {
90
+ return groupOptionsByExcursion(sharedOptions);
91
+ }, [sharedOptions]);
92
+
93
+ const paxGroups = useMemo(() => {
94
+ return editablePackagingEntry.pax.map((pax) => {
95
+ const paxOptions = allOptions.filter((option) => optionAppliesToPax(option, pax.id) && !optionAppliesToAllTravellers(option, travellerCount));
96
+
97
+ return {
98
+ pax,
99
+ paxId: pax.id,
100
+ excursions: groupOptionsByExcursion(paxOptions)
101
+ };
102
+ });
103
+ }, [editablePackagingEntry.pax, allOptions, travellerCount]);
104
+
105
+ const getSelectedSharedOption = () => {
106
+ return sharedOptions.find((option) => option.isSelected);
107
+ };
108
+
109
+ const getSelectedSharedOptionForExcursion = (accommodationCode: string) => {
110
+ return sharedOptions.find((option) => option.accommodationCode === accommodationCode && option.isSelected);
111
+ };
112
+
113
+ const getSelectedOptionForPax = (paxId: number) => {
114
+ return allOptions.find((option) => optionAppliesToPax(option, paxId) && !optionAppliesToAllTravellers(option, travellerCount) && option.isSelected);
115
+ };
116
+
117
+ const getSelectedOptionForExcursion = (paxId: number, accommodationCode: string) => {
118
+ return allOptions.find(
119
+ (option) =>
120
+ optionAppliesToPax(option, paxId) &&
121
+ !optionAppliesToAllTravellers(option, travellerCount) &&
122
+ option.accommodationCode === accommodationCode &&
123
+ option.isSelected
124
+ );
125
+ };
126
+
127
+ const handlePick = (selectedGuid?: string, paxId?: number) => {
128
+ const updatedExcursionSearchResult: PackagingAccommodationResponse = {
129
+ ...selectedExcursionSearchResult,
130
+ rooms: selectedExcursionSearchResult.rooms.map((room) => ({
131
+ ...room,
132
+ options: room.options.map((option) => {
133
+ const isSharedOption = optionAppliesToAllTravellers(option, travellerCount);
134
+
135
+ if (paxId === undefined) {
136
+ if (!isSharedOption) {
137
+ return option;
138
+ }
139
+
140
+ return {
141
+ ...option,
142
+ isSelected: option.guid === selectedGuid
143
+ };
144
+ }
145
+
146
+ if (isSharedOption || !optionAppliesToPax(option, paxId)) {
147
+ return option;
148
+ }
149
+
150
+ return {
151
+ ...option,
152
+ isSelected: option.guid === selectedGuid
153
+ };
154
+ })
155
+ }))
156
+ };
157
+
158
+ dispatch(setSelectedExcursionSearchResult(updatedExcursionSearchResult));
159
+ };
160
+
161
+ const calculateTotalPrice = () => {
162
+ const selectedOptions = allOptions.filter((option) => option.isSelected);
163
+ const totalPrice = selectedOptions.reduce((total, option) => total + (option.price || 0), 0);
164
+
165
+ return formatPrice(totalPrice, selectedExcursionSearchResult.currencyCode);
166
+ };
167
+
168
+ const getSharedPriceDifference = (accommodationCode: string) => {
169
+ const currentSelectedShared = getSelectedSharedOption();
170
+
171
+ let targetPrice = 0;
172
+
173
+ const selectedOption = getSelectedSharedOptionForExcursion(accommodationCode);
174
+
175
+ if (selectedOption?.price) {
176
+ targetPrice = selectedOption.price;
177
+ } else {
178
+ const firstOption = sharedOptions.find((option) => option.accommodationCode === accommodationCode);
179
+ targetPrice = firstOption?.price || 0;
180
+ }
181
+
182
+ return targetPrice - (currentSelectedShared?.price || 0);
183
+ };
184
+
185
+ const getPriceDifference = (currentSelectedPrice: number | undefined, paxId: number, accommodationCode: string) => {
186
+ let targetPrice = 0;
187
+
188
+ const selectedOption = getSelectedOptionForExcursion(paxId, accommodationCode);
189
+
190
+ if (selectedOption?.price) {
191
+ targetPrice = selectedOption.price;
192
+ } else {
193
+ const firstOption = allOptions.find(
194
+ (option) => optionAppliesToPax(option, paxId) && !optionAppliesToAllTravellers(option, travellerCount) && option.accommodationCode === accommodationCode
195
+ );
196
+
197
+ targetPrice = firstOption?.price || 0;
198
+ }
199
+
200
+ return targetPrice - (currentSelectedPrice || 0);
201
+ };
202
+
203
+ const formatPriceDifference = (difference: number, currencyCode: string) => {
204
+ if (difference === 0) {
205
+ return null;
206
+ }
207
+
208
+ const formattedAbsoluteValue = formatPrice(Math.abs(difference), currencyCode);
209
+ return `${difference > 0 ? '+' : '-'} ${formattedAbsoluteValue}`;
210
+ };
211
+
212
+ const getPriceDifferenceClassName = (difference: number) => {
213
+ if (difference < 0) {
214
+ return 'flyin__acco__price flyin__acco__price--decrease';
215
+ }
216
+
217
+ if (difference > 0) {
218
+ return 'flyin__acco__price flyin__acco__price--increase';
219
+ }
220
+
221
+ return 'flyin__acco__price';
222
+ };
223
+
224
+ const handleConfirm = () => {
225
+ const dayKey = getExcursionDayKey(excursionSearchParams.date);
226
+
227
+ dispatch(
228
+ confirmExcursionForDay({
229
+ dayKey,
230
+ excursion: selectedExcursionSearchResult
231
+ })
232
+ );
233
+
234
+ dispatch(setFlyInIsOpen(false));
235
+ };
236
+
237
+ return (
238
+ <>
239
+ <div className="flyin__content">
240
+ {sharedExcursions.length > 0 && (
241
+ <div className="flyin__acco">
242
+ <h3 className="flyin__acco__room-title">{translations.QSM.ALL_TRAVELERS}</h3>
243
+
244
+ <div className="flyin__acco__cards">
245
+ {sharedExcursions.map((excursion) => {
246
+ const selectedOption = getSelectedSharedOptionForExcursion(excursion.accommodationCode);
247
+ const priceDifference = getSharedPriceDifference(excursion.accommodationCode);
248
+
249
+ return (
250
+ <div className="flyin__acco__card" key={`all-${excursion.accommodationCode}`}>
251
+ <div className="flyin__acco__content">
252
+ <h4 className="flyin__acco__title">{excursion.accommodationName}</h4>
253
+ </div>
254
+
255
+ <div className="flyin__acco__footer">
256
+ <div className="flyin__acco__footer__actions">
257
+ <button
258
+ className={selectedOption ? 'cta cta--select cta--selected' : 'cta cta--select'}
259
+ onClick={() => handlePick(selectedOption ? selectedOption.guid : first(excursion.options)?.guid)}>
260
+ {selectedOption ? translations?.SHARED.SELECTED : translations?.SHARED.SELECT}
261
+ </button>
262
+
263
+ <div className="flyin__acco__price__wrapper">
264
+ <span className={getPriceDifferenceClassName(priceDifference)}>
265
+ {formatPriceDifference(priceDifference, selectedExcursionSearchResult.currencyCode)}
266
+ </span>
267
+ </div>
268
+ </div>
269
+ </div>
270
+ </div>
271
+ );
272
+ })}
273
+ </div>
274
+ </div>
275
+ )}
276
+
277
+ {paxGroups.map(({ pax, paxId, excursions }) => {
278
+ if (excursions.length === 0) {
279
+ return null;
280
+ }
281
+
282
+ const selectedPaxOption = getSelectedOptionForPax(paxId);
283
+
284
+ return (
285
+ <div className="flyin__acco" key={`pax-${pax.id}`}>
286
+ <h3 className="flyin__acco__room-title">
287
+ {translations.SUMMARY.TRAVELER} {pax.id + 1}
288
+ </h3>
289
+
290
+ <div className="flyin__acco__cards">
291
+ {excursions.map((excursion) => {
292
+ const selectedOption = getSelectedOptionForExcursion(paxId, excursion.accommodationCode);
293
+ const priceDifference = getPriceDifference(selectedPaxOption?.price, paxId, excursion.accommodationCode);
294
+
295
+ return (
296
+ <div className="flyin__acco__card" key={`${pax.id}-${excursion.accommodationCode}`}>
297
+ <div className="flyin__acco__content">
298
+ <h4 className="flyin__acco__title">{excursion.accommodationName}</h4>
299
+ </div>
300
+
301
+ <div className="flyin__acco__footer">
302
+ <div className="flyin__acco__footer__actions">
303
+ <button
304
+ className={
305
+ selectedPaxOption?.accommodationCode === excursion.accommodationCode ? 'cta cta--select cta--selected' : 'cta cta--select'
306
+ }
307
+ onClick={() => handlePick(selectedOption ? selectedOption.guid : first(excursion.options)?.guid, paxId)}>
308
+ {selectedPaxOption?.accommodationCode === excursion.accommodationCode ? translations?.SHARED.SELECTED : translations?.SHARED.SELECT}
309
+ </button>
310
+
311
+ <div className="flyin__acco__price__wrapper">
312
+ <span className={getPriceDifferenceClassName(priceDifference)}>
313
+ {formatPriceDifference(priceDifference, selectedExcursionSearchResult.currencyCode)}
314
+ </span>
315
+ </div>
316
+ </div>
317
+ </div>
318
+ </div>
319
+ );
320
+ })}
321
+ </div>
322
+ </div>
323
+ );
324
+ })}
325
+ </div>
326
+
327
+ <div className="flyin__footer">
328
+ <div className="flyin__footer__price">
329
+ {translations.SHARED.TOTAL_PRICE}: {calculateTotalPrice()}
330
+ </div>
331
+
332
+ <button type="button" className="cta cta--primary" onClick={handleConfirm}>
333
+ {translations?.QSM.CONFIRM}
334
+ </button>
335
+ </div>
336
+ </>
337
+ );
338
+ };
339
+
340
+ export default ExcursionDetails;
@@ -0,0 +1,186 @@
1
+ import React, { useContext, useEffect, useState } from 'react';
2
+ import Spinner from '../spinner/spinner';
3
+ import { useDispatch, useSelector } from 'react-redux';
4
+ import { SearchResultsRootState } from '../../store/search-results-store';
5
+ import { getTranslations } from '../../../shared/utils/localization-util';
6
+ import SearchResultsConfigurationContext from '../../search-results-configuration-context';
7
+ import {
8
+ PackagingAccommodationRequest,
9
+ PackagingAccommodationResponse,
10
+ PackagingDestination,
11
+ searchPackagingExcursions,
12
+ TideClientConfig
13
+ } from '@qite/tide-client';
14
+ import { EXCURSION_SERVICE_TYPE } from '../../utils/query-utils';
15
+ import { SearchSeed } from '../../types';
16
+ import he from 'he';
17
+ import { setFlyInType, setSelectedExcursionSearchResult } from '../../store/search-results-slice';
18
+
19
+ interface ExcursionResultsProps {
20
+ isFlyIn?: boolean;
21
+ activeSearchSeed?: SearchSeed | null;
22
+ }
23
+
24
+ const ExcursionResults: React.FC<ExcursionResultsProps> = ({ isFlyIn, activeSearchSeed }) => {
25
+ const context = useContext(SearchResultsConfigurationContext);
26
+ const dispatch = useDispatch();
27
+ const [isLoading, setIsLoading] = useState(false);
28
+ const [excursions, setExcursions] = useState<PackagingAccommodationResponse[] | null>(null);
29
+
30
+ const { flyInIsOpen, flyInType, excursionSearchParams, transactionId } = useSelector((state: SearchResultsRootState) => state.searchResults);
31
+
32
+ useEffect(() => {
33
+ if (!context || !activeSearchSeed || !excursionSearchParams) return;
34
+
35
+ (async () => {
36
+ try {
37
+ setIsLoading(true);
38
+ console.log('Excursion search params changed, fetching excursions...', excursionSearchParams);
39
+
40
+ const config: TideClientConfig = {
41
+ host: context.tideConnection.host,
42
+ apiKey: context.tideConnection.apiKey
43
+ };
44
+
45
+ const destination = excursionSearchParams.locationId
46
+ ? { id: Number(excursionSearchParams.locationId), type: 'location' }
47
+ : excursionSearchParams.oordId
48
+ ? { id: Number(excursionSearchParams.oordId), type: 'oord' }
49
+ : excursionSearchParams.regionId
50
+ ? { id: Number(excursionSearchParams.regionId), type: 'region' }
51
+ : excursionSearchParams.countryId
52
+ ? { id: Number(excursionSearchParams.countryId), type: 'country' }
53
+ : { id: 0, type: null };
54
+
55
+ const allPax = activeSearchSeed.rooms.flatMap((room) => room.pax);
56
+
57
+ const searchRequest: PackagingAccommodationRequest = {
58
+ transactionId: transactionId ?? '',
59
+ officeId: context.tideConnection.officeId ?? 1,
60
+ agentId: context.agentId ?? null,
61
+ portalId: context.portalId ?? null,
62
+ catalogueId: context.searchConfiguration.defaultCatalogueId ?? 0,
63
+ searchConfigurationId: context.searchConfiguration.id,
64
+ language: context.languageCode ?? 'en-GB',
65
+ serviceType: EXCURSION_SERVICE_TYPE,
66
+ fromDate: excursionSearchParams.fromDate,
67
+ toDate: excursionSearchParams.toDate,
68
+ destination: {
69
+ id: destination.id,
70
+ isCountry: destination.type === 'country',
71
+ isRegion: destination.type === 'region',
72
+ isOord: destination.type === 'oord',
73
+ isLocation: destination.type === 'location',
74
+ isAirport: false,
75
+ code: ''
76
+ } as PackagingDestination,
77
+ productCode: '',
78
+ // rooms: activeSearchSeed.rooms.map((room) => ({
79
+ // travellers: room.pax.map((pax) => ({
80
+ // id: pax.id,
81
+ // age: pax.age,
82
+ // dateOfBirth: pax.dateOfBirth
83
+ // }))
84
+ // })),
85
+ rooms: [
86
+ {
87
+ travellers: allPax.map((pax) => ({
88
+ id: pax.id,
89
+ age: pax.age,
90
+ dateOfBirth: pax.dateOfBirth
91
+ }))
92
+ }
93
+ ],
94
+ tagIds: []
95
+ };
96
+
97
+ const packageExcursionSearchResults = await searchPackagingExcursions(config, searchRequest);
98
+ console.log('Excursion search results', packageExcursionSearchResults);
99
+ setExcursions(packageExcursionSearchResults);
100
+ } catch (err) {
101
+ console.error('Excursion search failed', err);
102
+ } finally {
103
+ setIsLoading(false);
104
+ }
105
+ })();
106
+ }, [context, activeSearchSeed, excursionSearchParams, transactionId]);
107
+
108
+ if (!context || !activeSearchSeed) {
109
+ return null;
110
+ }
111
+
112
+ if (!flyInIsOpen || flyInType !== 'excursion-results') {
113
+ return null;
114
+ }
115
+
116
+ const translations = getTranslations(context.languageCode ?? 'en-GB');
117
+
118
+ const handleChange = (excursion: PackagingAccommodationResponse): void => {
119
+ console.log('Selected excursion', excursion);
120
+ dispatch(setFlyInType('excursion-details'));
121
+ dispatch(setSelectedExcursionSearchResult(excursion));
122
+ };
123
+
124
+ return isLoading ? (
125
+ <Spinner />
126
+ ) : (
127
+ <div className="flyin__content flyin__content--columns">
128
+ {/* <Filters
129
+ initialFilters={initialFilters}
130
+ filters={filters}
131
+ isOpen={false}
132
+ handleSetIsOpen={() => { }}
133
+ // handleApplyFilters={() => setSearchTrigger((prev) => prev + 1)}
134
+ isLoading={isLoading}
135
+ setFilters={(filters) => dispatch(setFilters(filters))}
136
+ resetFilters={(filters) => dispatch(resetFilters(filters))}
137
+ /> */}
138
+ <div className="search__results__wrapper">
139
+ <div className="search__result-row">
140
+ <span className="search__result-row-text">
141
+ {!isLoading && (
142
+ <>
143
+ {excursions?.length && excursions.length}
144
+ &nbsp;{translations.SRP.TOTAL_RESULTS_LABEL}
145
+ </>
146
+ )}
147
+ </span>
148
+ {/* {sortByTypes && sortByTypes.length > 0 && (
149
+ <div className="search__result-row-filter">
150
+ <ItemPicker
151
+ items={sortByTypes}
152
+ selection={selectedSortType?.label || undefined}
153
+ selectedSortByType={selectedSortType}
154
+ label={translations.SRP.SORTBY}
155
+ placeholder={translations.SRP.SORTBY}
156
+ classModifier="travel-class-picker__items"
157
+ valueFormatter={(value, direction) => getSortingName(translations, findSortByType(sortByTypes, value, direction ?? 'asc'))}
158
+ onPick={(newSortKey, direction) => handleSortChange(newSortKey, direction)}
159
+ />
160
+ </div>
161
+ )} */}
162
+ </div>
163
+ <div className="search__results__cards search__results__cards--compact">
164
+ {excursions &&
165
+ excursions.length > 0 &&
166
+ excursions.map((excursion) => (
167
+ <div
168
+ key={excursion.code}
169
+ className="search__result-card__wrapper search__result-card__wrapper--custom"
170
+ onMouseEnter={(e) => (e.currentTarget.style.transform = 'scale(1.02)')}
171
+ onMouseLeave={(e) => (e.currentTarget.style.transform = 'scale(1)')}>
172
+ {excursion.contents ? <div dangerouslySetInnerHTML={{ __html: he.decode(excursion.contents) }}></div> : 'no contents'}
173
+ <div className="search__result-card__footer">
174
+ <button type="button" className="cta cta--select" onClick={() => handleChange(excursion)}>
175
+ {translations?.SHARED.SELECT}
176
+ </button>
177
+ </div>
178
+ </div>
179
+ ))}
180
+ </div>
181
+ </div>
182
+ </div>
183
+ );
184
+ };
185
+
186
+ export default ExcursionResults;
@@ -1,9 +1,7 @@
1
- import React, { useContext, useEffect, useState } from 'react';
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
- dispatch(setFilters(updated));
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
- dispatch(setFilters(updated));
60
+ setFilters(updated);
62
61
  };
63
62
 
64
63
  const handleFullReset = () => {
65
64
  if (!isLoading) {
66
- dispatch(resetFilters(initialFilters));
65
+ resetFilters(initialFilters);
67
66
  }
68
67
  };
69
68