@qite/tide-booking-component 1.4.104 → 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.
- package/build/build-cjs/index.js +1079 -116
- package/build/build-cjs/src/search-results/components/excursions/day-by-day-excursions.d.ts +4 -0
- package/build/build-cjs/src/search-results/components/excursions/excursion-details.d.ts +3 -0
- package/build/build-cjs/src/search-results/components/excursions/excursion-results.d.ts +8 -0
- package/build/build-cjs/src/search-results/store/search-results-selectors.d.ts +122 -0
- package/build/build-cjs/src/search-results/store/search-results-slice.d.ts +30 -2
- package/build/build-cjs/src/search-results/types.d.ts +27 -1
- package/build/build-cjs/src/search-results/utils/query-utils.d.ts +1 -0
- package/build/build-cjs/src/shared/components/flyin/flyin.d.ts +2 -1
- package/build/build-cjs/src/shared/utils/localization-util.d.ts +3 -0
- package/build/build-cjs/src/shared/utils/tide-api-utils.d.ts +6 -0
- package/build/build-esm/index.js +1071 -116
- package/build/build-esm/src/search-results/components/excursions/day-by-day-excursions.d.ts +4 -0
- package/build/build-esm/src/search-results/components/excursions/excursion-details.d.ts +3 -0
- package/build/build-esm/src/search-results/components/excursions/excursion-results.d.ts +8 -0
- package/build/build-esm/src/search-results/store/search-results-selectors.d.ts +122 -0
- package/build/build-esm/src/search-results/store/search-results-slice.d.ts +30 -2
- package/build/build-esm/src/search-results/types.d.ts +27 -1
- package/build/build-esm/src/search-results/utils/query-utils.d.ts +1 -0
- package/build/build-esm/src/shared/components/flyin/flyin.d.ts +2 -1
- package/build/build-esm/src/shared/utils/localization-util.d.ts +3 -0
- package/build/build-esm/src/shared/utils/tide-api-utils.d.ts +6 -0
- package/package.json +2 -2
- package/src/search-results/components/excursions/day-by-day-excursions.tsx +169 -0
- package/src/search-results/components/excursions/excursion-details.tsx +340 -0
- package/src/search-results/components/excursions/excursion-results.tsx +186 -0
- package/src/search-results/components/hotel/hotel-card.tsx +0 -3
- package/src/search-results/components/icon.tsx +1 -4
- package/src/search-results/components/search-results-container/search-results-container.tsx +90 -28
- package/src/search-results/store/search-results-selectors.ts +11 -0
- package/src/search-results/store/search-results-slice.ts +46 -3
- package/src/search-results/types.ts +42 -1
- package/src/search-results/utils/query-utils.ts +1 -0
- package/src/shared/components/flyin/accommodation-flyin.tsx +4 -2
- package/src/shared/components/flyin/flights-flyin.tsx +3 -1
- package/src/shared/components/flyin/flyin.tsx +18 -6
- package/src/shared/components/flyin/group-tour-flyin.tsx +3 -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/utils/localization-util.ts +14 -0
- package/src/shared/utils/tide-api-utils.ts +8 -0
- package/styles/components/_search.scss +11 -1
|
@@ -85,6 +85,7 @@ import {
|
|
|
85
85
|
} from '../../utils/search-results-utils';
|
|
86
86
|
import {
|
|
87
87
|
ACCOMMODATION_SERVICE_TYPE,
|
|
88
|
+
EXCURSION_SERVICE_TYPE,
|
|
88
89
|
FLIGHT_SERVICE_TYPE,
|
|
89
90
|
getDepartureAirportFromEntry,
|
|
90
91
|
getDestinationAirportFromEntry,
|
|
@@ -109,12 +110,14 @@ import {
|
|
|
109
110
|
selectUniqueOutwardFlights,
|
|
110
111
|
selectUniqueReturnFlights
|
|
111
112
|
} from '../../store/search-results-selectors';
|
|
113
|
+
import DayByDayExcursions from '../excursions/day-by-day-excursions';
|
|
112
114
|
|
|
113
115
|
type BuildPackagingEntryPartialArgs = {
|
|
114
116
|
sourceEntry: PackagingEntry | null | undefined;
|
|
115
117
|
selectedHotelCode: string | null | undefined;
|
|
116
118
|
accommodationResults: PackagingAccommodationResponse[];
|
|
117
119
|
selectedFlight: PackagingFlightResponse | null;
|
|
120
|
+
confirmedExcursionsByDay: Record<string, PackagingAccommodationResponse[]>;
|
|
118
121
|
seed: SearchSeed;
|
|
119
122
|
transactionId: string;
|
|
120
123
|
language: string;
|
|
@@ -148,7 +151,8 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
148
151
|
transactionId,
|
|
149
152
|
flyInType,
|
|
150
153
|
itinerary,
|
|
151
|
-
packagingFlightResults
|
|
154
|
+
packagingFlightResults,
|
|
155
|
+
confirmedExcursionsByDay
|
|
152
156
|
} = useSelector((state: SearchResultsRootState) => state.searchResults);
|
|
153
157
|
|
|
154
158
|
const isMobile = useMediaQuery('(max-width: 1200px)');
|
|
@@ -1099,11 +1103,12 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
1099
1103
|
selectedHotelCode: selectedPackagingAccoResultCode,
|
|
1100
1104
|
accommodationResults: packagingAccoResults,
|
|
1101
1105
|
selectedFlight: selectedCombinationFlight ?? null,
|
|
1106
|
+
confirmedExcursionsByDay,
|
|
1102
1107
|
seed,
|
|
1103
1108
|
transactionId: transactionId ?? context.packagingEntry?.transactionId ?? '',
|
|
1104
1109
|
language: context.languageCode ?? 'en-GB'
|
|
1105
1110
|
});
|
|
1106
|
-
|
|
1111
|
+
console.log('Built nextEntry', nextEntry);
|
|
1107
1112
|
if (!nextEntry) return;
|
|
1108
1113
|
|
|
1109
1114
|
dispatch(setEditablePackagingEntry(nextEntry));
|
|
@@ -1118,6 +1123,7 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
1118
1123
|
packagingAccoResults,
|
|
1119
1124
|
packagingAccoSearchDetails,
|
|
1120
1125
|
selectedCombinationFlight,
|
|
1126
|
+
confirmedExcursionsByDay,
|
|
1121
1127
|
transactionId,
|
|
1122
1128
|
dispatch
|
|
1123
1129
|
]);
|
|
@@ -1126,23 +1132,70 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
1126
1132
|
|
|
1127
1133
|
const removeFlightLines = (lines: PackagingEntryLine[]) => lines.filter((line) => line.serviceType !== FLIGHT_SERVICE_TYPE);
|
|
1128
1134
|
|
|
1135
|
+
const removeExcursionLines = (lines: PackagingEntryLine[]) => lines.filter((line) => line.serviceType !== EXCURSION_SERVICE_TYPE);
|
|
1136
|
+
|
|
1129
1137
|
const buildAccommodationLinesFromSelection = (selectedHotel: PackagingAccommodationResponse, seed: SearchSeed): PackagingEntryLine[] => {
|
|
1130
|
-
|
|
1138
|
+
return buildPackagingAccommodationLines(selectedHotel, seed, ACCOMMODATION_SERVICE_TYPE);
|
|
1139
|
+
};
|
|
1140
|
+
|
|
1141
|
+
const buildExcursionLinesFromConfirmedDays = (confirmedExcursionsByDay: Record<string, PackagingAccommodationResponse[]>): PackagingEntryLine[] => {
|
|
1142
|
+
return Object.values(confirmedExcursionsByDay)
|
|
1143
|
+
.flat()
|
|
1144
|
+
.flatMap((excursion) => {
|
|
1145
|
+
const selectedOptions = excursion.rooms.flatMap((room) => room.options.filter((option) => option.isSelected));
|
|
1146
|
+
|
|
1147
|
+
const parentGuid = crypto.randomUUID();
|
|
1148
|
+
|
|
1149
|
+
return selectedOptions.map(
|
|
1150
|
+
(option, index) =>
|
|
1151
|
+
({
|
|
1152
|
+
guid: option.guid ?? crypto.randomUUID(),
|
|
1153
|
+
moment: '',
|
|
1154
|
+
parentGuid: index === 0 ? null : parentGuid,
|
|
1155
|
+
order: index,
|
|
1156
|
+
isChanged: true,
|
|
1157
|
+
from: excursion.fromDate,
|
|
1158
|
+
to: excursion.toDate,
|
|
1159
|
+
serviceType: EXCURSION_SERVICE_TYPE,
|
|
1160
|
+
productName: excursion.name,
|
|
1161
|
+
productCode: excursion.code,
|
|
1162
|
+
accommodationName: option.accommodationName,
|
|
1163
|
+
accommodationCode: option.accommodationCode,
|
|
1164
|
+
regimeName: option.regimeName,
|
|
1165
|
+
regimeCode: option.regimeCode,
|
|
1166
|
+
country: excursion.countryId ? { id: excursion.countryId, name: excursion.countryName, localizations: [] } : null,
|
|
1167
|
+
region: excursion.regionId ? { id: excursion.regionId, name: excursion.regionName, localizations: [] } : null,
|
|
1168
|
+
oord: excursion.oordId ? { id: excursion.oordId, name: excursion.oordName, localizations: [] } : null,
|
|
1169
|
+
location: excursion.locationId ? { id: excursion.locationId, name: excursion.locationName, localizations: [] } : null,
|
|
1170
|
+
longitude: excursion.longitude ?? null,
|
|
1171
|
+
latitude: excursion.latitude ?? null,
|
|
1172
|
+
pax: Array.isArray(option.paxIds)
|
|
1173
|
+
? option.paxIds.map((paxId, paxIndex) => ({
|
|
1174
|
+
paxId: paxId,
|
|
1175
|
+
room: 0,
|
|
1176
|
+
order: paxIndex
|
|
1177
|
+
}))
|
|
1178
|
+
: [],
|
|
1179
|
+
flightInformation: null
|
|
1180
|
+
} satisfies PackagingEntryLine)
|
|
1181
|
+
);
|
|
1182
|
+
});
|
|
1183
|
+
};
|
|
1184
|
+
|
|
1185
|
+
const buildPackagingAccommodationLines = (selectedItem: PackagingAccommodationResponse, seed: SearchSeed, serviceType: number): PackagingEntryLine[] => {
|
|
1186
|
+
if (!selectedItem) return [];
|
|
1131
1187
|
const parentGuid = crypto.randomUUID();
|
|
1132
1188
|
|
|
1133
|
-
return
|
|
1189
|
+
return selectedItem.rooms
|
|
1134
1190
|
.filter((room) => room.options.some((o) => o.isSelected))
|
|
1135
1191
|
.map((room, index) => {
|
|
1136
1192
|
const option = room.options.find((o) => o.isSelected)!;
|
|
1137
1193
|
|
|
1138
|
-
const pax =
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
order: paxIndex
|
|
1144
|
-
}))
|
|
1145
|
-
) ?? [];
|
|
1194
|
+
const pax = option.paxIds.map((p, paxIndex) => ({
|
|
1195
|
+
paxId: p,
|
|
1196
|
+
room: index,
|
|
1197
|
+
order: paxIndex
|
|
1198
|
+
}));
|
|
1146
1199
|
|
|
1147
1200
|
return {
|
|
1148
1201
|
guid: option.guid ?? crypto.randomUUID(),
|
|
@@ -1150,21 +1203,21 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
1150
1203
|
parentGuid: index === 0 ? null : parentGuid,
|
|
1151
1204
|
order: index,
|
|
1152
1205
|
isChanged: true,
|
|
1153
|
-
from:
|
|
1154
|
-
to:
|
|
1155
|
-
serviceType:
|
|
1156
|
-
productName:
|
|
1157
|
-
productCode:
|
|
1206
|
+
from: selectedItem.fromDate,
|
|
1207
|
+
to: selectedItem.toDate,
|
|
1208
|
+
serviceType: serviceType,
|
|
1209
|
+
productName: selectedItem.name,
|
|
1210
|
+
productCode: selectedItem.code,
|
|
1158
1211
|
accommodationName: option.accommodationName,
|
|
1159
1212
|
accommodationCode: option.accommodationCode,
|
|
1160
1213
|
regimeName: option.regimeName,
|
|
1161
1214
|
regimeCode: option.regimeCode,
|
|
1162
|
-
country:
|
|
1163
|
-
region:
|
|
1164
|
-
oord:
|
|
1165
|
-
location:
|
|
1166
|
-
longitude:
|
|
1167
|
-
latitude:
|
|
1215
|
+
country: selectedItem.countryId ? { id: selectedItem.countryId, name: selectedItem.countryName, localizations: [] } : null,
|
|
1216
|
+
region: selectedItem.regionId ? { id: selectedItem.regionId, name: selectedItem.regionName, localizations: [] } : null,
|
|
1217
|
+
oord: selectedItem.oordId ? { id: selectedItem.oordId, name: selectedItem.oordName, localizations: [] } : null,
|
|
1218
|
+
location: selectedItem.locationId ? { id: selectedItem.locationId, name: selectedItem.locationName, localizations: [] } : null,
|
|
1219
|
+
longitude: selectedItem.longitude ?? null,
|
|
1220
|
+
latitude: selectedItem.latitude ?? null,
|
|
1168
1221
|
pax,
|
|
1169
1222
|
flightInformation: null
|
|
1170
1223
|
} satisfies PackagingEntryLine;
|
|
@@ -1298,6 +1351,7 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
1298
1351
|
selectedHotelCode,
|
|
1299
1352
|
accommodationResults,
|
|
1300
1353
|
selectedFlight,
|
|
1354
|
+
confirmedExcursionsByDay,
|
|
1301
1355
|
seed,
|
|
1302
1356
|
transactionId,
|
|
1303
1357
|
language
|
|
@@ -1329,6 +1383,15 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
1329
1383
|
}
|
|
1330
1384
|
}
|
|
1331
1385
|
|
|
1386
|
+
// excursions
|
|
1387
|
+
const excursionLines = buildExcursionLinesFromConfirmedDays(confirmedExcursionsByDay);
|
|
1388
|
+
|
|
1389
|
+
nextLines = removeExcursionLines(nextLines);
|
|
1390
|
+
|
|
1391
|
+
if (excursionLines.length) {
|
|
1392
|
+
nextLines = [...nextLines, ...excursionLines];
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1332
1395
|
nextLines = nextLines.map((line, index) => ({
|
|
1333
1396
|
...line,
|
|
1334
1397
|
order: index
|
|
@@ -1353,7 +1416,7 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
1353
1416
|
return structuredClone(sourceEntry);
|
|
1354
1417
|
}
|
|
1355
1418
|
|
|
1356
|
-
let paxId =
|
|
1419
|
+
let paxId = 0;
|
|
1357
1420
|
|
|
1358
1421
|
const pax =
|
|
1359
1422
|
seed.rooms?.flatMap((room, roomIndex) =>
|
|
@@ -1498,8 +1561,6 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
1498
1561
|
{context.searchConfiguration.qsmType === PortalQsmType.GroupTour && <GroupTourResults isLoading={isLoading} />}
|
|
1499
1562
|
|
|
1500
1563
|
{context.searchConfiguration.qsmType === PortalQsmType.AccommodationAndFlight && !context.packagingEntry && context.showFlightResults && (
|
|
1501
|
-
// bookingPackageDetails?.outwardFlights &&
|
|
1502
|
-
// <FlightResults flights={bookingPackageDetails?.outwardFlights} isDeparture={true} />
|
|
1503
1564
|
<>
|
|
1504
1565
|
<div className="search__results__label search__results__label--secondary">
|
|
1505
1566
|
<div className="search__results__label__date">
|
|
@@ -1555,9 +1616,9 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
1555
1616
|
|
|
1556
1617
|
{context.showHotelAccommodationResults && !context.packagingEntry && <HotelAccommodationResults isLoading={isLoading} />}
|
|
1557
1618
|
|
|
1619
|
+
{context.searchConfiguration.qsmType === PortalQsmType.AccommodationAndFlight && !context.packagingEntry && <DayByDayExcursions />}
|
|
1620
|
+
|
|
1558
1621
|
{context.searchConfiguration.qsmType === PortalQsmType.AccommodationAndFlight && !context.packagingEntry && context.showFlightResults && (
|
|
1559
|
-
// bookingPackageDetails?.returnFlights &&
|
|
1560
|
-
// <FlightResults flights={bookingPackageDetails?.returnFlights} isDeparture={false} />
|
|
1561
1622
|
<>
|
|
1562
1623
|
<div className="search__results__label search__results__label--secondary">
|
|
1563
1624
|
<div className="search__results__label__date">
|
|
@@ -1613,6 +1674,7 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
1613
1674
|
flyInType={flyInType}
|
|
1614
1675
|
isPackageEditFlow={!!context.packagingEntry}
|
|
1615
1676
|
sortByTypes={sortByTypes}
|
|
1677
|
+
activeSearchSeed={activeSearchSeed}
|
|
1616
1678
|
/>
|
|
1617
1679
|
</>
|
|
1618
1680
|
)}
|
|
@@ -5,6 +5,17 @@ import { PackagingFlightResponse } from '@qite/tide-client';
|
|
|
5
5
|
|
|
6
6
|
const selectSearchResultsState = (state: SearchResultsRootState) => state.searchResults;
|
|
7
7
|
|
|
8
|
+
export const selectPackagingAccoResults = createSelector([selectSearchResultsState], (state) => state.packagingAccoResults);
|
|
9
|
+
export const selectSelectedPackagingAccoResultCode = createSelector([selectSearchResultsState], (state) => state.selectedPackagingAccoResultCode);
|
|
10
|
+
export const selectSelectedPackagingAccoResult = createSelector(
|
|
11
|
+
[selectPackagingAccoResults, selectSelectedPackagingAccoResultCode],
|
|
12
|
+
(packagingAccoResults, selectedPackagingAccoResultCode) => {
|
|
13
|
+
if (!selectedPackagingAccoResultCode) return null;
|
|
14
|
+
|
|
15
|
+
return packagingAccoResults.find((accoResult) => accoResult.code === selectedPackagingAccoResultCode) ?? null;
|
|
16
|
+
}
|
|
17
|
+
);
|
|
18
|
+
|
|
8
19
|
export const selectPackagingFlightResults = createSelector([selectSearchResultsState], (state) => state.packagingFlightResults);
|
|
9
20
|
export const selectFilteredPackagingFlightResults = createSelector([selectSearchResultsState], (state) => state.filteredPackagingFlightResults);
|
|
10
21
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
|
2
|
-
import { ExtendedFlightSearchResponseItem, Filter, FlyInType, SortByType } from '../types';
|
|
2
|
+
import { ExcursionSearchParams, ExtendedFlightSearchResponseItem, Filter, FlyInType, SortByType } from '../types';
|
|
3
3
|
import {
|
|
4
4
|
BookingPackage,
|
|
5
5
|
BookingPackageItem,
|
|
@@ -52,6 +52,10 @@ export interface SearchResultsState {
|
|
|
52
52
|
|
|
53
53
|
itinerary: ClientPortalItinerary | null;
|
|
54
54
|
editablePackagingEntry: PackagingEntry | null;
|
|
55
|
+
|
|
56
|
+
excursionSearchParams: ExcursionSearchParams | null;
|
|
57
|
+
selectedExcursionSearchResult: PackagingAccommodationResponse | null;
|
|
58
|
+
confirmedExcursionsByDay: Record<string, PackagingAccommodationResponse[]>;
|
|
55
59
|
}
|
|
56
60
|
|
|
57
61
|
const initialState: SearchResultsState = {
|
|
@@ -95,7 +99,11 @@ const initialState: SearchResultsState = {
|
|
|
95
99
|
flyInType: null,
|
|
96
100
|
|
|
97
101
|
editablePackagingEntry: null,
|
|
98
|
-
itinerary: null
|
|
102
|
+
itinerary: null,
|
|
103
|
+
|
|
104
|
+
excursionSearchParams: null,
|
|
105
|
+
selectedExcursionSearchResult: null,
|
|
106
|
+
confirmedExcursionsByDay: {}
|
|
99
107
|
};
|
|
100
108
|
|
|
101
109
|
const searchResultsSlice = createSlice({
|
|
@@ -242,6 +250,36 @@ const searchResultsSlice = createSlice({
|
|
|
242
250
|
resetFlightSelection: (state) => {
|
|
243
251
|
state.selectedOutwardKey = null;
|
|
244
252
|
state.selectedReturnKey = null;
|
|
253
|
+
},
|
|
254
|
+
setExcursionSearchParams(state, action: PayloadAction<ExcursionSearchParams>) {
|
|
255
|
+
state.excursionSearchParams = action.payload;
|
|
256
|
+
},
|
|
257
|
+
setSelectedExcursionSearchResult(state, action: PayloadAction<PackagingAccommodationResponse | null>) {
|
|
258
|
+
state.selectedExcursionSearchResult = action.payload;
|
|
259
|
+
},
|
|
260
|
+
confirmExcursionForDay: (state, action: PayloadAction<{ dayKey: string; excursion: PackagingAccommodationResponse }>) => {
|
|
261
|
+
const { dayKey, excursion } = action.payload;
|
|
262
|
+
|
|
263
|
+
const existingForDay = state.confirmedExcursionsByDay[dayKey] ?? [];
|
|
264
|
+
|
|
265
|
+
const existingIndex = existingForDay.findIndex((item) => item.code === excursion.code);
|
|
266
|
+
|
|
267
|
+
if (existingIndex >= 0) {
|
|
268
|
+
// replace existing confirmed version of same excursion
|
|
269
|
+
existingForDay[existingIndex] = excursion;
|
|
270
|
+
} else {
|
|
271
|
+
existingForDay.push(excursion);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
state.confirmedExcursionsByDay[dayKey] = existingForDay;
|
|
275
|
+
},
|
|
276
|
+
removeConfirmedExcursionForDay: (state, action: PayloadAction<{ dayKey: string; excursionCode: string }>) => {
|
|
277
|
+
const { dayKey, excursionCode } = action.payload;
|
|
278
|
+
|
|
279
|
+
state.confirmedExcursionsByDay[dayKey] = (state.confirmedExcursionsByDay[dayKey] ?? []).filter((item) => item.code !== excursionCode);
|
|
280
|
+
},
|
|
281
|
+
clearConfirmedExcursionsForDay: (state, action: PayloadAction<{ dayKey: string }>) => {
|
|
282
|
+
delete state.confirmedExcursionsByDay[action.payload.dayKey];
|
|
245
283
|
}
|
|
246
284
|
}
|
|
247
285
|
});
|
|
@@ -282,7 +320,12 @@ export const {
|
|
|
282
320
|
setItinerary,
|
|
283
321
|
setSelectedOutwardKey,
|
|
284
322
|
setSelectedReturnKey,
|
|
285
|
-
resetFlightSelection
|
|
323
|
+
resetFlightSelection,
|
|
324
|
+
setExcursionSearchParams,
|
|
325
|
+
setSelectedExcursionSearchResult,
|
|
326
|
+
confirmExcursionForDay,
|
|
327
|
+
removeConfirmedExcursionForDay,
|
|
328
|
+
clearConfirmedExcursionsForDay
|
|
286
329
|
} = searchResultsSlice.actions;
|
|
287
330
|
|
|
288
331
|
export default searchResultsSlice.reducer;
|
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
BookingPackage,
|
|
3
3
|
BookingPackageRequestRoom,
|
|
4
4
|
FlightSearchResponseItem,
|
|
5
|
+
PackageMainRoom,
|
|
5
6
|
PackagingEntry,
|
|
6
7
|
WebsiteConfigurationSearchConfiguration
|
|
7
8
|
} from '@qite/tide-client';
|
|
@@ -217,4 +218,44 @@ export type SearchSeed = {
|
|
|
217
218
|
rooms: BookingPackageRequestRoom[];
|
|
218
219
|
};
|
|
219
220
|
|
|
220
|
-
export type FlyInType =
|
|
221
|
+
export type FlyInType =
|
|
222
|
+
| 'flight-outward-results'
|
|
223
|
+
| 'flight-return-results'
|
|
224
|
+
| 'flight-details'
|
|
225
|
+
| 'acco-results'
|
|
226
|
+
| 'acco-details'
|
|
227
|
+
| 'excursion-results'
|
|
228
|
+
| 'excursion-details';
|
|
229
|
+
|
|
230
|
+
export interface ExcursionSearchParams {
|
|
231
|
+
// Core: which day are we searching for
|
|
232
|
+
date: string; // ISO string (e.g. "2026-07-01")
|
|
233
|
+
|
|
234
|
+
// Optional but useful if your API expects a range
|
|
235
|
+
fromDate: string; // usually same as date
|
|
236
|
+
toDate: string; // usually same as date
|
|
237
|
+
|
|
238
|
+
// Location context
|
|
239
|
+
countryId?: number | null;
|
|
240
|
+
regionId?: number | null;
|
|
241
|
+
oordId?: number | null;
|
|
242
|
+
locationId?: number | null;
|
|
243
|
+
locationName?: string;
|
|
244
|
+
|
|
245
|
+
// Accommodation context (important for availability/pricing)
|
|
246
|
+
hotelCode: string;
|
|
247
|
+
hotelName: string;
|
|
248
|
+
|
|
249
|
+
accommodationCode: string | null;
|
|
250
|
+
accommodationName: string | null;
|
|
251
|
+
|
|
252
|
+
// Occupancy (VERY likely needed later)
|
|
253
|
+
rooms?: PackageMainRoom[];
|
|
254
|
+
|
|
255
|
+
// Optional: currency / language (depends on your backend)
|
|
256
|
+
currencyCode?: string;
|
|
257
|
+
languageCode?: string;
|
|
258
|
+
|
|
259
|
+
// Optional: traceability/debugging
|
|
260
|
+
source?: 'day-by-day-excursions';
|
|
261
|
+
}
|
|
@@ -11,6 +11,7 @@ import { range } from 'lodash';
|
|
|
11
11
|
|
|
12
12
|
export const GROUP_TOUR_SERVICE_TYPE = 1;
|
|
13
13
|
export const ACCOMMODATION_SERVICE_TYPE = 3;
|
|
14
|
+
export const EXCURSION_SERVICE_TYPE = 4;
|
|
14
15
|
export const FLIGHT_SERVICE_TYPE = 7;
|
|
15
16
|
|
|
16
17
|
export const toDateOnlyString = (value: string | Date): string => {
|
|
@@ -245,7 +245,9 @@ const AccommodationFlyIn: React.FC<AccommodationFlyInProps> = ({ isLoading, hand
|
|
|
245
245
|
</div>
|
|
246
246
|
|
|
247
247
|
<div className="flyin__footer">
|
|
248
|
-
<div className="flyin__footer__price">
|
|
248
|
+
<div className="flyin__footer__price">
|
|
249
|
+
{translations.SHARED.TOTAL_PRICE}: {calculateTotalPrice()}
|
|
250
|
+
</div>
|
|
249
251
|
|
|
250
252
|
<div className="flyin__button-wrapper">
|
|
251
253
|
<button className="cta cta--select" onClick={handleConfirm}>
|
|
@@ -409,7 +411,7 @@ const AccommodationFlyIn: React.FC<AccommodationFlyInProps> = ({ isLoading, hand
|
|
|
409
411
|
// </div>
|
|
410
412
|
|
|
411
413
|
// <div className="flyin__footer">
|
|
412
|
-
// <div className="flyin__footer__price">
|
|
414
|
+
// <div className="flyin__footer__price">{translations.SHARED.TOTAL_PRICE}: €</div>
|
|
413
415
|
// <div className="flyin__button-wrapper">
|
|
414
416
|
// <button className="cta cta--select" onClick={handleConfirm}>
|
|
415
417
|
// Toevoegen
|
|
@@ -488,7 +488,9 @@ const FlightsFlyIn: React.FC<FlightsFlyInProps> = ({ isOpen, setIsOpen }) => {
|
|
|
488
488
|
)}
|
|
489
489
|
{!flightSearchDetailsLoading && (
|
|
490
490
|
<div className="flyin__footer">
|
|
491
|
-
<div className="flyin__footer__price">
|
|
491
|
+
<div className="flyin__footer__price">
|
|
492
|
+
{translations.SHARED.TOTAL_PRICE}: €{selectedCombinationFlight?.price?.toFixed(2)}
|
|
493
|
+
</div>
|
|
492
494
|
<div className="flyin__button-wrapper">
|
|
493
495
|
<button className="cta cta--select" onClick={handleConfirm}>
|
|
494
496
|
{translations.PRODUCT.BOOK_NOW}
|
|
@@ -8,7 +8,6 @@ import {
|
|
|
8
8
|
setFlyInType,
|
|
9
9
|
setSelectedFlight,
|
|
10
10
|
setSelectedFlightDetails,
|
|
11
|
-
setSelectedPackagingAccoResult,
|
|
12
11
|
setSelectedSearchResult,
|
|
13
12
|
setSortType
|
|
14
13
|
} from '../../../search-results/store/search-results-slice';
|
|
@@ -16,7 +15,7 @@ import FlightsFlyIn from './flights-flyin';
|
|
|
16
15
|
import AccommodationFlyIn from './accommodation-flyin';
|
|
17
16
|
import { PortalQsmType } from '@qite/tide-client';
|
|
18
17
|
import GroupTourFlyIn from './group-tour-flyin';
|
|
19
|
-
import { FlyInType, SortByType } from '../../../search-results/types';
|
|
18
|
+
import { FlyInType, SearchSeed, SortByType } from '../../../search-results/types';
|
|
20
19
|
import HotelAccommodationResults from '../../../search-results/components/hotel/hotel-accommodation-results';
|
|
21
20
|
import PackageingFlightsFlyIn from './packaging-flights-flyin';
|
|
22
21
|
import SearchResultsConfigurationContext from '../../../search-results/search-results-configuration-context';
|
|
@@ -24,6 +23,8 @@ import { findSortByType, getSortingName, getTranslations } from '../../utils/loc
|
|
|
24
23
|
import Filters from '../../../search-results/components/filters/filters';
|
|
25
24
|
import { SearchResultsRootState } from '../../../search-results/store/search-results-store';
|
|
26
25
|
import ItemPicker from '../../../search-results/components/item-picker';
|
|
26
|
+
import ExcursionResults from '../../../search-results/components/excursions/excursion-results';
|
|
27
|
+
import ExcursionDetails from '../../../search-results/components/excursions/excursion-details';
|
|
27
28
|
|
|
28
29
|
type FlyInProps = {
|
|
29
30
|
srpType: PortalQsmType;
|
|
@@ -36,6 +37,7 @@ type FlyInProps = {
|
|
|
36
37
|
flyInType?: FlyInType | null;
|
|
37
38
|
isPackageEditFlow?: boolean;
|
|
38
39
|
sortByTypes?: SortByType[];
|
|
40
|
+
activeSearchSeed?: SearchSeed | null;
|
|
39
41
|
};
|
|
40
42
|
|
|
41
43
|
const FlyIn: React.FC<FlyInProps> = ({
|
|
@@ -48,7 +50,8 @@ const FlyIn: React.FC<FlyInProps> = ({
|
|
|
48
50
|
flyInType,
|
|
49
51
|
isPackageEditFlow,
|
|
50
52
|
handleConfirm,
|
|
51
|
-
sortByTypes
|
|
53
|
+
sortByTypes,
|
|
54
|
+
activeSearchSeed
|
|
52
55
|
}) => {
|
|
53
56
|
const dispatch = useDispatch();
|
|
54
57
|
const context = useContext(SearchResultsConfigurationContext);
|
|
@@ -96,7 +99,6 @@ const FlyIn: React.FC<FlyInProps> = ({
|
|
|
96
99
|
onCancelSearch();
|
|
97
100
|
} else {
|
|
98
101
|
dispatch(setSelectedSearchResult(null));
|
|
99
|
-
dispatch(setSelectedPackagingAccoResult(null));
|
|
100
102
|
}
|
|
101
103
|
dispatch(setFlyInType('acco-details'));
|
|
102
104
|
setIsOpen(false);
|
|
@@ -104,7 +106,11 @@ const FlyIn: React.FC<FlyInProps> = ({
|
|
|
104
106
|
};
|
|
105
107
|
|
|
106
108
|
const handleGoBack = () => {
|
|
107
|
-
|
|
109
|
+
if (flyInType === 'acco-details') {
|
|
110
|
+
dispatch(setFlyInType('acco-results'));
|
|
111
|
+
} else if (flyInType === 'excursion-details') {
|
|
112
|
+
dispatch(setFlyInType('excursion-results'));
|
|
113
|
+
}
|
|
108
114
|
};
|
|
109
115
|
|
|
110
116
|
const handleSortChange = (newSortKey: string, direction?: string) => {
|
|
@@ -138,12 +144,13 @@ const FlyIn: React.FC<FlyInProps> = ({
|
|
|
138
144
|
flyInType === 'flight-return-results' &&
|
|
139
145
|
`${translations.SRP.SELECT} ${translations.FLIGHTS_FORM.RETURN_FLIGHT}`}
|
|
140
146
|
{srpType === PortalQsmType.AccommodationAndFlight && flyInType === 'flight-details' && 'Select your fare'}
|
|
147
|
+
{flyInType === 'excursion-results' || (flyInType === 'excursion-details' && 'Select excursion')}
|
|
141
148
|
</h3>
|
|
142
149
|
<span className="flyin__close" onClick={() => handleClose()}>
|
|
143
150
|
<Icon name="ui-close" width={30} height={30} aria-hidden="true" />
|
|
144
151
|
</span>
|
|
145
152
|
</div>
|
|
146
|
-
{isPackageEditFlow && flyInType === 'acco-details' && (
|
|
153
|
+
{((isPackageEditFlow && flyInType === 'acco-details') || flyInType === 'excursion-details') && (
|
|
147
154
|
<div className="flyin__content-title-row">
|
|
148
155
|
<div onClick={handleGoBack} className="flyin__content-title__back">
|
|
149
156
|
<Icon name="ui-chevron" width={14} height={14} aria-hidden="true" />
|
|
@@ -204,6 +211,11 @@ const FlyIn: React.FC<FlyInProps> = ({
|
|
|
204
211
|
<PackageingFlightsFlyIn isOpen={isOpen} setIsOpen={setIsOpen} />
|
|
205
212
|
)}
|
|
206
213
|
|
|
214
|
+
{srpType === PortalQsmType.AccommodationAndFlight && flyInType === 'excursion-results' && (
|
|
215
|
+
<ExcursionResults isFlyIn={true} activeSearchSeed={activeSearchSeed} />
|
|
216
|
+
)}
|
|
217
|
+
{srpType === PortalQsmType.AccommodationAndFlight && flyInType === 'excursion-details' && <ExcursionDetails />}
|
|
218
|
+
|
|
207
219
|
{srpType === PortalQsmType.GroupTour && <GroupTourFlyIn isLoading={detailsLoading} isOpen={isOpen} setIsOpen={setIsOpen} />}
|
|
208
220
|
</div>
|
|
209
221
|
</div>
|
|
@@ -278,7 +278,9 @@ const GroupTourFlyIn: React.FC<GroupTourFlyInProps> = ({ isLoading, isOpen, setI
|
|
|
278
278
|
</div>
|
|
279
279
|
|
|
280
280
|
<div className="flyin__footer">
|
|
281
|
-
<div className="flyin__footer__price">
|
|
281
|
+
<div className="flyin__footer__price">
|
|
282
|
+
{translations.SHARED.TOTAL_PRICE}: {formatPrice(adjustedTotalPrice, bookingPackageDetails.currencyCode)}
|
|
283
|
+
</div>
|
|
282
284
|
|
|
283
285
|
<div className="flyin__button-wrapper">
|
|
284
286
|
<button className="cta cta--select" onClick={handleConfirm}>
|
|
@@ -328,7 +328,8 @@
|
|
|
328
328
|
"RETURN_DATE": "تاريخ العودة",
|
|
329
329
|
"CONFIRM": "تأكيد",
|
|
330
330
|
"TRAVELERS": "المسافرون",
|
|
331
|
-
"GROUP_TOUR": "جولة جماعية"
|
|
331
|
+
"GROUP_TOUR": "جولة جماعية",
|
|
332
|
+
"ALL_TRAVELERS": "جميع المسافرين"
|
|
332
333
|
},
|
|
333
334
|
"SRP": {
|
|
334
335
|
"SHOW_MORE": "عرض المزيد",
|
|
@@ -379,6 +380,7 @@
|
|
|
379
380
|
"DEPARTURE_TIME_DESC": "وقت المغادرة تنازلياً",
|
|
380
381
|
"DURATION_ASC": "المدة تصاعدياً",
|
|
381
382
|
"DURATION_DESC": "المدة تنازلياً",
|
|
382
|
-
"TRAVEL_GROUP": "مجموعة المسافرين"
|
|
383
|
+
"TRAVEL_GROUP": "مجموعة المسافرين",
|
|
384
|
+
"EXCURSION": "رحلة"
|
|
383
385
|
}
|
|
384
386
|
}
|
|
@@ -328,7 +328,8 @@
|
|
|
328
328
|
"RETURN_DATE": "Hjemrejsedato",
|
|
329
329
|
"CONFIRM": "Bekræft",
|
|
330
330
|
"TRAVELERS": "Rejsende",
|
|
331
|
-
"GROUP_TOUR": "Grupperejse"
|
|
331
|
+
"GROUP_TOUR": "Grupperejse",
|
|
332
|
+
"ALL_TRAVELERS": "Alle rejsende"
|
|
332
333
|
},
|
|
333
334
|
"SRP": {
|
|
334
335
|
"SHOW_MORE": "Vis flere",
|
|
@@ -379,6 +380,7 @@
|
|
|
379
380
|
"DEPARTURE_TIME_DESC": "Afgangstid faldende",
|
|
380
381
|
"DURATION_ASC": "Varighed stigende",
|
|
381
382
|
"DURATION_DESC": "Varighed faldende",
|
|
382
|
-
"TRAVEL_GROUP": "Rejseselskab"
|
|
383
|
+
"TRAVEL_GROUP": "Rejseselskab",
|
|
384
|
+
"EXCURSION": "Udflugt"
|
|
383
385
|
}
|
|
384
386
|
}
|
|
@@ -328,7 +328,8 @@
|
|
|
328
328
|
"RETURN_DATE": "Rückreisedatum",
|
|
329
329
|
"CONFIRM": "Bestätigen",
|
|
330
330
|
"TRAVELERS": "Reisende",
|
|
331
|
-
"GROUP_TOUR": "Gruppentour"
|
|
331
|
+
"GROUP_TOUR": "Gruppentour",
|
|
332
|
+
"ALL_TRAVELERS": "Alle Reisenden"
|
|
332
333
|
},
|
|
333
334
|
"SRP": {
|
|
334
335
|
"SHOW_MORE": "Mehr anzeigen",
|
|
@@ -379,6 +380,7 @@
|
|
|
379
380
|
"DEPARTURE_AIRPORTS": "Abflughäfen",
|
|
380
381
|
"ARRIVAL_AIRPORTS": "Ankunftsflughäfen",
|
|
381
382
|
"PRICE": "Preis",
|
|
382
|
-
"TRAVEL_GROUP": "Reisegruppe"
|
|
383
|
+
"TRAVEL_GROUP": "Reisegruppe",
|
|
384
|
+
"EXCURSION": "Ausflug"
|
|
383
385
|
}
|
|
384
386
|
}
|
|
@@ -332,7 +332,8 @@
|
|
|
332
332
|
"RETURN_DATE": "Return date",
|
|
333
333
|
"CONFIRM": "Confirm",
|
|
334
334
|
"TRAVELERS": "Travelers",
|
|
335
|
-
"GROUP_TOUR": "Group tour"
|
|
335
|
+
"GROUP_TOUR": "Group tour",
|
|
336
|
+
"ALL_TRAVELERS": "All travelers"
|
|
336
337
|
},
|
|
337
338
|
"SRP": {
|
|
338
339
|
"SHOW_MORE": "Show more",
|
|
@@ -383,6 +384,7 @@
|
|
|
383
384
|
"DEPARTURE_RANGE": "Departure range",
|
|
384
385
|
"DEPARTURE_AIRPORTS": "Departure airports",
|
|
385
386
|
"ARRIVAL_AIRPORTS": "Arrival airports",
|
|
386
|
-
"TRAVEL_GROUP": "Travel group"
|
|
387
|
+
"TRAVEL_GROUP": "Travel group",
|
|
388
|
+
"EXCURSION": "Excursion"
|
|
387
389
|
}
|
|
388
390
|
}
|
|
@@ -328,7 +328,8 @@
|
|
|
328
328
|
"RETURN_DATE": "Fecha de regreso",
|
|
329
329
|
"CONFIRM": "Confirmar",
|
|
330
330
|
"TRAVELERS": "Viajeros",
|
|
331
|
-
"GROUP_TOUR": "Tour grupal"
|
|
331
|
+
"GROUP_TOUR": "Tour grupal",
|
|
332
|
+
"ALL_TRAVELERS": "Todos los viajeros"
|
|
332
333
|
},
|
|
333
334
|
"SRP": {
|
|
334
335
|
"SHOW_MORE": "Mostrar más",
|
|
@@ -379,6 +380,7 @@
|
|
|
379
380
|
"DEPARTURE_AIRPORTS": "Aeropuertos de salida",
|
|
380
381
|
"ARRIVAL_AIRPORTS": "Aeropuertos de llegada",
|
|
381
382
|
"PRICE": "Precio",
|
|
382
|
-
"TRAVEL_GROUP": "Grupo de viaje"
|
|
383
|
+
"TRAVEL_GROUP": "Grupo de viaje",
|
|
384
|
+
"EXCURSION": "Excursión"
|
|
383
385
|
}
|
|
384
386
|
}
|
|
@@ -332,7 +332,8 @@
|
|
|
332
332
|
"RETURN_DATE": "Date de retour",
|
|
333
333
|
"CONFIRM": "Confirmer",
|
|
334
334
|
"TRAVELERS": "Voyageurs",
|
|
335
|
-
"GROUP_TOUR": "Tour en groupe"
|
|
335
|
+
"GROUP_TOUR": "Tour en groupe",
|
|
336
|
+
"ALL_TRAVELERS": "Tous les voyageurs"
|
|
336
337
|
},
|
|
337
338
|
"SRP": {
|
|
338
339
|
"SHOW_MORE": "Afficher plus",
|
|
@@ -383,6 +384,7 @@
|
|
|
383
384
|
"DEPARTURE_AIRPORTS": "Aéroports de départ",
|
|
384
385
|
"ARRIVAL_AIRPORTS": "Aéroports d’arrivée",
|
|
385
386
|
"PRICE": "Prix",
|
|
386
|
-
"TRAVEL_GROUP": "Groupe de voyageurs"
|
|
387
|
+
"TRAVEL_GROUP": "Groupe de voyageurs",
|
|
388
|
+
"EXCURSION": "Excursion"
|
|
387
389
|
}
|
|
388
390
|
}
|
|
@@ -328,7 +328,8 @@
|
|
|
328
328
|
"RETURN_DATE": "Date de retour",
|
|
329
329
|
"CONFIRM": "Confirmer",
|
|
330
330
|
"TRAVELERS": "Voyageurs",
|
|
331
|
-
"GROUP_TOUR": "Tour en groupe"
|
|
331
|
+
"GROUP_TOUR": "Tour en groupe",
|
|
332
|
+
"ALL_TRAVELERS": "Tous les voyageurs"
|
|
332
333
|
},
|
|
333
334
|
"SRP": {
|
|
334
335
|
"SHOW_MORE": "Afficher plus",
|
|
@@ -379,6 +380,7 @@
|
|
|
379
380
|
"DEPARTURE_AIRPORTS": "Aéroports de départ",
|
|
380
381
|
"ARRIVAL_AIRPORTS": "Aéroports d’arrivée",
|
|
381
382
|
"PRICE": "Prix",
|
|
382
|
-
"TRAVEL_GROUP": "Groupe de voyageurs"
|
|
383
|
+
"TRAVEL_GROUP": "Groupe de voyageurs",
|
|
384
|
+
"EXCURSION": "Excursion"
|
|
383
385
|
}
|
|
384
386
|
}
|