@qite/tide-components 1.0.3 → 1.0.6
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 +477 -347
- package/build/build-cjs/src/search-results/components/traject/traject-node-card.d.ts +1 -0
- package/build/build-cjs/src/search-results/components/traject/traject-results-flyin.d.ts +6 -0
- package/build/build-cjs/src/search-results/store/search-results-slice.d.ts +20 -2
- package/build/build-cjs/src/search-results/types.d.ts +1 -0
- package/build/build-cjs/src/search-results/utils/traject-utils.d.ts +7 -8
- package/build/build-cjs/src/shared/booking/travelers-form.d.ts +1 -0
- package/build/build-esm/index.js +478 -348
- package/build/build-esm/src/search-results/components/traject/traject-node-card.d.ts +1 -0
- package/build/build-esm/src/search-results/components/traject/traject-results-flyin.d.ts +6 -0
- package/build/build-esm/src/search-results/store/search-results-slice.d.ts +20 -2
- package/build/build-esm/src/search-results/types.d.ts +1 -0
- package/build/build-esm/src/search-results/utils/traject-utils.d.ts +7 -8
- package/build/build-esm/src/shared/booking/travelers-form.d.ts +1 -0
- package/package.json +2 -2
package/build/build-cjs/index.js
CHANGED
|
@@ -22890,6 +22890,10 @@ const NAME_CHARACTERS_REGEX = /^[\p{L}\s-]+$/u;
|
|
|
22890
22890
|
function hasInvalidNameCharacters(name) {
|
|
22891
22891
|
return !NAME_CHARACTERS_REGEX.test(name);
|
|
22892
22892
|
}
|
|
22893
|
+
// Rejects values like a 5-digit year, which the native date input allows while typing
|
|
22894
|
+
function isValidBirthDate(birthDateText) {
|
|
22895
|
+
return dateFns.isValid(dateFns.parse(birthDateText, 'yyyy-MM-dd', new Date()));
|
|
22896
|
+
}
|
|
22893
22897
|
function getAge(birthDateText, startDateText) {
|
|
22894
22898
|
var birthDate = new Date(birthDateText);
|
|
22895
22899
|
var startDate = new Date(startDateText);
|
|
@@ -22957,7 +22961,7 @@ const validateForm$1 = (values, agentRequired, bookingType, translations, formFi
|
|
|
22957
22961
|
}
|
|
22958
22962
|
}
|
|
22959
22963
|
if (isFormFieldPresent('birthDate')) {
|
|
22960
|
-
if (lodash.isEmpty(adult.birthDate)) {
|
|
22964
|
+
if (lodash.isEmpty(adult.birthDate) || !isValidBirthDate(adult.birthDate)) {
|
|
22961
22965
|
lodash.set(errors, `rooms[${rIndex}].adults[${index}].birthDate`, formatTravelerField(rIndex + 1, index + 1, translations.TRAVELERS_FORM.BIRTHDATE));
|
|
22962
22966
|
}
|
|
22963
22967
|
else if (values.endDate) {
|
|
@@ -22990,7 +22994,7 @@ const validateForm$1 = (values, agentRequired, bookingType, translations, formFi
|
|
|
22990
22994
|
}
|
|
22991
22995
|
}
|
|
22992
22996
|
if (isFormFieldPresent('birthDate')) {
|
|
22993
|
-
if (lodash.isEmpty(child.birthDate)) {
|
|
22997
|
+
if (lodash.isEmpty(child.birthDate) || !isValidBirthDate(child.birthDate)) {
|
|
22994
22998
|
lodash.set(errors, `rooms[${rIndex}].children[${index}].birthDate`, formatTravelerField(rIndex + 1, r.adults.length + index + 1, translations.TRAVELERS_FORM.BIRTHDATE));
|
|
22995
22999
|
}
|
|
22996
23000
|
else if (child.isBaby && values.startDate) {
|
|
@@ -23215,6 +23219,13 @@ const TypeAheadInput = ({ name, value, placeholder, options, onChange, onSelect,
|
|
|
23215
23219
|
function isBabyAge(age, maxBabyAge = BABY_MAX_AGE) {
|
|
23216
23220
|
return typeof age === 'number' && age <= maxBabyAge;
|
|
23217
23221
|
}
|
|
23222
|
+
// The native date input can hold values format() throws on (e.g. a 5+ digit year while typing), so never format an unvalidated birthDate
|
|
23223
|
+
function formatBirthDate$1(birthDate) {
|
|
23224
|
+
if (!birthDate)
|
|
23225
|
+
return '';
|
|
23226
|
+
const parsed = dateFns.parse(birthDate, 'yyyy-MM-dd', new Date());
|
|
23227
|
+
return dateFns.isValid(parsed) ? dateFns.format(parsed, 'dd-MM-yyyy') : '';
|
|
23228
|
+
}
|
|
23218
23229
|
function createTraveler(traveler, followNumber, personTranslation, isCompact, maxBabyAge) {
|
|
23219
23230
|
if (isCompact) {
|
|
23220
23231
|
return {
|
|
@@ -23522,10 +23533,7 @@ const SharedTravelersForm = ({ formik, translations, travellersSettings, countri
|
|
|
23522
23533
|
!useCompactForm && (bookingType !== 'b2b' || travellersSettings?.mainBookerFormFields?.length) ? (React__default["default"].createElement("div", { className: "form__region" },
|
|
23523
23534
|
React__default["default"].createElement("div", { className: "form__region-header" },
|
|
23524
23535
|
React__default["default"].createElement("h5", { className: "form__region-heading" }, translations.TRAVELERS_FORM.MAIN_BOOKER),
|
|
23525
|
-
React__default["default"].createElement("p", { className: "form__region-label" }, lodash.compact([
|
|
23526
|
-
lodash.compact([mainBooker?.firstName, mainBooker?.lastName]).join(' '),
|
|
23527
|
-
mainBooker?.birthDate && dateFns.format(dateFns.parse(mainBooker.birthDate, 'yyyy-MM-dd', new Date()), 'dd-MM-yyyy')
|
|
23528
|
-
]).join(', '))),
|
|
23536
|
+
React__default["default"].createElement("p", { className: "form__region-label" }, lodash.compact([lodash.compact([mainBooker?.firstName, mainBooker?.lastName]).join(' '), formatBirthDate$1(mainBooker?.birthDate)]).join(', '))),
|
|
23529
23537
|
travellersSettings?.mainBookerFormFields?.length ? (React__default["default"].createElement("div", { className: "main-booker-form__grid" }, travellersSettings.mainBookerFormFields.map((field, index) => (React__default["default"].createElement("div", { key: index, className: `control control--${field.type}` }, getControl(field.type, {}, field.type)))))) : (React__default["default"].createElement(React__default["default"].Fragment, null,
|
|
23530
23538
|
React__default["default"].createElement("div", { className: "form__twocolumn" },
|
|
23531
23539
|
React__default["default"].createElement("div", { className: "form__twocolumn-column" },
|
|
@@ -27370,7 +27378,8 @@ const initialState$1 = {
|
|
|
27370
27378
|
currentStep: 0,
|
|
27371
27379
|
bookingNumber: undefined,
|
|
27372
27380
|
trajectNodeAvailability: {},
|
|
27373
|
-
|
|
27381
|
+
trajectFlightAvailability: {},
|
|
27382
|
+
trajectEditLineGuid: null
|
|
27374
27383
|
};
|
|
27375
27384
|
const searchResultsSlice = toolkit.createSlice({
|
|
27376
27385
|
name: 'searchResults',
|
|
@@ -27592,16 +27601,51 @@ const searchResultsSlice = toolkit.createSlice({
|
|
|
27592
27601
|
const guids = new Set(action.payload);
|
|
27593
27602
|
state.editablePackagingEntry.lines = (state.editablePackagingEntry.lines ?? []).filter((x) => !guids.has(x.guid));
|
|
27594
27603
|
},
|
|
27595
|
-
|
|
27596
|
-
|
|
27604
|
+
setTrajectFlightLoading(state, action) {
|
|
27605
|
+
const lineGuid = action.payload;
|
|
27606
|
+
state.trajectFlightAvailability[lineGuid] = {
|
|
27607
|
+
lineGuid,
|
|
27608
|
+
status: 'loading',
|
|
27609
|
+
results: state.trajectFlightAvailability[lineGuid]?.results ?? [],
|
|
27610
|
+
selectedGuid: state.trajectFlightAvailability[lineGuid]?.selectedGuid ?? null
|
|
27611
|
+
};
|
|
27612
|
+
},
|
|
27613
|
+
setTrajectFlightAvailability(state, action) {
|
|
27614
|
+
const { lineGuid, results, selectedGuid } = action.payload;
|
|
27615
|
+
state.trajectFlightAvailability[lineGuid] = {
|
|
27616
|
+
lineGuid,
|
|
27617
|
+
status: selectedGuid ? 'resolved' : 'unavailable',
|
|
27618
|
+
results,
|
|
27619
|
+
selectedGuid
|
|
27620
|
+
};
|
|
27621
|
+
},
|
|
27622
|
+
setTrajectFlightSelection(state, action) {
|
|
27623
|
+
const flight = state.trajectFlightAvailability[action.payload.lineGuid];
|
|
27624
|
+
if (!flight)
|
|
27625
|
+
return;
|
|
27626
|
+
flight.selectedGuid = action.payload.guid;
|
|
27627
|
+
flight.status = 'resolved';
|
|
27628
|
+
},
|
|
27629
|
+
setTrajectEditLineGuid(state, action) {
|
|
27630
|
+
state.trajectEditLineGuid = action.payload;
|
|
27631
|
+
},
|
|
27632
|
+
updateTrajectNodeResult(state, action) {
|
|
27633
|
+
const { lineGuid, result } = action.payload;
|
|
27634
|
+
const node = state.trajectNodeAvailability[lineGuid];
|
|
27635
|
+
if (!node)
|
|
27636
|
+
return;
|
|
27637
|
+
node.results = node.results.map((x) => (x.code === result.code ? result : x));
|
|
27638
|
+
node.selectedCode = result.code;
|
|
27639
|
+
node.status = 'resolved';
|
|
27597
27640
|
},
|
|
27598
27641
|
resetTrajectAvailability(state) {
|
|
27599
27642
|
state.trajectNodeAvailability = {};
|
|
27600
|
-
state.
|
|
27643
|
+
state.trajectFlightAvailability = {};
|
|
27644
|
+
state.trajectEditLineGuid = null;
|
|
27601
27645
|
}
|
|
27602
27646
|
}
|
|
27603
27647
|
});
|
|
27604
|
-
const { setResults, setFilteredResults, setSelectedSearchResult, setPackagingAccoResults, setFilteredPackagingAccoResults, setFilteredPackagingFlightResults, setPackagingAccoSearchDetails, setSelectedPackagingAccoResult, setPackagingFlightResults, setSelectedPackagingFlight, setSelectedFlight, setSelectedFlightDetails, setBookingPackageDetails, selectFlight, setIsLoading, setFlightsLoading, setInitialFilters, setFilters, resetFilters, setInitialFlightFilters, setFlightFilters, resetFlightFilters, setSortType, setFlightSortType, setActiveTab, setCurrentPage, resetSearchState, setFlyInIsOpen, setEditablePackagingEntry, setTransactionId, setFlyInType, setPriceDetails, setItinerary, setSelectedOutwardKey, setSelectedReturnKey, resetFlightSelection, setExcursionSearchParams, setSelectedExcursionSearchResult, confirmExcursionForDay, removeConfirmedExcursionForDay, clearConfirmedExcursionsForDay, setBookPackagingEntry, setCurrentStep, setBookingNumber, setTrajectNodeLoading, setTrajectNodeAvailability, setTrajectNodeSelection,
|
|
27648
|
+
const { setResults, setFilteredResults, setSelectedSearchResult, setPackagingAccoResults, setFilteredPackagingAccoResults, setFilteredPackagingFlightResults, setPackagingAccoSearchDetails, setSelectedPackagingAccoResult, setPackagingFlightResults, setSelectedPackagingFlight, setSelectedFlight, setSelectedFlightDetails, setBookingPackageDetails, selectFlight, setIsLoading, setFlightsLoading, setInitialFilters, setFilters, resetFilters, setInitialFlightFilters, setFlightFilters, resetFlightFilters, setSortType, setFlightSortType, setActiveTab, setCurrentPage, resetSearchState, setFlyInIsOpen, setEditablePackagingEntry, setTransactionId, setFlyInType, setPriceDetails, setItinerary, setSelectedOutwardKey, setSelectedReturnKey, resetFlightSelection, setExcursionSearchParams, setSelectedExcursionSearchResult, confirmExcursionForDay, removeConfirmedExcursionForDay, clearConfirmedExcursionsForDay, setBookPackagingEntry, setCurrentStep, setBookingNumber, setTrajectNodeLoading, setTrajectNodeAvailability, setTrajectNodeSelection, setTrajectFlightLoading, setTrajectFlightAvailability, setTrajectFlightSelection, setTrajectEditLineGuid, updateTrajectNodeResult, resetTrajectAvailability, updateEditableEntryLine, removeEditableEntryLines } = searchResultsSlice.actions;
|
|
27605
27649
|
var searchResultsReducer = searchResultsSlice.reducer;
|
|
27606
27650
|
|
|
27607
27651
|
const ItemPicker = ({ items, selection, selectedSortByType, label, placeholder, classModifier, onPick, valueFormatter }) => {
|
|
@@ -30581,6 +30625,293 @@ const ExcursionDetails = () => {
|
|
|
30581
30625
|
React__default["default"].createElement("button", { type: "button", className: "cta cta--primary", onClick: handleConfirm }, translations?.QSM.CONFIRM))));
|
|
30582
30626
|
};
|
|
30583
30627
|
|
|
30628
|
+
const REGULAR_TRAJECT_NODE_TYPE = 0;
|
|
30629
|
+
const isSearchableTrajectNode = (node) => node.serviceType === ACCOMMODATION_SERVICE_TYPE || node.serviceType === EXCURSION_SERVICE_TYPE;
|
|
30630
|
+
const isTrajectFlightNode = (node) => node.serviceType === FLIGHT_SERVICE_TYPE;
|
|
30631
|
+
const getTrajectNodesWithLines = (trajectEntry) => {
|
|
30632
|
+
const linesByGuid = new Map((trajectEntry.entry.lines ?? []).map((line) => [line.guid, line]));
|
|
30633
|
+
return [...(trajectEntry.nodes ?? [])]
|
|
30634
|
+
.sort((a, b) => a.dayOrder - b.dayOrder || a.order - b.order)
|
|
30635
|
+
.map((node) => {
|
|
30636
|
+
const guids = node.lineGuids?.length ? node.lineGuids : [node.lineGuid];
|
|
30637
|
+
const lines = guids.map((guid) => linesByGuid.get(guid)).filter((x) => !!x);
|
|
30638
|
+
return { node, line: lines[0], lines };
|
|
30639
|
+
})
|
|
30640
|
+
.filter((x) => !!x.line);
|
|
30641
|
+
};
|
|
30642
|
+
const buildDestination = (line) => {
|
|
30643
|
+
if (line.location?.id)
|
|
30644
|
+
return { id: line.location.id, isLocation: true };
|
|
30645
|
+
if (line.oord?.id)
|
|
30646
|
+
return { id: line.oord.id, isOord: true };
|
|
30647
|
+
if (line.region?.id)
|
|
30648
|
+
return { id: line.region.id, isRegion: true };
|
|
30649
|
+
if (line.country?.id)
|
|
30650
|
+
return { id: line.country.id, isCountry: true };
|
|
30651
|
+
if (line.latitude && line.longitude)
|
|
30652
|
+
return { latitude: line.latitude, longitude: line.longitude };
|
|
30653
|
+
return { id: 0 };
|
|
30654
|
+
};
|
|
30655
|
+
const buildTrajectNodeRequest = ({ node, line }, context, productCode) => ({
|
|
30656
|
+
transactionId: context.transactionId,
|
|
30657
|
+
officeId: context.officeId,
|
|
30658
|
+
agentId: context.agentId ?? null,
|
|
30659
|
+
catalogueId: context.catalogueId,
|
|
30660
|
+
searchConfigurationId: context.searchConfigurationId,
|
|
30661
|
+
portalId: context.portalId ?? null,
|
|
30662
|
+
vendorConfigurationId: node.externalVendorId ?? null,
|
|
30663
|
+
language: context.language,
|
|
30664
|
+
serviceType: node.serviceType,
|
|
30665
|
+
fromDate: toDateOnlyString(line.from),
|
|
30666
|
+
toDate: toDateOnlyString(line.to),
|
|
30667
|
+
destination: buildDestination(line),
|
|
30668
|
+
productCode,
|
|
30669
|
+
rooms: context.rooms,
|
|
30670
|
+
tagIds: []
|
|
30671
|
+
});
|
|
30672
|
+
const getNodeCandidates = (node) => {
|
|
30673
|
+
const configured = (node.alternatives ?? []).filter((x) => !!x.code && x.code.trim().length > 0);
|
|
30674
|
+
if (configured.length)
|
|
30675
|
+
return configured;
|
|
30676
|
+
const fallbackCode = node.code ?? node.preferredAccommodationCode;
|
|
30677
|
+
if (!fallbackCode || !fallbackCode.trim().length)
|
|
30678
|
+
return [];
|
|
30679
|
+
return [
|
|
30680
|
+
{
|
|
30681
|
+
code: fallbackCode,
|
|
30682
|
+
name: node.name,
|
|
30683
|
+
description: node.description,
|
|
30684
|
+
imageUrl: node.imageUrl,
|
|
30685
|
+
contentSource: node.contentSource,
|
|
30686
|
+
vendor: node.vendor,
|
|
30687
|
+
externalVendorId: node.externalVendorId,
|
|
30688
|
+
isPreferred: true
|
|
30689
|
+
}
|
|
30690
|
+
];
|
|
30691
|
+
};
|
|
30692
|
+
const findCandidateForResult = (node, result) => getNodeCandidates(node).find((candidate) => matchesCode(result, candidate.code)) ?? null;
|
|
30693
|
+
const matchesCode = (result, code) => {
|
|
30694
|
+
if (result.code === code)
|
|
30695
|
+
return true;
|
|
30696
|
+
return (result.rooms ?? []).some((room) => (room.options ?? []).some((option) => option.accommodationCode === code));
|
|
30697
|
+
};
|
|
30698
|
+
const getPackagingRoomsFromEntry = (entry) => {
|
|
30699
|
+
const paxById = new Map((entry.pax ?? []).map((p) => [p.id, p]));
|
|
30700
|
+
const rooms = (entry.rooms ?? []).map((room) => ({
|
|
30701
|
+
travellers: (room.paxIds ?? []).map((paxId) => {
|
|
30702
|
+
const pax = paxById.get(paxId);
|
|
30703
|
+
return {
|
|
30704
|
+
id: paxId,
|
|
30705
|
+
age: pax?.age ?? null,
|
|
30706
|
+
dateOfBirth: pax?.dateOfBirth ?? null
|
|
30707
|
+
};
|
|
30708
|
+
})
|
|
30709
|
+
}));
|
|
30710
|
+
return rooms.filter((room) => room.travellers.length > 0);
|
|
30711
|
+
};
|
|
30712
|
+
const pickCheapest = (results) => [...results].sort((a, b) => a.price - b.price)[0] ?? null;
|
|
30713
|
+
const applyResultToLines = (lines, result) => lines.map((line, index) => applyResultToLine(line, result, index));
|
|
30714
|
+
const getOptionForRoom = (result, roomIndex) => {
|
|
30715
|
+
const room = (result.rooms ?? [])[roomIndex];
|
|
30716
|
+
if (room)
|
|
30717
|
+
return (room.options ?? []).find((x) => x.isSelected) ?? (room.options ?? [])[0];
|
|
30718
|
+
return (result.rooms ?? []).flatMap((x) => x.options ?? []).find((x) => x.isSelected) ?? (result.rooms ?? [])[0]?.options?.[0];
|
|
30719
|
+
};
|
|
30720
|
+
const applyResultToLine = (line, result, roomIndex = 0) => {
|
|
30721
|
+
const option = getOptionForRoom(result, roomIndex);
|
|
30722
|
+
return {
|
|
30723
|
+
...line,
|
|
30724
|
+
productName: result.name ?? line.productName,
|
|
30725
|
+
productCode: result.code ?? line.productCode,
|
|
30726
|
+
accommodationCode: option?.accommodationCode ?? line.accommodationCode,
|
|
30727
|
+
accommodationName: option?.accommodationName ?? line.accommodationName,
|
|
30728
|
+
regimeCode: option?.regimeCode ?? line.regimeCode,
|
|
30729
|
+
regimeName: option?.regimeName ?? line.regimeName,
|
|
30730
|
+
latitude: result.latitude ?? line.latitude,
|
|
30731
|
+
longitude: result.longitude ?? line.longitude,
|
|
30732
|
+
isChanged: true
|
|
30733
|
+
};
|
|
30734
|
+
};
|
|
30735
|
+
const toDateOnlyUtcString = (value) => {
|
|
30736
|
+
const date = new Date(value);
|
|
30737
|
+
return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate())).toISOString();
|
|
30738
|
+
};
|
|
30739
|
+
const toTimeOnlyString = (value) => {
|
|
30740
|
+
const date = new Date(value);
|
|
30741
|
+
const hh = String(date.getUTCHours()).padStart(2, '0');
|
|
30742
|
+
const mm = String(date.getUTCMinutes()).padStart(2, '0');
|
|
30743
|
+
const ss = String(date.getUTCSeconds()).padStart(2, '0');
|
|
30744
|
+
return `${hh}:${mm}:${ss}`;
|
|
30745
|
+
};
|
|
30746
|
+
const mapFlightSegmentsToFlightLines = (segments) => segments.map((segment) => ({
|
|
30747
|
+
airlineCode: segment.marketingAirlineCode,
|
|
30748
|
+
airlineDescription: segment.marketingAirlineName,
|
|
30749
|
+
operatingAirlineCode: segment.operatingAirlineCode,
|
|
30750
|
+
operatingAirlineDescription: segment.operatingAirlineName,
|
|
30751
|
+
flightNumber: segment.flightNumber,
|
|
30752
|
+
operatingFlightNumber: segment.operatingFlightNumber ?? null,
|
|
30753
|
+
departureDate: toDateOnlyUtcString(segment.departureDateTime),
|
|
30754
|
+
departureTime: toTimeOnlyString(segment.departureDateTime),
|
|
30755
|
+
departureAirportCode: segment.departureAirportCode,
|
|
30756
|
+
departureAirportDescription: segment.departureAirportName,
|
|
30757
|
+
arrivalDate: toDateOnlyUtcString(segment.arrivalDateTime),
|
|
30758
|
+
arrivalTime: toTimeOnlyString(segment.arrivalDateTime),
|
|
30759
|
+
arrivalAirportCode: segment.arrivalAirportCode,
|
|
30760
|
+
arrivalAirportDescription: segment.arrivalAirportName,
|
|
30761
|
+
durationInTicks: segment.durationInTicks
|
|
30762
|
+
}));
|
|
30763
|
+
const buildTrajectFlightRequest = ({ node, line }, context, pax) => ({
|
|
30764
|
+
transactionId: context.transactionId,
|
|
30765
|
+
officeId: context.officeId,
|
|
30766
|
+
catalogueId: context.catalogueId,
|
|
30767
|
+
agentId: context.agentId ?? null,
|
|
30768
|
+
language: context.language,
|
|
30769
|
+
departureAirportCode: node.departureAirportCode ?? '',
|
|
30770
|
+
arrivalAirportCode: node.arrivalAirportCode ?? '',
|
|
30771
|
+
returnAirportCode: null,
|
|
30772
|
+
luggageIncluded: null,
|
|
30773
|
+
maxStops: null,
|
|
30774
|
+
travelClass: null,
|
|
30775
|
+
vendorConfigurationId: node.externalVendorId ?? null,
|
|
30776
|
+
pax,
|
|
30777
|
+
outward: { date: dateToDateStruct(new Date(line.from)) },
|
|
30778
|
+
return: null
|
|
30779
|
+
});
|
|
30780
|
+
const pickCheapestFlight = (flights) => [...flights].sort((a, b) => a.price - b.price)[0] ?? null;
|
|
30781
|
+
const applyFlightToLine = (line, flight) => {
|
|
30782
|
+
const segments = flight.outward?.segments ?? [];
|
|
30783
|
+
if (!segments.length)
|
|
30784
|
+
return line;
|
|
30785
|
+
const firstSegment = segments[0];
|
|
30786
|
+
const lastSegment = segments[segments.length - 1];
|
|
30787
|
+
return {
|
|
30788
|
+
...line,
|
|
30789
|
+
from: new Date(firstSegment.departureDateTime).toISOString(),
|
|
30790
|
+
to: new Date(lastSegment.arrivalDateTime).toISOString(),
|
|
30791
|
+
productName: `${firstSegment.departureAirportName} - ${lastSegment.arrivalAirportName} (${firstSegment.marketingAirlineName})`,
|
|
30792
|
+
productCode: `${firstSegment.departureAirportCode} ${lastSegment.arrivalAirportCode}/${firstSegment.marketingAirlineCode}`,
|
|
30793
|
+
accommodationName: firstSegment.metaData?.farePriceClassName ?? line.accommodationName,
|
|
30794
|
+
accommodationCode: firstSegment.metaData?.fareCode ?? line.accommodationCode,
|
|
30795
|
+
flightInformation: {
|
|
30796
|
+
pnr: '',
|
|
30797
|
+
flightLines: mapFlightSegmentsToFlightLines(segments)
|
|
30798
|
+
},
|
|
30799
|
+
isChanged: true
|
|
30800
|
+
};
|
|
30801
|
+
};
|
|
30802
|
+
|
|
30803
|
+
const getLocation = (result) => {
|
|
30804
|
+
const place = result.locationName || result.regionName || result.oordName;
|
|
30805
|
+
if (!place)
|
|
30806
|
+
return result.countryName ?? '';
|
|
30807
|
+
return result.countryName ? `${place}, ${result.countryName}` : place;
|
|
30808
|
+
};
|
|
30809
|
+
const toPlainText = (value) => {
|
|
30810
|
+
if (!value)
|
|
30811
|
+
return '';
|
|
30812
|
+
return he
|
|
30813
|
+
.decode(value.replace(/<[^>]*>/g, ' '))
|
|
30814
|
+
.replace(/\s+/g, ' ')
|
|
30815
|
+
.trim();
|
|
30816
|
+
};
|
|
30817
|
+
const TrajectNodeCard = ({ item, result, isSelected, languageCode, translations, onSelect, onEditOptions, showNights }) => {
|
|
30818
|
+
const { node } = item;
|
|
30819
|
+
const selectedPerRoom = (result.rooms ?? []).map((room) => (room.options ?? []).find((x) => x.isSelected) ?? (room.options ?? [])[0]);
|
|
30820
|
+
const isMultiRoom = selectedPerRoom.length > 1;
|
|
30821
|
+
const price = formatPrice$3(result.price, result.currencyCode, languageCode);
|
|
30822
|
+
const nights = calculateNights(new Date(result.fromDate), new Date(result.toDate));
|
|
30823
|
+
const candidate = findCandidateForResult(node, result);
|
|
30824
|
+
const image = candidate?.imageUrl ?? null;
|
|
30825
|
+
const description = toPlainText(candidate?.description);
|
|
30826
|
+
const title = candidate?.name ?? result.name;
|
|
30827
|
+
const priceBlock = (React__default["default"].createElement("div", { className: "search__result-card__price__wrapper" },
|
|
30828
|
+
React__default["default"].createElement("span", { className: "search__result-card__price__label" }, translations?.SHARED.TOTAL_PRICE),
|
|
30829
|
+
React__default["default"].createElement("span", { className: "search__result-card__price" }, price)));
|
|
30830
|
+
const canEditOptions = isSelected && !!onEditOptions && (result.rooms ?? []).some((room) => (room.options ?? []).length > 1);
|
|
30831
|
+
const selectButton = (React__default["default"].createElement(React__default["default"].Fragment, null,
|
|
30832
|
+
React__default["default"].createElement("button", { type: "button", className: `cta ${isSelected ? 'cta--selected' : 'cta--select'}`, onClick: onSelect }, isSelected ? translations?.SHARED.SELECTED : translations?.SHARED.SELECT),
|
|
30833
|
+
canEditOptions && (React__default["default"].createElement("button", { type: "button", className: "cta cta--secondary", onClick: onEditOptions },
|
|
30834
|
+
translations?.SRP.SELECT,
|
|
30835
|
+
" ",
|
|
30836
|
+
translations?.SRP.ACCOMMODATION))));
|
|
30837
|
+
if (result.contents?.length) {
|
|
30838
|
+
return (React__default["default"].createElement("div", { className: `search__result-card__wrapper search__result-card__wrapper--custom ${isSelected ? 'search__result-card__wrapper--selected' : ''}` },
|
|
30839
|
+
React__default["default"].createElement("div", { className: "search__result-card__top", dangerouslySetInnerHTML: { __html: he.decode(result.contents) } }),
|
|
30840
|
+
React__default["default"].createElement("div", { className: "search__result-card__footer" }, selectButton)));
|
|
30841
|
+
}
|
|
30842
|
+
return (React__default["default"].createElement("div", { className: `search__result-card__wrapper ${isSelected ? 'search__result-card__wrapper--selected' : ''}` },
|
|
30843
|
+
image && (React__default["default"].createElement("div", { className: "search__result-card__img-wrapper" },
|
|
30844
|
+
React__default["default"].createElement("img", { src: image, alt: title, className: "search__result-card__img" }),
|
|
30845
|
+
priceBlock)),
|
|
30846
|
+
React__default["default"].createElement("div", { className: "search__result-card__content" },
|
|
30847
|
+
React__default["default"].createElement("div", { className: "search__result-card__content__wrapper" },
|
|
30848
|
+
React__default["default"].createElement("div", { className: "search__result-card__header" },
|
|
30849
|
+
React__default["default"].createElement("div", { className: "search__result-card__header__wrapper" },
|
|
30850
|
+
!!result.stars && (React__default["default"].createElement("div", { className: "rating" }, [...Array(result.stars)].map((_, index) => (React__default["default"].createElement(Icon, { name: "ui-star", key: `rating-star-${index + 1}`, width: 14, height: 14 }))))),
|
|
30851
|
+
React__default["default"].createElement("h3", { className: "search__result-card__title" }, title)),
|
|
30852
|
+
!image && priceBlock),
|
|
30853
|
+
React__default["default"].createElement("span", { className: "search__result-card__location" },
|
|
30854
|
+
React__default["default"].createElement(Icon, { name: "ui-location", height: 16 }),
|
|
30855
|
+
getLocation(result)),
|
|
30856
|
+
React__default["default"].createElement("div", { className: "search__result-card__options" },
|
|
30857
|
+
showNights && nights > 0 && (React__default["default"].createElement("div", { className: "search__result-card__option" },
|
|
30858
|
+
React__default["default"].createElement(Icon, { name: "ui-moon", height: 16 }),
|
|
30859
|
+
nights,
|
|
30860
|
+
" ",
|
|
30861
|
+
translations?.SRP.NIGHTS)),
|
|
30862
|
+
selectedPerRoom.map((option, roomIndex) => (React__default["default"].createElement(React__default["default"].Fragment, { key: `room-${roomIndex}` },
|
|
30863
|
+
option?.accommodationName && (React__default["default"].createElement("div", { className: "search__result-card__option" },
|
|
30864
|
+
React__default["default"].createElement(Icon, { name: "ui-bed", height: 16 }),
|
|
30865
|
+
isMultiRoom && `${translations?.SHARED.ROOM} ${roomIndex + 1}: `,
|
|
30866
|
+
option.accommodationName)),
|
|
30867
|
+
option?.regimeName && (React__default["default"].createElement("div", { className: "search__result-card__option" },
|
|
30868
|
+
React__default["default"].createElement(Icon, { name: "ui-utensils", height: 16 }),
|
|
30869
|
+
option.regimeName)))))),
|
|
30870
|
+
description && React__default["default"].createElement("p", { className: "search__result-card__description" }, description)),
|
|
30871
|
+
React__default["default"].createElement("div", { className: "search__result-card__footer" }, selectButton))));
|
|
30872
|
+
};
|
|
30873
|
+
|
|
30874
|
+
const TrajectResultsFlyIn = ({ isLoading }) => {
|
|
30875
|
+
const context = React.useContext(SearchResultsConfigurationContext);
|
|
30876
|
+
const dispatch = reactRedux.useDispatch();
|
|
30877
|
+
const translations = getTranslations(context?.languageCode ?? 'en-GB');
|
|
30878
|
+
const { trajectNodeAvailability, trajectEditLineGuid } = reactRedux.useSelector((state) => state.searchResults);
|
|
30879
|
+
const trajectEntry = context?.trajectEntry;
|
|
30880
|
+
const item = React.useMemo(() => {
|
|
30881
|
+
if (!trajectEntry || !trajectEditLineGuid)
|
|
30882
|
+
return null;
|
|
30883
|
+
return getTrajectNodesWithLines(trajectEntry).find((x) => x.line.guid === trajectEditLineGuid) ?? null;
|
|
30884
|
+
}, [trajectEntry, trajectEditLineGuid]);
|
|
30885
|
+
if (!context || !item)
|
|
30886
|
+
return null;
|
|
30887
|
+
const availability = trajectNodeAvailability[item.line.guid];
|
|
30888
|
+
const results = availability?.results ?? [];
|
|
30889
|
+
const ordered = [...results].sort((a, b) => {
|
|
30890
|
+
const aConfigured = findCandidateForResult(item.node, a) ? 0 : 1;
|
|
30891
|
+
const bConfigured = findCandidateForResult(item.node, b) ? 0 : 1;
|
|
30892
|
+
return aConfigured - bConfigured || a.price - b.price;
|
|
30893
|
+
});
|
|
30894
|
+
const handleSelect = (code) => {
|
|
30895
|
+
const result = results.find((x) => x.code === code);
|
|
30896
|
+
if (!result)
|
|
30897
|
+
return;
|
|
30898
|
+
dispatch(setTrajectNodeSelection({ lineGuid: item.line.guid, code }));
|
|
30899
|
+
applyResultToLines(item.lines, result).forEach((line) => dispatch(updateEditableEntryLine(line)));
|
|
30900
|
+
dispatch(setTrajectEditLineGuid(null));
|
|
30901
|
+
dispatch(setFlyInIsOpen(false));
|
|
30902
|
+
};
|
|
30903
|
+
if (isLoading) {
|
|
30904
|
+
return React__default["default"].createElement(React__default["default"].Fragment, null, context.customSpinner ?? React__default["default"].createElement(Spinner, { label: translations.SRP.LOADING_ACCOMMODATIONS }));
|
|
30905
|
+
}
|
|
30906
|
+
return (React__default["default"].createElement("div", { className: "flyin__content" },
|
|
30907
|
+
React__default["default"].createElement("div", { className: "search__result-row" },
|
|
30908
|
+
React__default["default"].createElement("span", { className: "search__result-row-text" },
|
|
30909
|
+
ordered.length,
|
|
30910
|
+
"\u00A0",
|
|
30911
|
+
translations.SRP.TOTAL_RESULTS_LABEL)),
|
|
30912
|
+
ordered.length === 0 ? (React__default["default"].createElement("div", { className: "no-results" }, translations.SRP.NO_RESULTS)) : (React__default["default"].createElement("div", { className: "search__results__cards search__results__cards--compact" }, ordered.map((result) => (React__default["default"].createElement(TrajectNodeCard, { key: result.code, item: item, result: result, isSelected: result.code === availability?.selectedCode, languageCode: context.languageCode, translations: translations, onSelect: () => handleSelect(result.code), showNights: item.node.serviceType === ACCOMMODATION_SERVICE_TYPE })))))));
|
|
30913
|
+
};
|
|
30914
|
+
|
|
30584
30915
|
const FlyIn = ({ srpType, isOpen, setIsOpen, className = '', onPanelRef, detailsLoading, flyInType, isPackageEditFlow, handleConfirm, sortByTypes, activeSearchSeed, toggleFilters, filtersOpen }) => {
|
|
30585
30916
|
const dispatch = reactRedux.useDispatch();
|
|
30586
30917
|
const context = React.useContext(SearchResultsConfigurationContext);
|
|
@@ -30626,6 +30957,10 @@ const FlyIn = ({ srpType, isOpen, setIsOpen, className = '', onPanelRef, details
|
|
|
30626
30957
|
}
|
|
30627
30958
|
};
|
|
30628
30959
|
const handleGoBack = () => {
|
|
30960
|
+
if (context?.trajectEntry) {
|
|
30961
|
+
handleClose();
|
|
30962
|
+
return;
|
|
30963
|
+
}
|
|
30629
30964
|
if (flyInType === 'acco-details') {
|
|
30630
30965
|
dispatch(setFlyInType('acco-results'));
|
|
30631
30966
|
}
|
|
@@ -30649,7 +30984,7 @@ const FlyIn = ({ srpType, isOpen, setIsOpen, className = '', onPanelRef, details
|
|
|
30649
30984
|
dispatch(setBookPackagingEntry(true));
|
|
30650
30985
|
}
|
|
30651
30986
|
};
|
|
30652
|
-
return (React__default["default"].createElement("div", { className: `flyin ${isOpen ? 'flyin--active' : ''} ${className} ${isPackageEditFlow || flyInType === 'acco-results' ? 'flyin--large' : ''} ${flyInType === 'excursion-results' || flyInType === 'excursion-details' ? 'flyin--medium' : ''}
|
|
30987
|
+
return (React__default["default"].createElement("div", { className: `flyin ${isOpen ? 'flyin--active' : ''} ${className} ${isPackageEditFlow || flyInType === 'acco-results' ? 'flyin--large' : ''} ${flyInType === 'excursion-results' || flyInType === 'excursion-details' ? 'flyin--medium' : ''}
|
|
30653
30988
|
${flyInType === 'flight-outward-results' || flyInType === 'flight-return-results' ? 'flyin--flight' : ''}` },
|
|
30654
30989
|
React__default["default"].createElement("div", { className: `flyin__panel ${isOpen ? 'flyin__panel--active' : ''}`, ref: panelRef },
|
|
30655
30990
|
React__default["default"].createElement("div", { className: "flyin__content" },
|
|
@@ -30675,7 +31010,10 @@ const FlyIn = ({ srpType, isOpen, setIsOpen, className = '', onPanelRef, details
|
|
|
30675
31010
|
React__default["default"].createElement(Icon, { name: "ui-chevron", width: 14, height: 14, "aria-hidden": "true" }),
|
|
30676
31011
|
"Go Back")))),
|
|
30677
31012
|
srpType === build.PortalQsmType.Flight && React__default["default"].createElement(FlightsFlyIn, { isOpen: isOpen, setIsOpen: setIsOpen }),
|
|
30678
|
-
|
|
31013
|
+
context?.trajectEntry && flyInType === 'acco-results' && React__default["default"].createElement(TrajectResultsFlyIn, { isLoading: detailsLoading }),
|
|
31014
|
+
!context?.trajectEntry &&
|
|
31015
|
+
(srpType === build.PortalQsmType.Accommodation || srpType === build.PortalQsmType.AccommodationAndFlight) &&
|
|
31016
|
+
flyInType === 'acco-results' && (React__default["default"].createElement("div", { className: "flyin__content flyin__content--columns" },
|
|
30679
31017
|
React__default["default"].createElement(Filters, { initialFilters: initialFilters, filters: filters, isOpen: filtersOpen, handleSetIsOpen: () => toggleFilters && toggleFilters(),
|
|
30680
31018
|
// handleApplyFilters={() => setSearchTrigger((prev) => prev + 1)}
|
|
30681
31019
|
isLoading: isLoading, setFilters: (filters) => dispatch(setFilters(filters)), resetFilters: (filters) => dispatch(resetFilters(filters)) }),
|
|
@@ -32948,222 +33286,6 @@ const BookPackagingEntry = ({ activeSearchSeed, isLoading, isConfirmationPage })
|
|
|
32948
33286
|
React__default["default"].createElement(WLSidebar, { activeSearchSeed: activeSearchSeed, packagingAccoResult: selectedPackagingAccoResult }))));
|
|
32949
33287
|
};
|
|
32950
33288
|
|
|
32951
|
-
const REGULAR_TRAJECT_NODE_TYPE = 0;
|
|
32952
|
-
const isSearchableTrajectNode = (node) => node.serviceType === ACCOMMODATION_SERVICE_TYPE || node.serviceType === EXCURSION_SERVICE_TYPE;
|
|
32953
|
-
const isTrajectFlightNode = (node) => node.serviceType === FLIGHT_SERVICE_TYPE;
|
|
32954
|
-
const getTrajectNodesWithLines = (trajectEntry) => {
|
|
32955
|
-
const linesByGuid = new Map((trajectEntry.entry.lines ?? []).map((line) => [line.guid, line]));
|
|
32956
|
-
return [...(trajectEntry.nodes ?? [])]
|
|
32957
|
-
.sort((a, b) => a.dayOrder - b.dayOrder || a.order - b.order)
|
|
32958
|
-
.map((node) => ({ node, line: linesByGuid.get(node.lineGuid) }))
|
|
32959
|
-
.filter((x) => !!x.line);
|
|
32960
|
-
};
|
|
32961
|
-
const buildDestination = (line) => {
|
|
32962
|
-
if (line.location?.id)
|
|
32963
|
-
return { id: line.location.id, isLocation: true };
|
|
32964
|
-
if (line.oord?.id)
|
|
32965
|
-
return { id: line.oord.id, isOord: true };
|
|
32966
|
-
if (line.region?.id)
|
|
32967
|
-
return { id: line.region.id, isRegion: true };
|
|
32968
|
-
if (line.country?.id)
|
|
32969
|
-
return { id: line.country.id, isCountry: true };
|
|
32970
|
-
if (line.latitude && line.longitude)
|
|
32971
|
-
return { latitude: line.latitude, longitude: line.longitude };
|
|
32972
|
-
return { id: 0 };
|
|
32973
|
-
};
|
|
32974
|
-
const buildTrajectNodeRequest = ({ node, line }, context, productCode) => ({
|
|
32975
|
-
transactionId: context.transactionId,
|
|
32976
|
-
officeId: context.officeId,
|
|
32977
|
-
agentId: context.agentId ?? null,
|
|
32978
|
-
catalogueId: context.catalogueId,
|
|
32979
|
-
searchConfigurationId: context.searchConfigurationId,
|
|
32980
|
-
portalId: context.portalId ?? null,
|
|
32981
|
-
vendorConfigurationId: node.externalVendorId ?? null,
|
|
32982
|
-
language: context.language,
|
|
32983
|
-
serviceType: node.serviceType,
|
|
32984
|
-
fromDate: toDateOnlyString(line.from),
|
|
32985
|
-
toDate: toDateOnlyString(line.to),
|
|
32986
|
-
destination: buildDestination(line),
|
|
32987
|
-
productCode,
|
|
32988
|
-
rooms: context.rooms,
|
|
32989
|
-
tagIds: []
|
|
32990
|
-
});
|
|
32991
|
-
const getNodeCandidates = (node) => {
|
|
32992
|
-
const configured = (node.alternatives ?? []).filter((x) => !!x.code && x.code.trim().length > 0);
|
|
32993
|
-
if (configured.length)
|
|
32994
|
-
return configured;
|
|
32995
|
-
const fallbackCode = node.code ?? node.preferredAccommodationCode;
|
|
32996
|
-
if (!fallbackCode || !fallbackCode.trim().length)
|
|
32997
|
-
return [];
|
|
32998
|
-
return [
|
|
32999
|
-
{
|
|
33000
|
-
code: fallbackCode,
|
|
33001
|
-
name: node.name,
|
|
33002
|
-
description: node.description,
|
|
33003
|
-
imageUrl: node.imageUrl,
|
|
33004
|
-
contentSource: node.contentSource,
|
|
33005
|
-
vendor: node.vendor,
|
|
33006
|
-
externalVendorId: node.externalVendorId,
|
|
33007
|
-
isPreferred: true
|
|
33008
|
-
}
|
|
33009
|
-
];
|
|
33010
|
-
};
|
|
33011
|
-
const findCandidateForResult = (node, result) => getNodeCandidates(node).find((candidate) => matchesCode(result, candidate.code)) ?? null;
|
|
33012
|
-
const matchesCode = (result, code) => {
|
|
33013
|
-
if (result.code === code)
|
|
33014
|
-
return true;
|
|
33015
|
-
return (result.rooms ?? []).some((room) => (room.options ?? []).some((option) => option.accommodationCode === code));
|
|
33016
|
-
};
|
|
33017
|
-
const getPackagingRoomsFromEntry = (entry) => {
|
|
33018
|
-
const paxById = new Map((entry.pax ?? []).map((p) => [p.id, p]));
|
|
33019
|
-
const rooms = (entry.rooms ?? []).map((room) => ({
|
|
33020
|
-
travellers: (room.paxIds ?? []).map((paxId) => {
|
|
33021
|
-
const pax = paxById.get(paxId);
|
|
33022
|
-
return {
|
|
33023
|
-
id: paxId,
|
|
33024
|
-
age: pax?.age ?? null,
|
|
33025
|
-
dateOfBirth: pax?.dateOfBirth ?? null
|
|
33026
|
-
};
|
|
33027
|
-
})
|
|
33028
|
-
}));
|
|
33029
|
-
return rooms.filter((room) => room.travellers.length > 0);
|
|
33030
|
-
};
|
|
33031
|
-
const pickCheapest = (results) => [...results].sort((a, b) => a.price - b.price)[0] ?? null;
|
|
33032
|
-
const applyResultToLine = (line, result) => {
|
|
33033
|
-
const option = (result.rooms ?? []).flatMap((room) => room.options ?? []).find((x) => x.isSelected) ?? (result.rooms ?? [])[0]?.options?.[0];
|
|
33034
|
-
return {
|
|
33035
|
-
...line,
|
|
33036
|
-
productName: result.name ?? line.productName,
|
|
33037
|
-
productCode: result.code ?? line.productCode,
|
|
33038
|
-
accommodationCode: option?.accommodationCode ?? line.accommodationCode,
|
|
33039
|
-
accommodationName: option?.accommodationName ?? line.accommodationName,
|
|
33040
|
-
regimeCode: option?.regimeCode ?? line.regimeCode,
|
|
33041
|
-
regimeName: option?.regimeName ?? line.regimeName,
|
|
33042
|
-
latitude: result.latitude ?? line.latitude,
|
|
33043
|
-
longitude: result.longitude ?? line.longitude,
|
|
33044
|
-
isChanged: true
|
|
33045
|
-
};
|
|
33046
|
-
};
|
|
33047
|
-
const toDateOnlyUtcString = (value) => {
|
|
33048
|
-
const date = new Date(value);
|
|
33049
|
-
return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate())).toISOString();
|
|
33050
|
-
};
|
|
33051
|
-
const toTimeOnlyString = (value) => {
|
|
33052
|
-
const date = new Date(value);
|
|
33053
|
-
const hh = String(date.getUTCHours()).padStart(2, '0');
|
|
33054
|
-
const mm = String(date.getUTCMinutes()).padStart(2, '0');
|
|
33055
|
-
const ss = String(date.getUTCSeconds()).padStart(2, '0');
|
|
33056
|
-
return `${hh}:${mm}:${ss}`;
|
|
33057
|
-
};
|
|
33058
|
-
const mapFlightSegmentsToFlightLines = (segments) => segments.map((segment) => ({
|
|
33059
|
-
airlineCode: segment.marketingAirlineCode,
|
|
33060
|
-
airlineDescription: segment.marketingAirlineName,
|
|
33061
|
-
operatingAirlineCode: segment.operatingAirlineCode,
|
|
33062
|
-
operatingAirlineDescription: segment.operatingAirlineName,
|
|
33063
|
-
flightNumber: segment.flightNumber,
|
|
33064
|
-
operatingFlightNumber: segment.operatingFlightNumber ?? null,
|
|
33065
|
-
departureDate: toDateOnlyUtcString(segment.departureDateTime),
|
|
33066
|
-
departureTime: toTimeOnlyString(segment.departureDateTime),
|
|
33067
|
-
departureAirportCode: segment.departureAirportCode,
|
|
33068
|
-
departureAirportDescription: segment.departureAirportName,
|
|
33069
|
-
arrivalDate: toDateOnlyUtcString(segment.arrivalDateTime),
|
|
33070
|
-
arrivalTime: toTimeOnlyString(segment.arrivalDateTime),
|
|
33071
|
-
arrivalAirportCode: segment.arrivalAirportCode,
|
|
33072
|
-
arrivalAirportDescription: segment.arrivalAirportName,
|
|
33073
|
-
durationInTicks: segment.durationInTicks
|
|
33074
|
-
}));
|
|
33075
|
-
const applyFlightToLine = (line, flight, isOutbound) => {
|
|
33076
|
-
const segments = (isOutbound ? flight.outward?.segments : flight.return?.segments) ?? [];
|
|
33077
|
-
if (!segments.length)
|
|
33078
|
-
return line;
|
|
33079
|
-
const firstSegment = segments[0];
|
|
33080
|
-
const lastSegment = segments[segments.length - 1];
|
|
33081
|
-
return {
|
|
33082
|
-
...line,
|
|
33083
|
-
from: new Date(firstSegment.departureDateTime).toISOString(),
|
|
33084
|
-
to: new Date(lastSegment.arrivalDateTime).toISOString(),
|
|
33085
|
-
productName: `${firstSegment.departureAirportName} - ${lastSegment.arrivalAirportName} (${firstSegment.marketingAirlineName})`,
|
|
33086
|
-
productCode: `${firstSegment.departureAirportCode} ${lastSegment.arrivalAirportCode}/${firstSegment.marketingAirlineCode}`,
|
|
33087
|
-
accommodationName: firstSegment.metaData?.farePriceClassName ?? line.accommodationName,
|
|
33088
|
-
accommodationCode: firstSegment.metaData?.fareCode ?? line.accommodationCode,
|
|
33089
|
-
flightInformation: {
|
|
33090
|
-
pnr: '',
|
|
33091
|
-
flightLines: mapFlightSegmentsToFlightLines(segments)
|
|
33092
|
-
},
|
|
33093
|
-
isChanged: true
|
|
33094
|
-
};
|
|
33095
|
-
};
|
|
33096
|
-
const pairTrajectFlights = (nodes) => {
|
|
33097
|
-
const flights = nodes.filter((x) => isTrajectFlightNode(x.node));
|
|
33098
|
-
return {
|
|
33099
|
-
outbound: flights[0] ?? null,
|
|
33100
|
-
inbound: flights.length > 1 ? flights[flights.length - 1] : null,
|
|
33101
|
-
unsupported: flights.slice(1, Math.max(flights.length - 1, 1))
|
|
33102
|
-
};
|
|
33103
|
-
};
|
|
33104
|
-
|
|
33105
|
-
const getLocation = (result) => {
|
|
33106
|
-
const place = result.locationName || result.regionName || result.oordName;
|
|
33107
|
-
if (!place)
|
|
33108
|
-
return result.countryName ?? '';
|
|
33109
|
-
return result.countryName ? `${place}, ${result.countryName}` : place;
|
|
33110
|
-
};
|
|
33111
|
-
const toPlainText = (value) => {
|
|
33112
|
-
if (!value)
|
|
33113
|
-
return '';
|
|
33114
|
-
return he
|
|
33115
|
-
.decode(value.replace(/<[^>]*>/g, ' '))
|
|
33116
|
-
.replace(/\s+/g, ' ')
|
|
33117
|
-
.trim();
|
|
33118
|
-
};
|
|
33119
|
-
const TrajectNodeCard = ({ item, result, isSelected, languageCode, translations, onSelect, showNights }) => {
|
|
33120
|
-
const { node } = item;
|
|
33121
|
-
const selectedOption = lodash.first(result.rooms)?.options?.find((x) => x.isSelected) ?? lodash.first(result.rooms)?.options?.[0];
|
|
33122
|
-
const price = formatPrice$3(result.price, result.currencyCode, languageCode);
|
|
33123
|
-
const nights = calculateNights(new Date(result.fromDate), new Date(result.toDate));
|
|
33124
|
-
const candidate = findCandidateForResult(node, result);
|
|
33125
|
-
const image = candidate?.imageUrl ?? null;
|
|
33126
|
-
const description = toPlainText(candidate?.description);
|
|
33127
|
-
const title = candidate?.name ?? result.name;
|
|
33128
|
-
const priceBlock = (React__default["default"].createElement("div", { className: "search__result-card__price__wrapper" },
|
|
33129
|
-
React__default["default"].createElement("span", { className: "search__result-card__price__label" }, translations?.SHARED.TOTAL_PRICE),
|
|
33130
|
-
React__default["default"].createElement("span", { className: "search__result-card__price" }, price)));
|
|
33131
|
-
const selectButton = (React__default["default"].createElement("button", { type: "button", className: `cta ${isSelected ? 'cta--selected' : 'cta--select'}`, onClick: onSelect }, isSelected ? translations?.SHARED.SELECTED : translations?.SHARED.SELECT));
|
|
33132
|
-
if (result.contents?.length) {
|
|
33133
|
-
return (React__default["default"].createElement("div", { className: `search__result-card__wrapper search__result-card__wrapper--custom ${isSelected ? 'search__result-card__wrapper--selected' : ''}` },
|
|
33134
|
-
React__default["default"].createElement("div", { className: "search__result-card__top", dangerouslySetInnerHTML: { __html: he.decode(result.contents) } }),
|
|
33135
|
-
React__default["default"].createElement("div", { className: "search__result-card__footer" }, selectButton)));
|
|
33136
|
-
}
|
|
33137
|
-
return (React__default["default"].createElement("div", { className: `search__result-card__wrapper ${isSelected ? 'search__result-card__wrapper--selected' : ''}` },
|
|
33138
|
-
image && (React__default["default"].createElement("div", { className: "search__result-card__img-wrapper" },
|
|
33139
|
-
React__default["default"].createElement("img", { src: image, alt: title, className: "search__result-card__img" }),
|
|
33140
|
-
priceBlock)),
|
|
33141
|
-
React__default["default"].createElement("div", { className: "search__result-card__content" },
|
|
33142
|
-
React__default["default"].createElement("div", { className: "search__result-card__content__wrapper" },
|
|
33143
|
-
React__default["default"].createElement("div", { className: "search__result-card__header" },
|
|
33144
|
-
React__default["default"].createElement("div", { className: "search__result-card__header__wrapper" },
|
|
33145
|
-
!!result.stars && (React__default["default"].createElement("div", { className: "rating" }, [...Array(result.stars)].map((_, index) => (React__default["default"].createElement(Icon, { name: "ui-star", key: `rating-star-${index + 1}`, width: 14, height: 14 }))))),
|
|
33146
|
-
React__default["default"].createElement("h3", { className: "search__result-card__title" }, title)),
|
|
33147
|
-
!image && priceBlock),
|
|
33148
|
-
React__default["default"].createElement("span", { className: "search__result-card__location" },
|
|
33149
|
-
React__default["default"].createElement(Icon, { name: "ui-location", height: 16 }),
|
|
33150
|
-
getLocation(result)),
|
|
33151
|
-
React__default["default"].createElement("div", { className: "search__result-card__options" },
|
|
33152
|
-
showNights && nights > 0 && (React__default["default"].createElement("div", { className: "search__result-card__option" },
|
|
33153
|
-
React__default["default"].createElement(Icon, { name: "ui-moon", height: 16 }),
|
|
33154
|
-
nights,
|
|
33155
|
-
" ",
|
|
33156
|
-
translations?.SRP.NIGHTS)),
|
|
33157
|
-
selectedOption?.accommodationName && (React__default["default"].createElement("div", { className: "search__result-card__option" },
|
|
33158
|
-
React__default["default"].createElement(Icon, { name: "ui-bed", height: 16 }),
|
|
33159
|
-
selectedOption.accommodationName)),
|
|
33160
|
-
selectedOption?.regimeName && (React__default["default"].createElement("div", { className: "search__result-card__option" },
|
|
33161
|
-
React__default["default"].createElement(Icon, { name: "ui-utensils", height: 16 }),
|
|
33162
|
-
selectedOption.regimeName))),
|
|
33163
|
-
description && React__default["default"].createElement("p", { className: "search__result-card__description" }, description)),
|
|
33164
|
-
React__default["default"].createElement("div", { className: "search__result-card__footer" }, selectButton))));
|
|
33165
|
-
};
|
|
33166
|
-
|
|
33167
33289
|
const getNodeIcon = (node) => {
|
|
33168
33290
|
if (isTrajectFlightNode(node))
|
|
33169
33291
|
return 'ui-flight';
|
|
@@ -33178,15 +33300,10 @@ const TrajectResults = ({ isLoading }) => {
|
|
|
33178
33300
|
const dispatch = reactRedux.useDispatch();
|
|
33179
33301
|
const translations = getTranslations(context?.languageCode ?? 'en-GB');
|
|
33180
33302
|
const locale = getLocale(context?.languageCode ?? 'en-GB');
|
|
33181
|
-
const { trajectNodeAvailability,
|
|
33182
|
-
const uniqueOutwardFlights = reactRedux.useSelector(selectUniqueOutwardFlights);
|
|
33183
|
-
const uniqueReturnFlights = reactRedux.useSelector(selectUniqueReturnFlights);
|
|
33184
|
-
const selectedOutward = reactRedux.useSelector(selectSelectedOutward);
|
|
33185
|
-
const selectedReturn = reactRedux.useSelector(selectSelectedReturn);
|
|
33303
|
+
const { trajectNodeAvailability, trajectFlightAvailability } = reactRedux.useSelector((state) => state.searchResults);
|
|
33186
33304
|
const [expandedNodes, setExpandedNodes] = React.useState({});
|
|
33187
33305
|
const trajectEntry = context?.trajectEntry;
|
|
33188
33306
|
const nodesWithLines = React.useMemo(() => (trajectEntry ? getTrajectNodesWithLines(trajectEntry) : []), [trajectEntry]);
|
|
33189
|
-
const flightPairing = React.useMemo(() => pairTrajectFlights(nodesWithLines), [nodesWithLines]);
|
|
33190
33307
|
const lineByNodeId = React.useMemo(() => new Map(nodesWithLines.map((x) => [x.node.nodeId, x])), [nodesWithLines]);
|
|
33191
33308
|
const nodesByDay = React.useMemo(() => {
|
|
33192
33309
|
const map = new Map();
|
|
@@ -33208,12 +33325,28 @@ const TrajectResults = ({ isLoading }) => {
|
|
|
33208
33325
|
if (!result)
|
|
33209
33326
|
return;
|
|
33210
33327
|
dispatch(setTrajectNodeSelection({ lineGuid: item.line.guid, code }));
|
|
33211
|
-
|
|
33328
|
+
applyResultToLines(item.lines, result).forEach((line) => dispatch(updateEditableEntryLine(line)));
|
|
33329
|
+
};
|
|
33330
|
+
const handleShowMore = (item) => {
|
|
33331
|
+
dispatch(setTrajectEditLineGuid(item.line.guid));
|
|
33332
|
+
dispatch(setFlyInType('acco-results'));
|
|
33333
|
+
dispatch(setFlyInIsOpen(true));
|
|
33334
|
+
};
|
|
33335
|
+
const handleEditOptions = (item) => {
|
|
33336
|
+
const availability = trajectNodeAvailability[item.line.guid];
|
|
33337
|
+
const result = availability?.results.find((x) => x.code === availability?.selectedCode);
|
|
33338
|
+
if (!result)
|
|
33339
|
+
return;
|
|
33340
|
+
dispatch(setPackagingAccoSearchDetails([result]));
|
|
33341
|
+
dispatch(setSelectedPackagingAccoResult(result.code));
|
|
33342
|
+
dispatch(setTrajectEditLineGuid(item.line.guid));
|
|
33343
|
+
dispatch(setFlyInType('acco-details'));
|
|
33344
|
+
dispatch(setFlyInIsOpen(true));
|
|
33212
33345
|
};
|
|
33213
33346
|
const renderServiceNode = (item) => {
|
|
33347
|
+
console.log('renderServiceNode', item);
|
|
33214
33348
|
const { node, line } = item;
|
|
33215
33349
|
const availability = trajectNodeAvailability[line.guid];
|
|
33216
|
-
const isExpanded = !!expandedNodes[line.guid];
|
|
33217
33350
|
const results = availability?.results ?? [];
|
|
33218
33351
|
const selected = results.find((x) => x.code === availability?.selectedCode) ?? null;
|
|
33219
33352
|
const configured = results.filter((x) => x.code !== selected?.code && !!findCandidateForResult(node, x));
|
|
@@ -33235,34 +33368,27 @@ const TrajectResults = ({ isLoading }) => {
|
|
|
33235
33368
|
node.preferredAccommodationName,
|
|
33236
33369
|
" \u2014 ",
|
|
33237
33370
|
translations.SRP.NO_RESULTS))),
|
|
33238
|
-
React__default["default"].createElement("div", { className: "search__results__cards search__results__cards--
|
|
33371
|
+
React__default["default"].createElement("div", { className: "search__results__cards search__results__cards--compact" }, [selected, ...configured].map((result) => (React__default["default"].createElement(TrajectNodeCard, { key: result.code, item: item, result: result, isSelected: result.code === selected.code, languageCode: context.languageCode, translations: translations, onSelect: () => handleSelect(item, result.code), onEditOptions: node.serviceType === ACCOMMODATION_SERVICE_TYPE ? () => handleEditOptions(item) : undefined, showNights: node.serviceType === ACCOMMODATION_SERVICE_TYPE })))),
|
|
33239
33372
|
others.length > 0 && (React__default["default"].createElement("div", { className: "search__results__cards__actions" },
|
|
33240
|
-
React__default["default"].createElement("button", { type: "button", className: "cta cta--secondary", onClick: () =>
|
|
33373
|
+
React__default["default"].createElement("button", { type: "button", className: "cta cta--secondary", onClick: () => handleShowMore(item) },
|
|
33374
|
+
translations.SRP.SHOW_MORE,
|
|
33375
|
+
" (",
|
|
33376
|
+
others.length,
|
|
33377
|
+
")")))))));
|
|
33378
|
+
};
|
|
33379
|
+
const handleSelectFlight = (item, flight) => {
|
|
33380
|
+
dispatch(setTrajectFlightSelection({ lineGuid: item.line.guid, guid: flight.outwardGuid }));
|
|
33381
|
+
dispatch(updateEditableEntryLine(applyFlightToLine(item.line, flight)));
|
|
33241
33382
|
};
|
|
33242
33383
|
const renderFlightNode = (item) => {
|
|
33243
33384
|
const { node, line } = item;
|
|
33244
|
-
const
|
|
33245
|
-
const
|
|
33246
|
-
|
|
33247
|
-
|
|
33248
|
-
|
|
33249
|
-
|
|
33250
|
-
|
|
33251
|
-
React__default["default"].createElement("p", { className: "search__results__label__date-date" }, dateFns.format(new Date(line.from), 'd', { locale })),
|
|
33252
|
-
React__default["default"].createElement("p", null, dateFns.format(new Date(line.from), 'MMM', { locale }))),
|
|
33253
|
-
React__default["default"].createElement("div", { className: "search__results__label__text" },
|
|
33254
|
-
React__default["default"].createElement(Icon, { name: "ui-flight", height: 16 }),
|
|
33255
|
-
React__default["default"].createElement("h3", null,
|
|
33256
|
-
React__default["default"].createElement("strong", null, node.name)))),
|
|
33257
|
-
React__default["default"].createElement("div", { className: "no-results" },
|
|
33258
|
-
node.departureAirportCode,
|
|
33259
|
-
" \u2192 ",
|
|
33260
|
-
node.arrivalAirportCode)));
|
|
33261
|
-
}
|
|
33262
|
-
const flights = isOutbound ? uniqueOutwardFlights : uniqueReturnFlights;
|
|
33263
|
-
const selectedFlight = isOutbound ? selectedOutward : selectedReturn;
|
|
33264
|
-
const selectedKey = isOutbound ? selectedOutwardKey : selectedReturnKey;
|
|
33265
|
-
const visible = flights.filter((x) => getFlightKey(isOutbound ? x.outward.segments : x.return.segments) !== selectedKey);
|
|
33385
|
+
const availability = trajectFlightAvailability[line.guid];
|
|
33386
|
+
const status = availability?.status ?? 'loading';
|
|
33387
|
+
const flights = availability?.results ?? [];
|
|
33388
|
+
const selected = flights.find((x) => x.outwardGuid === availability?.selectedGuid) ?? null;
|
|
33389
|
+
const others = flights.filter((x) => x.outwardGuid !== availability?.selectedGuid);
|
|
33390
|
+
const isExpanded = !!expandedNodes[line.guid];
|
|
33391
|
+
const visible = isExpanded ? others : others.slice(0, 2);
|
|
33266
33392
|
return (React__default["default"].createElement(React__default["default"].Fragment, { key: line.guid },
|
|
33267
33393
|
React__default["default"].createElement("div", { className: "search__results__label search__results__label--secondary" },
|
|
33268
33394
|
React__default["default"].createElement("div", { className: "search__results__label__date" },
|
|
@@ -33272,11 +33398,19 @@ const TrajectResults = ({ isLoading }) => {
|
|
|
33272
33398
|
React__default["default"].createElement(Icon, { name: "ui-flight", height: 16 }),
|
|
33273
33399
|
React__default["default"].createElement("h3", null,
|
|
33274
33400
|
translations.SRP.SELECT,
|
|
33275
|
-
|
|
33276
|
-
React__default["default"].createElement("strong", null,
|
|
33277
|
-
|
|
33278
|
-
|
|
33279
|
-
|
|
33401
|
+
' ',
|
|
33402
|
+
React__default["default"].createElement("strong", null,
|
|
33403
|
+
node.departureAirportCode,
|
|
33404
|
+
" - ",
|
|
33405
|
+
node.arrivalAirportCode)))),
|
|
33406
|
+
status === 'loading' && React__default["default"].createElement(Spinner, { label: translations.SRP.LOADING_FLIGHTS }),
|
|
33407
|
+
status === 'unavailable' && React__default["default"].createElement("div", { className: "no-results" }, translations.SRP.NO_RESULTS),
|
|
33408
|
+
status === 'resolved' && selected && (React__default["default"].createElement(React__default["default"].Fragment, null,
|
|
33409
|
+
React__default["default"].createElement("div", { className: "search__results__cards search__results__cards--extended" },
|
|
33410
|
+
React__default["default"].createElement(IndependentFlightOption, { key: `flight-${selected.outwardGuid}`, item: selected.outward, guid: selected.outwardGuid, selectedGuid: selected.outwardGuid, isOutward: true, showSelectedState: true, price: selected.price }),
|
|
33411
|
+
visible.map((result) => (React__default["default"].createElement(IndependentFlightOption, { key: `flight-${result.outwardGuid}`, item: result.outward, guid: result.outwardGuid, isOutward: true, price: result.price, currentSelectedPrice: selected.price, onSelect: () => handleSelectFlight(item, result) })))),
|
|
33412
|
+
others.length > 2 && (React__default["default"].createElement("div", { className: "search__results__cards__actions" },
|
|
33413
|
+
React__default["default"].createElement("button", { type: "button", className: "cta cta--secondary", onClick: () => toggleExpanded(line.guid) }, isExpanded ? translations.SRP.SHOW_LESS : `${translations.SRP.SHOW_MORE} (${others.length - 2})`)))))));
|
|
33280
33414
|
};
|
|
33281
33415
|
if (isLoading)
|
|
33282
33416
|
return React__default["default"].createElement(Spinner, { label: translations.SRP.LOADING_ITINERARY });
|
|
@@ -33285,6 +33419,8 @@ const TrajectResults = ({ isLoading }) => {
|
|
|
33285
33419
|
if (!dayNodes.length)
|
|
33286
33420
|
return null;
|
|
33287
33421
|
return (React__default["default"].createElement(React__default["default"].Fragment, { key: `day-${day.order}` }, dayNodes.map((node) => {
|
|
33422
|
+
if (node.serviceType !== ACCOMMODATION_SERVICE_TYPE && node.serviceType !== EXCURSION_SERVICE_TYPE && node.serviceType !== FLIGHT_SERVICE_TYPE)
|
|
33423
|
+
return null;
|
|
33288
33424
|
// if (node.trajectNodeType === CONNECTING_TRAJECT_NODE_TYPE) return null;
|
|
33289
33425
|
// if (node.trajectNodeType !== REGULAR_TRAJECT_NODE_TYPE) return renderContextNode(node);
|
|
33290
33426
|
if (node.trajectNodeType !== REGULAR_TRAJECT_NODE_TYPE)
|
|
@@ -33302,7 +33438,7 @@ const useTrajectAvailability = () => {
|
|
|
33302
33438
|
const context = React.useContext(SearchResultsConfigurationContext);
|
|
33303
33439
|
const dispatch = reactRedux.useDispatch();
|
|
33304
33440
|
const trajectEntry = context?.trajectEntry;
|
|
33305
|
-
const
|
|
33441
|
+
const showNonPreferredItems = !!context?.showNonPreferredItems;
|
|
33306
33442
|
React.useEffect(() => {
|
|
33307
33443
|
if (!context || !trajectEntry)
|
|
33308
33444
|
return;
|
|
@@ -33322,6 +33458,13 @@ const useTrajectAvailability = () => {
|
|
|
33322
33458
|
language: context.languageCode ?? 'en-GB',
|
|
33323
33459
|
rooms: getPackagingRoomsFromEntry(trajectEntry.entry)
|
|
33324
33460
|
};
|
|
33461
|
+
// Flight searches take passengers rather than rooms; the API buckets by age.
|
|
33462
|
+
const pax = trajectEntry.entry.pax ?? [];
|
|
33463
|
+
const ageOf = (p) => p.age ?? 30;
|
|
33464
|
+
const adults = pax.filter((p) => ageOf(p) >= 12).length;
|
|
33465
|
+
const kids = pax.filter((p) => ageOf(p) >= 2 && ageOf(p) < 12).length;
|
|
33466
|
+
const babies = pax.filter((p) => ageOf(p) < 2).length;
|
|
33467
|
+
const flightPax = lodash.concat(Array.from({ length: adults }, (_, index) => ({ id: index, age: 31 })), Array.from({ length: kids }, (_, index) => ({ id: index + adults, age: 8 })), Array.from({ length: babies }, (_, index) => ({ id: index + adults + kids, age: 1 })));
|
|
33325
33468
|
const nodesWithLines = getTrajectNodesWithLines(trajectEntry);
|
|
33326
33469
|
const resolveNode = async (item) => {
|
|
33327
33470
|
const lineGuid = item.line.guid;
|
|
@@ -33340,18 +33483,20 @@ const useTrajectAvailability = () => {
|
|
|
33340
33483
|
return null;
|
|
33341
33484
|
}
|
|
33342
33485
|
}));
|
|
33343
|
-
|
|
33344
|
-
|
|
33486
|
+
const configuredResults = attempts
|
|
33487
|
+
.filter((x) => !!x)
|
|
33488
|
+
.filter((x, i, all) => all.findIndex((y) => y.code === x.code) === i);
|
|
33345
33489
|
const preferredCandidate = candidates.find((x) => x.isPreferred) ?? candidates[0];
|
|
33346
|
-
let preferredUnavailable = !!preferredCandidate && !
|
|
33347
|
-
|
|
33348
|
-
|
|
33349
|
-
|
|
33350
|
-
|
|
33490
|
+
let preferredUnavailable = !!preferredCandidate && !configuredResults.some((x) => matchesCode(x, preferredCandidate.code));
|
|
33491
|
+
const destinationResults = showNonPreferredItems
|
|
33492
|
+
? ((await search(config, buildTrajectNodeRequest(item, searchContext, ''), controller.signal)) ?? []).filter((x) => !configuredResults.some((configured) => configured.code === x.code))
|
|
33493
|
+
: [];
|
|
33494
|
+
const results = [...configuredResults, ...destinationResults];
|
|
33495
|
+
const picked = configuredResults[0] ?? pickCheapest(destinationResults);
|
|
33496
|
+
if (!configuredResults.length)
|
|
33351
33497
|
preferredUnavailable = candidates.length > 0;
|
|
33352
|
-
}
|
|
33353
33498
|
if (controller.signal.aborted)
|
|
33354
|
-
return
|
|
33499
|
+
return [];
|
|
33355
33500
|
dispatch(setTrajectNodeAvailability({
|
|
33356
33501
|
lineGuid,
|
|
33357
33502
|
results,
|
|
@@ -33359,91 +33504,58 @@ const useTrajectAvailability = () => {
|
|
|
33359
33504
|
preferredUnavailable
|
|
33360
33505
|
}));
|
|
33361
33506
|
if (!picked)
|
|
33362
|
-
return
|
|
33363
|
-
|
|
33364
|
-
return
|
|
33507
|
+
return item.lines.map((x) => x.guid);
|
|
33508
|
+
applyResultToLines(item.lines, picked).forEach((line) => dispatch(updateEditableEntryLine(line)));
|
|
33509
|
+
return [];
|
|
33365
33510
|
}
|
|
33366
33511
|
catch (error) {
|
|
33367
33512
|
if (controller.signal.aborted)
|
|
33368
|
-
return
|
|
33513
|
+
return [];
|
|
33369
33514
|
console.error('Failed to resolve traject node availability', item.node.name, error);
|
|
33370
33515
|
dispatch(setTrajectNodeAvailability({ lineGuid, results: [], selectedCode: null, preferredUnavailable: false }));
|
|
33371
|
-
return
|
|
33516
|
+
return item.lines.map((x) => x.guid);
|
|
33372
33517
|
}
|
|
33373
33518
|
};
|
|
33374
|
-
const
|
|
33375
|
-
const
|
|
33376
|
-
if (!
|
|
33377
|
-
dispatch(
|
|
33378
|
-
return;
|
|
33519
|
+
const resolveFlightNode = async (item) => {
|
|
33520
|
+
const lineGuid = item.line.guid;
|
|
33521
|
+
if (!item.node.departureAirportCode || !item.node.arrivalAirportCode) {
|
|
33522
|
+
dispatch(setTrajectFlightAvailability({ lineGuid, results: [], selectedGuid: null }));
|
|
33523
|
+
return item.lines.map((x) => x.guid);
|
|
33379
33524
|
}
|
|
33380
|
-
dispatch(
|
|
33525
|
+
dispatch(setTrajectFlightLoading(lineGuid));
|
|
33381
33526
|
try {
|
|
33382
|
-
const
|
|
33383
|
-
const ageOf = (p) => p.age ?? 30;
|
|
33384
|
-
const adults = pax.filter((p) => ageOf(p) >= 12).length;
|
|
33385
|
-
const kids = pax.filter((p) => ageOf(p) >= 2 && ageOf(p) < 12).length;
|
|
33386
|
-
const babies = pax.filter((p) => ageOf(p) < 2).length;
|
|
33387
|
-
const request = {
|
|
33388
|
-
transactionId: trajectEntry.entry.transactionId,
|
|
33389
|
-
officeId: searchContext.officeId,
|
|
33390
|
-
catalogueId: searchContext.catalogueId,
|
|
33391
|
-
agentId: searchContext.agentId,
|
|
33392
|
-
language: searchContext.language,
|
|
33393
|
-
departureAirportCode: outbound.node.departureAirportCode ?? '',
|
|
33394
|
-
arrivalAirportCode: outbound.node.arrivalAirportCode ?? '',
|
|
33395
|
-
returnAirportCode: inbound?.node.arrivalAirportCode ?? outbound.node.departureAirportCode ?? null,
|
|
33396
|
-
luggageIncluded: null,
|
|
33397
|
-
maxStops: null,
|
|
33398
|
-
travelClass: null,
|
|
33399
|
-
pax: lodash.concat(Array.from({ length: adults }, (_, index) => ({ id: index, age: 31 })), Array.from({ length: kids }, (_, index) => ({ id: index + adults, age: 8 })), Array.from({ length: babies }, (_, index) => ({ id: index + adults + kids, age: 1 }))),
|
|
33400
|
-
outward: { date: dateToDateStruct(new Date(outbound.line.from)) },
|
|
33401
|
-
return: { date: dateToDateStruct(new Date((inbound ?? outbound).line.from)) }
|
|
33402
|
-
};
|
|
33403
|
-
const flights = (await build.searchPackagingFlights(config, request, controller.signal)) ?? [];
|
|
33527
|
+
const flights = (await build.searchPackagingFlights(config, buildTrajectFlightRequest(item, searchContext, flightPax), controller.signal)) ?? [];
|
|
33404
33528
|
if (controller.signal.aborted)
|
|
33405
|
-
return;
|
|
33406
|
-
|
|
33407
|
-
|
|
33408
|
-
if (
|
|
33409
|
-
|
|
33410
|
-
|
|
33411
|
-
|
|
33529
|
+
return [];
|
|
33530
|
+
const picked = pickCheapestFlight(flights);
|
|
33531
|
+
dispatch(setTrajectFlightAvailability({ lineGuid, results: flights, selectedGuid: picked?.outwardGuid ?? null }));
|
|
33532
|
+
if (!picked)
|
|
33533
|
+
return item.lines.map((x) => x.guid);
|
|
33534
|
+
dispatch(updateEditableEntryLine(applyFlightToLine(item.line, picked)));
|
|
33535
|
+
return [];
|
|
33412
33536
|
}
|
|
33413
33537
|
catch (error) {
|
|
33414
|
-
if (
|
|
33415
|
-
|
|
33416
|
-
|
|
33417
|
-
|
|
33418
|
-
|
|
33419
|
-
dispatch(setFlightsLoading(false));
|
|
33420
|
-
dispatch(setTrajectFlightsResolved(true));
|
|
33421
|
-
}
|
|
33538
|
+
if (controller.signal.aborted)
|
|
33539
|
+
return [];
|
|
33540
|
+
console.error('Failed to resolve traject flight', item.node.name, error);
|
|
33541
|
+
dispatch(setTrajectFlightAvailability({ lineGuid, results: [], selectedGuid: null }));
|
|
33542
|
+
return item.lines.map((x) => x.guid);
|
|
33422
33543
|
}
|
|
33423
33544
|
};
|
|
33424
33545
|
(async () => {
|
|
33425
33546
|
const searchable = nodesWithLines.filter((x) => isSearchableTrajectNode(x.node));
|
|
33426
|
-
const
|
|
33547
|
+
const flightNodes = nodesWithLines.filter((x) => isTrajectFlightNode(x.node));
|
|
33548
|
+
const unresolved = (await Promise.all([...searchable.map(resolveNode), ...flightNodes.map(resolveFlightNode)])).flat();
|
|
33427
33549
|
if (controller.signal.aborted)
|
|
33428
33550
|
return;
|
|
33429
33551
|
// A node with no availability anywhere cannot be priced or booked, so its line comes off
|
|
33430
33552
|
// the entry. The card stays visible and reports the gap.
|
|
33431
|
-
const emptyLineGuids = unresolved.filter(
|
|
33553
|
+
const emptyLineGuids = unresolved.filter(Boolean);
|
|
33432
33554
|
if (emptyLineGuids.length)
|
|
33433
33555
|
dispatch(removeEditableEntryLines(emptyLineGuids));
|
|
33434
33556
|
})();
|
|
33435
33557
|
return () => controller.abort();
|
|
33436
|
-
}, [trajectEntry]);
|
|
33437
|
-
// Keep the traject's own flight lines in step with whatever outward/return the user picks.
|
|
33438
|
-
React.useEffect(() => {
|
|
33439
|
-
if (!trajectEntry || !selectedCombinationFlight)
|
|
33440
|
-
return;
|
|
33441
|
-
const { outbound, inbound } = pairTrajectFlights(getTrajectNodesWithLines(trajectEntry));
|
|
33442
|
-
if (outbound)
|
|
33443
|
-
dispatch(updateEditableEntryLine(applyFlightToLine(outbound.line, selectedCombinationFlight, true)));
|
|
33444
|
-
if (inbound)
|
|
33445
|
-
dispatch(updateEditableEntryLine(applyFlightToLine(inbound.line, selectedCombinationFlight, false)));
|
|
33446
|
-
}, [trajectEntry, selectedCombinationFlight]);
|
|
33558
|
+
}, [trajectEntry, showNonPreferredItems]);
|
|
33447
33559
|
};
|
|
33448
33560
|
|
|
33449
33561
|
const SearchResultsContainer = ({ onBookingStarted }) => {
|
|
@@ -33451,7 +33563,7 @@ const SearchResultsContainer = ({ onBookingStarted }) => {
|
|
|
33451
33563
|
const dispatch = reactRedux.useDispatch();
|
|
33452
33564
|
const context = React.useContext(SearchResultsConfigurationContext);
|
|
33453
33565
|
const translations = getTranslations(context?.languageCode ?? 'en-GB');
|
|
33454
|
-
const { results, filteredResults, packagingAccoResults, filteredPackagingAccoResults, isLoading, flightsLoading, initialFilters, filters, flightFilters, selectedSortType, selectedFlightSortType, selectedSearchResult, selectedPackagingAccoResultCode, flyInIsOpen, packagingAccoSearchDetails, editablePackagingEntry, transactionId, flyInType, packagingFlightResults, confirmedExcursionsByDay, bookPackagingEntry } = reactRedux.useSelector((state) => state.searchResults);
|
|
33566
|
+
const { results, filteredResults, packagingAccoResults, filteredPackagingAccoResults, isLoading, flightsLoading, initialFilters, filters, flightFilters, selectedSortType, selectedFlightSortType, selectedSearchResult, selectedPackagingAccoResultCode, flyInIsOpen, packagingAccoSearchDetails, editablePackagingEntry, transactionId, flyInType, packagingFlightResults, confirmedExcursionsByDay, bookPackagingEntry, trajectEditLineGuid } = reactRedux.useSelector((state) => state.searchResults);
|
|
33455
33567
|
const isMobile = useMediaQuery('(max-width: 1200px)');
|
|
33456
33568
|
// Resolves live availability per traject node; no-op unless a traject entry is configured.
|
|
33457
33569
|
useTrajectAvailability();
|
|
@@ -33740,6 +33852,20 @@ const SearchResultsContainer = ({ onBookingStarted }) => {
|
|
|
33740
33852
|
setDetailsIsLoading(false);
|
|
33741
33853
|
};
|
|
33742
33854
|
const handleConfirmHotelSwap = () => {
|
|
33855
|
+
if (context?.trajectEntry && trajectEditLineGuid) {
|
|
33856
|
+
const item = getTrajectNodesWithLines(context.trajectEntry).find((x) => x.line.guid === trajectEditLineGuid);
|
|
33857
|
+
const result = packagingAccoSearchDetails.find((x) => x.code === selectedPackagingAccoResultCode);
|
|
33858
|
+
if (item && result) {
|
|
33859
|
+
const currentLines = item.lines
|
|
33860
|
+
.map((line) => editablePackagingEntry?.lines?.find((x) => x.guid === line.guid))
|
|
33861
|
+
.filter((x) => !!x);
|
|
33862
|
+
applyResultToLines(currentLines, result).forEach((line) => dispatch(updateEditableEntryLine(line)));
|
|
33863
|
+
dispatch(updateTrajectNodeResult({ lineGuid: trajectEditLineGuid, result }));
|
|
33864
|
+
}
|
|
33865
|
+
dispatch(setTrajectEditLineGuid(null));
|
|
33866
|
+
handleFlyInToggle(false);
|
|
33867
|
+
return;
|
|
33868
|
+
}
|
|
33743
33869
|
const updatedEntry = swapHotelInPackagingEntry();
|
|
33744
33870
|
if (!updatedEntry)
|
|
33745
33871
|
return;
|
|
@@ -34088,6 +34214,10 @@ const SearchResultsContainer = ({ onBookingStarted }) => {
|
|
|
34088
34214
|
const fetchPackagingAccoSearchDetails = async () => {
|
|
34089
34215
|
if (!selectedPackagingAccoResultCode || !context)
|
|
34090
34216
|
return;
|
|
34217
|
+
// A traject node already resolved its own availability, options per room included, and hands
|
|
34218
|
+
// that result straight to the fly-in.
|
|
34219
|
+
if (context.trajectEntry)
|
|
34220
|
+
return;
|
|
34091
34221
|
if (skipInitialPackagingAccoDetailsRef.current) {
|
|
34092
34222
|
skipInitialPackagingAccoDetailsRef.current = false;
|
|
34093
34223
|
return;
|
|
@@ -37602,20 +37732,20 @@ function ItineraryMapView({ destinations: propDestinations, adults = 2, dateFrom
|
|
|
37602
37732
|
const isStartEnd = dest.isStart || dest.isEnd;
|
|
37603
37733
|
let iconHtml;
|
|
37604
37734
|
if (isStartEnd) {
|
|
37605
|
-
iconHtml = `
|
|
37606
|
-
<div class="map-view__marker map-view__marker--plane">
|
|
37607
|
-
<svg viewBox="0 0 576 512" fill="currentColor" width="16" height="16" xmlns="http://www.w3.org/2000/svg">
|
|
37608
|
-
<path d="M482.3 192c34.2 0 93.7 29 93.7 64c0 36-59.5 64-93.7 64l-116.6 0L265.2 495.9c-5.7 10.1-16.3 16.1-27.8 16.1l-56.2 0c-10.6 0-18.3-10.3-15.8-20.6l49-196.3L112 288 68.8 377.6c-3 6.1-9.2 10.4-16 10.4l-2.8 0c-9.2 0-16-8.8-13.3-17.6L80 256L36.7 141.6C34 132.8 40.8 124 50 124l2.8 0c6.8 0 13 4.3 16 10.4L112 224l102.9 0-49-196.3C163.5 17.3 171.2 7 181.8 7l56.2 0c11.5 0 22.1 6 27.8 16.1L365.7 192l116.6 0z"/>
|
|
37609
|
-
</svg>
|
|
37735
|
+
iconHtml = `
|
|
37736
|
+
<div class="map-view__marker map-view__marker--plane">
|
|
37737
|
+
<svg viewBox="0 0 576 512" fill="currentColor" width="16" height="16" xmlns="http://www.w3.org/2000/svg">
|
|
37738
|
+
<path d="M482.3 192c34.2 0 93.7 29 93.7 64c0 36-59.5 64-93.7 64l-116.6 0L265.2 495.9c-5.7 10.1-16.3 16.1-27.8 16.1l-56.2 0c-10.6 0-18.3-10.3-15.8-20.6l49-196.3L112 288 68.8 377.6c-3 6.1-9.2 10.4-16 10.4l-2.8 0c-9.2 0-16-8.8-13.3-17.6L80 256L36.7 141.6C34 132.8 40.8 124 50 124l2.8 0c6.8 0 13 4.3 16 10.4L112 224l102.9 0-49-196.3C163.5 17.3 171.2 7 181.8 7l56.2 0c11.5 0 22.1 6 27.8 16.1L365.7 192l116.6 0z"/>
|
|
37739
|
+
</svg>
|
|
37610
37740
|
</div>`;
|
|
37611
37741
|
}
|
|
37612
37742
|
else {
|
|
37613
37743
|
stopIndex++;
|
|
37614
37744
|
const imgStyle = dest.imageUrl ? `style="background-image:url('${dest.imageUrl}')"` : '';
|
|
37615
|
-
iconHtml = `
|
|
37616
|
-
<div class="map-view__marker map-view__marker--stop" ${imgStyle}>
|
|
37617
|
-
${!dest.imageUrl ? `<span class="map-view__marker-number">${stopIndex}</span>` : ''}
|
|
37618
|
-
<span class="map-view__marker-badge">${stopIndex}</span>
|
|
37745
|
+
iconHtml = `
|
|
37746
|
+
<div class="map-view__marker map-view__marker--stop" ${imgStyle}>
|
|
37747
|
+
${!dest.imageUrl ? `<span class="map-view__marker-number">${stopIndex}</span>` : ''}
|
|
37748
|
+
<span class="map-view__marker-badge">${stopIndex}</span>
|
|
37619
37749
|
</div>`;
|
|
37620
37750
|
}
|
|
37621
37751
|
const icon = L.divIcon({
|