@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-esm/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import * as React from 'react';
|
|
3
3
|
import React__default, { useLayoutEffect as useLayoutEffect$1, useEffect, useContext, useState, useRef, useCallback, useMemo } from 'react';
|
|
4
4
|
import JsonURL from '@jsonurl/jsonurl';
|
|
5
|
-
import { format as format$2, isToday, getDate, startOfWeek, startOfMonth, endOfWeek, addWeeks, endOfMonth, eachDayOfInterval, getYear, getMonth, getISOWeek, getISODay, isSameMonth, startOfDay, isAfter, isEqual, isWithinInterval, endOfDay, isBefore, isSameDay, differenceInCalendarDays, addMonths, addDays, formatISO, addYears, differenceInYears, parseISO, differenceInMinutes, parse, startOfToday
|
|
5
|
+
import { format as format$2, isToday, getDate, startOfWeek, startOfMonth, endOfWeek, addWeeks, endOfMonth, eachDayOfInterval, getYear, getMonth, getISOWeek, getISODay, isSameMonth, startOfDay, isAfter, isEqual, isWithinInterval, endOfDay, isBefore, isSameDay, differenceInCalendarDays, addMonths, addDays, formatISO, addYears, differenceInYears, parseISO, differenceInMinutes, isValid, parse, startOfToday } from 'date-fns';
|
|
6
6
|
import { merge, cloneDeep, isNil, isArray as isArray$1, compact, isEmpty as isEmpty$1, range, chunk, isFunction as isFunction$1, clamp, now, omit, isPlainObject as isPlainObject$1, mapValues, pickBy, first, orderBy, uniq, uniqBy, sortBy, sum, last, findIndex as findIndex$1, set, get, isEqual as isEqual$1, groupBy, minBy, concat, flatMap } from 'lodash';
|
|
7
7
|
import { arSA, da, de, enGB, es, fr, is, it, nl, nb, pl, pt, sv, ja, enUS } from 'date-fns/locale';
|
|
8
8
|
import { usePopper } from 'react-popper';
|
|
@@ -22861,6 +22861,10 @@ const NAME_CHARACTERS_REGEX = /^[\p{L}\s-]+$/u;
|
|
|
22861
22861
|
function hasInvalidNameCharacters(name) {
|
|
22862
22862
|
return !NAME_CHARACTERS_REGEX.test(name);
|
|
22863
22863
|
}
|
|
22864
|
+
// Rejects values like a 5-digit year, which the native date input allows while typing
|
|
22865
|
+
function isValidBirthDate(birthDateText) {
|
|
22866
|
+
return isValid(parse(birthDateText, 'yyyy-MM-dd', new Date()));
|
|
22867
|
+
}
|
|
22864
22868
|
function getAge(birthDateText, startDateText) {
|
|
22865
22869
|
var birthDate = new Date(birthDateText);
|
|
22866
22870
|
var startDate = new Date(startDateText);
|
|
@@ -22928,7 +22932,7 @@ const validateForm$1 = (values, agentRequired, bookingType, translations, formFi
|
|
|
22928
22932
|
}
|
|
22929
22933
|
}
|
|
22930
22934
|
if (isFormFieldPresent('birthDate')) {
|
|
22931
|
-
if (isEmpty$1(adult.birthDate)) {
|
|
22935
|
+
if (isEmpty$1(adult.birthDate) || !isValidBirthDate(adult.birthDate)) {
|
|
22932
22936
|
set(errors, `rooms[${rIndex}].adults[${index}].birthDate`, formatTravelerField(rIndex + 1, index + 1, translations.TRAVELERS_FORM.BIRTHDATE));
|
|
22933
22937
|
}
|
|
22934
22938
|
else if (values.endDate) {
|
|
@@ -22961,7 +22965,7 @@ const validateForm$1 = (values, agentRequired, bookingType, translations, formFi
|
|
|
22961
22965
|
}
|
|
22962
22966
|
}
|
|
22963
22967
|
if (isFormFieldPresent('birthDate')) {
|
|
22964
|
-
if (isEmpty$1(child.birthDate)) {
|
|
22968
|
+
if (isEmpty$1(child.birthDate) || !isValidBirthDate(child.birthDate)) {
|
|
22965
22969
|
set(errors, `rooms[${rIndex}].children[${index}].birthDate`, formatTravelerField(rIndex + 1, r.adults.length + index + 1, translations.TRAVELERS_FORM.BIRTHDATE));
|
|
22966
22970
|
}
|
|
22967
22971
|
else if (child.isBaby && values.startDate) {
|
|
@@ -23186,6 +23190,13 @@ const TypeAheadInput = ({ name, value, placeholder, options, onChange, onSelect,
|
|
|
23186
23190
|
function isBabyAge(age, maxBabyAge = BABY_MAX_AGE) {
|
|
23187
23191
|
return typeof age === 'number' && age <= maxBabyAge;
|
|
23188
23192
|
}
|
|
23193
|
+
// The native date input can hold values format() throws on (e.g. a 5+ digit year while typing), so never format an unvalidated birthDate
|
|
23194
|
+
function formatBirthDate$1(birthDate) {
|
|
23195
|
+
if (!birthDate)
|
|
23196
|
+
return '';
|
|
23197
|
+
const parsed = parse(birthDate, 'yyyy-MM-dd', new Date());
|
|
23198
|
+
return isValid(parsed) ? format$2(parsed, 'dd-MM-yyyy') : '';
|
|
23199
|
+
}
|
|
23189
23200
|
function createTraveler(traveler, followNumber, personTranslation, isCompact, maxBabyAge) {
|
|
23190
23201
|
if (isCompact) {
|
|
23191
23202
|
return {
|
|
@@ -23493,10 +23504,7 @@ const SharedTravelersForm = ({ formik, translations, travellersSettings, countri
|
|
|
23493
23504
|
!useCompactForm && (bookingType !== 'b2b' || travellersSettings?.mainBookerFormFields?.length) ? (React__default.createElement("div", { className: "form__region" },
|
|
23494
23505
|
React__default.createElement("div", { className: "form__region-header" },
|
|
23495
23506
|
React__default.createElement("h5", { className: "form__region-heading" }, translations.TRAVELERS_FORM.MAIN_BOOKER),
|
|
23496
|
-
React__default.createElement("p", { className: "form__region-label" }, compact([
|
|
23497
|
-
compact([mainBooker?.firstName, mainBooker?.lastName]).join(' '),
|
|
23498
|
-
mainBooker?.birthDate && format$2(parse(mainBooker.birthDate, 'yyyy-MM-dd', new Date()), 'dd-MM-yyyy')
|
|
23499
|
-
]).join(', '))),
|
|
23507
|
+
React__default.createElement("p", { className: "form__region-label" }, compact([compact([mainBooker?.firstName, mainBooker?.lastName]).join(' '), formatBirthDate$1(mainBooker?.birthDate)]).join(', '))),
|
|
23500
23508
|
travellersSettings?.mainBookerFormFields?.length ? (React__default.createElement("div", { className: "main-booker-form__grid" }, travellersSettings.mainBookerFormFields.map((field, index) => (React__default.createElement("div", { key: index, className: `control control--${field.type}` }, getControl(field.type, {}, field.type)))))) : (React__default.createElement(React__default.Fragment, null,
|
|
23501
23509
|
React__default.createElement("div", { className: "form__twocolumn" },
|
|
23502
23510
|
React__default.createElement("div", { className: "form__twocolumn-column" },
|
|
@@ -27341,7 +27349,8 @@ const initialState$1 = {
|
|
|
27341
27349
|
currentStep: 0,
|
|
27342
27350
|
bookingNumber: undefined,
|
|
27343
27351
|
trajectNodeAvailability: {},
|
|
27344
|
-
|
|
27352
|
+
trajectFlightAvailability: {},
|
|
27353
|
+
trajectEditLineGuid: null
|
|
27345
27354
|
};
|
|
27346
27355
|
const searchResultsSlice = createSlice({
|
|
27347
27356
|
name: 'searchResults',
|
|
@@ -27563,16 +27572,51 @@ const searchResultsSlice = createSlice({
|
|
|
27563
27572
|
const guids = new Set(action.payload);
|
|
27564
27573
|
state.editablePackagingEntry.lines = (state.editablePackagingEntry.lines ?? []).filter((x) => !guids.has(x.guid));
|
|
27565
27574
|
},
|
|
27566
|
-
|
|
27567
|
-
|
|
27575
|
+
setTrajectFlightLoading(state, action) {
|
|
27576
|
+
const lineGuid = action.payload;
|
|
27577
|
+
state.trajectFlightAvailability[lineGuid] = {
|
|
27578
|
+
lineGuid,
|
|
27579
|
+
status: 'loading',
|
|
27580
|
+
results: state.trajectFlightAvailability[lineGuid]?.results ?? [],
|
|
27581
|
+
selectedGuid: state.trajectFlightAvailability[lineGuid]?.selectedGuid ?? null
|
|
27582
|
+
};
|
|
27583
|
+
},
|
|
27584
|
+
setTrajectFlightAvailability(state, action) {
|
|
27585
|
+
const { lineGuid, results, selectedGuid } = action.payload;
|
|
27586
|
+
state.trajectFlightAvailability[lineGuid] = {
|
|
27587
|
+
lineGuid,
|
|
27588
|
+
status: selectedGuid ? 'resolved' : 'unavailable',
|
|
27589
|
+
results,
|
|
27590
|
+
selectedGuid
|
|
27591
|
+
};
|
|
27592
|
+
},
|
|
27593
|
+
setTrajectFlightSelection(state, action) {
|
|
27594
|
+
const flight = state.trajectFlightAvailability[action.payload.lineGuid];
|
|
27595
|
+
if (!flight)
|
|
27596
|
+
return;
|
|
27597
|
+
flight.selectedGuid = action.payload.guid;
|
|
27598
|
+
flight.status = 'resolved';
|
|
27599
|
+
},
|
|
27600
|
+
setTrajectEditLineGuid(state, action) {
|
|
27601
|
+
state.trajectEditLineGuid = action.payload;
|
|
27602
|
+
},
|
|
27603
|
+
updateTrajectNodeResult(state, action) {
|
|
27604
|
+
const { lineGuid, result } = action.payload;
|
|
27605
|
+
const node = state.trajectNodeAvailability[lineGuid];
|
|
27606
|
+
if (!node)
|
|
27607
|
+
return;
|
|
27608
|
+
node.results = node.results.map((x) => (x.code === result.code ? result : x));
|
|
27609
|
+
node.selectedCode = result.code;
|
|
27610
|
+
node.status = 'resolved';
|
|
27568
27611
|
},
|
|
27569
27612
|
resetTrajectAvailability(state) {
|
|
27570
27613
|
state.trajectNodeAvailability = {};
|
|
27571
|
-
state.
|
|
27614
|
+
state.trajectFlightAvailability = {};
|
|
27615
|
+
state.trajectEditLineGuid = null;
|
|
27572
27616
|
}
|
|
27573
27617
|
}
|
|
27574
27618
|
});
|
|
27575
|
-
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,
|
|
27619
|
+
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;
|
|
27576
27620
|
var searchResultsReducer = searchResultsSlice.reducer;
|
|
27577
27621
|
|
|
27578
27622
|
const ItemPicker = ({ items, selection, selectedSortByType, label, placeholder, classModifier, onPick, valueFormatter }) => {
|
|
@@ -30552,6 +30596,293 @@ const ExcursionDetails = () => {
|
|
|
30552
30596
|
React__default.createElement("button", { type: "button", className: "cta cta--primary", onClick: handleConfirm }, translations?.QSM.CONFIRM))));
|
|
30553
30597
|
};
|
|
30554
30598
|
|
|
30599
|
+
const REGULAR_TRAJECT_NODE_TYPE = 0;
|
|
30600
|
+
const isSearchableTrajectNode = (node) => node.serviceType === ACCOMMODATION_SERVICE_TYPE || node.serviceType === EXCURSION_SERVICE_TYPE;
|
|
30601
|
+
const isTrajectFlightNode = (node) => node.serviceType === FLIGHT_SERVICE_TYPE;
|
|
30602
|
+
const getTrajectNodesWithLines = (trajectEntry) => {
|
|
30603
|
+
const linesByGuid = new Map((trajectEntry.entry.lines ?? []).map((line) => [line.guid, line]));
|
|
30604
|
+
return [...(trajectEntry.nodes ?? [])]
|
|
30605
|
+
.sort((a, b) => a.dayOrder - b.dayOrder || a.order - b.order)
|
|
30606
|
+
.map((node) => {
|
|
30607
|
+
const guids = node.lineGuids?.length ? node.lineGuids : [node.lineGuid];
|
|
30608
|
+
const lines = guids.map((guid) => linesByGuid.get(guid)).filter((x) => !!x);
|
|
30609
|
+
return { node, line: lines[0], lines };
|
|
30610
|
+
})
|
|
30611
|
+
.filter((x) => !!x.line);
|
|
30612
|
+
};
|
|
30613
|
+
const buildDestination = (line) => {
|
|
30614
|
+
if (line.location?.id)
|
|
30615
|
+
return { id: line.location.id, isLocation: true };
|
|
30616
|
+
if (line.oord?.id)
|
|
30617
|
+
return { id: line.oord.id, isOord: true };
|
|
30618
|
+
if (line.region?.id)
|
|
30619
|
+
return { id: line.region.id, isRegion: true };
|
|
30620
|
+
if (line.country?.id)
|
|
30621
|
+
return { id: line.country.id, isCountry: true };
|
|
30622
|
+
if (line.latitude && line.longitude)
|
|
30623
|
+
return { latitude: line.latitude, longitude: line.longitude };
|
|
30624
|
+
return { id: 0 };
|
|
30625
|
+
};
|
|
30626
|
+
const buildTrajectNodeRequest = ({ node, line }, context, productCode) => ({
|
|
30627
|
+
transactionId: context.transactionId,
|
|
30628
|
+
officeId: context.officeId,
|
|
30629
|
+
agentId: context.agentId ?? null,
|
|
30630
|
+
catalogueId: context.catalogueId,
|
|
30631
|
+
searchConfigurationId: context.searchConfigurationId,
|
|
30632
|
+
portalId: context.portalId ?? null,
|
|
30633
|
+
vendorConfigurationId: node.externalVendorId ?? null,
|
|
30634
|
+
language: context.language,
|
|
30635
|
+
serviceType: node.serviceType,
|
|
30636
|
+
fromDate: toDateOnlyString(line.from),
|
|
30637
|
+
toDate: toDateOnlyString(line.to),
|
|
30638
|
+
destination: buildDestination(line),
|
|
30639
|
+
productCode,
|
|
30640
|
+
rooms: context.rooms,
|
|
30641
|
+
tagIds: []
|
|
30642
|
+
});
|
|
30643
|
+
const getNodeCandidates = (node) => {
|
|
30644
|
+
const configured = (node.alternatives ?? []).filter((x) => !!x.code && x.code.trim().length > 0);
|
|
30645
|
+
if (configured.length)
|
|
30646
|
+
return configured;
|
|
30647
|
+
const fallbackCode = node.code ?? node.preferredAccommodationCode;
|
|
30648
|
+
if (!fallbackCode || !fallbackCode.trim().length)
|
|
30649
|
+
return [];
|
|
30650
|
+
return [
|
|
30651
|
+
{
|
|
30652
|
+
code: fallbackCode,
|
|
30653
|
+
name: node.name,
|
|
30654
|
+
description: node.description,
|
|
30655
|
+
imageUrl: node.imageUrl,
|
|
30656
|
+
contentSource: node.contentSource,
|
|
30657
|
+
vendor: node.vendor,
|
|
30658
|
+
externalVendorId: node.externalVendorId,
|
|
30659
|
+
isPreferred: true
|
|
30660
|
+
}
|
|
30661
|
+
];
|
|
30662
|
+
};
|
|
30663
|
+
const findCandidateForResult = (node, result) => getNodeCandidates(node).find((candidate) => matchesCode(result, candidate.code)) ?? null;
|
|
30664
|
+
const matchesCode = (result, code) => {
|
|
30665
|
+
if (result.code === code)
|
|
30666
|
+
return true;
|
|
30667
|
+
return (result.rooms ?? []).some((room) => (room.options ?? []).some((option) => option.accommodationCode === code));
|
|
30668
|
+
};
|
|
30669
|
+
const getPackagingRoomsFromEntry = (entry) => {
|
|
30670
|
+
const paxById = new Map((entry.pax ?? []).map((p) => [p.id, p]));
|
|
30671
|
+
const rooms = (entry.rooms ?? []).map((room) => ({
|
|
30672
|
+
travellers: (room.paxIds ?? []).map((paxId) => {
|
|
30673
|
+
const pax = paxById.get(paxId);
|
|
30674
|
+
return {
|
|
30675
|
+
id: paxId,
|
|
30676
|
+
age: pax?.age ?? null,
|
|
30677
|
+
dateOfBirth: pax?.dateOfBirth ?? null
|
|
30678
|
+
};
|
|
30679
|
+
})
|
|
30680
|
+
}));
|
|
30681
|
+
return rooms.filter((room) => room.travellers.length > 0);
|
|
30682
|
+
};
|
|
30683
|
+
const pickCheapest = (results) => [...results].sort((a, b) => a.price - b.price)[0] ?? null;
|
|
30684
|
+
const applyResultToLines = (lines, result) => lines.map((line, index) => applyResultToLine(line, result, index));
|
|
30685
|
+
const getOptionForRoom = (result, roomIndex) => {
|
|
30686
|
+
const room = (result.rooms ?? [])[roomIndex];
|
|
30687
|
+
if (room)
|
|
30688
|
+
return (room.options ?? []).find((x) => x.isSelected) ?? (room.options ?? [])[0];
|
|
30689
|
+
return (result.rooms ?? []).flatMap((x) => x.options ?? []).find((x) => x.isSelected) ?? (result.rooms ?? [])[0]?.options?.[0];
|
|
30690
|
+
};
|
|
30691
|
+
const applyResultToLine = (line, result, roomIndex = 0) => {
|
|
30692
|
+
const option = getOptionForRoom(result, roomIndex);
|
|
30693
|
+
return {
|
|
30694
|
+
...line,
|
|
30695
|
+
productName: result.name ?? line.productName,
|
|
30696
|
+
productCode: result.code ?? line.productCode,
|
|
30697
|
+
accommodationCode: option?.accommodationCode ?? line.accommodationCode,
|
|
30698
|
+
accommodationName: option?.accommodationName ?? line.accommodationName,
|
|
30699
|
+
regimeCode: option?.regimeCode ?? line.regimeCode,
|
|
30700
|
+
regimeName: option?.regimeName ?? line.regimeName,
|
|
30701
|
+
latitude: result.latitude ?? line.latitude,
|
|
30702
|
+
longitude: result.longitude ?? line.longitude,
|
|
30703
|
+
isChanged: true
|
|
30704
|
+
};
|
|
30705
|
+
};
|
|
30706
|
+
const toDateOnlyUtcString = (value) => {
|
|
30707
|
+
const date = new Date(value);
|
|
30708
|
+
return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate())).toISOString();
|
|
30709
|
+
};
|
|
30710
|
+
const toTimeOnlyString = (value) => {
|
|
30711
|
+
const date = new Date(value);
|
|
30712
|
+
const hh = String(date.getUTCHours()).padStart(2, '0');
|
|
30713
|
+
const mm = String(date.getUTCMinutes()).padStart(2, '0');
|
|
30714
|
+
const ss = String(date.getUTCSeconds()).padStart(2, '0');
|
|
30715
|
+
return `${hh}:${mm}:${ss}`;
|
|
30716
|
+
};
|
|
30717
|
+
const mapFlightSegmentsToFlightLines = (segments) => segments.map((segment) => ({
|
|
30718
|
+
airlineCode: segment.marketingAirlineCode,
|
|
30719
|
+
airlineDescription: segment.marketingAirlineName,
|
|
30720
|
+
operatingAirlineCode: segment.operatingAirlineCode,
|
|
30721
|
+
operatingAirlineDescription: segment.operatingAirlineName,
|
|
30722
|
+
flightNumber: segment.flightNumber,
|
|
30723
|
+
operatingFlightNumber: segment.operatingFlightNumber ?? null,
|
|
30724
|
+
departureDate: toDateOnlyUtcString(segment.departureDateTime),
|
|
30725
|
+
departureTime: toTimeOnlyString(segment.departureDateTime),
|
|
30726
|
+
departureAirportCode: segment.departureAirportCode,
|
|
30727
|
+
departureAirportDescription: segment.departureAirportName,
|
|
30728
|
+
arrivalDate: toDateOnlyUtcString(segment.arrivalDateTime),
|
|
30729
|
+
arrivalTime: toTimeOnlyString(segment.arrivalDateTime),
|
|
30730
|
+
arrivalAirportCode: segment.arrivalAirportCode,
|
|
30731
|
+
arrivalAirportDescription: segment.arrivalAirportName,
|
|
30732
|
+
durationInTicks: segment.durationInTicks
|
|
30733
|
+
}));
|
|
30734
|
+
const buildTrajectFlightRequest = ({ node, line }, context, pax) => ({
|
|
30735
|
+
transactionId: context.transactionId,
|
|
30736
|
+
officeId: context.officeId,
|
|
30737
|
+
catalogueId: context.catalogueId,
|
|
30738
|
+
agentId: context.agentId ?? null,
|
|
30739
|
+
language: context.language,
|
|
30740
|
+
departureAirportCode: node.departureAirportCode ?? '',
|
|
30741
|
+
arrivalAirportCode: node.arrivalAirportCode ?? '',
|
|
30742
|
+
returnAirportCode: null,
|
|
30743
|
+
luggageIncluded: null,
|
|
30744
|
+
maxStops: null,
|
|
30745
|
+
travelClass: null,
|
|
30746
|
+
vendorConfigurationId: node.externalVendorId ?? null,
|
|
30747
|
+
pax,
|
|
30748
|
+
outward: { date: dateToDateStruct(new Date(line.from)) },
|
|
30749
|
+
return: null
|
|
30750
|
+
});
|
|
30751
|
+
const pickCheapestFlight = (flights) => [...flights].sort((a, b) => a.price - b.price)[0] ?? null;
|
|
30752
|
+
const applyFlightToLine = (line, flight) => {
|
|
30753
|
+
const segments = flight.outward?.segments ?? [];
|
|
30754
|
+
if (!segments.length)
|
|
30755
|
+
return line;
|
|
30756
|
+
const firstSegment = segments[0];
|
|
30757
|
+
const lastSegment = segments[segments.length - 1];
|
|
30758
|
+
return {
|
|
30759
|
+
...line,
|
|
30760
|
+
from: new Date(firstSegment.departureDateTime).toISOString(),
|
|
30761
|
+
to: new Date(lastSegment.arrivalDateTime).toISOString(),
|
|
30762
|
+
productName: `${firstSegment.departureAirportName} - ${lastSegment.arrivalAirportName} (${firstSegment.marketingAirlineName})`,
|
|
30763
|
+
productCode: `${firstSegment.departureAirportCode} ${lastSegment.arrivalAirportCode}/${firstSegment.marketingAirlineCode}`,
|
|
30764
|
+
accommodationName: firstSegment.metaData?.farePriceClassName ?? line.accommodationName,
|
|
30765
|
+
accommodationCode: firstSegment.metaData?.fareCode ?? line.accommodationCode,
|
|
30766
|
+
flightInformation: {
|
|
30767
|
+
pnr: '',
|
|
30768
|
+
flightLines: mapFlightSegmentsToFlightLines(segments)
|
|
30769
|
+
},
|
|
30770
|
+
isChanged: true
|
|
30771
|
+
};
|
|
30772
|
+
};
|
|
30773
|
+
|
|
30774
|
+
const getLocation = (result) => {
|
|
30775
|
+
const place = result.locationName || result.regionName || result.oordName;
|
|
30776
|
+
if (!place)
|
|
30777
|
+
return result.countryName ?? '';
|
|
30778
|
+
return result.countryName ? `${place}, ${result.countryName}` : place;
|
|
30779
|
+
};
|
|
30780
|
+
const toPlainText = (value) => {
|
|
30781
|
+
if (!value)
|
|
30782
|
+
return '';
|
|
30783
|
+
return he
|
|
30784
|
+
.decode(value.replace(/<[^>]*>/g, ' '))
|
|
30785
|
+
.replace(/\s+/g, ' ')
|
|
30786
|
+
.trim();
|
|
30787
|
+
};
|
|
30788
|
+
const TrajectNodeCard = ({ item, result, isSelected, languageCode, translations, onSelect, onEditOptions, showNights }) => {
|
|
30789
|
+
const { node } = item;
|
|
30790
|
+
const selectedPerRoom = (result.rooms ?? []).map((room) => (room.options ?? []).find((x) => x.isSelected) ?? (room.options ?? [])[0]);
|
|
30791
|
+
const isMultiRoom = selectedPerRoom.length > 1;
|
|
30792
|
+
const price = formatPrice$3(result.price, result.currencyCode, languageCode);
|
|
30793
|
+
const nights = calculateNights(new Date(result.fromDate), new Date(result.toDate));
|
|
30794
|
+
const candidate = findCandidateForResult(node, result);
|
|
30795
|
+
const image = candidate?.imageUrl ?? null;
|
|
30796
|
+
const description = toPlainText(candidate?.description);
|
|
30797
|
+
const title = candidate?.name ?? result.name;
|
|
30798
|
+
const priceBlock = (React__default.createElement("div", { className: "search__result-card__price__wrapper" },
|
|
30799
|
+
React__default.createElement("span", { className: "search__result-card__price__label" }, translations?.SHARED.TOTAL_PRICE),
|
|
30800
|
+
React__default.createElement("span", { className: "search__result-card__price" }, price)));
|
|
30801
|
+
const canEditOptions = isSelected && !!onEditOptions && (result.rooms ?? []).some((room) => (room.options ?? []).length > 1);
|
|
30802
|
+
const selectButton = (React__default.createElement(React__default.Fragment, null,
|
|
30803
|
+
React__default.createElement("button", { type: "button", className: `cta ${isSelected ? 'cta--selected' : 'cta--select'}`, onClick: onSelect }, isSelected ? translations?.SHARED.SELECTED : translations?.SHARED.SELECT),
|
|
30804
|
+
canEditOptions && (React__default.createElement("button", { type: "button", className: "cta cta--secondary", onClick: onEditOptions },
|
|
30805
|
+
translations?.SRP.SELECT,
|
|
30806
|
+
" ",
|
|
30807
|
+
translations?.SRP.ACCOMMODATION))));
|
|
30808
|
+
if (result.contents?.length) {
|
|
30809
|
+
return (React__default.createElement("div", { className: `search__result-card__wrapper search__result-card__wrapper--custom ${isSelected ? 'search__result-card__wrapper--selected' : ''}` },
|
|
30810
|
+
React__default.createElement("div", { className: "search__result-card__top", dangerouslySetInnerHTML: { __html: he.decode(result.contents) } }),
|
|
30811
|
+
React__default.createElement("div", { className: "search__result-card__footer" }, selectButton)));
|
|
30812
|
+
}
|
|
30813
|
+
return (React__default.createElement("div", { className: `search__result-card__wrapper ${isSelected ? 'search__result-card__wrapper--selected' : ''}` },
|
|
30814
|
+
image && (React__default.createElement("div", { className: "search__result-card__img-wrapper" },
|
|
30815
|
+
React__default.createElement("img", { src: image, alt: title, className: "search__result-card__img" }),
|
|
30816
|
+
priceBlock)),
|
|
30817
|
+
React__default.createElement("div", { className: "search__result-card__content" },
|
|
30818
|
+
React__default.createElement("div", { className: "search__result-card__content__wrapper" },
|
|
30819
|
+
React__default.createElement("div", { className: "search__result-card__header" },
|
|
30820
|
+
React__default.createElement("div", { className: "search__result-card__header__wrapper" },
|
|
30821
|
+
!!result.stars && (React__default.createElement("div", { className: "rating" }, [...Array(result.stars)].map((_, index) => (React__default.createElement(Icon, { name: "ui-star", key: `rating-star-${index + 1}`, width: 14, height: 14 }))))),
|
|
30822
|
+
React__default.createElement("h3", { className: "search__result-card__title" }, title)),
|
|
30823
|
+
!image && priceBlock),
|
|
30824
|
+
React__default.createElement("span", { className: "search__result-card__location" },
|
|
30825
|
+
React__default.createElement(Icon, { name: "ui-location", height: 16 }),
|
|
30826
|
+
getLocation(result)),
|
|
30827
|
+
React__default.createElement("div", { className: "search__result-card__options" },
|
|
30828
|
+
showNights && nights > 0 && (React__default.createElement("div", { className: "search__result-card__option" },
|
|
30829
|
+
React__default.createElement(Icon, { name: "ui-moon", height: 16 }),
|
|
30830
|
+
nights,
|
|
30831
|
+
" ",
|
|
30832
|
+
translations?.SRP.NIGHTS)),
|
|
30833
|
+
selectedPerRoom.map((option, roomIndex) => (React__default.createElement(React__default.Fragment, { key: `room-${roomIndex}` },
|
|
30834
|
+
option?.accommodationName && (React__default.createElement("div", { className: "search__result-card__option" },
|
|
30835
|
+
React__default.createElement(Icon, { name: "ui-bed", height: 16 }),
|
|
30836
|
+
isMultiRoom && `${translations?.SHARED.ROOM} ${roomIndex + 1}: `,
|
|
30837
|
+
option.accommodationName)),
|
|
30838
|
+
option?.regimeName && (React__default.createElement("div", { className: "search__result-card__option" },
|
|
30839
|
+
React__default.createElement(Icon, { name: "ui-utensils", height: 16 }),
|
|
30840
|
+
option.regimeName)))))),
|
|
30841
|
+
description && React__default.createElement("p", { className: "search__result-card__description" }, description)),
|
|
30842
|
+
React__default.createElement("div", { className: "search__result-card__footer" }, selectButton))));
|
|
30843
|
+
};
|
|
30844
|
+
|
|
30845
|
+
const TrajectResultsFlyIn = ({ isLoading }) => {
|
|
30846
|
+
const context = useContext(SearchResultsConfigurationContext);
|
|
30847
|
+
const dispatch = useDispatch();
|
|
30848
|
+
const translations = getTranslations(context?.languageCode ?? 'en-GB');
|
|
30849
|
+
const { trajectNodeAvailability, trajectEditLineGuid } = useSelector((state) => state.searchResults);
|
|
30850
|
+
const trajectEntry = context?.trajectEntry;
|
|
30851
|
+
const item = useMemo(() => {
|
|
30852
|
+
if (!trajectEntry || !trajectEditLineGuid)
|
|
30853
|
+
return null;
|
|
30854
|
+
return getTrajectNodesWithLines(trajectEntry).find((x) => x.line.guid === trajectEditLineGuid) ?? null;
|
|
30855
|
+
}, [trajectEntry, trajectEditLineGuid]);
|
|
30856
|
+
if (!context || !item)
|
|
30857
|
+
return null;
|
|
30858
|
+
const availability = trajectNodeAvailability[item.line.guid];
|
|
30859
|
+
const results = availability?.results ?? [];
|
|
30860
|
+
const ordered = [...results].sort((a, b) => {
|
|
30861
|
+
const aConfigured = findCandidateForResult(item.node, a) ? 0 : 1;
|
|
30862
|
+
const bConfigured = findCandidateForResult(item.node, b) ? 0 : 1;
|
|
30863
|
+
return aConfigured - bConfigured || a.price - b.price;
|
|
30864
|
+
});
|
|
30865
|
+
const handleSelect = (code) => {
|
|
30866
|
+
const result = results.find((x) => x.code === code);
|
|
30867
|
+
if (!result)
|
|
30868
|
+
return;
|
|
30869
|
+
dispatch(setTrajectNodeSelection({ lineGuid: item.line.guid, code }));
|
|
30870
|
+
applyResultToLines(item.lines, result).forEach((line) => dispatch(updateEditableEntryLine(line)));
|
|
30871
|
+
dispatch(setTrajectEditLineGuid(null));
|
|
30872
|
+
dispatch(setFlyInIsOpen(false));
|
|
30873
|
+
};
|
|
30874
|
+
if (isLoading) {
|
|
30875
|
+
return React__default.createElement(React__default.Fragment, null, context.customSpinner ?? React__default.createElement(Spinner, { label: translations.SRP.LOADING_ACCOMMODATIONS }));
|
|
30876
|
+
}
|
|
30877
|
+
return (React__default.createElement("div", { className: "flyin__content" },
|
|
30878
|
+
React__default.createElement("div", { className: "search__result-row" },
|
|
30879
|
+
React__default.createElement("span", { className: "search__result-row-text" },
|
|
30880
|
+
ordered.length,
|
|
30881
|
+
"\u00A0",
|
|
30882
|
+
translations.SRP.TOTAL_RESULTS_LABEL)),
|
|
30883
|
+
ordered.length === 0 ? (React__default.createElement("div", { className: "no-results" }, translations.SRP.NO_RESULTS)) : (React__default.createElement("div", { className: "search__results__cards search__results__cards--compact" }, ordered.map((result) => (React__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 })))))));
|
|
30884
|
+
};
|
|
30885
|
+
|
|
30555
30886
|
const FlyIn = ({ srpType, isOpen, setIsOpen, className = '', onPanelRef, detailsLoading, flyInType, isPackageEditFlow, handleConfirm, sortByTypes, activeSearchSeed, toggleFilters, filtersOpen }) => {
|
|
30556
30887
|
const dispatch = useDispatch();
|
|
30557
30888
|
const context = useContext(SearchResultsConfigurationContext);
|
|
@@ -30597,6 +30928,10 @@ const FlyIn = ({ srpType, isOpen, setIsOpen, className = '', onPanelRef, details
|
|
|
30597
30928
|
}
|
|
30598
30929
|
};
|
|
30599
30930
|
const handleGoBack = () => {
|
|
30931
|
+
if (context?.trajectEntry) {
|
|
30932
|
+
handleClose();
|
|
30933
|
+
return;
|
|
30934
|
+
}
|
|
30600
30935
|
if (flyInType === 'acco-details') {
|
|
30601
30936
|
dispatch(setFlyInType('acco-results'));
|
|
30602
30937
|
}
|
|
@@ -30620,7 +30955,7 @@ const FlyIn = ({ srpType, isOpen, setIsOpen, className = '', onPanelRef, details
|
|
|
30620
30955
|
dispatch(setBookPackagingEntry(true));
|
|
30621
30956
|
}
|
|
30622
30957
|
};
|
|
30623
|
-
return (React__default.createElement("div", { className: `flyin ${isOpen ? 'flyin--active' : ''} ${className} ${isPackageEditFlow || flyInType === 'acco-results' ? 'flyin--large' : ''} ${flyInType === 'excursion-results' || flyInType === 'excursion-details' ? 'flyin--medium' : ''}
|
|
30958
|
+
return (React__default.createElement("div", { className: `flyin ${isOpen ? 'flyin--active' : ''} ${className} ${isPackageEditFlow || flyInType === 'acco-results' ? 'flyin--large' : ''} ${flyInType === 'excursion-results' || flyInType === 'excursion-details' ? 'flyin--medium' : ''}
|
|
30624
30959
|
${flyInType === 'flight-outward-results' || flyInType === 'flight-return-results' ? 'flyin--flight' : ''}` },
|
|
30625
30960
|
React__default.createElement("div", { className: `flyin__panel ${isOpen ? 'flyin__panel--active' : ''}`, ref: panelRef },
|
|
30626
30961
|
React__default.createElement("div", { className: "flyin__content" },
|
|
@@ -30646,7 +30981,10 @@ const FlyIn = ({ srpType, isOpen, setIsOpen, className = '', onPanelRef, details
|
|
|
30646
30981
|
React__default.createElement(Icon, { name: "ui-chevron", width: 14, height: 14, "aria-hidden": "true" }),
|
|
30647
30982
|
"Go Back")))),
|
|
30648
30983
|
srpType === build.PortalQsmType.Flight && React__default.createElement(FlightsFlyIn, { isOpen: isOpen, setIsOpen: setIsOpen }),
|
|
30649
|
-
|
|
30984
|
+
context?.trajectEntry && flyInType === 'acco-results' && React__default.createElement(TrajectResultsFlyIn, { isLoading: detailsLoading }),
|
|
30985
|
+
!context?.trajectEntry &&
|
|
30986
|
+
(srpType === build.PortalQsmType.Accommodation || srpType === build.PortalQsmType.AccommodationAndFlight) &&
|
|
30987
|
+
flyInType === 'acco-results' && (React__default.createElement("div", { className: "flyin__content flyin__content--columns" },
|
|
30650
30988
|
React__default.createElement(Filters, { initialFilters: initialFilters, filters: filters, isOpen: filtersOpen, handleSetIsOpen: () => toggleFilters && toggleFilters(),
|
|
30651
30989
|
// handleApplyFilters={() => setSearchTrigger((prev) => prev + 1)}
|
|
30652
30990
|
isLoading: isLoading, setFilters: (filters) => dispatch(setFilters(filters)), resetFilters: (filters) => dispatch(resetFilters(filters)) }),
|
|
@@ -32919,222 +33257,6 @@ const BookPackagingEntry = ({ activeSearchSeed, isLoading, isConfirmationPage })
|
|
|
32919
33257
|
React__default.createElement(WLSidebar, { activeSearchSeed: activeSearchSeed, packagingAccoResult: selectedPackagingAccoResult }))));
|
|
32920
33258
|
};
|
|
32921
33259
|
|
|
32922
|
-
const REGULAR_TRAJECT_NODE_TYPE = 0;
|
|
32923
|
-
const isSearchableTrajectNode = (node) => node.serviceType === ACCOMMODATION_SERVICE_TYPE || node.serviceType === EXCURSION_SERVICE_TYPE;
|
|
32924
|
-
const isTrajectFlightNode = (node) => node.serviceType === FLIGHT_SERVICE_TYPE;
|
|
32925
|
-
const getTrajectNodesWithLines = (trajectEntry) => {
|
|
32926
|
-
const linesByGuid = new Map((trajectEntry.entry.lines ?? []).map((line) => [line.guid, line]));
|
|
32927
|
-
return [...(trajectEntry.nodes ?? [])]
|
|
32928
|
-
.sort((a, b) => a.dayOrder - b.dayOrder || a.order - b.order)
|
|
32929
|
-
.map((node) => ({ node, line: linesByGuid.get(node.lineGuid) }))
|
|
32930
|
-
.filter((x) => !!x.line);
|
|
32931
|
-
};
|
|
32932
|
-
const buildDestination = (line) => {
|
|
32933
|
-
if (line.location?.id)
|
|
32934
|
-
return { id: line.location.id, isLocation: true };
|
|
32935
|
-
if (line.oord?.id)
|
|
32936
|
-
return { id: line.oord.id, isOord: true };
|
|
32937
|
-
if (line.region?.id)
|
|
32938
|
-
return { id: line.region.id, isRegion: true };
|
|
32939
|
-
if (line.country?.id)
|
|
32940
|
-
return { id: line.country.id, isCountry: true };
|
|
32941
|
-
if (line.latitude && line.longitude)
|
|
32942
|
-
return { latitude: line.latitude, longitude: line.longitude };
|
|
32943
|
-
return { id: 0 };
|
|
32944
|
-
};
|
|
32945
|
-
const buildTrajectNodeRequest = ({ node, line }, context, productCode) => ({
|
|
32946
|
-
transactionId: context.transactionId,
|
|
32947
|
-
officeId: context.officeId,
|
|
32948
|
-
agentId: context.agentId ?? null,
|
|
32949
|
-
catalogueId: context.catalogueId,
|
|
32950
|
-
searchConfigurationId: context.searchConfigurationId,
|
|
32951
|
-
portalId: context.portalId ?? null,
|
|
32952
|
-
vendorConfigurationId: node.externalVendorId ?? null,
|
|
32953
|
-
language: context.language,
|
|
32954
|
-
serviceType: node.serviceType,
|
|
32955
|
-
fromDate: toDateOnlyString(line.from),
|
|
32956
|
-
toDate: toDateOnlyString(line.to),
|
|
32957
|
-
destination: buildDestination(line),
|
|
32958
|
-
productCode,
|
|
32959
|
-
rooms: context.rooms,
|
|
32960
|
-
tagIds: []
|
|
32961
|
-
});
|
|
32962
|
-
const getNodeCandidates = (node) => {
|
|
32963
|
-
const configured = (node.alternatives ?? []).filter((x) => !!x.code && x.code.trim().length > 0);
|
|
32964
|
-
if (configured.length)
|
|
32965
|
-
return configured;
|
|
32966
|
-
const fallbackCode = node.code ?? node.preferredAccommodationCode;
|
|
32967
|
-
if (!fallbackCode || !fallbackCode.trim().length)
|
|
32968
|
-
return [];
|
|
32969
|
-
return [
|
|
32970
|
-
{
|
|
32971
|
-
code: fallbackCode,
|
|
32972
|
-
name: node.name,
|
|
32973
|
-
description: node.description,
|
|
32974
|
-
imageUrl: node.imageUrl,
|
|
32975
|
-
contentSource: node.contentSource,
|
|
32976
|
-
vendor: node.vendor,
|
|
32977
|
-
externalVendorId: node.externalVendorId,
|
|
32978
|
-
isPreferred: true
|
|
32979
|
-
}
|
|
32980
|
-
];
|
|
32981
|
-
};
|
|
32982
|
-
const findCandidateForResult = (node, result) => getNodeCandidates(node).find((candidate) => matchesCode(result, candidate.code)) ?? null;
|
|
32983
|
-
const matchesCode = (result, code) => {
|
|
32984
|
-
if (result.code === code)
|
|
32985
|
-
return true;
|
|
32986
|
-
return (result.rooms ?? []).some((room) => (room.options ?? []).some((option) => option.accommodationCode === code));
|
|
32987
|
-
};
|
|
32988
|
-
const getPackagingRoomsFromEntry = (entry) => {
|
|
32989
|
-
const paxById = new Map((entry.pax ?? []).map((p) => [p.id, p]));
|
|
32990
|
-
const rooms = (entry.rooms ?? []).map((room) => ({
|
|
32991
|
-
travellers: (room.paxIds ?? []).map((paxId) => {
|
|
32992
|
-
const pax = paxById.get(paxId);
|
|
32993
|
-
return {
|
|
32994
|
-
id: paxId,
|
|
32995
|
-
age: pax?.age ?? null,
|
|
32996
|
-
dateOfBirth: pax?.dateOfBirth ?? null
|
|
32997
|
-
};
|
|
32998
|
-
})
|
|
32999
|
-
}));
|
|
33000
|
-
return rooms.filter((room) => room.travellers.length > 0);
|
|
33001
|
-
};
|
|
33002
|
-
const pickCheapest = (results) => [...results].sort((a, b) => a.price - b.price)[0] ?? null;
|
|
33003
|
-
const applyResultToLine = (line, result) => {
|
|
33004
|
-
const option = (result.rooms ?? []).flatMap((room) => room.options ?? []).find((x) => x.isSelected) ?? (result.rooms ?? [])[0]?.options?.[0];
|
|
33005
|
-
return {
|
|
33006
|
-
...line,
|
|
33007
|
-
productName: result.name ?? line.productName,
|
|
33008
|
-
productCode: result.code ?? line.productCode,
|
|
33009
|
-
accommodationCode: option?.accommodationCode ?? line.accommodationCode,
|
|
33010
|
-
accommodationName: option?.accommodationName ?? line.accommodationName,
|
|
33011
|
-
regimeCode: option?.regimeCode ?? line.regimeCode,
|
|
33012
|
-
regimeName: option?.regimeName ?? line.regimeName,
|
|
33013
|
-
latitude: result.latitude ?? line.latitude,
|
|
33014
|
-
longitude: result.longitude ?? line.longitude,
|
|
33015
|
-
isChanged: true
|
|
33016
|
-
};
|
|
33017
|
-
};
|
|
33018
|
-
const toDateOnlyUtcString = (value) => {
|
|
33019
|
-
const date = new Date(value);
|
|
33020
|
-
return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate())).toISOString();
|
|
33021
|
-
};
|
|
33022
|
-
const toTimeOnlyString = (value) => {
|
|
33023
|
-
const date = new Date(value);
|
|
33024
|
-
const hh = String(date.getUTCHours()).padStart(2, '0');
|
|
33025
|
-
const mm = String(date.getUTCMinutes()).padStart(2, '0');
|
|
33026
|
-
const ss = String(date.getUTCSeconds()).padStart(2, '0');
|
|
33027
|
-
return `${hh}:${mm}:${ss}`;
|
|
33028
|
-
};
|
|
33029
|
-
const mapFlightSegmentsToFlightLines = (segments) => segments.map((segment) => ({
|
|
33030
|
-
airlineCode: segment.marketingAirlineCode,
|
|
33031
|
-
airlineDescription: segment.marketingAirlineName,
|
|
33032
|
-
operatingAirlineCode: segment.operatingAirlineCode,
|
|
33033
|
-
operatingAirlineDescription: segment.operatingAirlineName,
|
|
33034
|
-
flightNumber: segment.flightNumber,
|
|
33035
|
-
operatingFlightNumber: segment.operatingFlightNumber ?? null,
|
|
33036
|
-
departureDate: toDateOnlyUtcString(segment.departureDateTime),
|
|
33037
|
-
departureTime: toTimeOnlyString(segment.departureDateTime),
|
|
33038
|
-
departureAirportCode: segment.departureAirportCode,
|
|
33039
|
-
departureAirportDescription: segment.departureAirportName,
|
|
33040
|
-
arrivalDate: toDateOnlyUtcString(segment.arrivalDateTime),
|
|
33041
|
-
arrivalTime: toTimeOnlyString(segment.arrivalDateTime),
|
|
33042
|
-
arrivalAirportCode: segment.arrivalAirportCode,
|
|
33043
|
-
arrivalAirportDescription: segment.arrivalAirportName,
|
|
33044
|
-
durationInTicks: segment.durationInTicks
|
|
33045
|
-
}));
|
|
33046
|
-
const applyFlightToLine = (line, flight, isOutbound) => {
|
|
33047
|
-
const segments = (isOutbound ? flight.outward?.segments : flight.return?.segments) ?? [];
|
|
33048
|
-
if (!segments.length)
|
|
33049
|
-
return line;
|
|
33050
|
-
const firstSegment = segments[0];
|
|
33051
|
-
const lastSegment = segments[segments.length - 1];
|
|
33052
|
-
return {
|
|
33053
|
-
...line,
|
|
33054
|
-
from: new Date(firstSegment.departureDateTime).toISOString(),
|
|
33055
|
-
to: new Date(lastSegment.arrivalDateTime).toISOString(),
|
|
33056
|
-
productName: `${firstSegment.departureAirportName} - ${lastSegment.arrivalAirportName} (${firstSegment.marketingAirlineName})`,
|
|
33057
|
-
productCode: `${firstSegment.departureAirportCode} ${lastSegment.arrivalAirportCode}/${firstSegment.marketingAirlineCode}`,
|
|
33058
|
-
accommodationName: firstSegment.metaData?.farePriceClassName ?? line.accommodationName,
|
|
33059
|
-
accommodationCode: firstSegment.metaData?.fareCode ?? line.accommodationCode,
|
|
33060
|
-
flightInformation: {
|
|
33061
|
-
pnr: '',
|
|
33062
|
-
flightLines: mapFlightSegmentsToFlightLines(segments)
|
|
33063
|
-
},
|
|
33064
|
-
isChanged: true
|
|
33065
|
-
};
|
|
33066
|
-
};
|
|
33067
|
-
const pairTrajectFlights = (nodes) => {
|
|
33068
|
-
const flights = nodes.filter((x) => isTrajectFlightNode(x.node));
|
|
33069
|
-
return {
|
|
33070
|
-
outbound: flights[0] ?? null,
|
|
33071
|
-
inbound: flights.length > 1 ? flights[flights.length - 1] : null,
|
|
33072
|
-
unsupported: flights.slice(1, Math.max(flights.length - 1, 1))
|
|
33073
|
-
};
|
|
33074
|
-
};
|
|
33075
|
-
|
|
33076
|
-
const getLocation = (result) => {
|
|
33077
|
-
const place = result.locationName || result.regionName || result.oordName;
|
|
33078
|
-
if (!place)
|
|
33079
|
-
return result.countryName ?? '';
|
|
33080
|
-
return result.countryName ? `${place}, ${result.countryName}` : place;
|
|
33081
|
-
};
|
|
33082
|
-
const toPlainText = (value) => {
|
|
33083
|
-
if (!value)
|
|
33084
|
-
return '';
|
|
33085
|
-
return he
|
|
33086
|
-
.decode(value.replace(/<[^>]*>/g, ' '))
|
|
33087
|
-
.replace(/\s+/g, ' ')
|
|
33088
|
-
.trim();
|
|
33089
|
-
};
|
|
33090
|
-
const TrajectNodeCard = ({ item, result, isSelected, languageCode, translations, onSelect, showNights }) => {
|
|
33091
|
-
const { node } = item;
|
|
33092
|
-
const selectedOption = first(result.rooms)?.options?.find((x) => x.isSelected) ?? first(result.rooms)?.options?.[0];
|
|
33093
|
-
const price = formatPrice$3(result.price, result.currencyCode, languageCode);
|
|
33094
|
-
const nights = calculateNights(new Date(result.fromDate), new Date(result.toDate));
|
|
33095
|
-
const candidate = findCandidateForResult(node, result);
|
|
33096
|
-
const image = candidate?.imageUrl ?? null;
|
|
33097
|
-
const description = toPlainText(candidate?.description);
|
|
33098
|
-
const title = candidate?.name ?? result.name;
|
|
33099
|
-
const priceBlock = (React__default.createElement("div", { className: "search__result-card__price__wrapper" },
|
|
33100
|
-
React__default.createElement("span", { className: "search__result-card__price__label" }, translations?.SHARED.TOTAL_PRICE),
|
|
33101
|
-
React__default.createElement("span", { className: "search__result-card__price" }, price)));
|
|
33102
|
-
const selectButton = (React__default.createElement("button", { type: "button", className: `cta ${isSelected ? 'cta--selected' : 'cta--select'}`, onClick: onSelect }, isSelected ? translations?.SHARED.SELECTED : translations?.SHARED.SELECT));
|
|
33103
|
-
if (result.contents?.length) {
|
|
33104
|
-
return (React__default.createElement("div", { className: `search__result-card__wrapper search__result-card__wrapper--custom ${isSelected ? 'search__result-card__wrapper--selected' : ''}` },
|
|
33105
|
-
React__default.createElement("div", { className: "search__result-card__top", dangerouslySetInnerHTML: { __html: he.decode(result.contents) } }),
|
|
33106
|
-
React__default.createElement("div", { className: "search__result-card__footer" }, selectButton)));
|
|
33107
|
-
}
|
|
33108
|
-
return (React__default.createElement("div", { className: `search__result-card__wrapper ${isSelected ? 'search__result-card__wrapper--selected' : ''}` },
|
|
33109
|
-
image && (React__default.createElement("div", { className: "search__result-card__img-wrapper" },
|
|
33110
|
-
React__default.createElement("img", { src: image, alt: title, className: "search__result-card__img" }),
|
|
33111
|
-
priceBlock)),
|
|
33112
|
-
React__default.createElement("div", { className: "search__result-card__content" },
|
|
33113
|
-
React__default.createElement("div", { className: "search__result-card__content__wrapper" },
|
|
33114
|
-
React__default.createElement("div", { className: "search__result-card__header" },
|
|
33115
|
-
React__default.createElement("div", { className: "search__result-card__header__wrapper" },
|
|
33116
|
-
!!result.stars && (React__default.createElement("div", { className: "rating" }, [...Array(result.stars)].map((_, index) => (React__default.createElement(Icon, { name: "ui-star", key: `rating-star-${index + 1}`, width: 14, height: 14 }))))),
|
|
33117
|
-
React__default.createElement("h3", { className: "search__result-card__title" }, title)),
|
|
33118
|
-
!image && priceBlock),
|
|
33119
|
-
React__default.createElement("span", { className: "search__result-card__location" },
|
|
33120
|
-
React__default.createElement(Icon, { name: "ui-location", height: 16 }),
|
|
33121
|
-
getLocation(result)),
|
|
33122
|
-
React__default.createElement("div", { className: "search__result-card__options" },
|
|
33123
|
-
showNights && nights > 0 && (React__default.createElement("div", { className: "search__result-card__option" },
|
|
33124
|
-
React__default.createElement(Icon, { name: "ui-moon", height: 16 }),
|
|
33125
|
-
nights,
|
|
33126
|
-
" ",
|
|
33127
|
-
translations?.SRP.NIGHTS)),
|
|
33128
|
-
selectedOption?.accommodationName && (React__default.createElement("div", { className: "search__result-card__option" },
|
|
33129
|
-
React__default.createElement(Icon, { name: "ui-bed", height: 16 }),
|
|
33130
|
-
selectedOption.accommodationName)),
|
|
33131
|
-
selectedOption?.regimeName && (React__default.createElement("div", { className: "search__result-card__option" },
|
|
33132
|
-
React__default.createElement(Icon, { name: "ui-utensils", height: 16 }),
|
|
33133
|
-
selectedOption.regimeName))),
|
|
33134
|
-
description && React__default.createElement("p", { className: "search__result-card__description" }, description)),
|
|
33135
|
-
React__default.createElement("div", { className: "search__result-card__footer" }, selectButton))));
|
|
33136
|
-
};
|
|
33137
|
-
|
|
33138
33260
|
const getNodeIcon = (node) => {
|
|
33139
33261
|
if (isTrajectFlightNode(node))
|
|
33140
33262
|
return 'ui-flight';
|
|
@@ -33149,15 +33271,10 @@ const TrajectResults = ({ isLoading }) => {
|
|
|
33149
33271
|
const dispatch = useDispatch();
|
|
33150
33272
|
const translations = getTranslations(context?.languageCode ?? 'en-GB');
|
|
33151
33273
|
const locale = getLocale(context?.languageCode ?? 'en-GB');
|
|
33152
|
-
const { trajectNodeAvailability,
|
|
33153
|
-
const uniqueOutwardFlights = useSelector(selectUniqueOutwardFlights);
|
|
33154
|
-
const uniqueReturnFlights = useSelector(selectUniqueReturnFlights);
|
|
33155
|
-
const selectedOutward = useSelector(selectSelectedOutward);
|
|
33156
|
-
const selectedReturn = useSelector(selectSelectedReturn);
|
|
33274
|
+
const { trajectNodeAvailability, trajectFlightAvailability } = useSelector((state) => state.searchResults);
|
|
33157
33275
|
const [expandedNodes, setExpandedNodes] = useState({});
|
|
33158
33276
|
const trajectEntry = context?.trajectEntry;
|
|
33159
33277
|
const nodesWithLines = useMemo(() => (trajectEntry ? getTrajectNodesWithLines(trajectEntry) : []), [trajectEntry]);
|
|
33160
|
-
const flightPairing = useMemo(() => pairTrajectFlights(nodesWithLines), [nodesWithLines]);
|
|
33161
33278
|
const lineByNodeId = useMemo(() => new Map(nodesWithLines.map((x) => [x.node.nodeId, x])), [nodesWithLines]);
|
|
33162
33279
|
const nodesByDay = useMemo(() => {
|
|
33163
33280
|
const map = new Map();
|
|
@@ -33179,12 +33296,28 @@ const TrajectResults = ({ isLoading }) => {
|
|
|
33179
33296
|
if (!result)
|
|
33180
33297
|
return;
|
|
33181
33298
|
dispatch(setTrajectNodeSelection({ lineGuid: item.line.guid, code }));
|
|
33182
|
-
|
|
33299
|
+
applyResultToLines(item.lines, result).forEach((line) => dispatch(updateEditableEntryLine(line)));
|
|
33300
|
+
};
|
|
33301
|
+
const handleShowMore = (item) => {
|
|
33302
|
+
dispatch(setTrajectEditLineGuid(item.line.guid));
|
|
33303
|
+
dispatch(setFlyInType('acco-results'));
|
|
33304
|
+
dispatch(setFlyInIsOpen(true));
|
|
33305
|
+
};
|
|
33306
|
+
const handleEditOptions = (item) => {
|
|
33307
|
+
const availability = trajectNodeAvailability[item.line.guid];
|
|
33308
|
+
const result = availability?.results.find((x) => x.code === availability?.selectedCode);
|
|
33309
|
+
if (!result)
|
|
33310
|
+
return;
|
|
33311
|
+
dispatch(setPackagingAccoSearchDetails([result]));
|
|
33312
|
+
dispatch(setSelectedPackagingAccoResult(result.code));
|
|
33313
|
+
dispatch(setTrajectEditLineGuid(item.line.guid));
|
|
33314
|
+
dispatch(setFlyInType('acco-details'));
|
|
33315
|
+
dispatch(setFlyInIsOpen(true));
|
|
33183
33316
|
};
|
|
33184
33317
|
const renderServiceNode = (item) => {
|
|
33318
|
+
console.log('renderServiceNode', item);
|
|
33185
33319
|
const { node, line } = item;
|
|
33186
33320
|
const availability = trajectNodeAvailability[line.guid];
|
|
33187
|
-
const isExpanded = !!expandedNodes[line.guid];
|
|
33188
33321
|
const results = availability?.results ?? [];
|
|
33189
33322
|
const selected = results.find((x) => x.code === availability?.selectedCode) ?? null;
|
|
33190
33323
|
const configured = results.filter((x) => x.code !== selected?.code && !!findCandidateForResult(node, x));
|
|
@@ -33206,34 +33339,27 @@ const TrajectResults = ({ isLoading }) => {
|
|
|
33206
33339
|
node.preferredAccommodationName,
|
|
33207
33340
|
" \u2014 ",
|
|
33208
33341
|
translations.SRP.NO_RESULTS))),
|
|
33209
|
-
React__default.createElement("div", { className: "search__results__cards search__results__cards--
|
|
33342
|
+
React__default.createElement("div", { className: "search__results__cards search__results__cards--compact" }, [selected, ...configured].map((result) => (React__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 })))),
|
|
33210
33343
|
others.length > 0 && (React__default.createElement("div", { className: "search__results__cards__actions" },
|
|
33211
|
-
React__default.createElement("button", { type: "button", className: "cta cta--secondary", onClick: () =>
|
|
33344
|
+
React__default.createElement("button", { type: "button", className: "cta cta--secondary", onClick: () => handleShowMore(item) },
|
|
33345
|
+
translations.SRP.SHOW_MORE,
|
|
33346
|
+
" (",
|
|
33347
|
+
others.length,
|
|
33348
|
+
")")))))));
|
|
33349
|
+
};
|
|
33350
|
+
const handleSelectFlight = (item, flight) => {
|
|
33351
|
+
dispatch(setTrajectFlightSelection({ lineGuid: item.line.guid, guid: flight.outwardGuid }));
|
|
33352
|
+
dispatch(updateEditableEntryLine(applyFlightToLine(item.line, flight)));
|
|
33212
33353
|
};
|
|
33213
33354
|
const renderFlightNode = (item) => {
|
|
33214
33355
|
const { node, line } = item;
|
|
33215
|
-
const
|
|
33216
|
-
const
|
|
33217
|
-
|
|
33218
|
-
|
|
33219
|
-
|
|
33220
|
-
|
|
33221
|
-
|
|
33222
|
-
React__default.createElement("p", { className: "search__results__label__date-date" }, format$2(new Date(line.from), 'd', { locale })),
|
|
33223
|
-
React__default.createElement("p", null, format$2(new Date(line.from), 'MMM', { locale }))),
|
|
33224
|
-
React__default.createElement("div", { className: "search__results__label__text" },
|
|
33225
|
-
React__default.createElement(Icon, { name: "ui-flight", height: 16 }),
|
|
33226
|
-
React__default.createElement("h3", null,
|
|
33227
|
-
React__default.createElement("strong", null, node.name)))),
|
|
33228
|
-
React__default.createElement("div", { className: "no-results" },
|
|
33229
|
-
node.departureAirportCode,
|
|
33230
|
-
" \u2192 ",
|
|
33231
|
-
node.arrivalAirportCode)));
|
|
33232
|
-
}
|
|
33233
|
-
const flights = isOutbound ? uniqueOutwardFlights : uniqueReturnFlights;
|
|
33234
|
-
const selectedFlight = isOutbound ? selectedOutward : selectedReturn;
|
|
33235
|
-
const selectedKey = isOutbound ? selectedOutwardKey : selectedReturnKey;
|
|
33236
|
-
const visible = flights.filter((x) => getFlightKey(isOutbound ? x.outward.segments : x.return.segments) !== selectedKey);
|
|
33356
|
+
const availability = trajectFlightAvailability[line.guid];
|
|
33357
|
+
const status = availability?.status ?? 'loading';
|
|
33358
|
+
const flights = availability?.results ?? [];
|
|
33359
|
+
const selected = flights.find((x) => x.outwardGuid === availability?.selectedGuid) ?? null;
|
|
33360
|
+
const others = flights.filter((x) => x.outwardGuid !== availability?.selectedGuid);
|
|
33361
|
+
const isExpanded = !!expandedNodes[line.guid];
|
|
33362
|
+
const visible = isExpanded ? others : others.slice(0, 2);
|
|
33237
33363
|
return (React__default.createElement(React__default.Fragment, { key: line.guid },
|
|
33238
33364
|
React__default.createElement("div", { className: "search__results__label search__results__label--secondary" },
|
|
33239
33365
|
React__default.createElement("div", { className: "search__results__label__date" },
|
|
@@ -33243,11 +33369,19 @@ const TrajectResults = ({ isLoading }) => {
|
|
|
33243
33369
|
React__default.createElement(Icon, { name: "ui-flight", height: 16 }),
|
|
33244
33370
|
React__default.createElement("h3", null,
|
|
33245
33371
|
translations.SRP.SELECT,
|
|
33246
|
-
|
|
33247
|
-
React__default.createElement("strong", null,
|
|
33248
|
-
|
|
33249
|
-
|
|
33250
|
-
|
|
33372
|
+
' ',
|
|
33373
|
+
React__default.createElement("strong", null,
|
|
33374
|
+
node.departureAirportCode,
|
|
33375
|
+
" - ",
|
|
33376
|
+
node.arrivalAirportCode)))),
|
|
33377
|
+
status === 'loading' && React__default.createElement(Spinner, { label: translations.SRP.LOADING_FLIGHTS }),
|
|
33378
|
+
status === 'unavailable' && React__default.createElement("div", { className: "no-results" }, translations.SRP.NO_RESULTS),
|
|
33379
|
+
status === 'resolved' && selected && (React__default.createElement(React__default.Fragment, null,
|
|
33380
|
+
React__default.createElement("div", { className: "search__results__cards search__results__cards--extended" },
|
|
33381
|
+
React__default.createElement(IndependentFlightOption, { key: `flight-${selected.outwardGuid}`, item: selected.outward, guid: selected.outwardGuid, selectedGuid: selected.outwardGuid, isOutward: true, showSelectedState: true, price: selected.price }),
|
|
33382
|
+
visible.map((result) => (React__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) })))),
|
|
33383
|
+
others.length > 2 && (React__default.createElement("div", { className: "search__results__cards__actions" },
|
|
33384
|
+
React__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})`)))))));
|
|
33251
33385
|
};
|
|
33252
33386
|
if (isLoading)
|
|
33253
33387
|
return React__default.createElement(Spinner, { label: translations.SRP.LOADING_ITINERARY });
|
|
@@ -33256,6 +33390,8 @@ const TrajectResults = ({ isLoading }) => {
|
|
|
33256
33390
|
if (!dayNodes.length)
|
|
33257
33391
|
return null;
|
|
33258
33392
|
return (React__default.createElement(React__default.Fragment, { key: `day-${day.order}` }, dayNodes.map((node) => {
|
|
33393
|
+
if (node.serviceType !== ACCOMMODATION_SERVICE_TYPE && node.serviceType !== EXCURSION_SERVICE_TYPE && node.serviceType !== FLIGHT_SERVICE_TYPE)
|
|
33394
|
+
return null;
|
|
33259
33395
|
// if (node.trajectNodeType === CONNECTING_TRAJECT_NODE_TYPE) return null;
|
|
33260
33396
|
// if (node.trajectNodeType !== REGULAR_TRAJECT_NODE_TYPE) return renderContextNode(node);
|
|
33261
33397
|
if (node.trajectNodeType !== REGULAR_TRAJECT_NODE_TYPE)
|
|
@@ -33273,7 +33409,7 @@ const useTrajectAvailability = () => {
|
|
|
33273
33409
|
const context = useContext(SearchResultsConfigurationContext);
|
|
33274
33410
|
const dispatch = useDispatch();
|
|
33275
33411
|
const trajectEntry = context?.trajectEntry;
|
|
33276
|
-
const
|
|
33412
|
+
const showNonPreferredItems = !!context?.showNonPreferredItems;
|
|
33277
33413
|
useEffect(() => {
|
|
33278
33414
|
if (!context || !trajectEntry)
|
|
33279
33415
|
return;
|
|
@@ -33293,6 +33429,13 @@ const useTrajectAvailability = () => {
|
|
|
33293
33429
|
language: context.languageCode ?? 'en-GB',
|
|
33294
33430
|
rooms: getPackagingRoomsFromEntry(trajectEntry.entry)
|
|
33295
33431
|
};
|
|
33432
|
+
// Flight searches take passengers rather than rooms; the API buckets by age.
|
|
33433
|
+
const pax = trajectEntry.entry.pax ?? [];
|
|
33434
|
+
const ageOf = (p) => p.age ?? 30;
|
|
33435
|
+
const adults = pax.filter((p) => ageOf(p) >= 12).length;
|
|
33436
|
+
const kids = pax.filter((p) => ageOf(p) >= 2 && ageOf(p) < 12).length;
|
|
33437
|
+
const babies = pax.filter((p) => ageOf(p) < 2).length;
|
|
33438
|
+
const flightPax = 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 })));
|
|
33296
33439
|
const nodesWithLines = getTrajectNodesWithLines(trajectEntry);
|
|
33297
33440
|
const resolveNode = async (item) => {
|
|
33298
33441
|
const lineGuid = item.line.guid;
|
|
@@ -33311,18 +33454,20 @@ const useTrajectAvailability = () => {
|
|
|
33311
33454
|
return null;
|
|
33312
33455
|
}
|
|
33313
33456
|
}));
|
|
33314
|
-
|
|
33315
|
-
|
|
33457
|
+
const configuredResults = attempts
|
|
33458
|
+
.filter((x) => !!x)
|
|
33459
|
+
.filter((x, i, all) => all.findIndex((y) => y.code === x.code) === i);
|
|
33316
33460
|
const preferredCandidate = candidates.find((x) => x.isPreferred) ?? candidates[0];
|
|
33317
|
-
let preferredUnavailable = !!preferredCandidate && !
|
|
33318
|
-
|
|
33319
|
-
|
|
33320
|
-
|
|
33321
|
-
|
|
33461
|
+
let preferredUnavailable = !!preferredCandidate && !configuredResults.some((x) => matchesCode(x, preferredCandidate.code));
|
|
33462
|
+
const destinationResults = showNonPreferredItems
|
|
33463
|
+
? ((await search(config, buildTrajectNodeRequest(item, searchContext, ''), controller.signal)) ?? []).filter((x) => !configuredResults.some((configured) => configured.code === x.code))
|
|
33464
|
+
: [];
|
|
33465
|
+
const results = [...configuredResults, ...destinationResults];
|
|
33466
|
+
const picked = configuredResults[0] ?? pickCheapest(destinationResults);
|
|
33467
|
+
if (!configuredResults.length)
|
|
33322
33468
|
preferredUnavailable = candidates.length > 0;
|
|
33323
|
-
}
|
|
33324
33469
|
if (controller.signal.aborted)
|
|
33325
|
-
return
|
|
33470
|
+
return [];
|
|
33326
33471
|
dispatch(setTrajectNodeAvailability({
|
|
33327
33472
|
lineGuid,
|
|
33328
33473
|
results,
|
|
@@ -33330,91 +33475,58 @@ const useTrajectAvailability = () => {
|
|
|
33330
33475
|
preferredUnavailable
|
|
33331
33476
|
}));
|
|
33332
33477
|
if (!picked)
|
|
33333
|
-
return
|
|
33334
|
-
|
|
33335
|
-
return
|
|
33478
|
+
return item.lines.map((x) => x.guid);
|
|
33479
|
+
applyResultToLines(item.lines, picked).forEach((line) => dispatch(updateEditableEntryLine(line)));
|
|
33480
|
+
return [];
|
|
33336
33481
|
}
|
|
33337
33482
|
catch (error) {
|
|
33338
33483
|
if (controller.signal.aborted)
|
|
33339
|
-
return
|
|
33484
|
+
return [];
|
|
33340
33485
|
console.error('Failed to resolve traject node availability', item.node.name, error);
|
|
33341
33486
|
dispatch(setTrajectNodeAvailability({ lineGuid, results: [], selectedCode: null, preferredUnavailable: false }));
|
|
33342
|
-
return
|
|
33487
|
+
return item.lines.map((x) => x.guid);
|
|
33343
33488
|
}
|
|
33344
33489
|
};
|
|
33345
|
-
const
|
|
33346
|
-
const
|
|
33347
|
-
if (!
|
|
33348
|
-
dispatch(
|
|
33349
|
-
return;
|
|
33490
|
+
const resolveFlightNode = async (item) => {
|
|
33491
|
+
const lineGuid = item.line.guid;
|
|
33492
|
+
if (!item.node.departureAirportCode || !item.node.arrivalAirportCode) {
|
|
33493
|
+
dispatch(setTrajectFlightAvailability({ lineGuid, results: [], selectedGuid: null }));
|
|
33494
|
+
return item.lines.map((x) => x.guid);
|
|
33350
33495
|
}
|
|
33351
|
-
dispatch(
|
|
33496
|
+
dispatch(setTrajectFlightLoading(lineGuid));
|
|
33352
33497
|
try {
|
|
33353
|
-
const
|
|
33354
|
-
const ageOf = (p) => p.age ?? 30;
|
|
33355
|
-
const adults = pax.filter((p) => ageOf(p) >= 12).length;
|
|
33356
|
-
const kids = pax.filter((p) => ageOf(p) >= 2 && ageOf(p) < 12).length;
|
|
33357
|
-
const babies = pax.filter((p) => ageOf(p) < 2).length;
|
|
33358
|
-
const request = {
|
|
33359
|
-
transactionId: trajectEntry.entry.transactionId,
|
|
33360
|
-
officeId: searchContext.officeId,
|
|
33361
|
-
catalogueId: searchContext.catalogueId,
|
|
33362
|
-
agentId: searchContext.agentId,
|
|
33363
|
-
language: searchContext.language,
|
|
33364
|
-
departureAirportCode: outbound.node.departureAirportCode ?? '',
|
|
33365
|
-
arrivalAirportCode: outbound.node.arrivalAirportCode ?? '',
|
|
33366
|
-
returnAirportCode: inbound?.node.arrivalAirportCode ?? outbound.node.departureAirportCode ?? null,
|
|
33367
|
-
luggageIncluded: null,
|
|
33368
|
-
maxStops: null,
|
|
33369
|
-
travelClass: null,
|
|
33370
|
-
pax: 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 }))),
|
|
33371
|
-
outward: { date: dateToDateStruct(new Date(outbound.line.from)) },
|
|
33372
|
-
return: { date: dateToDateStruct(new Date((inbound ?? outbound).line.from)) }
|
|
33373
|
-
};
|
|
33374
|
-
const flights = (await build.searchPackagingFlights(config, request, controller.signal)) ?? [];
|
|
33498
|
+
const flights = (await build.searchPackagingFlights(config, buildTrajectFlightRequest(item, searchContext, flightPax), controller.signal)) ?? [];
|
|
33375
33499
|
if (controller.signal.aborted)
|
|
33376
|
-
return;
|
|
33377
|
-
|
|
33378
|
-
|
|
33379
|
-
if (
|
|
33380
|
-
|
|
33381
|
-
|
|
33382
|
-
|
|
33500
|
+
return [];
|
|
33501
|
+
const picked = pickCheapestFlight(flights);
|
|
33502
|
+
dispatch(setTrajectFlightAvailability({ lineGuid, results: flights, selectedGuid: picked?.outwardGuid ?? null }));
|
|
33503
|
+
if (!picked)
|
|
33504
|
+
return item.lines.map((x) => x.guid);
|
|
33505
|
+
dispatch(updateEditableEntryLine(applyFlightToLine(item.line, picked)));
|
|
33506
|
+
return [];
|
|
33383
33507
|
}
|
|
33384
33508
|
catch (error) {
|
|
33385
|
-
if (
|
|
33386
|
-
|
|
33387
|
-
|
|
33388
|
-
|
|
33389
|
-
|
|
33390
|
-
dispatch(setFlightsLoading(false));
|
|
33391
|
-
dispatch(setTrajectFlightsResolved(true));
|
|
33392
|
-
}
|
|
33509
|
+
if (controller.signal.aborted)
|
|
33510
|
+
return [];
|
|
33511
|
+
console.error('Failed to resolve traject flight', item.node.name, error);
|
|
33512
|
+
dispatch(setTrajectFlightAvailability({ lineGuid, results: [], selectedGuid: null }));
|
|
33513
|
+
return item.lines.map((x) => x.guid);
|
|
33393
33514
|
}
|
|
33394
33515
|
};
|
|
33395
33516
|
(async () => {
|
|
33396
33517
|
const searchable = nodesWithLines.filter((x) => isSearchableTrajectNode(x.node));
|
|
33397
|
-
const
|
|
33518
|
+
const flightNodes = nodesWithLines.filter((x) => isTrajectFlightNode(x.node));
|
|
33519
|
+
const unresolved = (await Promise.all([...searchable.map(resolveNode), ...flightNodes.map(resolveFlightNode)])).flat();
|
|
33398
33520
|
if (controller.signal.aborted)
|
|
33399
33521
|
return;
|
|
33400
33522
|
// A node with no availability anywhere cannot be priced or booked, so its line comes off
|
|
33401
33523
|
// the entry. The card stays visible and reports the gap.
|
|
33402
|
-
const emptyLineGuids = unresolved.filter(
|
|
33524
|
+
const emptyLineGuids = unresolved.filter(Boolean);
|
|
33403
33525
|
if (emptyLineGuids.length)
|
|
33404
33526
|
dispatch(removeEditableEntryLines(emptyLineGuids));
|
|
33405
33527
|
})();
|
|
33406
33528
|
return () => controller.abort();
|
|
33407
|
-
}, [trajectEntry]);
|
|
33408
|
-
// Keep the traject's own flight lines in step with whatever outward/return the user picks.
|
|
33409
|
-
useEffect(() => {
|
|
33410
|
-
if (!trajectEntry || !selectedCombinationFlight)
|
|
33411
|
-
return;
|
|
33412
|
-
const { outbound, inbound } = pairTrajectFlights(getTrajectNodesWithLines(trajectEntry));
|
|
33413
|
-
if (outbound)
|
|
33414
|
-
dispatch(updateEditableEntryLine(applyFlightToLine(outbound.line, selectedCombinationFlight, true)));
|
|
33415
|
-
if (inbound)
|
|
33416
|
-
dispatch(updateEditableEntryLine(applyFlightToLine(inbound.line, selectedCombinationFlight, false)));
|
|
33417
|
-
}, [trajectEntry, selectedCombinationFlight]);
|
|
33529
|
+
}, [trajectEntry, showNonPreferredItems]);
|
|
33418
33530
|
};
|
|
33419
33531
|
|
|
33420
33532
|
const SearchResultsContainer = ({ onBookingStarted }) => {
|
|
@@ -33422,7 +33534,7 @@ const SearchResultsContainer = ({ onBookingStarted }) => {
|
|
|
33422
33534
|
const dispatch = useDispatch();
|
|
33423
33535
|
const context = useContext(SearchResultsConfigurationContext);
|
|
33424
33536
|
const translations = getTranslations(context?.languageCode ?? 'en-GB');
|
|
33425
|
-
const { results, filteredResults, packagingAccoResults, filteredPackagingAccoResults, isLoading, flightsLoading, initialFilters, filters, flightFilters, selectedSortType, selectedFlightSortType, selectedSearchResult, selectedPackagingAccoResultCode, flyInIsOpen, packagingAccoSearchDetails, editablePackagingEntry, transactionId, flyInType, packagingFlightResults, confirmedExcursionsByDay, bookPackagingEntry } = useSelector((state) => state.searchResults);
|
|
33537
|
+
const { results, filteredResults, packagingAccoResults, filteredPackagingAccoResults, isLoading, flightsLoading, initialFilters, filters, flightFilters, selectedSortType, selectedFlightSortType, selectedSearchResult, selectedPackagingAccoResultCode, flyInIsOpen, packagingAccoSearchDetails, editablePackagingEntry, transactionId, flyInType, packagingFlightResults, confirmedExcursionsByDay, bookPackagingEntry, trajectEditLineGuid } = useSelector((state) => state.searchResults);
|
|
33426
33538
|
const isMobile = useMediaQuery('(max-width: 1200px)');
|
|
33427
33539
|
// Resolves live availability per traject node; no-op unless a traject entry is configured.
|
|
33428
33540
|
useTrajectAvailability();
|
|
@@ -33711,6 +33823,20 @@ const SearchResultsContainer = ({ onBookingStarted }) => {
|
|
|
33711
33823
|
setDetailsIsLoading(false);
|
|
33712
33824
|
};
|
|
33713
33825
|
const handleConfirmHotelSwap = () => {
|
|
33826
|
+
if (context?.trajectEntry && trajectEditLineGuid) {
|
|
33827
|
+
const item = getTrajectNodesWithLines(context.trajectEntry).find((x) => x.line.guid === trajectEditLineGuid);
|
|
33828
|
+
const result = packagingAccoSearchDetails.find((x) => x.code === selectedPackagingAccoResultCode);
|
|
33829
|
+
if (item && result) {
|
|
33830
|
+
const currentLines = item.lines
|
|
33831
|
+
.map((line) => editablePackagingEntry?.lines?.find((x) => x.guid === line.guid))
|
|
33832
|
+
.filter((x) => !!x);
|
|
33833
|
+
applyResultToLines(currentLines, result).forEach((line) => dispatch(updateEditableEntryLine(line)));
|
|
33834
|
+
dispatch(updateTrajectNodeResult({ lineGuid: trajectEditLineGuid, result }));
|
|
33835
|
+
}
|
|
33836
|
+
dispatch(setTrajectEditLineGuid(null));
|
|
33837
|
+
handleFlyInToggle(false);
|
|
33838
|
+
return;
|
|
33839
|
+
}
|
|
33714
33840
|
const updatedEntry = swapHotelInPackagingEntry();
|
|
33715
33841
|
if (!updatedEntry)
|
|
33716
33842
|
return;
|
|
@@ -34059,6 +34185,10 @@ const SearchResultsContainer = ({ onBookingStarted }) => {
|
|
|
34059
34185
|
const fetchPackagingAccoSearchDetails = async () => {
|
|
34060
34186
|
if (!selectedPackagingAccoResultCode || !context)
|
|
34061
34187
|
return;
|
|
34188
|
+
// A traject node already resolved its own availability, options per room included, and hands
|
|
34189
|
+
// that result straight to the fly-in.
|
|
34190
|
+
if (context.trajectEntry)
|
|
34191
|
+
return;
|
|
34062
34192
|
if (skipInitialPackagingAccoDetailsRef.current) {
|
|
34063
34193
|
skipInitialPackagingAccoDetailsRef.current = false;
|
|
34064
34194
|
return;
|
|
@@ -37573,20 +37703,20 @@ function ItineraryMapView({ destinations: propDestinations, adults = 2, dateFrom
|
|
|
37573
37703
|
const isStartEnd = dest.isStart || dest.isEnd;
|
|
37574
37704
|
let iconHtml;
|
|
37575
37705
|
if (isStartEnd) {
|
|
37576
|
-
iconHtml = `
|
|
37577
|
-
<div class="map-view__marker map-view__marker--plane">
|
|
37578
|
-
<svg viewBox="0 0 576 512" fill="currentColor" width="16" height="16" xmlns="http://www.w3.org/2000/svg">
|
|
37579
|
-
<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"/>
|
|
37580
|
-
</svg>
|
|
37706
|
+
iconHtml = `
|
|
37707
|
+
<div class="map-view__marker map-view__marker--plane">
|
|
37708
|
+
<svg viewBox="0 0 576 512" fill="currentColor" width="16" height="16" xmlns="http://www.w3.org/2000/svg">
|
|
37709
|
+
<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"/>
|
|
37710
|
+
</svg>
|
|
37581
37711
|
</div>`;
|
|
37582
37712
|
}
|
|
37583
37713
|
else {
|
|
37584
37714
|
stopIndex++;
|
|
37585
37715
|
const imgStyle = dest.imageUrl ? `style="background-image:url('${dest.imageUrl}')"` : '';
|
|
37586
|
-
iconHtml = `
|
|
37587
|
-
<div class="map-view__marker map-view__marker--stop" ${imgStyle}>
|
|
37588
|
-
${!dest.imageUrl ? `<span class="map-view__marker-number">${stopIndex}</span>` : ''}
|
|
37589
|
-
<span class="map-view__marker-badge">${stopIndex}</span>
|
|
37716
|
+
iconHtml = `
|
|
37717
|
+
<div class="map-view__marker map-view__marker--stop" ${imgStyle}>
|
|
37718
|
+
${!dest.imageUrl ? `<span class="map-view__marker-number">${stopIndex}</span>` : ''}
|
|
37719
|
+
<span class="map-view__marker-badge">${stopIndex}</span>
|
|
37590
37720
|
</div>`;
|
|
37591
37721
|
}
|
|
37592
37722
|
const icon = L.divIcon({
|