@qite/tide-components 1.0.4 → 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.
@@ -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, isValid } from 'date-fns';
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
- trajectFlightAvailability: {}
27352
+ trajectFlightAvailability: {},
27353
+ trajectEditLineGuid: null
27345
27354
  };
27346
27355
  const searchResultsSlice = createSlice({
27347
27356
  name: 'searchResults',
@@ -27588,13 +27597,26 @@ const searchResultsSlice = createSlice({
27588
27597
  flight.selectedGuid = action.payload.guid;
27589
27598
  flight.status = 'resolved';
27590
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';
27611
+ },
27591
27612
  resetTrajectAvailability(state) {
27592
27613
  state.trajectNodeAvailability = {};
27593
27614
  state.trajectFlightAvailability = {};
27615
+ state.trajectEditLineGuid = null;
27594
27616
  }
27595
27617
  }
27596
27618
  });
27597
- 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, resetTrajectAvailability, updateEditableEntryLine, removeEditableEntryLines } = searchResultsSlice.actions;
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;
27598
27620
  var searchResultsReducer = searchResultsSlice.reducer;
27599
27621
 
27600
27622
  const ItemPicker = ({ items, selection, selectedSortByType, label, placeholder, classModifier, onPick, valueFormatter }) => {
@@ -30574,6 +30596,293 @@ const ExcursionDetails = () => {
30574
30596
  React__default.createElement("button", { type: "button", className: "cta cta--primary", onClick: handleConfirm }, translations?.QSM.CONFIRM))));
30575
30597
  };
