@qite/tide-booking-component 1.4.29 → 1.4.31
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 +621 -2822
- package/build/build-cjs/search-results/store/search-results-slice.d.ts +6 -3
- package/build/build-cjs/search-results/types.d.ts +9 -12
- package/build/build-esm/index.js +617 -2625
- package/build/build-esm/search-results/store/search-results-slice.d.ts +6 -3
- package/build/build-esm/search-results/types.d.ts +9 -12
- package/package.json +2 -3
- package/src/booking-product/components/dates.tsx +9 -4
- package/src/booking-wizard/components/step-indicator.tsx +11 -2
- package/src/booking-wizard/features/booking/booking-slice.ts +27 -2
- package/src/booking-wizard/features/booking/booking.tsx +32 -15
- package/src/booking-wizard/features/booking/selectors.ts +6 -0
- package/src/booking-wizard/features/flight-options/index.tsx +27 -3
- package/src/booking-wizard/features/product-options/option-room.tsx +1 -1
- package/src/booking-wizard/features/product-options/options-form.tsx +14 -4
- package/src/booking-wizard/features/room-options/room.tsx +1 -1
- package/src/booking-wizard/features/sidebar/index.tsx +4 -1
- package/src/booking-wizard/features/sidebar/sidebar-util.ts +1 -3
- package/src/booking-wizard/features/sidebar/sidebar.tsx +112 -104
- package/src/booking-wizard/features/travelers-form/travelers-form-slice.ts +1 -0
- package/src/booking-wizard/features/travelers-form/travelers-form.tsx +146 -10
- package/src/booking-wizard/settings-context.ts +2 -1
- package/src/booking-wizard/types.ts +1 -0
- package/src/qsm/components/search-input-group/index.tsx +17 -8
- package/src/search-results/components/hotel/hotel-accommodation-results.tsx +78 -83
- package/src/search-results/components/hotel/hotel-card.tsx +54 -24
- package/src/search-results/components/icon.tsx +13 -0
- package/src/search-results/components/item-picker/index.tsx +5 -7
- package/src/search-results/components/search-results-container/search-results-container.tsx +72 -117
- package/src/search-results/components/tab-views/index.tsx +22 -3
- package/src/search-results/features/flights/flight-search-results-self-contained.tsx +0 -13
- package/src/search-results/features/hotels/hotel-flight-search-results-self-contained.tsx +1 -8
- package/src/search-results/features/hotels/hotel-search-results-self-contained.tsx +1 -14
- package/src/search-results/features/roundtrips/roundtrip-search-results-self-contained.tsx +1 -9
- package/src/search-results/store/search-results-slice.ts +11 -4
- package/src/search-results/types.ts +11 -16
- package/src/shared/translations/en-GB.json +5 -1
- package/src/shared/translations/fr-BE.json +5 -1
- package/src/shared/translations/nl-BE.json +5 -1
- package/styles/components/_form.scss +51 -2
- package/styles/components/_passenger-picker.scss +3 -2
- package/styles/components/_qsm.scss +1 -1
- package/styles/qsm/_qsm.scss +67 -6
|
@@ -4,28 +4,20 @@ import { SearchResultsRootState } from '../../store/search-results-store';
|
|
|
4
4
|
import SearchResultsConfigurationContext from '../../search-results-configuration-context';
|
|
5
5
|
|
|
6
6
|
import { resetFilters, setSortKey, setResults, setIsLoading } from '../../store/search-results-slice';
|
|
7
|
-
import { Filter,
|
|
7
|
+
import { Filter, SortingOption } from '../../types';
|
|
8
8
|
import useMediaQuery from '../../../shared/utils/use-media-query-util';
|
|
9
9
|
import Filters from '../filters/filters';
|
|
10
10
|
import ItemPicker from '../item-picker';
|
|
11
11
|
|
|
12
12
|
import { TideClientConfig, search } from '@qite/tide-client';
|
|
13
|
-
import {
|
|
14
|
-
BookingPackageDestination,
|
|
15
|
-
BookingPackageItem,
|
|
16
|
-
BookingPackagePax,
|
|
17
|
-
BookingPackageRequestRoom,
|
|
18
|
-
BookingPackageSearchRequest
|
|
19
|
-
} from '@qite/tide-client/build/types';
|
|
13
|
+
import { BookingPackageDestination, BookingPackagePax, BookingPackageRequestRoom, BookingPackageSearchRequest } from '@qite/tide-client/build/types';
|
|
20
14
|
import { getDateFromParams, getNumberFromParams, getRoomsFromParams } from '../../../shared/utils/query-string-util';
|
|
21
15
|
import { range } from 'lodash';
|
|
22
16
|
import { Room } from '../../../booking-wizard/types';
|
|
23
17
|
import Icon from '../icon';
|
|
24
18
|
import Itinerary from '../itinerary';
|
|
25
19
|
import TabViews from '../tab-views';
|
|
26
|
-
import FlightResults from '../flight/flight-results';
|
|
27
20
|
import HotelAccommodationResults from '../hotel/hotel-accommodation-results';
|
|
28
|
-
import FlightAccommodationResults from '../flight/flight-accommodation-results';
|
|
29
21
|
import RoundTripResults from '../round-trip/round-trip-results';
|
|
30
22
|
import { enrichFiltersWithResults } from '../filters/utility';
|
|
31
23
|
|
|
@@ -48,20 +40,32 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
48
40
|
}
|
|
49
41
|
|
|
50
42
|
const params = new URLSearchParams(location.search);
|
|
51
|
-
|
|
52
|
-
|
|
43
|
+
let from = getDateFromParams(params, 'from');
|
|
44
|
+
let to = getDateFromParams(params, 'to');
|
|
53
45
|
const rooms = getRoomsFromParams(params, 'rooms');
|
|
54
|
-
|
|
55
|
-
|
|
46
|
+
let country = getNumberFromParams(params, 'country');
|
|
47
|
+
let region = getNumberFromParams(params, 'region');
|
|
48
|
+
let oord = getNumberFromParams(params, 'oord');
|
|
49
|
+
let city = getNumberFromParams(params, 'location');
|
|
50
|
+
|
|
51
|
+
// temp hardcoded params
|
|
52
|
+
if (!from || !to) {
|
|
53
|
+
from = '2026-04-07';
|
|
54
|
+
to = '2026-04-13';
|
|
55
|
+
}
|
|
56
|
+
if (!country && !region && !oord && !city) {
|
|
57
|
+
region = 1;
|
|
58
|
+
}
|
|
56
59
|
|
|
57
60
|
if (typeof window !== 'undefined') {
|
|
58
61
|
window.scrollTo(0, 0);
|
|
59
62
|
}
|
|
60
63
|
|
|
61
|
-
// Determine destination
|
|
62
64
|
let destinationId: number | null = null;
|
|
63
65
|
let destinationIsCountry = false;
|
|
64
66
|
let destinationIsRegion = false;
|
|
67
|
+
let destinationIsOord = false;
|
|
68
|
+
let destinationIsLocation = false;
|
|
65
69
|
|
|
66
70
|
if (country) {
|
|
67
71
|
destinationId = country;
|
|
@@ -69,20 +73,27 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
69
73
|
} else if (region) {
|
|
70
74
|
destinationId = region;
|
|
71
75
|
destinationIsRegion = true;
|
|
76
|
+
} else if (oord) {
|
|
77
|
+
destinationId = oord;
|
|
78
|
+
destinationIsOord = true;
|
|
79
|
+
} else if (city) {
|
|
80
|
+
destinationId = city;
|
|
81
|
+
destinationIsLocation = true;
|
|
72
82
|
}
|
|
73
83
|
|
|
74
84
|
const searchRequest = {
|
|
75
85
|
officeId: 1,
|
|
76
86
|
payload: {
|
|
77
87
|
catalogueIds: context.tideConnection.catalogueIds,
|
|
78
|
-
serviceType:
|
|
88
|
+
serviceType:
|
|
89
|
+
context?.type === 'hotel' || context?.type === 'hotel-flight' ? 3 : context?.type === 'flight' ? 7 : context?.type === 'roundTrip' ? 1 : 0,
|
|
79
90
|
searchType: 0,
|
|
80
91
|
destination: {
|
|
81
92
|
id: Number(destinationId),
|
|
82
93
|
isCountry: destinationIsCountry,
|
|
83
94
|
isRegion: destinationIsRegion,
|
|
84
|
-
isOord:
|
|
85
|
-
isLocation:
|
|
95
|
+
isOord: destinationIsOord,
|
|
96
|
+
isLocation: destinationIsLocation
|
|
86
97
|
} as BookingPackageDestination,
|
|
87
98
|
rooms: getRequestRooms(rooms),
|
|
88
99
|
fromDate: from,
|
|
@@ -110,7 +121,8 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
110
121
|
|
|
111
122
|
const packageSearchResults = await search(config, searchRequest);
|
|
112
123
|
|
|
113
|
-
|
|
124
|
+
console.log('Search results', packageSearchResults);
|
|
125
|
+
|
|
114
126
|
const enrichedFilters = enrichFiltersWithResults(packageSearchResults, context.filters);
|
|
115
127
|
if (!initialFiltersSet) {
|
|
116
128
|
dispatch(resetFilters(enrichedFilters));
|
|
@@ -118,7 +130,7 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
118
130
|
setInitialFiltersSet(true);
|
|
119
131
|
}
|
|
120
132
|
|
|
121
|
-
dispatch(setResults({ results:
|
|
133
|
+
dispatch(setResults({ results: packageSearchResults }));
|
|
122
134
|
dispatch(setIsLoading(false));
|
|
123
135
|
} catch (err) {
|
|
124
136
|
console.error('Search failed', err);
|
|
@@ -126,9 +138,8 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
126
138
|
}
|
|
127
139
|
};
|
|
128
140
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
}, [location.search, searchTrigger, sortKey]);
|
|
141
|
+
runSearch();
|
|
142
|
+
}, [location.search, searchTrigger]);
|
|
132
143
|
|
|
133
144
|
const getRequestRooms = (rooms: Room[] | null) => {
|
|
134
145
|
if (!rooms) {
|
|
@@ -160,53 +171,11 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
160
171
|
return requestRooms;
|
|
161
172
|
};
|
|
162
173
|
|
|
163
|
-
const createSearchResults = (searchResults: BookingPackageItem[]) => {
|
|
164
|
-
return searchResults.map((searchResult) => {
|
|
165
|
-
return {
|
|
166
|
-
type: context?.type,
|
|
167
|
-
id: searchResult.code,
|
|
168
|
-
title: searchResult.name,
|
|
169
|
-
image: getPlaceholderImage(), // TODO: We should not be using a placeholder image, but a result-specific image
|
|
170
|
-
description: `${searchResult.accommodationName}`,
|
|
171
|
-
location: `${searchResult.locationName}, ${searchResult.countryName}`,
|
|
172
|
-
price: formatPrice(searchResult.price),
|
|
173
|
-
ctaText: context?.translations?.searchResultCTA,
|
|
174
|
-
days: calculateNights(searchResult.stayFromDate, searchResult.stayToDate),
|
|
175
|
-
flightInfo: searchResult.flightDescription || null,
|
|
176
|
-
accommodation: searchResult.accommodationName || null,
|
|
177
|
-
regime: searchResult.regimeCode || null,
|
|
178
|
-
stars: searchResult.hotelStars
|
|
179
|
-
};
|
|
180
|
-
});
|
|
181
|
-
};
|
|
182
|
-
|
|
183
|
-
const getPlaceholderImage = () => {
|
|
184
|
-
// Placeholder image URL
|
|
185
|
-
return 'https://images.unsplash.com/photo-1573790387438-4da905039392?q=80&w=1925&auto=format&fit=crop';
|
|
186
|
-
};
|
|
187
|
-
|
|
188
|
-
const formatPrice = (price: number) => {
|
|
189
|
-
if (!price) {
|
|
190
|
-
return 'Price unavailable';
|
|
191
|
-
}
|
|
192
|
-
return `$${Math.round(price).toLocaleString()}`;
|
|
193
|
-
};
|
|
194
|
-
|
|
195
|
-
const calculateNights = (fromDate: Date, toDate: Date): string => {
|
|
196
|
-
const from = new Date(fromDate).getTime(); // returns a number
|
|
197
|
-
const to = new Date(toDate).getTime(); // returns a number
|
|
198
|
-
const diffTime = Math.abs(to - from);
|
|
199
|
-
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
|
200
|
-
return `${diffDays} nights`;
|
|
201
|
-
};
|
|
202
|
-
|
|
203
174
|
const [isMobileFiltersOpen, setIsMobileFiltersOpen] = useState(false);
|
|
204
175
|
|
|
205
176
|
const handleSortChange = (newSortKey: string) => {
|
|
177
|
+
console.log('Sort changed to:', newSortKey);
|
|
206
178
|
dispatch(setSortKey(newSortKey));
|
|
207
|
-
if (context) {
|
|
208
|
-
context.onSortChange?.(newSortKey);
|
|
209
|
-
}
|
|
210
179
|
};
|
|
211
180
|
|
|
212
181
|
const handleSetIsMobileFiltersOpen = () => {
|
|
@@ -219,31 +188,32 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
219
188
|
}
|
|
220
189
|
}, [isMobileFiltersOpen]);
|
|
221
190
|
|
|
222
|
-
const getFilteredResults = () => {
|
|
223
|
-
|
|
224
|
-
|
|
191
|
+
// const getFilteredResults = () => {
|
|
192
|
+
// const filteredResults = results.filter((result) => {
|
|
193
|
+
// // Apply frontend filters here
|
|
225
194
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
// Add more frontend filters if needed
|
|
236
|
-
return true;
|
|
237
|
-
});
|
|
195
|
+
// const ratingFilter = filters.find((f) => f.property === 'rating' && f.selectedRating != null);
|
|
196
|
+
// if (ratingFilter) {
|
|
197
|
+
// const minRating = ratingFilter.selectedRating!;
|
|
198
|
+
// const hotelStars = result.stars ?? 0;
|
|
199
|
+
// if (hotelStars < minRating) {
|
|
200
|
+
// return false;
|
|
201
|
+
// }
|
|
202
|
+
// }
|
|
238
203
|
|
|
239
|
-
|
|
240
|
-
|
|
204
|
+
// // Add more frontend filters if needed
|
|
205
|
+
// return true;
|
|
206
|
+
// });
|
|
241
207
|
|
|
242
|
-
|
|
208
|
+
// return filteredResults;
|
|
209
|
+
// };
|
|
243
210
|
|
|
244
|
-
const [
|
|
211
|
+
const sortingOptions: SortingOption[] = [
|
|
212
|
+
{ key: 'price-asc', label: 'Price: Low to High' },
|
|
213
|
+
{ key: 'price-desc', label: 'Price: High to Low' },
|
|
214
|
+
{ key: 'departure-date', label: 'Departure Date' }
|
|
215
|
+
];
|
|
245
216
|
|
|
246
|
-
console.log('SearchResultsContainer render', context);
|
|
247
217
|
return (
|
|
248
218
|
<div id="tide-booking" className="search__bg">
|
|
249
219
|
{context && (
|
|
@@ -269,21 +239,14 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
269
239
|
<Icon name="ui-filter" className="mobile-filters-button__icon" height={16} />
|
|
270
240
|
{context.translations?.filters}
|
|
271
241
|
</div>
|
|
272
|
-
{
|
|
273
|
-
// <select className="search__result-row-button" value={sortKey ?? ''} onChange={(e) => handleSortChange(e.target.value)}>
|
|
274
|
-
// {context.sortingOptions.map((opt) => (
|
|
275
|
-
// <option key={opt.key} value={opt.key}>
|
|
276
|
-
// {opt.label}
|
|
277
|
-
// </option>
|
|
278
|
-
// ))}
|
|
279
|
-
// </select>
|
|
242
|
+
{sortingOptions && sortingOptions.length > 0 && (
|
|
280
243
|
<ItemPicker
|
|
281
|
-
items={
|
|
282
|
-
selection={
|
|
283
|
-
label="
|
|
284
|
-
placeholder="
|
|
244
|
+
items={sortingOptions}
|
|
245
|
+
selection={sortKey || undefined}
|
|
246
|
+
label="Sort by"
|
|
247
|
+
placeholder="Sort by"
|
|
285
248
|
classModifier="travel-class-picker__items"
|
|
286
|
-
onPick={
|
|
249
|
+
onPick={handleSortChange}
|
|
287
250
|
/>
|
|
288
251
|
)}
|
|
289
252
|
</div>
|
|
@@ -293,27 +256,19 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
293
256
|
<span className="search__result-row-text">
|
|
294
257
|
{!isLoading && (
|
|
295
258
|
<>
|
|
296
|
-
{
|
|
259
|
+
{results?.length ?? 4} {context.translations?.totalResultsLabel}
|
|
297
260
|
</>
|
|
298
261
|
)}
|
|
299
262
|
</span>
|
|
300
|
-
{!isMobile &&
|
|
263
|
+
{!isMobile && sortingOptions && sortingOptions.length > 0 && (
|
|
301
264
|
<div className="search__result-row-filter">
|
|
302
|
-
{/* <span className="search__result-row-text">{context.translations?.sortBy}</span>
|
|
303
|
-
<select className="search__result-row-button" value={sortKey ?? ''} onChange={(e) => handleSortChange(e.target.value)}>
|
|
304
|
-
{context.sortingOptions.map((opt) => (
|
|
305
|
-
<option key={opt.key} value={opt.key}>
|
|
306
|
-
{opt.label}
|
|
307
|
-
</option>
|
|
308
|
-
))}
|
|
309
|
-
</select> */}
|
|
310
265
|
<ItemPicker
|
|
311
|
-
items={
|
|
312
|
-
selection={
|
|
313
|
-
label="
|
|
314
|
-
placeholder="
|
|
266
|
+
items={sortingOptions}
|
|
267
|
+
selection={sortKey || undefined}
|
|
268
|
+
label="Sort by"
|
|
269
|
+
placeholder="Sort by"
|
|
315
270
|
classModifier="travel-class-picker__items"
|
|
316
|
-
onPick={
|
|
271
|
+
onPick={handleSortChange}
|
|
317
272
|
/>
|
|
318
273
|
</div>
|
|
319
274
|
)}
|
|
@@ -324,12 +279,12 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
324
279
|
|
|
325
280
|
{context.showRoundTripResults && <RoundTripResults />}
|
|
326
281
|
|
|
327
|
-
{context.showFlightResults && <FlightResults isDeparture={true} />}
|
|
282
|
+
{/* {context.showFlightResults && <FlightResults isDeparture={true} />} */}
|
|
328
283
|
|
|
329
284
|
{context.showHotelAccommodationResults && <HotelAccommodationResults isLoading={isLoading} context={context} />}
|
|
330
|
-
{context.showFlightAccommodationResults && <FlightAccommodationResults />}
|
|
285
|
+
{/* {context.showFlightAccommodationResults && <FlightAccommodationResults />} */}
|
|
331
286
|
|
|
332
|
-
{context.showFlightResults && <FlightResults isDeparture={false} />}
|
|
287
|
+
{/* {context.showFlightResults && <FlightResults isDeparture={false} />} */}
|
|
333
288
|
</div>
|
|
334
289
|
</div>
|
|
335
290
|
</div>
|
|
@@ -1,20 +1,39 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import Icon from '../icon';
|
|
3
|
+
import { useDispatch, useSelector } from 'react-redux';
|
|
4
|
+
import { SearchResultsRootState } from '../../store/search-results-store';
|
|
5
|
+
import { setActiveTab } from '../../store/search-results-slice';
|
|
3
6
|
|
|
4
7
|
interface TabViewsProps {}
|
|
5
8
|
|
|
6
9
|
const TabViews: React.FC<TabViewsProps> = () => {
|
|
10
|
+
const dispatch = useDispatch();
|
|
11
|
+
const { activeTab } = useSelector((state: SearchResultsRootState) => state.searchResults);
|
|
12
|
+
|
|
13
|
+
const handleSortChange = (tab: string) => {
|
|
14
|
+
dispatch(setActiveTab(tab));
|
|
15
|
+
};
|
|
16
|
+
|
|
7
17
|
return (
|
|
8
18
|
<div className="search__results__tab-views">
|
|
9
|
-
<button
|
|
19
|
+
<button
|
|
20
|
+
type="button"
|
|
21
|
+
className={`search__results__tab-view ${activeTab === 'extended' ? 'search__results__tab-view--active' : ''}`}
|
|
22
|
+
onClick={() => handleSortChange('extended')}>
|
|
10
23
|
<Icon name="ui-extended" height={16} />
|
|
11
24
|
Extended
|
|
12
25
|
</button>
|
|
13
|
-
<button
|
|
26
|
+
<button
|
|
27
|
+
type="button"
|
|
28
|
+
className={`search__results__tab-view ${activeTab === 'compact' ? 'search__results__tab-view--active' : ''}`}
|
|
29
|
+
onClick={() => handleSortChange('compact')}>
|
|
14
30
|
<Icon name="ui-compact" height={16} />
|
|
15
31
|
Compact
|
|
16
32
|
</button>
|
|
17
|
-
<button
|
|
33
|
+
<button
|
|
34
|
+
type="button"
|
|
35
|
+
className={`search__results__tab-view ${activeTab === 'comparator' ? 'search__results__tab-view--active' : ''}`}
|
|
36
|
+
onClick={() => handleSortChange('comparator')}>
|
|
18
37
|
<Icon name="ui-comparator" height={16} />
|
|
19
38
|
Comparator
|
|
20
39
|
</button>
|
|
@@ -48,14 +48,6 @@ const FlightSearchResultsSelfContained: React.FC<FlightSearchResultsSelfContaine
|
|
|
48
48
|
}
|
|
49
49
|
];
|
|
50
50
|
|
|
51
|
-
// Sorting options
|
|
52
|
-
const sortingOptions: SortingOption[] = [
|
|
53
|
-
{ key: 'price-asc', label: 'Prijs oplopend' },
|
|
54
|
-
{ key: 'price-desc', label: 'Prijs aflopend' }
|
|
55
|
-
// { key: 'departuretime-desc', label: 'Vertrektijd oplopend' },
|
|
56
|
-
// { key: 'departuretime-desc', label: 'Vertrektijd aflopend' }
|
|
57
|
-
];
|
|
58
|
-
|
|
59
51
|
const customCardRenderer = (result: SearchResult): ReactNode => {
|
|
60
52
|
switch (result.type) {
|
|
61
53
|
case 'flight':
|
|
@@ -248,12 +240,7 @@ const FlightSearchResultsSelfContained: React.FC<FlightSearchResultsSelfContaine
|
|
|
248
240
|
console.log('Clicked on card with id:', id);
|
|
249
241
|
},
|
|
250
242
|
|
|
251
|
-
// Sorting
|
|
252
|
-
sortingOptions,
|
|
253
243
|
filters,
|
|
254
|
-
onSortChange: (sortKey) => {
|
|
255
|
-
console.log('Sort changed to:', sortKey);
|
|
256
|
-
},
|
|
257
244
|
|
|
258
245
|
// Map View
|
|
259
246
|
showMapView: false,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { ReactNode } from 'react';
|
|
2
|
-
import { SearchResult, SearchResultsConfiguration
|
|
2
|
+
import { SearchResult, SearchResultsConfiguration } from '../../types';
|
|
3
3
|
import SearchResults from '../..';
|
|
4
4
|
import { Navbar, Footer } from '../../../../src';
|
|
5
5
|
import { TideLogo, language, languages, topLinks, navItems } from '../../../../src/content/navbar/placeholderData';
|
|
@@ -8,12 +8,6 @@ import Icon from '../../components/icon';
|
|
|
8
8
|
interface HotelFlightSearchResultsSelfContainedProps {}
|
|
9
9
|
|
|
10
10
|
const HotelFlightSearchResultsSelfContained: React.FC<HotelFlightSearchResultsSelfContainedProps> = () => {
|
|
11
|
-
const sortingOptions: SortingOption[] = [
|
|
12
|
-
{ key: 'price-asc', label: 'Price: Low to High' },
|
|
13
|
-
{ key: 'price-desc', label: 'Price: High to Low' },
|
|
14
|
-
{ key: 'rating-desc', label: 'Top Rated' }
|
|
15
|
-
];
|
|
16
|
-
|
|
17
11
|
const customCardRenderer = (result: SearchResult): ReactNode => {
|
|
18
12
|
switch (result.type) {
|
|
19
13
|
case 'hotel':
|
|
@@ -99,7 +93,6 @@ const HotelFlightSearchResultsSelfContained: React.FC<HotelFlightSearchResultsSe
|
|
|
99
93
|
apiKey: 'e9b95d79-de4c-41d6-ab7e-3dd429873058',
|
|
100
94
|
catalogueIds: [1, 2]
|
|
101
95
|
},
|
|
102
|
-
sortingOptions: sortingOptions,
|
|
103
96
|
showTabViews: true,
|
|
104
97
|
showFlightResults: true,
|
|
105
98
|
showHotelAccommodationResults: true,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { ReactNode } from 'react';
|
|
2
|
-
import { Filter, SearchResult, SearchResultsConfiguration
|
|
2
|
+
import { Filter, SearchResult, SearchResultsConfiguration } from '../../types';
|
|
3
3
|
import SearchResults from '../..';
|
|
4
4
|
import { Navbar, Footer } from '../../../../src';
|
|
5
5
|
import { TideLogo, language, languages, topLinks, navItems } from '../../../../src/content/navbar/placeholderData';
|
|
@@ -48,13 +48,6 @@ const HotelSearchResultsSelfContained: React.FC<HotelSearchResultsSelfContainedP
|
|
|
48
48
|
}
|
|
49
49
|
];
|
|
50
50
|
|
|
51
|
-
// Sorting options
|
|
52
|
-
const sortingOptions: SortingOption[] = [
|
|
53
|
-
{ key: 'price-asc', label: 'Price: Low to High' },
|
|
54
|
-
{ key: 'price-desc', label: 'Price: High to Low' },
|
|
55
|
-
{ key: 'rating-desc', label: 'Top Rated' }
|
|
56
|
-
];
|
|
57
|
-
|
|
58
51
|
const customCardRenderer = (result: SearchResult): ReactNode => {
|
|
59
52
|
switch (result.type) {
|
|
60
53
|
case 'hotel':
|
|
@@ -168,12 +161,6 @@ const HotelSearchResultsSelfContained: React.FC<HotelSearchResultsSelfContainedP
|
|
|
168
161
|
},
|
|
169
162
|
customCardRenderer,
|
|
170
163
|
|
|
171
|
-
// Sorting
|
|
172
|
-
sortingOptions,
|
|
173
|
-
onSortChange: (sortKey) => {
|
|
174
|
-
console.log('Sort changed to:', sortKey);
|
|
175
|
-
},
|
|
176
|
-
|
|
177
164
|
// Map View
|
|
178
165
|
showMapView: false,
|
|
179
166
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { SearchResultsConfiguration
|
|
2
|
+
import { SearchResultsConfiguration } from '../../types';
|
|
3
3
|
import SearchResults from '../..';
|
|
4
4
|
import { Navbar, Footer } from '../../../../src';
|
|
5
5
|
import { TideLogo, language, languages, topLinks, navItems } from '../../../../src/content/navbar/placeholderData';
|
|
@@ -7,13 +7,6 @@ import { TideLogo, language, languages, topLinks, navItems } from '../../../../s
|
|
|
7
7
|
interface RoundtripSearchResultsSelfContainedProps {}
|
|
8
8
|
|
|
9
9
|
const RoundtripSearchResultsSelfContained: React.FC<RoundtripSearchResultsSelfContainedProps> = () => {
|
|
10
|
-
// Sorting options
|
|
11
|
-
const sortingOptions: SortingOption[] = [
|
|
12
|
-
{ key: 'price-asc', label: 'Price: Low to High' },
|
|
13
|
-
{ key: 'price-desc', label: 'Price: High to Low' },
|
|
14
|
-
{ key: 'rating-desc', label: 'Top Rated' }
|
|
15
|
-
];
|
|
16
|
-
|
|
17
10
|
const configuration: SearchResultsConfiguration = {
|
|
18
11
|
type: 'roundTrip',
|
|
19
12
|
showFilters: true,
|
|
@@ -22,7 +15,6 @@ const RoundtripSearchResultsSelfContained: React.FC<RoundtripSearchResultsSelfCo
|
|
|
22
15
|
apiKey: 'e9b95d79-de4c-41d6-ab7e-3dd429873058',
|
|
23
16
|
catalogueIds: [1, 2]
|
|
24
17
|
},
|
|
25
|
-
sortingOptions: sortingOptions,
|
|
26
18
|
showTabViews: false,
|
|
27
19
|
showFlightResults: false,
|
|
28
20
|
showHotelAccommodationResults: false,
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
|
2
|
-
import { Filter
|
|
2
|
+
import { Filter } from '../types';
|
|
3
|
+
import { BookingPackageItem } from '@qite/tide-client/build/types';
|
|
3
4
|
|
|
4
5
|
export interface SearchResultsState {
|
|
5
|
-
results:
|
|
6
|
+
results: BookingPackageItem[];
|
|
6
7
|
isLoading: boolean;
|
|
7
8
|
filters: Filter[];
|
|
8
9
|
sortKey: string | null;
|
|
10
|
+
activeTab: string | null;
|
|
9
11
|
currentPage: number;
|
|
10
12
|
}
|
|
11
13
|
|
|
@@ -14,6 +16,7 @@ const initialState: SearchResultsState = {
|
|
|
14
16
|
isLoading: false,
|
|
15
17
|
filters: [],
|
|
16
18
|
sortKey: null,
|
|
19
|
+
activeTab: 'compact',
|
|
17
20
|
currentPage: 1
|
|
18
21
|
};
|
|
19
22
|
|
|
@@ -21,7 +24,7 @@ const searchResultsSlice = createSlice({
|
|
|
21
24
|
name: 'searchResults',
|
|
22
25
|
initialState,
|
|
23
26
|
reducers: {
|
|
24
|
-
setResults(state, action: PayloadAction<{ results:
|
|
27
|
+
setResults(state, action: PayloadAction<{ results: BookingPackageItem[] }>) {
|
|
25
28
|
state.results = action.payload.results;
|
|
26
29
|
},
|
|
27
30
|
setIsLoading(state, action: PayloadAction<boolean>) {
|
|
@@ -45,6 +48,9 @@ const searchResultsSlice = createSlice({
|
|
|
45
48
|
setSortKey(state, action: PayloadAction<string | null>) {
|
|
46
49
|
state.sortKey = action.payload;
|
|
47
50
|
},
|
|
51
|
+
setActiveTab(state, action: PayloadAction<string | null>) {
|
|
52
|
+
state.activeTab = action.payload;
|
|
53
|
+
},
|
|
48
54
|
setCurrentPage(state, action: PayloadAction<number>) {
|
|
49
55
|
state.currentPage = action.payload;
|
|
50
56
|
},
|
|
@@ -53,11 +59,12 @@ const searchResultsSlice = createSlice({
|
|
|
53
59
|
state.isLoading = false;
|
|
54
60
|
state.filters = [];
|
|
55
61
|
state.sortKey = null;
|
|
62
|
+
state.activeTab = 'compact';
|
|
56
63
|
state.currentPage = 1;
|
|
57
64
|
}
|
|
58
65
|
}
|
|
59
66
|
});
|
|
60
67
|
|
|
61
|
-
export const { setResults, setIsLoading, setFilters, resetFilters, setSortKey, setCurrentPage, resetSearchState } = searchResultsSlice.actions;
|
|
68
|
+
export const { setResults, setIsLoading, setFilters, resetFilters, setSortKey, setActiveTab, setCurrentPage, resetSearchState } = searchResultsSlice.actions;
|
|
62
69
|
|
|
63
70
|
export default searchResultsSlice.reducer;
|
|
@@ -29,10 +29,6 @@ export interface SearchResultsConfiguration {
|
|
|
29
29
|
customCardRenderer?: (result: SearchResult) => ReactNode;
|
|
30
30
|
onResultClick?: (id: string) => void;
|
|
31
31
|
|
|
32
|
-
// Sorting
|
|
33
|
-
sortingOptions?: SortingOption[];
|
|
34
|
-
onSortChange?: (sortKey: string) => void;
|
|
35
|
-
|
|
36
32
|
// Map view
|
|
37
33
|
// not supported for now
|
|
38
34
|
showMapView?: boolean;
|
|
@@ -59,6 +55,8 @@ export interface SearchResultsConfiguration {
|
|
|
59
55
|
loading?: string;
|
|
60
56
|
searchResultCTA?: string;
|
|
61
57
|
};
|
|
58
|
+
|
|
59
|
+
cmsHotelData?: any[];
|
|
62
60
|
}
|
|
63
61
|
|
|
64
62
|
export type FilterType = 'checkbox' | 'toggle' | 'slider' | 'star-rating';
|
|
@@ -86,17 +84,6 @@ export interface Filter {
|
|
|
86
84
|
selectedRating?: number; // For star-rating type
|
|
87
85
|
}
|
|
88
86
|
|
|
89
|
-
export interface Sort {
|
|
90
|
-
label: string;
|
|
91
|
-
icon?: ReactNode;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
export interface SortingOption {
|
|
95
|
-
key: 'price-asc' | 'price-desc' | 'duration-asc' | 'duration-desc' | 'rating-asc' | 'rating-desc';
|
|
96
|
-
label: string;
|
|
97
|
-
icon?: ReactNode;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
87
|
export interface PaginationConfig {
|
|
101
88
|
totalResults: number;
|
|
102
89
|
currentPage: number;
|
|
@@ -112,9 +99,11 @@ export interface BaseSearchResult {
|
|
|
112
99
|
description?: string;
|
|
113
100
|
location?: string;
|
|
114
101
|
tags?: Tag[];
|
|
115
|
-
price: string;
|
|
102
|
+
price: string | number;
|
|
116
103
|
ctaText: string;
|
|
117
104
|
stars?: number;
|
|
105
|
+
accommodation?: string;
|
|
106
|
+
regime?: string;
|
|
118
107
|
}
|
|
119
108
|
|
|
120
109
|
export interface HotelResult extends BaseSearchResult {
|
|
@@ -159,3 +148,9 @@ export interface TravelClass {
|
|
|
159
148
|
label: string;
|
|
160
149
|
icon?: ReactNode;
|
|
161
150
|
}
|
|
151
|
+
|
|
152
|
+
export interface SortingOption {
|
|
153
|
+
key: 'price-asc' | 'price-desc' | 'departure-date' | 'duration-asc' | 'duration-desc' | 'rating-asc' | 'rating-desc';
|
|
154
|
+
label: string;
|
|
155
|
+
icon?: ReactNode;
|
|
156
|
+
}
|
|
@@ -122,7 +122,8 @@
|
|
|
122
122
|
"FLIGHT_DEPARTURE": "Departure",
|
|
123
123
|
"FLIGHT_ARRIVAL": "Arrival",
|
|
124
124
|
"ON_REQUEST": "On request",
|
|
125
|
-
"CHANGES": "transfers"
|
|
125
|
+
"CHANGES": "transfers",
|
|
126
|
+
"PACKAGE_NOT_AVAILABLE": "Package not available"
|
|
126
127
|
},
|
|
127
128
|
"TRAVELERS_FORM": {
|
|
128
129
|
"AGE": "Age",
|
|
@@ -155,6 +156,9 @@
|
|
|
155
156
|
"BOOK_WITH_AGENT": "I want to book through my local travel agent",
|
|
156
157
|
"CHOOSE_OFFICE": "I choose an office",
|
|
157
158
|
"PERSON": "Person",
|
|
159
|
+
"REMOVE_TRAVELER": "Remove traveler",
|
|
160
|
+
"ADD_TRAVELER": "Add traveler",
|
|
161
|
+
"ADD_ROOM": "Add travel party",
|
|
158
162
|
"COUNTRIES": {
|
|
159
163
|
"BELGIUM": "Belgium",
|
|
160
164
|
"NETHERLANDS": "Netherlands",
|
|
@@ -122,7 +122,8 @@
|
|
|
122
122
|
"FLIGHT_DEPARTURE": "Départ",
|
|
123
123
|
"FLIGHT_ARRIVAL": "Arrivée",
|
|
124
124
|
"ON_REQUEST": "Sur demande",
|
|
125
|
-
"CHANGES": "correspondances"
|
|
125
|
+
"CHANGES": "correspondances",
|
|
126
|
+
"PACKAGE_NOT_AVAILABLE": "Forfait non disponible"
|
|
126
127
|
},
|
|
127
128
|
"TRAVELERS_FORM": {
|
|
128
129
|
"AGE": "Age",
|
|
@@ -155,6 +156,9 @@
|
|
|
155
156
|
"BOOK_WITH_AGENT": "Je souhaite réserver auprès de mon agent de voyage local",
|
|
156
157
|
"CHOOSE_OFFICE": "Je choisis une agence",
|
|
157
158
|
"PERSON": "Personne",
|
|
159
|
+
"REMOVE_TRAVELER": "Supprimer le voyageur",
|
|
160
|
+
"ADD_TRAVELER": "Ajouter un voyageur",
|
|
161
|
+
"ADD_ROOM": "Ajouter un groupe de voyageurs",
|
|
158
162
|
"COUNTRIES": {
|
|
159
163
|
"BELGIUM": "Belgique",
|
|
160
164
|
"NETHERLANDS": "Pays-Bas",
|
|
@@ -122,7 +122,8 @@
|
|
|
122
122
|
"FLIGHT_DEPARTURE": "Vertrek",
|
|
123
123
|
"FLIGHT_ARRIVAL": "Aankomst",
|
|
124
124
|
"ON_REQUEST": "Op aanvraag",
|
|
125
|
-
"CHANGES": "overstappen"
|
|
125
|
+
"CHANGES": "overstappen",
|
|
126
|
+
"PACKAGE_NOT_AVAILABLE": "Pakket niet beschikbaar"
|
|
126
127
|
},
|
|
127
128
|
"TRAVELERS_FORM": {
|
|
128
129
|
"AGE": "Leeftijd",
|
|
@@ -155,6 +156,9 @@
|
|
|
155
156
|
"BOOK_WITH_AGENT": "Ik wens te boeken bij mijn lokale reisagent",
|
|
156
157
|
"CHOOSE_OFFICE": "Ik kies een kantoor",
|
|
157
158
|
"PERSON": "Persoon",
|
|
159
|
+
"REMOVE_TRAVELER": "Verwijder reiziger",
|
|
160
|
+
"ADD_TRAVELER": "Voeg reiziger toe",
|
|
161
|
+
"ADD_ROOM": "Voeg reisgezelschap toe",
|
|
158
162
|
"COUNTRIES": {
|
|
159
163
|
"BELGIUM": "België",
|
|
160
164
|
"NETHERLANDS": "Nederland",
|