@qite/tide-components 1.0.8 → 1.0.9

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, isValid, parse, startOfToday } from 'date-fns';
5
+ import { format as format$2, isToday, getDate, startOfWeek, startOfMonth, endOfWeek, addWeeks, endOfMonth, eachDayOfInterval, getYear, getMonth, getISOWeek, getISODay, isSameMonth, startOfDay, isAfter, isSameDay, isEqual, isWithinInterval, endOfDay, isBefore, 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';
@@ -388,6 +388,8 @@ var SRP$f = {
388
388
  DEPARTURE_ASC: "تاريخ المغادرة (الأقرب أولاً)",
389
389
  FILTERS: "عوامل التصفية",
390
390
  SHOW_ITINERARY: "عرض خط سير الرحلة",
391
+ VIEW_FULL_ITINERARY: "عرض خط السير الكامل",
392
+ SHOW_SERVICES: "عرض الخدمات",
391
393
  VIEW_BOOKING: "عرض الحجز",
392
394
  ITINERARY_TITLE: "ملخص رحلتك",
393
395
  DOSSIER_NUMBER: "رقم الملف",
@@ -833,6 +835,8 @@ var SRP$e = {
833
835
  DEPARTURE_ASC: "Afrejsedato (snarest først)",
834
836
  FILTERS: "Filtre",
835
837
  SHOW_ITINERARY: "Vis rejseplan",
838
+ VIEW_FULL_ITINERARY: "Se hele rejseplanen",
839
+ SHOW_SERVICES: "Vis ydelser",
836
840
  VIEW_BOOKING: "Se booking",
837
841
  ITINERARY_TITLE: "Din rejseoversigt",
838
842
  DOSSIER_NUMBER: "Sagsnummer",
@@ -1282,6 +1286,8 @@ var SRP$d = {
1282
1286
  DEPARTURE_ASC: "Abreisedatum (früheste zuerst)",
1283
1287
  FILTERS: "Filter",
1284
1288
  SHOW_ITINERARY: "Reiseverlauf anzeigen",
1289
+ VIEW_FULL_ITINERARY: "Vollständigen Reiseverlauf ansehen",
1290
+ SHOW_SERVICES: "Leistungen anzeigen",
1285
1291
  VIEW_BOOKING: "Buchung anzeigen",
1286
1292
  ITINERARY_TITLE: "Ihre Reiseübersicht",
1287
1293
  DOSSIER_NUMBER: "Vorgangsnummer",
@@ -1731,6 +1737,8 @@ var SRP$c = {
1731
1737
  DEPARTURE_ASC: "Departure date (soonest first)",
1732
1738
  FILTERS: "Filters",
1733
1739
  SHOW_ITINERARY: "Show itinerary",
1740
+ VIEW_FULL_ITINERARY: "View full itinerary",
1741
+ SHOW_SERVICES: "Show services",
1734
1742
  VIEW_BOOKING: "View booking",
1735
1743
  ITINERARY_TITLE: "Your trip summary",
1736
1744
  DOSSIER_NUMBER: "File number",
@@ -2176,6 +2184,8 @@ var SRP$b = {
2176
2184
  DEPARTURE_ASC: "Fecha de salida (más próxima primero)",
2177
2185
  FILTERS: "Filtros",
2178
2186
  SHOW_ITINERARY: "Mostrar itinerario",
2187
+ VIEW_FULL_ITINERARY: "Ver itinerario completo",
2188
+ SHOW_SERVICES: "Ver servicios",
2179
2189
  VIEW_BOOKING: "Ver reserva",
2180
2190
  ITINERARY_TITLE: "Resumen de tu viaje",
2181
2191
  DOSSIER_NUMBER: "Número de expediente",
@@ -2625,6 +2635,8 @@ var SRP$a = {
2625
2635
  DEPARTURE_ASC: "Date de départ (la plus proche d’abord)",
2626
2636
  FILTERS: "Filtres",
2627
2637
  SHOW_ITINERARY: "Afficher l’itinéraire",
2638
+ VIEW_FULL_ITINERARY: "Voir l'itinéraire complet",
2639
+ SHOW_SERVICES: "Voir les services",
2628
2640
  VIEW_BOOKING: "Voir la réservation",
2629
2641
  ITINERARY_TITLE: "Résumé de votre voyage",
2630
2642
  DOSSIER_NUMBER: "Numéro de dossier",
@@ -3070,6 +3082,8 @@ var SRP$9 = {
3070
3082
  DEPARTURE_ASC: "Date de départ (la plus proche d’abord)",
3071
3083
  FILTERS: "Filtres",
3072
3084
  SHOW_ITINERARY: "Afficher l’itinéraire",
3085
+ VIEW_FULL_ITINERARY: "Voir l'itinéraire complet",
3086
+ SHOW_SERVICES: "Voir les services",
3073
3087
  VIEW_BOOKING: "Voir la réservation",
3074
3088
  ITINERARY_TITLE: "Résumé de votre voyage",
3075
3089
  DOSSIER_NUMBER: "Numéro de dossier",
@@ -3515,6 +3529,8 @@ var SRP$8 = {
3515
3529
  DEPARTURE_ASC: "Brottfarardagur (nærst fyrst)",
3516
3530
  FILTERS: "Síur",
3517
3531
  SHOW_ITINERARY: "Sýna ferðaráætlun",
3532
+ VIEW_FULL_ITINERARY: "Skoða alla ferðaáætlunina",
3533
+ SHOW_SERVICES: "Sýna þjónustu",
3518
3534
  VIEW_BOOKING: "Skoða bókun",
3519
3535
  ITINERARY_TITLE: "Yfirlit ferðarinnar",
3520
3536
  DOSSIER_NUMBER: "Skrárnúmer",
@@ -3960,6 +3976,8 @@ var SRP$7 = {
3960
3976
  DEPARTURE_ASC: "Data di partenza (prima la più vicina)",
3961
3977
  FILTERS: "Filtri",
3962
3978
  SHOW_ITINERARY: "Mostra itinerario",
3979
+ VIEW_FULL_ITINERARY: "Vedi l'itinerario completo",
3980
+ SHOW_SERVICES: "Mostra i servizi",
3963
3981
  VIEW_BOOKING: "Visualizza prenotazione",
3964
3982
  ITINERARY_TITLE: "Riepilogo del tuo viaggio",
3965
3983
  DOSSIER_NUMBER: "Numero di pratica",
@@ -4409,6 +4427,8 @@ var SRP$6 = {
4409
4427
  DEPARTURE_ASC: "Vertrekdatum (dichtstbij eerst)",
4410
4428
  FILTERS: "Filters",
4411
4429
  SHOW_ITINERARY: "Toon reisplan",
4430
+ VIEW_FULL_ITINERARY: "Bekijk volledige reisroute",
4431
+ SHOW_SERVICES: "Toon diensten",
4412
4432
  VIEW_BOOKING: "Bekijk boeking",
4413
4433
  ITINERARY_TITLE: "Overzicht van je reis",
4414
4434
  DOSSIER_NUMBER: "Dossiernummer",
@@ -4854,6 +4874,8 @@ var SRP$5 = {
4854
4874
  DEPARTURE_ASC: "Vertrekdatum (dichtstbij eerst)",
4855
4875
  FILTERS: "Filters",
4856
4876
  SHOW_ITINERARY: "Toon reisplan",
4877
+ VIEW_FULL_ITINERARY: "Bekijk volledige reisroute",
4878
+ SHOW_SERVICES: "Toon diensten",
4857
4879
  VIEW_BOOKING: "Bekijk boeking",
4858
4880
  ITINERARY_TITLE: "Overzicht van je reis",
4859
4881
  DOSSIER_NUMBER: "Dossiernummer",
@@ -5299,6 +5321,8 @@ var SRP$4 = {
5299
5321
  DEPARTURE_ASC: "Avreisedato (nærmeste først)",
5300
5322
  FILTERS: "Filtre",
5301
5323
  SHOW_ITINERARY: "Vis reiserute",
5324
+ VIEW_FULL_ITINERARY: "Se hele reiseruten",
5325
+ SHOW_SERVICES: "Vis tjenester",
5302
5326
  VIEW_BOOKING: "Se bestilling",
5303
5327
  ITINERARY_TITLE: "Din reisemåloversikt",
5304
5328
  DOSSIER_NUMBER: "Saksnummer",
@@ -5744,6 +5768,8 @@ var SRP$3 = {
5744
5768
  DEPARTURE_ASC: "Data wyjazdu (najbliższa najpierw)",
5745
5769
  FILTERS: "Filtry",
5746
5770
  SHOW_ITINERARY: "Pokaż plan podróży",
5771
+ VIEW_FULL_ITINERARY: "Zobacz pełny plan podróży",
5772
+ SHOW_SERVICES: "Pokaż usługi",
5747
5773
  VIEW_BOOKING: "Zobacz rezerwację",
5748
5774
  ITINERARY_TITLE: "Podsumowanie Twojej podróży",
5749
5775
  DOSSIER_NUMBER: "Numer pliku",
@@ -6189,6 +6215,8 @@ var SRP$2 = {
6189
6215
  DEPARTURE_ASC: "Data de partida (mais próxima primeiro)",
6190
6216
  FILTERS: "Filtros",
6191
6217
  SHOW_ITINERARY: "Mostrar itinerário",
6218
+ VIEW_FULL_ITINERARY: "Ver itinerário completo",
6219
+ SHOW_SERVICES: "Ver serviços",
6192
6220
  VIEW_BOOKING: "Ver reserva",
6193
6221
  ITINERARY_TITLE: "Resumo da sua viagem",
6194
6222
  DOSSIER_NUMBER: "Número do processo",
@@ -6634,6 +6662,8 @@ var SRP$1 = {
6634
6662
  DEPARTURE_ASC: "Avresedatum (närmast först)",
6635
6663
  FILTERS: "Filter",
6636
6664
  SHOW_ITINERARY: "Visa resplan",
6665
+ VIEW_FULL_ITINERARY: "Visa hela resplanen",
6666
+ SHOW_SERVICES: "Visa tjänster",
6637
6667
  VIEW_BOOKING: "Visa bokning",
6638
6668
  ITINERARY_TITLE: "Din resesammanfattning",
6639
6669
  DOSSIER_NUMBER: "Filnummer",
@@ -7079,6 +7109,8 @@ var SRP = {
7079
7109
  DEPARTURE_ASC: "出発日(早い順)",
7080
7110
  FILTERS: "フィルター",
7081
7111
  SHOW_ITINERARY: "旅程を表示",
7112
+ VIEW_FULL_ITINERARY: "旅程の詳細を見る",
7113
+ SHOW_SERVICES: "サービスを表示",
7082
7114
  VIEW_BOOKING: "予約を見る",
7083
7115
  ITINERARY_TITLE: "あなたの旅行概要",
7084
7116
  DOSSIER_NUMBER: "ファイル番号",
@@ -7788,6 +7820,33 @@ var TrajectNodeType = {
7788
7820
  subroute: 3,
7789
7821
  };
7790
7822
 
7823
+ var DurationType = {
7824
+ hours: 0,
7825
+ days: 1,
7826
+ nights: 2,
7827
+ minutes: 3,
7828
+ };
7829
+
7830
+ var RouteTransportType = {
7831
+ none: 0,
7832
+ privateTransport: 1,
7833
+ publicTransport: 2,
7834
+ rentalCar: 3,
7835
+ bus: 4,
7836
+ camper: 5,
7837
+ car: 6,
7838
+ charter: 7,
7839
+ cruise: 8,
7840
+ ferry: 9,
7841
+ flight: 10,
7842
+ taxi: 11,
7843
+ train: 12,
7844
+ transfer: 13,
7845
+ rocket: 14,
7846
+ walking: 15,
7847
+ cycling: 16,
7848
+ };
7849
+
7791
7850
  /******************************************************************************
7792
7851
  Copyright (c) Microsoft Corporation.
7793
7852
 
@@ -15139,6 +15198,7 @@ var submitForm = function (config, id, request, signal) {
15139
15198
  exports.AllotmentType = AllotmentType;
15140
15199
  exports.DataType = DataType;
15141
15200
  exports.DossierType = DossierType;
15201
+ exports.DurationType = DurationType;
15142
15202
  exports.EntryLineFlag = EntryLineFlag;
15143
15203
  exports.EntryLineStatus = EntryLineStatus;
15144
15204
  exports.ExportStatus = ExportStatus;
@@ -15151,6 +15211,7 @@ exports.PaymentType = PaymentType;
15151
15211
  exports.PriceChartType = PriceChartType;
15152
15212
  exports.RequirementType = RequirementType;
15153
15213
  exports.RoomType = RoomType;
15214
+ exports.RouteTransportType = RouteTransportType;
15154
15215
  exports.SaleType = SaleType;
15155
15216
  exports.ServiceTokenError = ServiceTokenError;
15156
15217
  exports.ServiceType = ServiceType;
@@ -16628,6 +16689,16 @@ const Icon = ({ name, className, title, width, height, fill }) => {
16628
16689
  React__default.createElement(HTMLComment, { text: "!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc." }),
16629
16690
  title && React__default.createElement("title", null, title),
16630
16691
  React__default.createElement("path", { d: "M135.2 117.4l-26.1 74.6 293.8 0-26.1-74.6C372.3 104.6 360.2 96 346.6 96L165.4 96c-13.6 0-25.7 8.6-30.2 21.4zM39.6 196.8L74.8 96.3C88.3 57.8 124.6 32 165.4 32l181.2 0c40.8 0 77.1 25.8 90.6 64.3l35.2 100.5c23.2 9.6 39.6 32.5 39.6 59.2l0 192c0 17.7-14.3 32-32 32l-32 0c-17.7 0-32-14.3-32-32l0-32-320 0 0 32c0 17.7-14.3 32-32 32l-32 0c-17.7 0-32-14.3-32-32L0 256c0-26.7 16.4-49.6 39.6-59.2zM128 304a32 32 0 1 0 -64 0 32 32 0 1 0 64 0zm288 32a32 32 0 1 0 0-64 32 32 0 1 0 0 64z", fill: "currentColor" })));
16692
+ case 'ui-cycling':
16693
+ return (React__default.createElement("svg", { className: ['icon', `icon--${name}`, className].filter((className) => !isEmpty$1(className)).join(' '), width: width, height: height, viewBox: "0 0 640 512", fill: fill ?? 'currentColor' },
16694
+ React__default.createElement(HTMLComment, { text: "!Font Awesome Free v5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc." }),
16695
+ title && React__default.createElement("title", null, title),
16696
+ React__default.createElement("path", { d: "M512.509 192.001c-16.373-.064-32.03 2.955-46.436 8.495l-77.68-125.153A24 24 0 0 0 368.001 64h-64c-8.837 0-16 7.163-16 16v16c0 8.837 7.163 16 16 16h50.649l14.896 24H256.002v-16c0-8.837-7.163-16-16-16h-87.459c-13.441 0-24.777 10.999-24.536 24.437.232 13.044 10.876 23.563 23.995 23.563h48.726l-29.417 47.52c-13.433-4.83-27.904-7.483-42.992-7.52C58.094 191.83.412 249.012.002 319.236-.413 390.279 57.055 448 128.002 448c59.642 0 109.758-40.793 123.967-96h52.033a24 24 0 0 0 20.406-11.367L410.37 201.77l14.938 24.067c-25.455 23.448-41.385 57.081-41.307 94.437.145 68.833 57.899 127.051 126.729 127.719 70.606.685 128.181-55.803 129.255-125.996 1.086-70.941-56.526-129.72-127.476-129.996zM186.75 265.772c9.727 10.529 16.673 23.661 19.642 38.228h-43.306l23.664-38.228zM128.002 400c-44.112 0-80-35.888-80-80s35.888-80 80-80c5.869 0 11.586.653 17.099 1.859l-45.505 73.509C89.715 331.327 101.213 352 120.002 352h81.3c-12.37 28.225-40.562 48-73.3 48zm162.63-96h-35.624c-3.96-31.756-19.556-59.894-42.383-80.026L237.371 184h127.547l-74.286 120zm217.057 95.886c-41.036-2.165-74.049-35.692-75.627-76.755-.812-21.121 6.633-40.518 19.335-55.263l44.433 71.586c4.66 7.508 14.524 9.816 22.032 5.156l13.594-8.437c7.508-4.66 9.817-14.524 5.156-22.032l-44.468-71.643a79.901 79.901 0 0 1 19.858-2.497c44.112 0 80 35.888 80 80-.001 45.54-38.252 82.316-84.313 79.885z", fill: "currentColor" })));
16697
+ case 'ui-walking':
16698
+ return (React__default.createElement("svg", { className: ['icon', `icon--${name}`, className].filter((className) => !isEmpty$1(className)).join(' '), width: width, height: height, viewBox: "0 0 320 512", fill: fill ?? 'currentColor' },
16699
+ React__default.createElement(HTMLComment, { text: "!Font Awesome Free v5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc." }),
16700
+ title && React__default.createElement("title", null, title),
16701
+ React__default.createElement("path", { d: "M208 96c26.5 0 48-21.5 48-48S234.5 0 208 0s-48 21.5-48 48 21.5 48 48 48zm94.5 149.1l-23.3-11.8-9.7-29.4c-14.7-44.6-55.7-75.8-102.2-75.9-36-.1-55.9 10.1-93.3 25.2-21.6 8.7-39.3 25.2-49.7 46.2L17.6 213c-7.8 15.8-1.5 35 14.2 42.9 15.6 7.9 34.6 1.5 42.5-14.3L81 228c3.5-7 9.3-12.5 16.5-15.4l26.8-10.8-15.2 60.7c-5.2 20.8.4 42.9 14.9 58.8l59.9 65.4c7.2 7.9 12.3 17.4 14.9 27.7l18.3 73.3c4.3 17.1 21.7 27.6 38.8 23.3 17.1-4.3 27.6-21.7 23.3-38.8l-22.2-89c-2.6-10.3-7.7-19.9-14.9-27.7l-45.5-49.7 17.2-68.7 5.5 16.5c5.3 16.1 16.7 29.4 31.7 37l23.3 11.8c15.6 7.9 34.6 1.5 42.5-14.3 7.7-15.7 1.4-35.1-14.3-43zM73.6 385.8c-3.2 8.1-8 15.4-14.2 21.5l-50 50.1c-12.5 12.5-12.5 32.8 0 45.3s32.7 12.5 45.2 0l59.4-59.4c6.1-6.1 10.9-13.4 14.2-21.5l13.5-33.8c-55.3-60.3-38.7-41.8-47.4-53.7l-20.7 51.5z", fill: "currentColor" })));
16631
16702
  case 'ui-bus':
16632
16703
  return (React__default.createElement("svg", { className: ['icon', `icon--${name}`, className].filter((className) => !isEmpty$1(className)).join(' '), width: width, height: height, viewBox: "0 0 512 512", fill: fill ?? 'currentColor' },
16633
16704
  title && React__default.createElement("title", null, title),
@@ -16941,10 +17012,10 @@ const DateRangePicker$1 = (props) => {
16941
17012
  month: getMonth(new Date())
16942
17013
  });
16943
17014
  const [isWaitingForToDate, setWaitingForToDate] = useState(false);
16944
- const { searchType = 0 } = useContext(SettingsContext$1);
17015
+ const { searchType = 0, allowSingleDayDateSelect = false } = useContext(SettingsContext$1);
16945
17016
  const handleDayClick = (day) => {
16946
17017
  const { onSelectionChange } = props;
16947
- if (isWaitingForToDate && !isNil(fromDate) && isAfter(day, fromDate)) {
17018
+ if (isWaitingForToDate && !isNil(fromDate) && (isAfter(day, fromDate) || (allowSingleDayDateSelect && isSameDay(day, fromDate)))) {
16948
17019
  if (searchType === 1) {
16949
17020
  // Only allow selecting the exact toDate
16950
17021
  const expectedToDate = props.toDateByFromDate?.(fromDate);
@@ -16985,7 +17056,7 @@ const DateRangePicker$1 = (props) => {
16985
17056
  }
16986
17057
  };
16987
17058
  const handleDayMouseOver = (day) => {
16988
- if (isWaitingForToDate && (isNil(fromDate) || isEqual(day, fromDate) || isAfter(day, fromDate))) {
17059
+ if (isWaitingForToDate && (isNil(fromDate) || isSameDay(day, fromDate) || isAfter(day, fromDate))) {
16989
17060
  setToDate(day);
16990
17061
  }
16991
17062
  };
@@ -28004,8 +28075,97 @@ const getServiceTypeBadgeModifier = (serviceType) => {
28004
28075
  return '';
28005
28076
  }
28006
28077
  };
28078
+ // DurationType, as the SDK numbers it.
28079
+ const DURATION_HOURS = 0;
28080
+ const DURATION_DAYS = 1;
28081
+ const DURATION_NIGHTS = 2;
28082
+ const DURATION_MINUTES = 3;
28083
+ /**
28084
+ * How long a node takes, in the same shape the flight legs are written in — an hour count only
28085
+ * once there is one, so a short drive reads as plain minutes.
28086
+ */
28087
+ const getTrajectNodeDuration = (node) => {
28088
+ const minutes = toMinutes(node);
28089
+ if (!minutes)
28090
+ return '';
28091
+ const hours = Math.floor(minutes / 60);
28092
+ const rest = minutes % 60;
28093
+ return hours ? `${hours}h ${rest.toString().padStart(2, '0')}m` : `${rest} min`;
28094
+ };
28095
+ const toMinutes = (node) => {
28096
+ const duration = node.duration ?? 0;
28097
+ if (duration <= 0)
28098
+ return 0;
28099
+ switch (node.durationType) {
28100
+ case DURATION_MINUTES:
28101
+ return duration;
28102
+ case DURATION_HOURS:
28103
+ return duration * 60;
28104
+ case DURATION_DAYS:
28105
+ case DURATION_NIGHTS:
28106
+ return duration * 24 * 60;
28107
+ default:
28108
+ return 0;
28109
+ }
28110
+ };
28111
+ // RouteTransportType, as the SDK numbers it. It says how a leg is travelled, which is not the
28112
+ // same question as what was sold: cycling and walking have no service type of their own.
28113
+ const TRANSPORT_PRIVATE = 1;
28114
+ const TRANSPORT_PUBLIC = 2;
28115
+ const TRANSPORT_RENTAL_CAR = 3;
28116
+ const TRANSPORT_BUS = 4;
28117
+ const TRANSPORT_CAMPER = 5;
28118
+ const TRANSPORT_CAR = 6;
28119
+ const TRANSPORT_CHARTER = 7;
28120
+ const TRANSPORT_CRUISE = 8;
28121
+ const TRANSPORT_FERRY = 9;
28122
+ const TRANSPORT_FLIGHT = 10;
28123
+ const TRANSPORT_TAXI = 11;
28124
+ const TRANSPORT_TRAIN = 12;
28125
+ const TRANSPORT_TRANSFER = 13;
28126
+ const TRANSPORT_WALKING = 15;
28127
+ const TRANSPORT_CYCLING = 16;
28128
+ /** Icon per way of travelling, where the route records one. */
28129
+ const getTransportTypeIcon = (transportType) => {
28130
+ switch (transportType) {
28131
+ case TRANSPORT_WALKING:
28132
+ return 'ui-walking';
28133
+ case TRANSPORT_CYCLING:
28134
+ return 'ui-cycling';
28135
+ case TRANSPORT_PRIVATE:
28136
+ case TRANSPORT_RENTAL_CAR:
28137
+ case TRANSPORT_CAMPER:
28138
+ case TRANSPORT_CAR:
28139
+ return 'ui-car';
28140
+ case TRANSPORT_TAXI:
28141
+ case TRANSPORT_TRANSFER:
28142
+ return 'ui-transfer';
28143
+ case TRANSPORT_PUBLIC:
28144
+ case TRANSPORT_BUS:
28145
+ return 'ui-bus';
28146
+ case TRANSPORT_TRAIN:
28147
+ return 'ui-train';
28148
+ case TRANSPORT_CRUISE:
28149
+ case TRANSPORT_FERRY:
28150
+ return 'ui-ship';
28151
+ case TRANSPORT_FLIGHT:
28152
+ case TRANSPORT_CHARTER:
28153
+ return 'ui-plane';
28154
+ default:
28155
+ return null;
28156
+ }
28157
+ };
28007
28158
  const POI_TRAJECT_NODE_TYPE = 2;
28008
- const getTrajectNodeIcon = (node) => node.trajectNodeType === POI_TRAJECT_NODE_TYPE ? 'ui-location' : getServiceTypeIcon(node.serviceType);
28159
+ /**
28160
+ * A point of interest is a place on the map rather than a service, so it gets a marker. Anything
28161
+ * else goes by how it is travelled where the route says so — a leg on foot or by bike is sold as
28162
+ * nothing, and its service type falls back to a transfer — and by what it is where it does not.
28163
+ */
28164
+ const getTrajectNodeIcon = (node) => {
28165
+ if (node.trajectNodeType === POI_TRAJECT_NODE_TYPE)
28166
+ return 'ui-location';
28167
+ return getTransportTypeIcon(node.transportType) ?? getServiceTypeIcon(node.serviceType);
28168
+ };
28009
28169
  const getTrajectNodeBadgeModifier = (node) => node.trajectNodeType === POI_TRAJECT_NODE_TYPE ? 'poi' : getServiceTypeBadgeModifier(node.serviceType);
28010
28170
 
28011
28171
  const REGULAR_TRAJECT_NODE_TYPE = 0;
@@ -28444,6 +28604,8 @@ const TrajectItinerary = ({ trajectEntry, packagingEntry, translations }) => {
28444
28604
  trajectNodeType: REGULAR_TRAJECT_NODE_TYPE,
28445
28605
  requirementType: 0,
28446
28606
  contentSource: 0,
28607
+ duration: 0,
28608
+ durationType: 0,
28447
28609
  alternatives: [],
28448
28610
  from: line.from,
28449
28611
  to: line.to
@@ -28477,6 +28639,8 @@ const TrajectItinerary = ({ trajectEntry, packagingEntry, translations }) => {
28477
28639
  React__default.createElement("p", null, format$2(new Date(value), 'MMM'))));
28478
28640
  const renderConnectingNode = ({ node }) => {
28479
28641
  const distance = node.distanceInKm ? `${Math.round(node.distanceInKm)} km` : '';
28642
+ // How long the hop takes and how far it is, the way the traject editor puts them.
28643
+ const journey = [getTrajectNodeDuration(node), distance].filter(Boolean).join(' · ');
28480
28644
  return (React__default.createElement("div", { className: "search__filter__itinerary__transport", key: `connect-${node.nodeId}` },
28481
28645
  React__default.createElement("div", { className: "search__filter__itinerary__transport-timeline" },
28482
28646
  React__default.createElement("div", { className: "search__filter__itinerary__transport-timeline-line" })),
@@ -28485,8 +28649,8 @@ const TrajectItinerary = ({ trajectEntry, packagingEntry, translations }) => {
28485
28649
  React__default.createElement("div", { className: badgeClass('transport', getTrajectNodeBadgeModifier(node)) },
28486
28650
  React__default.createElement(Icon, { name: getTrajectNodeIcon(node), height: 15 })),
28487
28651
  React__default.createElement("div", { className: "search__filter__itinerary__transport-details" },
28488
- React__default.createElement("h6", null, node.name || distance),
28489
- !!node.name && !!distance && React__default.createElement("p", { className: "search__filter__itinerary__transport-details-time" }, distance)))));
28652
+ React__default.createElement("h6", null, node.name || journey),
28653
+ !!node.name && !!journey && React__default.createElement("p", { className: "search__filter__itinerary__transport-details-time" }, journey)))));
28490
28654
  };
28491
28655
  const renderOvernight = (item, index) => {
28492
28656
  const { dayOrder } = item.node;
@@ -28630,7 +28794,7 @@ const getServiceTypePriority = (serviceType) => {
28630
28794
  const Itinerary = ({ isOpen, handleSetIsOpen, isLoading, onEditAccommodation }) => {
28631
28795
  const context = useContext(SearchResultsConfigurationContext);
28632
28796
  const translations = getTranslations(context?.languageCode ?? 'en-GB');
28633
- const { editablePackagingEntry, priceDetails } = useSelector((state) => state.searchResults);
28797
+ const { editablePackagingEntry, priceDetails, trajectItineraryView } = useSelector((state) => state.searchResults);
28634
28798
  const dispatch = useDispatch();
28635
28799
  const packagingEntry = editablePackagingEntry ?? context?.packagingEntry;
28636
28800
  const sortedLines = useMemo(() => {
@@ -28673,6 +28837,11 @@ const Itinerary = ({ isOpen, handleSetIsOpen, isLoading, onEditAccommodation })
28673
28837
  const handleConfirm = () => {
28674
28838
  dispatch(setBookPackagingEntry(true));
28675
28839
  };
28840
+ const handleTrajectView = (itinerary) => {
28841
+ dispatch(setTrajectItineraryView(itinerary));
28842
+ if (typeof window !== 'undefined')
28843
+ window.scrollTo(0, 0);
28844
+ };
28676
28845
  return (React__default.createElement("div", { className: `search__filters--modal ${isOpen ? 'is-open' : ''}` },
28677
28846
  React__default.createElement("div", { className: "search__filters--background", onClick: handleSetIsOpen }),
28678
28847
  React__default.createElement("button", { className: "search__filters--close", onClick: handleSetIsOpen },
@@ -28700,7 +28869,8 @@ const Itinerary = ({ isOpen, handleSetIsOpen, isLoading, onEditAccommodation })
28700
28869
  " ",
28701
28870
  translations.SRP.TOTAL,
28702
28871
  ")"))),
28703
- React__default.createElement("button", { className: "cta", onClick: handleConfirm }, translations.QSM.CONFIRM)))),
28872
+ React__default.createElement("button", { className: "cta", onClick: handleConfirm }, translations.QSM.CONFIRM),
28873
+ !!context?.trajectEntry && (React__default.createElement("button", { type: "button", className: "cta cta--secondary", onClick: () => handleTrajectView(!trajectItineraryView) }, trajectItineraryView ? translations.SRP.SHOW_SERVICES : translations.SRP.VIEW_FULL_ITINERARY))))),
28704
28874
  React__default.createElement("div", { className: "search__filter__itinerary" },
28705
28875
  React__default.createElement("p", null, translations.SRP.DAY_BY_DAY),
28706
28876
  firstEntryLine && (React__default.createElement("div", { className: "search__filter__itinerary__country" },
@@ -33153,6 +33323,27 @@ const getRequestRoomsFromPackagingSegments = (entry, segments) => {
33153
33323
  }));
33154
33324
  };
33155
33325
 
33326
+ /**
33327
+ * The entry to send to the backend, as opposed to the one the page works with.
33328
+ *
33329
+ * They differ only for a traject: the page keeps the line guids the traject was rendered with,
33330
+ * because that is what joins its nodes to its lines, while pricing and booking need the guids the
33331
+ * searches cached their lines under. Anything else passes through untouched, so this can be used
33332
+ * wherever the entry leaves the component without checking which flow is running.
33333
+ */
33334
+ const useBookableEntry = () => {
33335
+ const context = useContext(SearchResultsConfigurationContext);
33336
+ const { editablePackagingEntry, trajectNodeAvailability, trajectFlightAvailability } = useSelector((state) => state.searchResults);
33337
+ const trajectEntry = context?.trajectEntry;
33338
+ return useMemo(() => {
33339
+ if (!editablePackagingEntry)
33340
+ return null;
33341
+ if (!trajectEntry)
33342
+ return editablePackagingEntry;
33343
+ return withCachedEntryLineGuids(editablePackagingEntry, trajectEntry, trajectNodeAvailability, trajectFlightAvailability);
33344
+ }, [editablePackagingEntry, trajectEntry, trajectNodeAvailability, trajectFlightAvailability]);
33345
+ };
33346
+
33156
33347
  const escapeHtml = (value) => {
33157
33348
  if (!value)
33158
33349
  return '';
@@ -33211,7 +33402,57 @@ const findAccommodationForDay = (nodes, currentDay) => {
33211
33402
  }
33212
33403
  return null;
33213
33404
  };
33214
- const buildItineraryHtml = (itinerary, translations) => {
33405
+ const formatTime = (time) => (time ? time.slice(0, 5) : '');
33406
+ /**
33407
+ * Whether generated content actually shows something.
33408
+ *
33409
+ * A content view whose data is missing still returns its wrapper markup, or the whitespace left by
33410
+ * a guard around its body — markup that renders as a blank gap. Only text or an image counts as
33411
+ * something the traveller can read.
33412
+ */
33413
+ const hasVisibleContent = (contents) => {
33414
+ if (!contents)
33415
+ return false;
33416
+ if (/<(img|svg|picture|video|iframe)\b/i.test(contents))
33417
+ return true;
33418
+ return (contents
33419
+ .replace(/<[^>]*>/g, ' ')
33420
+ .replace(/&nbsp;/gi, ' ')
33421
+ .trim().length > 0);
33422
+ };
33423
+ /** The legs of a flight, as the entry line records them. */
33424
+ const buildFlightHtml = (line) => {
33425
+ const flightLines = line.flightInformation?.flightLines ?? [];
33426
+ if (!flightLines.length)
33427
+ return '';
33428
+ const legs = flightLines
33429
+ .map((leg) => {
33430
+ const from = escapeHtml(leg.departureAirportDescription || leg.departureAirportCode);
33431
+ const to = escapeHtml(leg.arrivalAirportDescription || leg.arrivalAirportCode);
33432
+ const carrier = escapeHtml([leg.airlineDescription, leg.flightNumber].filter(Boolean).join(' '));
33433
+ return `<li>${from} ${formatTime(leg.departureTime)} &rarr; ${to} ${formatTime(leg.arrivalTime)}${carrier ? ` &middot; ${carrier}` : ''}</li>`;
33434
+ })
33435
+ .join('');
33436
+ return `<ul class="itinerary-item__legs">${legs}</ul>`;
33437
+ };
33438
+ /**
33439
+ * What to show for an item the generator returned no content for.
33440
+ *
33441
+ * An item comes back with a title and a template but an empty body whenever its template has no
33442
+ * content view of its own — flights, today. Rendering that empty body leaves a blank where a
33443
+ * service should be, so what the entry already knows about the line is drawn instead.
33444
+ */
33445
+ const buildFallbackHtml = (item, line) => {
33446
+ const details = [line?.accommodationName, line?.regimeName].filter(Boolean).join(' — ');
33447
+ return `
33448
+ <div class="itinerary-item__fallback">
33449
+ <h3 class="itinerary-item__fallback-title">${escapeHtml(item.title)}</h3>
33450
+ ${line ? buildFlightHtml(line) : ''}
33451
+ ${details ? `<p class="itinerary-item__fallback-details">${escapeHtml(details)}</p>` : ''}
33452
+ </div>
33453
+ `;
33454
+ };
33455
+ const buildItineraryHtml = (itinerary, translations, lines) => {
33215
33456
  if (!itinerary) {
33216
33457
  return `
33217
33458
  <div class="itinerary-shell">
@@ -33240,7 +33481,9 @@ const buildItineraryHtml = (itinerary, translations) => {
33240
33481
  ? uniqueItems
33241
33482
  .map((item) => `
33242
33483
  <article class="itinerary-item" data-template="${escapeHtml(item.templateName)}">
33243
- ${item.contents ?? ''}
33484
+ ${hasVisibleContent(item.contents)
33485
+ ? item.contents
33486
+ : buildFallbackHtml(item, lines.find((line) => line.guid === item.itemGuid))}
33244
33487
  </article>
33245
33488
  `)
33246
33489
  .join('')
@@ -33274,7 +33517,9 @@ const buildItineraryHtml = (itinerary, translations) => {
33274
33517
  ${getUniqueItems(itinerary.defaultItems)
33275
33518
  .map((item) => `
33276
33519
  <article class="itinerary-item" data-template="${escapeHtml(item.templateName)}">
33277
- ${item.contents ?? ''}
33520
+ ${hasVisibleContent(item.contents)
33521
+ ? item.contents
33522
+ : buildFallbackHtml(item, lines.find((line) => line.guid === item.itemGuid))}
33278
33523
  </article>
33279
33524
  `)
33280
33525
  .join('')}
@@ -33295,10 +33540,12 @@ const buildItineraryHtml = (itinerary, translations) => {
33295
33540
  const FullItinerary = ({ isLoading }) => {
33296
33541
  const { itinerary } = useSelector((state) => state.searchResults);
33297
33542
  const context = useContext(SearchResultsConfigurationContext);
33543
+ // The entry as it was sent: its line guids are the ones the itinerary items carry back.
33544
+ const bookableEntry = useBookableEntry();
33298
33545
  const translations = getTranslations(context?.languageCode ?? 'en-GB');
33299
33546
  const hostRef = useRef(null);
33300
33547
  const shadowRootRef = useRef(null);
33301
- const html = useMemo(() => buildItineraryHtml(itinerary, translations), [itinerary, translations]);
33548
+ const html = useMemo(() => buildItineraryHtml(itinerary, translations, bookableEntry?.lines ?? []), [itinerary, translations, bookableEntry]);
33302
33549
  useEffect(() => {
33303
33550
  if (isLoading) {
33304
33551
  return;
@@ -33412,6 +33659,35 @@ const FullItinerary = ({ isLoading }) => {
33412
33659
  background: #fff;
33413
33660
  }
33414
33661
 
33662
+ /* An item whose template has no content view of its own still has to read as a service. */
33663
+ .itinerary-item__fallback {
33664
+ padding: 16px 20px;
33665
+ border: 1px solid #e5e7eb;
33666
+ border-radius: 12px;
33667
+ }
33668
+
33669
+ .itinerary-item__fallback-title {
33670
+ margin: 0;
33671
+ font-size: 16px;
33672
+ font-weight: 600;
33673
+ color: #111827;
33674
+ }
33675
+
33676
+ .itinerary-item__legs {
33677
+ margin: 8px 0 0;
33678
+ padding-left: 18px;
33679
+ color: #374151;
33680
+ }
33681
+
33682
+ .itinerary-item__legs li + li {
33683
+ margin-top: 4px;
33684
+ }
33685
+
33686
+ .itinerary-item__fallback-details {
33687
+ margin: 8px 0 0;
33688
+ color: #6b7280;
33689
+ }
33690
+
33415
33691
  .itinerary-node__empty,
33416
33692
  .itinerary-empty {
33417
33693
  padding: 20px;
@@ -33547,27 +33823,6 @@ const DayByDayExcursions = () => {
33547
33823
  })));
33548
33824
  };
33549
33825
 
33550
- /**
33551
- * The entry to send to the backend, as opposed to the one the page works with.
33552
- *
33553
- * They differ only for a traject: the page keeps the line guids the traject was rendered with,
33554
- * because that is what joins its nodes to its lines, while pricing and booking need the guids the
33555
- * searches cached their lines under. Anything else passes through untouched, so this can be used
33556
- * wherever the entry leaves the component without checking which flow is running.
33557
- */
33558
- const useBookableEntry = () => {
33559
- const context = useContext(SearchResultsConfigurationContext);
33560
- const { editablePackagingEntry, trajectNodeAvailability, trajectFlightAvailability } = useSelector((state) => state.searchResults);
33561
- const trajectEntry = context?.trajectEntry;
33562
- return useMemo(() => {
33563
- if (!editablePackagingEntry)
33564
- return null;
33565
- if (!trajectEntry)
33566
- return editablePackagingEntry;
33567
- return withCachedEntryLineGuids(editablePackagingEntry, trajectEntry, trajectNodeAvailability, trajectFlightAvailability);
33568
- }, [editablePackagingEntry, trajectEntry, trajectNodeAvailability, trajectFlightAvailability]);
33569
- };
33570
-
33571
33826
  const renderEditablePackagingEntrySummaryOptions = (editablePackagingEntry, priceDetails, translations, currencyCode) => {
33572
33827
  const priceDetailsByProduct = groupBy(priceDetails?.details ?? [], (detail) => `${detail.productCode}|${detail.accommodationCode}`);
33573
33828
  return (editablePackagingEntry?.lines ?? []).map((line) => {
@@ -34064,7 +34319,7 @@ const TrajectResults = ({ isLoading }) => {
34064
34319
  const loadDay = useTrajectDayActivities();
34065
34320
  const translations = getTranslations(context?.languageCode ?? 'en-GB');
34066
34321
  const locale = getLocale(context?.languageCode ?? 'en-GB');
34067
- const { trajectNodeAvailability, trajectFlightAvailability, trajectDaySuggestions, editablePackagingEntry, trajectResolving } = useSelector((state) => state.searchResults);
34322
+ const { trajectNodeAvailability, trajectFlightAvailability, trajectDaySuggestions, editablePackagingEntry } = useSelector((state) => state.searchResults);
34068
34323
  const trajectEntry = context?.trajectEntry;
34069
34324
  const hideShowMoreResults = !!context?.hideShowMoreResults;
34070
34325
  const nodesWithLines = useMemo(() => (trajectEntry ? getTrajectNodesWithLines(trajectEntry) : []), [trajectEntry]);
@@ -34094,6 +34349,23 @@ const TrajectResults = ({ isLoading }) => {
34094
34349
  });
34095
34350
  return { stayByDay: byDay, stayStartByNodeId: byNodeId };
34096
34351
  }, [nodesWithLines]);
34352
+ /** Whether every node of the trip has an answer, one way or the other. */
34353
+ const tripResolved = useMemo(() => nodesWithLines
34354
+ .filter((x) => isSearchableTrajectNode(x.node) || isTrajectFlightNode(x.node))
34355
+ .every((x) => {
34356
+ const status = isTrajectFlightNode(x.node) ? trajectFlightAvailability[x.line.guid]?.status : trajectNodeAvailability[x.line.guid]?.status;
34357
+ return !!status && status !== 'loading';
34358
+ }), [nodesWithLines, trajectNodeAvailability, trajectFlightAvailability]);
34359
+ /**
34360
+ * Each day's activities are one more search, and a traject has as many as it has days. They go
34361
+ * out together, but only once the trip itself has resolved — sharing the connections with the
34362
+ * searches the trip cannot do without would only make those slower.
34363
+ */
34364
+ useEffect(() => {
34365
+ if (!trajectEntry || !tripResolved)
34366
+ return;
34367
+ (trajectEntry.traject.days ?? []).filter((day) => stayByDay.has(day.order)).forEach((day) => loadDay(day));
34368
+ }, [trajectEntry, tripResolved, stayByDay]);
34097
34369
  if (!context || !trajectEntry)
34098
34370
  return null;
34099
34371
  const handleSelect = (item, code) => {
@@ -34250,11 +34522,6 @@ const TrajectResults = ({ isLoading }) => {
34250
34522
  if (lines.length)
34251
34523
  dispatch(addEditableEntryLines(lines));
34252
34524
  };
34253
- const handleShowItinerary = () => {
34254
- dispatch(setTrajectItineraryView(true));
34255
- if (typeof window !== 'undefined')
34256
- window.scrollTo(0, 0);
34257
- };
34258
34525
  const handleShowMoreActivities = (dayOrder) => {
34259
34526
  dispatch(setTrajectActivitiesDayOrder(dayOrder));
34260
34527
  dispatch(setFlyInType('traject-day-activities'));
@@ -34279,16 +34546,16 @@ const TrajectResults = ({ isLoading }) => {
34279
34546
  const cards = buildDayActivityCards(items, trajectNodeAvailability, suggestions?.results ?? []);
34280
34547
  const nodesResolving = items.some((item) => (trajectNodeAvailability[item.line.guid]?.status ?? 'loading') === 'loading');
34281
34548
  const isSearching = suggestions?.status === 'loading';
34282
- const canSearch = !!getDayDestination(nodesWithLines, trajectNodeAvailability, day.order);
34549
+ // A day is worth filling only if the party is staying somewhere that night. The day they fly
34550
+ // home is covered by no stay — the last night was the night before — and nobody is looking for
34551
+ // something to do on it.
34552
+ const canSearch = stayByDay.has(day.order) && !!getDayDestination(nodesWithLines, trajectNodeAvailability, day.order);
34283
34553
  if (!items.length && !canSearch)
34284
34554
  return null;
34285
34555
  const visible = cards.slice(0, MAX_DAY_ACTIVITIES);
34286
34556
  const hidden = cards.length - visible.length;
34287
34557
  return (React__default.createElement(React__default.Fragment, { key: `activities-${day.order}` },
34288
- React__default.createElement("div", { className: "search__results__label search__results__label--secondary" },
34289
- React__default.createElement("div", { className: "search__results__label__date" },
34290
- React__default.createElement("p", { className: "search__results__label__date-date" }, format$2(new Date(day.date), 'd', { locale })),
34291
- React__default.createElement("p", null, format$2(new Date(day.date), 'MMM', { locale }))),
34558
+ React__default.createElement("div", { className: "search__results__label search__results__label--sub" },
34292
34559
  React__default.createElement("div", { className: "search__results__label__text" },
34293
34560
  React__default.createElement(Icon, { name: "ui-excursion", height: 16 }),
34294
34561
  React__default.createElement("h3", null,
@@ -34296,13 +34563,11 @@ const TrajectResults = ({ isLoading }) => {
34296
34563
  (nodesResolving || isSearching) && !cards.length && React__default.createElement(Spinner, { label: translations.SRP.LOADING_ACTIVITIES }),
34297
34564
  !!cards.length && React__default.createElement("div", { className: "search__results__cards search__results__cards--scroll" }, visible.map(renderActivityCard)),
34298
34565
  !nodesResolving && !isSearching && !cards.length && (!!items.length || !!suggestions) && (React__default.createElement("div", { className: "no-results" }, translations.SRP.NO_RESULTS)),
34299
- React__default.createElement("div", { className: "search__results__cards__actions" },
34300
- !suggestions && canSearch && !cards.length && !hideShowMoreResults && (React__default.createElement("button", { type: "button", className: "cta cta--secondary", onClick: () => loadDay(day) }, translations.SRP.SHOW_ACTIVITIES)),
34301
- hidden > 0 && !hideShowMoreResults && (React__default.createElement("button", { type: "button", className: "cta cta--secondary", onClick: () => handleShowMoreActivities(day.order) },
34302
- translations.SRP.SHOW_MORE,
34303
- " (",
34304
- hidden,
34305
- ")")))));
34566
+ React__default.createElement("div", { className: "search__results__cards__actions" }, hidden > 0 && !hideShowMoreResults && (React__default.createElement("button", { type: "button", className: "cta cta--secondary", onClick: () => handleShowMoreActivities(day.order) },
34567
+ translations.SRP.SHOW_MORE,
34568
+ " (",
34569
+ hidden,
34570
+ ")")))));
34306
34571
  };
34307
34572
  /**
34308
34573
  * An activity the traject books is swapped for another, the way a hotel is; one the traveller
@@ -34314,35 +34579,32 @@ const TrajectResults = ({ isLoading }) => {
34314
34579
  };
34315
34580
  if (isLoading)
34316
34581
  return React__default.createElement(Spinner, { label: translations.SRP.LOADING_ITINERARY });
34317
- return (React__default.createElement(React__default.Fragment, null,
34318
- trajectEntry.traject.days.map((day) => {
34319
- const stay = stayByDay.get(day.order);
34320
- const dayNodes = (nodesByDay.get(day.order) ?? []).filter((node) => isRenderableNode(node) && lineByNodeId.has(node.nodeId));
34321
- const stayLeadsDay = !!stay && (stay.night > 0 || dayNodes[0]?.nodeId === stay.item.node.nodeId);
34322
- // Where the day's activities belong in the order: with the first excursion it carries.
34323
- const firstExcursionNodeId = dayNodes.find((node) => node.serviceType === EXCURSION_SERVICE_TYPE)?.nodeId;
34324
- const showDayHeader = stayLeadsDay || !!day.name?.trim();
34325
- return (React__default.createElement(React__default.Fragment, { key: `day-${day.order}` },
34326
- showDayHeader && renderDayHeader(day, stayLeadsDay ? stay : undefined),
34327
- dayNodes.map((node) => {
34328
- const item = lineByNodeId.get(node.nodeId);
34329
- if (node.serviceType === ACCOMMODATION_SERVICE_TYPE) {
34330
- const stayStart = stayStartByNodeId.get(node.nodeId);
34331
- const titledByDayHeader = stayLeadsDay && stay.item.node.nodeId === node.nodeId;
34332
- return renderServiceNode(item, !titledByDayHeader, stayStart && getStayTitle(stayStart));
34333
- }
34334
- // Every excursion the day holds is drawn once, in one block, where the first of
34335
- // them falls in the order — so the activities still sit between the stay and the
34336
- // flight that follow them.
34337
- if (node.serviceType === EXCURSION_SERVICE_TYPE) {
34338
- return node.nodeId === firstExcursionNodeId ? renderDayActivities(day) : null;
34339
- }
34340
- return renderFlightNode(item);
34341
- }),
34342
- firstExcursionNodeId === undefined && renderDayActivities(day)));
34343
- }),
34344
- React__default.createElement("div", { className: "search__results__cards__actions" },
34345
- React__default.createElement("button", { type: "button", className: "cta", disabled: trajectResolving, onClick: handleShowItinerary }, translations.STEPS.NEXT))));
34582
+ return (React__default.createElement(React__default.Fragment, null, trajectEntry.traject.days.map((day) => {
34583
+ const stay = stayByDay.get(day.order);
34584
+ const dayNodes = (nodesByDay.get(day.order) ?? []).filter((node) => isRenderableNode(node) && lineByNodeId.has(node.nodeId));
34585
+ const stayLeadsDay = !!stay && (stay.night > 0 || dayNodes[0]?.nodeId === stay.item.node.nodeId);
34586
+ // Where the day's activities belong in the order: with the first excursion it carries.
34587
+ const firstExcursionNodeId = dayNodes.find((node) => node.serviceType === EXCURSION_SERVICE_TYPE)?.nodeId;
34588
+ const showDayHeader = stayLeadsDay || !!day.name?.trim();
34589
+ return (React__default.createElement(React__default.Fragment, { key: `day-${day.order}` },
34590
+ showDayHeader && renderDayHeader(day, stayLeadsDay ? stay : undefined),
34591
+ dayNodes.map((node) => {
34592
+ const item = lineByNodeId.get(node.nodeId);
34593
+ if (node.serviceType === ACCOMMODATION_SERVICE_TYPE) {
34594
+ const stayStart = stayStartByNodeId.get(node.nodeId);
34595
+ const titledByDayHeader = stayLeadsDay && stay.item.node.nodeId === node.nodeId;
34596
+ return renderServiceNode(item, !titledByDayHeader, stayStart && getStayTitle(stayStart));
34597
+ }
34598
+ // Every excursion the day holds is drawn once, in one block, where the first of
34599
+ // them falls in the order so the activities still sit between the stay and the
34600
+ // flight that follow them.
34601
+ if (node.serviceType === EXCURSION_SERVICE_TYPE) {
34602
+ return node.nodeId === firstExcursionNodeId ? renderDayActivities(day) : null;
34603
+ }
34604
+ return renderFlightNode(item);
34605
+ }),
34606
+ firstExcursionNodeId === undefined && renderDayActivities(day)));
34607
+ })));
34346
34608
  };
34347
34609
 
34348
34610
  const useTrajectAvailability = () => {
@@ -35289,6 +35551,9 @@ const SearchResultsContainer = ({ onBookingStarted }) => {
35289
35551
  setPricesAreLoading(false);
35290
35552
  }
35291
35553
  };
35554
+ fetchPriceDetails();
35555
+ }, [editablePackagingEntry, bookableEntry, trajectResolving]);
35556
+ useEffect(() => {
35292
35557
  const fetchItinerary = async () => {
35293
35558
  if (!context || !context.packagingEntry || !editablePackagingEntry || isEmpty$1(editablePackagingEntry.lines))
35294
35559
  return;
@@ -35318,7 +35583,6 @@ const SearchResultsContainer = ({ onBookingStarted }) => {
35318
35583
  setItineraryIsLoading(false);
35319
35584
  }
35320
35585
  };
35321
- fetchPriceDetails();
35322
35586
  fetchItinerary();
35323
35587
  }, [editablePackagingEntry, bookableEntry, trajectResolving, trajectItineraryView]);
35324
35588
  // Flight selection
@@ -35669,11 +35933,6 @@ const SearchResultsContainer = ({ onBookingStarted }) => {
35669
35933
  address: {}
35670
35934
  };
35671
35935
  };
35672
- const handleComposeTraject = () => {
35673
- dispatch(setTrajectItineraryView(false));
35674
- if (typeof window !== 'undefined')
35675
- window.scrollTo(0, 0);
35676
- };
35677
35936
  const handleShowMoreFlights = (flyInType) => {
35678
35937
  dispatch(setFlyInType(flyInType));
35679
35938
  dispatch(setFlyInIsOpen(true));
@@ -35764,10 +36023,7 @@ const SearchResultsContainer = ({ onBookingStarted }) => {
35764
36023
  selectedReturnKey && selectedReturn && (React__default.createElement(IndependentFlightOption, { key: `flight-${selectedReturnKey}`, item: selectedReturn.return, guid: selectedReturn.outwardGuid, selectedGuid: selectedReturn.outwardGuid, isOutward: false, showSelectedState: true, price: selectedReturn.price })),
35765
36024
  uniqueReturnFlights.map((result) => (React__default.createElement(IndependentFlightOption, { key: `flight-${result.outwardGuid}`, item: result.return, onSelect: () => dispatch(setSelectedReturnKey(getFlightKey(result.return.segments))), guid: result.outwardGuid, isOutward: false, currentSelectedPrice: selectedReturn?.price, price: result.price })))))),
35766
36025
  context.searchConfiguration.qsmType === build.PortalQsmType.AccommodationAndFlight && context.trajectEntry && !trajectItineraryView && (React__default.createElement(TrajectResults, { isLoading: isLoading })),
35767
- context.searchConfiguration.qsmType === build.PortalQsmType.AccommodationAndFlight && context.trajectEntry && trajectItineraryView && (React__default.createElement(React__default.Fragment, null,
35768
- React__default.createElement("div", { className: "search__results__cards__actions" },
35769
- React__default.createElement("button", { type: "button", className: "cta cta--secondary", onClick: handleComposeTraject }, translations.STEPS.PREVIOUS)),
35770
- React__default.createElement(FullItinerary, { isLoading: itineraryIsLoading }))),
36026
+ context.searchConfiguration.qsmType === build.PortalQsmType.AccommodationAndFlight && context.trajectEntry && trajectItineraryView && (React__default.createElement(FullItinerary, { isLoading: itineraryIsLoading })),
35771
36027
  context.searchConfiguration.qsmType === build.PortalQsmType.AccommodationAndFlight && context.packagingEntry && !context.trajectEntry && (React__default.createElement(FullItinerary, { isLoading: itineraryIsLoading })))),
35772
36028
  React__default.createElement(FlyIn, { srpType: context.searchConfiguration.qsmType, isOpen: flyInIsOpen, setIsOpen: handleFlyInToggle, handleConfirm: () => handleConfirmHotelSwap(), onPanelRef: (el) => (panelRef.current = el), detailsLoading: detailsIsLoading, flyInType: flyInType, isPackageEditFlow: !!context.packagingEntry, sortByTypes: sortByTypes, activeSearchSeed: activeSearchSeed, toggleFilters: () => setFiltersOpen(!filtersOpen), filtersOpen: filtersOpen })))))))));
35773
36029
  };