30576
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
+
30577
30886
  const FlyIn = ({ srpType, isOpen, setIsOpen, className = '', onPanelRef, detailsLoading, flyInType, isPackageEditFlow, handleConfirm, sortByTypes, activeSearchSeed, toggleFilters, filtersOpen }) => {
30578
30887
  const dispatch = useDispatch();
30579
30888
  const context = useContext(SearchResultsConfigurationContext);
@@ -30619,6 +30928,10 @@ const FlyIn = ({ srpType, isOpen, setIsOpen, className = '', onPanelRef, details
30619
30928
  }
30620
30929
  };
30621
30930
  const handleGoBack = () => {
30931
+ if (context?.trajectEntry) {
30932
+ handleClose();
30933
+ return;
30934
+ }
30622
30935
  if (flyInType === 'acco-details') {
30623
30936
  dispatch(setFlyInType('acco-results'));
30624
30937
  }
@@ -30642,7 +30955,7 @@ const FlyIn = ({ srpType, isOpen, setIsOpen, className = '', onPanelRef, details
30642
30955
  dispatch(setBookPackagingEntry(true));
30643
30956
  }
30644
30957
  };
30645
- 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' : ''}
30646
30959
  ${flyInType === 'flight-outward-results' || flyInType === 'flight-return-results' ? 'flyin--flight' : ''}` },
30647
30960
  React__default.createElement("div", { className: `flyin__panel ${isOpen ? 'flyin__panel--active' : ''}`, ref: panelRef },
30648
30961
  React__default.createElement("div", { className: "flyin__content" },
@@ -30668,7 +30981,10 @@ const FlyIn = ({ srpType, isOpen, setIsOpen, className = '', onPanelRef, details
30668
30981
  React__default.createElement(Icon, { name: "ui-chevron", width: 14, height: 14, "aria-hidden": "true" }),
30669
30982
  "Go Back")))),
30670
30983
  srpType === build.PortalQsmType.Flight && React__default.createElement(FlightsFlyIn, { isOpen: isOpen, setIsOpen: setIsOpen }),
30671
- (srpType === build.PortalQsmType.Accommodation || srpType === build.PortalQsmType.AccommodationAndFlight) && flyInType === 'acco-results' && (React__default.createElement("div", { className: "flyin__content flyin__content--columns" },
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" },
30672
30988
  React__default.createElement(Filters, { initialFilters: initialFilters, filters: filters, isOpen: filtersOpen, handleSetIsOpen: () => toggleFilters && toggleFilters(),
30673
30989
  // handleApplyFilters={() => setSearchTrigger((prev) => prev + 1)}
30674
30990
  isLoading: isLoading, setFilters: (filters) => dispatch(setFilters(filters)), resetFilters: (filters) => dispatch(resetFilters(filters)) }),
@@ -32941,232 +33257,6 @@ const BookPackagingEntry = ({ activeSearchSeed, isLoading, isConfirmationPage })
32941
33257
  React__default.createElement(WLSidebar, { activeSearchSeed: activeSearchSeed, packagingAccoResult: selectedPackagingAccoResult }))));
32942
33258
  };
32943
33259
 
32944
- const REGULAR_TRAJECT_NODE_TYPE = 0;
32945
- const isSearchableTrajectNode = (node) => node.serviceType === ACCOMMODATION_SERVICE_TYPE || node.serviceType === EXCURSION_SERVICE_TYPE;
32946
- const isTrajectFlightNode = (node) => node.serviceType === FLIGHT_SERVICE_TYPE;
32947
- const getTrajectNodesWithLines = (trajectEntry) => {
32948
- const linesByGuid = new Map((trajectEntry.entry.lines ?? []).map((line) => [line.guid, line]));
32949
- return [...(trajectEntry.nodes ?? [])]
32950
- .sort((a, b) => a.dayOrder - b.dayOrder || a.order - b.order)
32951
- .map((node) => ({ node, line: linesByGuid.get(node.lineGuid) }))
32952
- .filter((x) => !!x.line);
32953
- };
32954
- const buildDestination = (line) => {
32955
- if (line.location?.id)
32956
- return { id: line.location.id, isLocation: true };
32957
- if (line.oord?.id)
32958
- return { id: line.oord.id, isOord: true };
32959
- if (line.region?.id)
32960
- return { id: line.region.id, isRegion: true };
32961
- if (line.country?.id)
32962
- return { id: line.country.id, isCountry: true };
32963
- if (line.latitude && line.longitude)
32964
- return { latitude: line.latitude, longitude: line.longitude };
32965
- return { id: 0 };
32966
- };
32967
- const buildTrajectNodeRequest = ({ node, line }, context, productCode) => ({
32968
- transactionId: context.transactionId,
32969
- officeId: context.officeId,
32970
- agentId: context.agentId ?? null,
32971
- catalogueId: context.catalogueId,
32972
- searchConfigurationId: context.searchConfigurationId,
32973
- portalId: context.portalId ?? null,
32974
- vendorConfigurationId: node.externalVendorId ?? null,
32975
- language: context.language,
32976
- serviceType: node.serviceType,
32977
- fromDate: toDateOnlyString(line.from),
32978
- toDate: toDateOnlyString(line.to),
32979
- destination: buildDestination(line),
32980
- productCode,
32981
- rooms: context.rooms,
32982
- tagIds: []
32983
- });
32984
- const getNodeCandidates = (node) => {
32985
- const configured = (node.alternatives ?? []).filter((x) => !!x.code && x.code.trim().length > 0);
32986
- if (configured.length)
32987
- return configured;
32988
- const fallbackCode = node.code ?? node.preferredAccommodationCode;
32989
- if (!fallbackCode || !fallbackCode.trim().length)
32990
- return [];
32991
- return [
32992
- {
32993
- code: fallbackCode,
32994
- name: node.name,
32995
- description: node.description,
32996
- imageUrl: node.imageUrl,
32997
- contentSource: node.contentSource,
32998
- vendor: node.vendor,
32999
- externalVendorId: node.externalVendorId,
33000
- isPreferred: true
33001
- }
33002
- ];
33003
- };
33004
- const findCandidateForResult = (node, result) => getNodeCandidates(node).find((candidate) => matchesCode(result, candidate.code)) ?? null;
33005
- const matchesCode = (result, code) => {
33006
- if (result.code === code)
33007
- return true;
33008
- return (result.rooms ?? []).some((room) => (room.options ?? []).some((option) => option.accommodationCode === code));
33009
- };
33010
- const getPackagingRoomsFromEntry = (entry) => {
33011
- const paxById = new Map((entry.pax ?? []).map((p) => [p.id, p]));
33012
- const rooms = (entry.rooms ?? []).map((room) => ({
33013
- travellers: (room.paxIds ?? []).map((paxId) => {
33014
- const pax = paxById.get(paxId);
33015
- return {
33016
- id: paxId,
33017
- age: pax?.age ?? null,
33018
- dateOfBirth: pax?.dateOfBirth ?? null
33019
- };
33020
- })
33021
- }));
33022
- return rooms.filter((room) => room.travellers.length > 0);
33023
- };
33024
- const pickCheapest = (results) => [...results].sort((a, b) => a.price - b.price)[0] ?? null;
33025
- const applyResultToLine = (line, result) => {
33026
- const option = (result.rooms ?? []).flatMap((room) => room.options ?? []).find((x) => x.isSelected) ?? (result.rooms ?? [])[0]?.options?.[0];
33027
- return {
33028
- ...line,
33029
- productName: result.name ?? line.productName,
33030
- productCode: result.code ?? line.productCode,
33031
- accommodationCode: option?.accommodationCode ?? line.accommodationCode,
33032
- accommodationName: option?.accommodationName ?? line.accommodationName,
33033
- regimeCode: option?.regimeCode ?? line.regimeCode,
33034
- regimeName: option?.regimeName ?? line.regimeName,
33035
- latitude: result.latitude ?? line.latitude,
33036
- longitude: result.longitude ?? line.longitude,
33037
- isChanged: true
33038
- };
33039
- };
33040
- const toDateOnlyUtcString = (value) => {
33041
- const date = new Date(value);
33042
- return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate())).toISOString();
33043
- };
33044
- const toTimeOnlyString = (value) => {
33045
- const date = new Date(value);
33046
- const hh = String(date.getUTCHours()).padStart(2, '0');
33047
- const mm = String(date.getUTCMinutes()).padStart(2, '0');
33048
- const ss = String(date.getUTCSeconds()).padStart(2, '0');
33049
- return `${hh}:${mm}:${ss}`;
33050
- };
33051
- const mapFlightSegmentsToFlightLines = (segments) => segments.map((segment) => ({
33052
- airlineCode: segment.marketingAirlineCode,
33053
- airlineDescription: segment.marketingAirlineName,
33054
- operatingAirlineCode: segment.operatingAirlineCode,
33055
- operatingAirlineDescription: segment.operatingAirlineName,
33056
- flightNumber: segment.flightNumber,
33057
- operatingFlightNumber: segment.operatingFlightNumber ?? null,
33058
- departureDate: toDateOnlyUtcString(segment.departureDateTime),
33059
- departureTime: toTimeOnlyString(segment.departureDateTime),
33060
- departureAirportCode: segment.departureAirportCode,
33061
- departureAirportDescription: segment.departureAirportName,
33062
- arrivalDate: toDateOnlyUtcString(segment.arrivalDateTime),
33063
- arrivalTime: toTimeOnlyString(segment.arrivalDateTime),
33064
- arrivalAirportCode: segment.arrivalAirportCode,
33065
- arrivalAirportDescription: segment.arrivalAirportName,
33066
- durationInTicks: segment.durationInTicks
33067
- }));
33068
- const buildTrajectFlightRequest = ({ node, line }, context, pax) => ({
33069
- transactionId: context.transactionId,
33070
- officeId: context.officeId,
33071
- catalogueId: context.catalogueId,
33072
- agentId: context.agentId ?? null,
33073
- language: context.language,
33074
- departureAirportCode: node.departureAirportCode ?? '',
33075
- arrivalAirportCode: node.arrivalAirportCode ?? '',
33076
- returnAirportCode: null,
33077
- luggageIncluded: null,
33078
- maxStops: null,
33079
- travelClass: null,
33080
- vendorConfigurationId: node.externalVendorId ?? null,
33081
- pax,
33082
- outward: { date: dateToDateStruct(new Date(line.from)) },
33083
- return: null
33084
- });
33085
- const pickCheapestFlight = (flights) => [...flights].sort((a, b) => a.price - b.price)[0] ?? null;
33086
- const applyFlightToLine = (line, flight) => {
33087
- const segments = flight.outward?.segments ?? [];
33088
- if (!segments.length)
33089
- return line;
33090
- const firstSegment = segments[0];
33091
- const lastSegment = segments[segments.length - 1];
33092
- return {
33093
- ...line,
33094
- from: new Date(firstSegment.departureDateTime).toISOString(),
33095
- to: new Date(lastSegment.arrivalDateTime).toISOString(),
33096
- productName: `${firstSegment.departureAirportName} - ${lastSegment.arrivalAirportName} (${firstSegment.marketingAirlineName})`,
33097
- productCode: `${firstSegment.departureAirportCode} ${lastSegment.arrivalAirportCode}/${firstSegment.marketingAirlineCode}`,
33098
- accommodationName: firstSegment.metaData?.farePriceClassName ?? line.accommodationName,
33099
- accommodationCode: firstSegment.metaData?.fareCode ?? line.accommodationCode,
33100
- flightInformation: {
33101
- pnr: '',
33102
- flightLines: mapFlightSegmentsToFlightLines(segments)
33103
- },
33104
- isChanged: true
33105
- };
33106
- };
33107
-
33108
- const getLocation = (result) => {
33109
- const place = result.locationName || result.regionName || result.oordName;
33110
- if (!place)
33111
- return result.countryName ?? '';
33112
- return result.countryName ? `${place}, ${result.countryName}` : place;
33113
- };
33114
- const toPlainText = (value) => {
33115
- if (!value)
33116
- return '';
33117
- return he
33118
- .decode(value.replace(/<[^>]*>/g, ' '))
33119
- .replace(/\s+/g, ' ')
33120
- .trim();
33121
- };
33122
- const TrajectNodeCard = ({ item, result, isSelected, languageCode, translations, onSelect, showNights }) => {
33123
- const { node } = item;
33124
- const selectedOption = first(result.rooms)?.options?.find((x) => x.isSelected) ?? first(result.rooms)?.options?.[0];
33125
- const price = formatPrice$3(result.price, result.currencyCode, languageCode);
33126
- const nights = calculateNights(new Date(result.fromDate), new Date(result.toDate));
33127
- const candidate = findCandidateForResult(node, result);
33128
- const image = candidate?.imageUrl ?? null;
33129
- const description = toPlainText(candidate?.description);
33130
- const title = candidate?.name ?? result.name;
33131
- const priceBlock = (React__default.createElement("div", { className: "search__result-card__price__wrapper" },
33132
- React__default.createElement("span", { className: "search__result-card__price__label" }, translations?.SHARED.TOTAL_PRICE),
33133
- React__default.createElement("span", { className: "search__result-card__price" }, price)));
33134
- const selectButton = (React__default.createElement("button", { type: "button", className: `cta ${isSelected ? 'cta--selected' : 'cta--select'}`, onClick: onSelect }, isSelected ? translations?.SHARED.SELECTED : translations?.SHARED.SELECT));
33135
- if (result.contents?.length) {
33136
- return (React__default.createElement("div", { className: `search__result-card__wrapper search__result-card__wrapper--custom ${isSelected ? 'search__result-card__wrapper--selected' : ''}` },
33137
- React__default.createElement("div", { className: "search__result-card__top", dangerouslySetInnerHTML: { __html: he.decode(result.contents) } }),
33138
- React__default.createElement("div", { className: "search__result-card__footer" }, selectButton)));
33139
- }
33140
- return (React__default.createElement("div", { className: `search__result-card__wrapper ${isSelected ? 'search__result-card__wrapper--selected' : ''}` },
33141
- image && (React__default.createElement("div", { className: "search__result-card__img-wrapper" },
33142
- React__default.createElement("img", { src: image, alt: title, className: "search__result-card__img" }),
33143
- priceBlock)),
33144
- React__default.createElement("div", { className: "search__result-card__content" },
33145
- React__default.createElement("div", { className: "search__result-card__content__wrapper" },
33146
- React__default.createElement("div", { className: "search__result-card__header" },
33147
- React__default.createElement("div", { className: "search__result-card__header__wrapper" },
33148
- !!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 }))))),
33149
- React__default.createElement("h3", { className: "search__result-card__title" }, title)),
33150
- !image && priceBlock),
33151
- React__default.createElement("span", { className: "search__result-card__location" },
33152
- React__default.createElement(Icon, { name: "ui-location", height: 16 }),
33153
- getLocation(result)),
33154
- React__default.createElement("div", { className: "search__result-card__options" },
33155
- showNights && nights > 0 && (React__default.createElement("div", { className: "search__result-card__option" },
33156
- React__default.createElement(Icon, { name: "ui-moon", height: 16 }),
33157
- nights,
33158
- " ",
33159
- translations?.SRP.NIGHTS)),
33160
- selectedOption?.accommodationName && (React__default.createElement("div", { className: "search__result-card__option" },
33161
- React__default.createElement(Icon, { name: "ui-bed", height: 16 }),
33162
- selectedOption.accommodationName)),
33163
- selectedOption?.regimeName && (React__default.createElement("div", { className: "search__result-card__option" },
33164
- React__default.createElement(Icon, { name: "ui-utensils", height: 16 }),
33165
- selectedOption.regimeName))),
33166
- description && React__default.createElement("p", { className: "search__result-card__description" }, description)),
33167
- React__default.createElement("div", { className: "search__result-card__footer" }, selectButton))));
33168
- };
33169
-
33170
33260
  const getNodeIcon = (node) => {
33171
33261
  if (isTrajectFlightNode(node))
33172
33262
  return 'ui-flight';
@@ -33206,13 +33296,28 @@ const TrajectResults = ({ isLoading }) => {
33206
33296
  if (!result)
33207
33297
  return;
33208
33298
  dispatch(setTrajectNodeSelection({ lineGuid: item.line.guid, code }));
33209
- dispatch(updateEditableEntryLine(applyResultToLine(item.line, result)));
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));
33210
33316
  };
33211
33317
  const renderServiceNode = (item) => {
33212
33318
  console.log('renderServiceNode', item);
33213
33319
  const { node, line } = item;
33214
33320
  const availability = trajectNodeAvailability[line.guid];
33215
- const isExpanded = !!expandedNodes[line.guid];
33216
33321
  const results = availability?.results ?? [];
33217
33322
  const selected = results.find((x) => x.code === availability?.selectedCode) ?? null;
33218
33323
  const configured = results.filter((x) => x.code !== selected?.code && !!findCandidateForResult(node, x));
@@ -33234,9 +33339,13 @@ const TrajectResults = ({ isLoading }) => {
33234
33339
  node.preferredAccommodationName,
33235
33340
  " \u2014 ",
33236
33341
  translations.SRP.NO_RESULTS))),
33237
- React__default.createElement("div", { className: "search__results__cards search__results__cards--extended" }, [selected, ...configured, ...(isExpanded ? others : [])].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), showNights: node.serviceType === ACCOMMODATION_SERVICE_TYPE })))),
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 })))),
33238
33343
  others.length > 0 && (React__default.createElement("div", { className: "search__results__cards__actions" },
33239
- 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})`)))))));
33344
+ React__default.createElement("button", { type: "button", className: "cta cta--secondary", onClick: () => handleShowMore(item) },
33345
+ translations.SRP.SHOW_MORE,
33346
+ " (",
33347
+ others.length,
33348
+ ")")))))));
33240
33349
  };
33241
33350
  const handleSelectFlight = (item, flight) => {
33242
33351
  dispatch(setTrajectFlightSelection({ lineGuid: item.line.guid, guid: flight.outwardGuid }));
@@ -33281,6 +33390,8 @@ const TrajectResults = ({ isLoading }) => {
33281
33390
  if (!dayNodes.length)
33282
33391
  return null;
33283
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;
33284
33395
  // if (node.trajectNodeType === CONNECTING_TRAJECT_NODE_TYPE) return null;
33285
33396
  // if (node.trajectNodeType !== REGULAR_TRAJECT_NODE_TYPE) return renderContextNode(node);
33286
33397
  if (node.trajectNodeType !== REGULAR_TRAJECT_NODE_TYPE)
@@ -33298,6 +33409,7 @@ const useTrajectAvailability = () => {
33298
33409
  const context = useContext(SearchResultsConfigurationContext);
33299
33410
  const dispatch = useDispatch();
33300
33411
  const trajectEntry = context?.trajectEntry;
33412
+ const showNonPreferredItems = !!context?.showNonPreferredItems;
33301
33413
  useEffect(() => {
33302
33414
  if (!context || !trajectEntry)
33303
33415
  return;
@@ -33342,18 +33454,20 @@ const useTrajectAvailability = () => {
33342
33454
  return null;
33343
33455
  }
33344
33456
  }));
33345
- let results = attempts.filter((x) => !!x).filter((x, i, all) => all.findIndex((y) => y.code === x.code) === i);
33346
- let picked = results[0] ?? null;
33457
+ const configuredResults = attempts
33458
+ .filter((x) => !!x)
33459
+ .filter((x, i, all) => all.findIndex((y) => y.code === x.code) === i);
33347
33460
  const preferredCandidate = candidates.find((x) => x.isPreferred) ?? candidates[0];
33348
- let preferredUnavailable = !!preferredCandidate && !results.some((x) => matchesCode(x, preferredCandidate.code));
33349
- // Nothing configured was available — fall back to the cheapest result in the destination.
33350
- if (!picked) {
33351
- results = (await search(config, buildTrajectNodeRequest(item, searchContext, ''), controller.signal)) ?? [];
33352
- picked = pickCheapest(results);
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)
33353
33468
  preferredUnavailable = candidates.length > 0;
33354
- }
33355
33469
  if (controller.signal.aborted)
33356
- return null;
33470
+ return [];
33357
33471
  dispatch(setTrajectNodeAvailability({
33358
33472
  lineGuid,
33359
33473
  results,
@@ -33361,58 +33475,58 @@ const useTrajectAvailability = () => {
33361
33475
  preferredUnavailable
33362
33476
  }));
33363
33477
  if (!picked)
33364
- return lineGuid;
33365
- dispatch(updateEditableEntryLine(applyResultToLine(item.line, picked)));
33366
- return null;
33478
+ return item.lines.map((x) => x.guid);
33479
+ applyResultToLines(item.lines, picked).forEach((line) => dispatch(updateEditableEntryLine(line)));
33480
+ return [];
33367
33481
  }
33368
33482
  catch (error) {
33369
33483
  if (controller.signal.aborted)
33370
- return null;
33484
+ return [];
33371
33485
  console.error('Failed to resolve traject node availability', item.node.name, error);
33372
33486
  dispatch(setTrajectNodeAvailability({ lineGuid, results: [], selectedCode: null, preferredUnavailable: false }));
33373
- return lineGuid;
33487
+ return item.lines.map((x) => x.guid);
33374
33488
  }
33375
33489
  };
33376
33490
  const resolveFlightNode = async (item) => {
33377
33491
  const lineGuid = item.line.guid;
33378
33492
  if (!item.node.departureAirportCode || !item.node.arrivalAirportCode) {
33379
33493
  dispatch(setTrajectFlightAvailability({ lineGuid, results: [], selectedGuid: null }));
33380
- return lineGuid;
33494
+ return item.lines.map((x) => x.guid);
33381
33495
  }
33382
33496
  dispatch(setTrajectFlightLoading(lineGuid));
33383
33497
  try {
33384
33498
  const flights = (await build.searchPackagingFlights(config, buildTrajectFlightRequest(item, searchContext, flightPax), controller.signal)) ?? [];
33385
33499
  if (controller.signal.aborted)
33386
- return null;
33500
+ return [];
33387
33501
  const picked = pickCheapestFlight(flights);
33388
33502
  dispatch(setTrajectFlightAvailability({ lineGuid, results: flights, selectedGuid: picked?.outwardGuid ?? null }));
33389
33503
  if (!picked)
33390
- return lineGuid;
33504
+ return item.lines.map((x) => x.guid);
33391
33505
  dispatch(updateEditableEntryLine(applyFlightToLine(item.line, picked)));
33392
- return null;
33506
+ return [];
33393
33507
  }
33394
33508
  catch (error) {
33395
33509
  if (controller.signal.aborted)
33396
- return null;
33510
+ return [];
33397
33511
  console.error('Failed to resolve traject flight', item.node.name, error);
33398
33512
  dispatch(setTrajectFlightAvailability({ lineGuid, results: [], selectedGuid: null }));
33399
- return lineGuid;
33513
+ return item.lines.map((x) => x.guid);
33400
33514
  }
33401
33515
  };
33402
33516
  (async () => {
33403
33517
  const searchable = nodesWithLines.filter((x) => isSearchableTrajectNode(x.node));
33404
33518
  const flightNodes = nodesWithLines.filter((x) => isTrajectFlightNode(x.node));
33405
- const unresolved = await Promise.all([...searchable.map(resolveNode), ...flightNodes.map(resolveFlightNode)]);
33519
+ const unresolved = (await Promise.all([...searchable.map(resolveNode), ...flightNodes.map(resolveFlightNode)])).flat();
33406
33520
  if (controller.signal.aborted)
33407
33521
  return;
33408
33522
  // A node with no availability anywhere cannot be priced or booked, so its line comes off
33409
33523
  // the entry. The card stays visible and reports the gap.
33410
- const emptyLineGuids = unresolved.filter((guid) => !!guid);
33524
+ const emptyLineGuids = unresolved.filter(Boolean);
33411
33525
  if (emptyLineGuids.length)
33412
33526
  dispatch(removeEditableEntryLines(emptyLineGuids));
33413
33527
  })();
33414
33528
  return () => controller.abort();
33415
- }, [trajectEntry]);
33529
+ }, [trajectEntry, showNonPreferredItems]);
33416
33530
  };
33417
33531
 
33418
33532
  const SearchResultsContainer = ({ onBookingStarted }) => {
@@ -33420,7 +33534,7 @@ const SearchResultsContainer = ({ onBookingStarted }) => {
33420
33534
  const dispatch = useDispatch();
33421
33535
  const context = useContext(SearchResultsConfigurationContext);
33422
33536
  const translations = getTranslations(context?.languageCode ?? 'en-GB');
33423
- 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);
33424
33538
  const isMobile = useMediaQuery('(max-width: 1200px)');
33425
33539
  // Resolves live availability per traject node; no-op unless a traject entry is configured.
33426
33540
  useTrajectAvailability();
@@ -33709,6 +33823,20 @@ const SearchResultsContainer = ({ onBookingStarted }) => {
33709
33823
  setDetailsIsLoading(false);
33710
33824
  };
33711
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
+ }
33712
33840
  const updatedEntry = swapHotelInPackagingEntry();
33713
33841
  if (!updatedEntry)
33714
33842
  return;
@@ -34057,6 +34185,10 @@ const SearchResultsContainer = ({ onBookingStarted }) => {
34057
34185
  const fetchPackagingAccoSearchDetails = async () => {
34058
34186
  if (!selectedPackagingAccoResultCode || !context)
34059
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;
34060
34192
  if (skipInitialPackagingAccoDetailsRef.current) {
34061
34193
  skipInitialPackagingAccoDetailsRef.current = false;
34062
34194
  return;
@@ -37571,20 +37703,20 @@ function ItineraryMapView({ destinations: propDestinations, adults = 2, dateFrom
37571
37703
  const isStartEnd = dest.isStart || dest.isEnd;
37572
37704
  let iconHtml;
37573
37705
  if (isStartEnd) {
37574
- iconHtml = `
37575
- <div class="map-view__marker map-view__marker--plane">
37576
- <svg viewBox="0 0 576 512" fill="currentColor" width="16" height="16" xmlns="http://www.w3.org/2000/svg">
37577
- <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"/>
37578
- </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>
37579
37711
  </div>`;
37580
37712
  }
37581
37713
  else {
37582
37714
  stopIndex++;
37583
37715
  const imgStyle = dest.imageUrl ? `style="background-image:url('${dest.imageUrl}')"` : '';
37584
- iconHtml = `
37585
- <div class="map-view__marker map-view__marker--stop" ${imgStyle}>
37586
- ${!dest.imageUrl ? `<span class="map-view__marker-number">${stopIndex}</span>` : ''}
37587
- <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>
37588
37720
  </div>`;
37589
37721
  }
37590
37722
  const icon = L.divIcon({