@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.
@@ -417,6 +417,8 @@ var SRP$f = {
417
417
  DEPARTURE_ASC: "تاريخ المغادرة (الأقرب أولاً)",
418
418
  FILTERS: "عوامل التصفية",
419
419
  SHOW_ITINERARY: "عرض خط سير الرحلة",
420
+ VIEW_FULL_ITINERARY: "عرض خط السير الكامل",
421
+ SHOW_SERVICES: "عرض الخدمات",
420
422
  VIEW_BOOKING: "عرض الحجز",
421
423
  ITINERARY_TITLE: "ملخص رحلتك",
422
424
  DOSSIER_NUMBER: "رقم الملف",
@@ -862,6 +864,8 @@ var SRP$e = {
862
864
  DEPARTURE_ASC: "Afrejsedato (snarest først)",
863
865
  FILTERS: "Filtre",
864
866
  SHOW_ITINERARY: "Vis rejseplan",
867
+ VIEW_FULL_ITINERARY: "Se hele rejseplanen",
868
+ SHOW_SERVICES: "Vis ydelser",
865
869
  VIEW_BOOKING: "Se booking",
866
870
  ITINERARY_TITLE: "Din rejseoversigt",
867
871
  DOSSIER_NUMBER: "Sagsnummer",
@@ -1311,6 +1315,8 @@ var SRP$d = {
1311
1315
  DEPARTURE_ASC: "Abreisedatum (früheste zuerst)",
1312
1316
  FILTERS: "Filter",
1313
1317
  SHOW_ITINERARY: "Reiseverlauf anzeigen",
1318
+ VIEW_FULL_ITINERARY: "Vollständigen Reiseverlauf ansehen",
1319
+ SHOW_SERVICES: "Leistungen anzeigen",
1314
1320
  VIEW_BOOKING: "Buchung anzeigen",
1315
1321
  ITINERARY_TITLE: "Ihre Reiseübersicht",
1316
1322
  DOSSIER_NUMBER: "Vorgangsnummer",
@@ -1760,6 +1766,8 @@ var SRP$c = {
1760
1766
  DEPARTURE_ASC: "Departure date (soonest first)",
1761
1767
  FILTERS: "Filters",
1762
1768
  SHOW_ITINERARY: "Show itinerary",
1769
+ VIEW_FULL_ITINERARY: "View full itinerary",
1770
+ SHOW_SERVICES: "Show services",
1763
1771
  VIEW_BOOKING: "View booking",
1764
1772
  ITINERARY_TITLE: "Your trip summary",
1765
1773
  DOSSIER_NUMBER: "File number",
@@ -2205,6 +2213,8 @@ var SRP$b = {
2205
2213
  DEPARTURE_ASC: "Fecha de salida (más próxima primero)",
2206
2214
  FILTERS: "Filtros",
2207
2215
  SHOW_ITINERARY: "Mostrar itinerario",
2216
+ VIEW_FULL_ITINERARY: "Ver itinerario completo",
2217
+ SHOW_SERVICES: "Ver servicios",
2208
2218
  VIEW_BOOKING: "Ver reserva",
2209
2219
  ITINERARY_TITLE: "Resumen de tu viaje",
2210
2220
  DOSSIER_NUMBER: "Número de expediente",
@@ -2654,6 +2664,8 @@ var SRP$a = {
2654
2664
  DEPARTURE_ASC: "Date de départ (la plus proche d’abord)",
2655
2665
  FILTERS: "Filtres",
2656
2666
  SHOW_ITINERARY: "Afficher l’itinéraire",
2667
+ VIEW_FULL_ITINERARY: "Voir l'itinéraire complet",
2668
+ SHOW_SERVICES: "Voir les services",
2657
2669
  VIEW_BOOKING: "Voir la réservation",
2658
2670
  ITINERARY_TITLE: "Résumé de votre voyage",
2659
2671
  DOSSIER_NUMBER: "Numéro de dossier",
@@ -3099,6 +3111,8 @@ var SRP$9 = {
3099
3111
  DEPARTURE_ASC: "Date de départ (la plus proche d’abord)",
3100
3112
  FILTERS: "Filtres",
3101
3113
  SHOW_ITINERARY: "Afficher l’itinéraire",
3114
+ VIEW_FULL_ITINERARY: "Voir l'itinéraire complet",
3115
+ SHOW_SERVICES: "Voir les services",
3102
3116
  VIEW_BOOKING: "Voir la réservation",
3103
3117
  ITINERARY_TITLE: "Résumé de votre voyage",
3104
3118
  DOSSIER_NUMBER: "Numéro de dossier",
@@ -3544,6 +3558,8 @@ var SRP$8 = {
3544
3558
  DEPARTURE_ASC: "Brottfarardagur (nærst fyrst)",
3545
3559
  FILTERS: "Síur",
3546
3560
  SHOW_ITINERARY: "Sýna ferðaráætlun",
3561
+ VIEW_FULL_ITINERARY: "Skoða alla ferðaáætlunina",
3562
+ SHOW_SERVICES: "Sýna þjónustu",
3547
3563
  VIEW_BOOKING: "Skoða bókun",
3548
3564
  ITINERARY_TITLE: "Yfirlit ferðarinnar",
3549
3565
  DOSSIER_NUMBER: "Skrárnúmer",
@@ -3989,6 +4005,8 @@ var SRP$7 = {
3989
4005
  DEPARTURE_ASC: "Data di partenza (prima la più vicina)",
3990
4006
  FILTERS: "Filtri",
3991
4007
  SHOW_ITINERARY: "Mostra itinerario",
4008
+ VIEW_FULL_ITINERARY: "Vedi l'itinerario completo",
4009
+ SHOW_SERVICES: "Mostra i servizi",
3992
4010
  VIEW_BOOKING: "Visualizza prenotazione",
3993
4011
  ITINERARY_TITLE: "Riepilogo del tuo viaggio",
3994
4012
  DOSSIER_NUMBER: "Numero di pratica",
@@ -4438,6 +4456,8 @@ var SRP$6 = {
4438
4456
  DEPARTURE_ASC: "Vertrekdatum (dichtstbij eerst)",
4439
4457
  FILTERS: "Filters",
4440
4458
  SHOW_ITINERARY: "Toon reisplan",
4459
+ VIEW_FULL_ITINERARY: "Bekijk volledige reisroute",
4460
+ SHOW_SERVICES: "Toon diensten",
4441
4461
  VIEW_BOOKING: "Bekijk boeking",
4442
4462
  ITINERARY_TITLE: "Overzicht van je reis",
4443
4463
  DOSSIER_NUMBER: "Dossiernummer",
@@ -4883,6 +4903,8 @@ var SRP$5 = {
4883
4903
  DEPARTURE_ASC: "Vertrekdatum (dichtstbij eerst)",
4884
4904
  FILTERS: "Filters",
4885
4905
  SHOW_ITINERARY: "Toon reisplan",
4906
+ VIEW_FULL_ITINERARY: "Bekijk volledige reisroute",
4907
+ SHOW_SERVICES: "Toon diensten",
4886
4908
  VIEW_BOOKING: "Bekijk boeking",
4887
4909
  ITINERARY_TITLE: "Overzicht van je reis",
4888
4910
  DOSSIER_NUMBER: "Dossiernummer",
@@ -5328,6 +5350,8 @@ var SRP$4 = {
5328
5350
  DEPARTURE_ASC: "Avreisedato (nærmeste først)",
5329
5351
  FILTERS: "Filtre",
5330
5352
  SHOW_ITINERARY: "Vis reiserute",
5353
+ VIEW_FULL_ITINERARY: "Se hele reiseruten",
5354
+ SHOW_SERVICES: "Vis tjenester",
5331
5355
  VIEW_BOOKING: "Se bestilling",
5332
5356
  ITINERARY_TITLE: "Din reisemåloversikt",
5333
5357
  DOSSIER_NUMBER: "Saksnummer",
@@ -5773,6 +5797,8 @@ var SRP$3 = {
5773
5797
  DEPARTURE_ASC: "Data wyjazdu (najbliższa najpierw)",
5774
5798
  FILTERS: "Filtry",
5775
5799
  SHOW_ITINERARY: "Pokaż plan podróży",
5800
+ VIEW_FULL_ITINERARY: "Zobacz pełny plan podróży",
5801
+ SHOW_SERVICES: "Pokaż usługi",
5776
5802
  VIEW_BOOKING: "Zobacz rezerwację",
5777
5803
  ITINERARY_TITLE: "Podsumowanie Twojej podróży",
5778
5804
  DOSSIER_NUMBER: "Numer pliku",
@@ -6218,6 +6244,8 @@ var SRP$2 = {
6218
6244
  DEPARTURE_ASC: "Data de partida (mais próxima primeiro)",
6219
6245
  FILTERS: "Filtros",
6220
6246
  SHOW_ITINERARY: "Mostrar itinerário",
6247
+ VIEW_FULL_ITINERARY: "Ver itinerário completo",
6248
+ SHOW_SERVICES: "Ver serviços",
6221
6249
  VIEW_BOOKING: "Ver reserva",
6222
6250
  ITINERARY_TITLE: "Resumo da sua viagem",
6223
6251
  DOSSIER_NUMBER: "Número do processo",
@@ -6663,6 +6691,8 @@ var SRP$1 = {
6663
6691
  DEPARTURE_ASC: "Avresedatum (närmast först)",
6664
6692
  FILTERS: "Filter",
6665
6693
  SHOW_ITINERARY: "Visa resplan",
6694
+ VIEW_FULL_ITINERARY: "Visa hela resplanen",
6695
+ SHOW_SERVICES: "Visa tjänster",
6666
6696
  VIEW_BOOKING: "Visa bokning",
6667
6697
  ITINERARY_TITLE: "Din resesammanfattning",
6668
6698
  DOSSIER_NUMBER: "Filnummer",
@@ -7108,6 +7138,8 @@ var SRP = {
7108
7138
  DEPARTURE_ASC: "出発日(早い順)",
7109
7139
  FILTERS: "フィルター",
7110
7140
  SHOW_ITINERARY: "旅程を表示",
7141
+ VIEW_FULL_ITINERARY: "旅程の詳細を見る",
7142
+ SHOW_SERVICES: "サービスを表示",
7111
7143
  VIEW_BOOKING: "予約を見る",
7112
7144
  ITINERARY_TITLE: "あなたの旅行概要",
7113
7145
  DOSSIER_NUMBER: "ファイル番号",
@@ -7817,6 +7849,33 @@ var TrajectNodeType = {
7817
7849
  subroute: 3,
7818
7850
  };
7819
7851
 
7852
+ var DurationType = {
7853
+ hours: 0,
7854
+ days: 1,
7855
+ nights: 2,
7856
+ minutes: 3,
7857
+ };
7858
+
7859
+ var RouteTransportType = {
7860
+ none: 0,
7861
+ privateTransport: 1,
7862
+ publicTransport: 2,
7863
+ rentalCar: 3,
7864
+ bus: 4,
7865
+ camper: 5,
7866
+ car: 6,
7867
+ charter: 7,
7868
+ cruise: 8,
7869
+ ferry: 9,
7870
+ flight: 10,
7871
+ taxi: 11,
7872
+ train: 12,
7873
+ transfer: 13,
7874
+ rocket: 14,
7875
+ walking: 15,
7876
+ cycling: 16,
7877
+ };
7878
+
7820
7879
  /******************************************************************************
7821
7880
  Copyright (c) Microsoft Corporation.
7822
7881
 
@@ -15168,6 +15227,7 @@ var submitForm = function (config, id, request, signal) {
15168
15227
  exports.AllotmentType = AllotmentType;
15169
15228
  exports.DataType = DataType;
15170
15229
  exports.DossierType = DossierType;
15230
+ exports.DurationType = DurationType;
15171
15231
  exports.EntryLineFlag = EntryLineFlag;
15172
15232
  exports.EntryLineStatus = EntryLineStatus;
15173
15233
  exports.ExportStatus = ExportStatus;
@@ -15180,6 +15240,7 @@ exports.PaymentType = PaymentType;
15180
15240
  exports.PriceChartType = PriceChartType;
15181
15241
  exports.RequirementType = RequirementType;
15182
15242
  exports.RoomType = RoomType;
15243
+ exports.RouteTransportType = RouteTransportType;
15183
15244
  exports.SaleType = SaleType;
15184
15245
  exports.ServiceTokenError = ServiceTokenError;
15185
15246
  exports.ServiceType = ServiceType;
@@ -16657,6 +16718,16 @@ const Icon = ({ name, className, title, width, height, fill }) => {
16657
16718
  React__default["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." }),
16658
16719
  title && React__default["default"].createElement("title", null, title),
16659
16720
  React__default["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" })));
16721
+ case 'ui-cycling':
16722
+ return (React__default["default"].createElement("svg", { className: ['icon', `icon--${name}`, className].filter((className) => !lodash.isEmpty(className)).join(' '), width: width, height: height, viewBox: "0 0 640 512", fill: fill ?? 'currentColor' },
16723
+ React__default["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." }),
16724
+ title && React__default["default"].createElement("title", null, title),
16725
+ React__default["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" })));
16726
+ case 'ui-walking':
16727
+ return (React__default["default"].createElement("svg", { className: ['icon', `icon--${name}`, className].filter((className) => !lodash.isEmpty(className)).join(' '), width: width, height: height, viewBox: "0 0 320 512", fill: fill ?? 'currentColor' },
16728
+ React__default["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." }),
16729
+ title && React__default["default"].createElement("title", null, title),
16730
+ React__default["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" })));
16660
16731
  case 'ui-bus':
16661
16732
  return (React__default["default"].createElement("svg", { className: ['icon', `icon--${name}`, className].filter((className) => !lodash.isEmpty(className)).join(' '), width: width, height: height, viewBox: "0 0 512 512", fill: fill ?? 'currentColor' },
16662
16733
  title && React__default["default"].createElement("title", null, title),
@@ -16970,10 +17041,10 @@ const DateRangePicker$1 = (props) => {
16970
17041
  month: dateFns.getMonth(new Date())
16971
17042
  });
16972
17043
  const [isWaitingForToDate, setWaitingForToDate] = React.useState(false);
16973
- const { searchType = 0 } = React.useContext(SettingsContext$1);
17044
+ const { searchType = 0, allowSingleDayDateSelect = false } = React.useContext(SettingsContext$1);
16974
17045
  const handleDayClick = (day) => {
16975
17046
  const { onSelectionChange } = props;
16976
- if (isWaitingForToDate && !lodash.isNil(fromDate) && dateFns.isAfter(day, fromDate)) {
17047
+ if (isWaitingForToDate && !lodash.isNil(fromDate) && (dateFns.isAfter(day, fromDate) || (allowSingleDayDateSelect && dateFns.isSameDay(day, fromDate)))) {
16977
17048
  if (searchType === 1) {
16978
17049
  // Only allow selecting the exact toDate
16979
17050
  const expectedToDate = props.toDateByFromDate?.(fromDate);
@@ -17014,7 +17085,7 @@ const DateRangePicker$1 = (props) => {
17014
17085
  }
17015
17086
  };
17016
17087
  const handleDayMouseOver = (day) => {
17017
- if (isWaitingForToDate && (lodash.isNil(fromDate) || dateFns.isEqual(day, fromDate) || dateFns.isAfter(day, fromDate))) {
17088
+ if (isWaitingForToDate && (lodash.isNil(fromDate) || dateFns.isSameDay(day, fromDate) || dateFns.isAfter(day, fromDate))) {
17018
17089
  setToDate(day);
17019
17090
  }
17020
17091
  };
@@ -28033,8 +28104,97 @@ const getServiceTypeBadgeModifier = (serviceType) => {
28033
28104
  return '';
28034
28105
  }
28035
28106
  };
28107
+ // DurationType, as the SDK numbers it.
28108
+ const DURATION_HOURS = 0;
28109
+ const DURATION_DAYS = 1;
28110
+ const DURATION_NIGHTS = 2;
28111
+ const DURATION_MINUTES = 3;
28112
+ /**
28113
+ * How long a node takes, in the same shape the flight legs are written in — an hour count only
28114
+ * once there is one, so a short drive reads as plain minutes.
28115
+ */
28116
+ const getTrajectNodeDuration = (node) => {
28117
+ const minutes = toMinutes(node);
28118
+ if (!minutes)
28119
+ return '';
28120
+ const hours = Math.floor(minutes / 60);
28121
+ const rest = minutes % 60;
28122
+ return hours ? `${hours}h ${rest.toString().padStart(2, '0')}m` : `${rest} min`;
28123
+ };
28124
+ const toMinutes = (node) => {
28125
+ const duration = node.duration ?? 0;
28126
+ if (duration <= 0)
28127
+ return 0;
28128
+ switch (node.durationType) {
28129
+ case DURATION_MINUTES:
28130
+ return duration;
28131
+ case DURATION_HOURS:
28132
+ return duration * 60;
28133
+ case DURATION_DAYS:
28134
+ case DURATION_NIGHTS:
28135
+ return duration * 24 * 60;
28136
+ default:
28137
+ return 0;
28138
+ }
28139
+ };
28140
+ // RouteTransportType, as the SDK numbers it. It says how a leg is travelled, which is not the
28141
+ // same question as what was sold: cycling and walking have no service type of their own.
28142
+ const TRANSPORT_PRIVATE = 1;
28143
+ const TRANSPORT_PUBLIC = 2;
28144
+ const TRANSPORT_RENTAL_CAR = 3;
28145
+ const TRANSPORT_BUS = 4;
28146
+ const TRANSPORT_CAMPER = 5;
28147
+ const TRANSPORT_CAR = 6;
28148
+ const TRANSPORT_CHARTER = 7;
28149
+ const TRANSPORT_CRUISE = 8;
28150
+ const TRANSPORT_FERRY = 9;
28151
+ const TRANSPORT_FLIGHT = 10;
28152
+ const TRANSPORT_TAXI = 11;
28153
+ const TRANSPORT_TRAIN = 12;
28154
+ const TRANSPORT_TRANSFER = 13;
28155
+ const TRANSPORT_WALKING = 15;
28156
+ const TRANSPORT_CYCLING = 16;
28157
+ /** Icon per way of travelling, where the route records one. */
28158
+ const getTransportTypeIcon = (transportType) => {
28159
+ switch (transportType) {
28160
+ case TRANSPORT_WALKING:
28161
+ return 'ui-walking';
28162
+ case TRANSPORT_CYCLING:
28163
+ return 'ui-cycling';
28164
+ case TRANSPORT_PRIVATE:
28165
+ case TRANSPORT_RENTAL_CAR:
28166
+ case TRANSPORT_CAMPER:
28167
+ case TRANSPORT_CAR:
28168
+ return 'ui-car';
28169
+ case TRANSPORT_TAXI:
28170
+ case TRANSPORT_TRANSFER:
28171
+ return 'ui-transfer';
28172
+ case TRANSPORT_PUBLIC:
28173
+ case TRANSPORT_BUS:
28174
+ return 'ui-bus';
28175
+ case TRANSPORT_TRAIN:
28176
+ return 'ui-train';
28177
+ case TRANSPORT_CRUISE:
28178
+ case TRANSPORT_FERRY:
28179
+ return 'ui-ship';
28180
+ case TRANSPORT_FLIGHT:
28181
+ case TRANSPORT_CHARTER:
28182
+ return 'ui-plane';
28183
+ default:
28184
+ return null;
28185
+ }
28186
+ };
28036
28187
  const POI_TRAJECT_NODE_TYPE = 2;
28037
- const getTrajectNodeIcon = (node) => node.trajectNodeType === POI_TRAJECT_NODE_TYPE ? 'ui-location' : getServiceTypeIcon(node.serviceType);
28188
+ /**
28189
+ * A point of interest is a place on the map rather than a service, so it gets a marker. Anything
28190
+ * else goes by how it is travelled where the route says so — a leg on foot or by bike is sold as
28191
+ * nothing, and its service type falls back to a transfer — and by what it is where it does not.
28192
+ */
28193
+ const getTrajectNodeIcon = (node) => {
28194
+ if (node.trajectNodeType === POI_TRAJECT_NODE_TYPE)
28195
+ return 'ui-location';
28196
+ return getTransportTypeIcon(node.transportType) ?? getServiceTypeIcon(node.serviceType);
28197
+ };
28038
28198
  const getTrajectNodeBadgeModifier = (node) => node.trajectNodeType === POI_TRAJECT_NODE_TYPE ? 'poi' : getServiceTypeBadgeModifier(node.serviceType);
28039
28199
 
28040
28200
  const REGULAR_TRAJECT_NODE_TYPE = 0;
@@ -28473,6 +28633,8 @@ const TrajectItinerary = ({ trajectEntry, packagingEntry, translations }) => {
28473
28633
  trajectNodeType: REGULAR_TRAJECT_NODE_TYPE,
28474
28634
  requirementType: 0,
28475
28635
  contentSource: 0,
28636
+ duration: 0,
28637
+ durationType: 0,
28476
28638
  alternatives: [],
28477
28639
  from: line.from,
28478
28640
  to: line.to
@@ -28506,6 +28668,8 @@ const TrajectItinerary = ({ trajectEntry, packagingEntry, translations }) => {
28506
28668
  React__default["default"].createElement("p", null, dateFns.format(new Date(value), 'MMM'))));
28507
28669
  const renderConnectingNode = ({ node }) => {
28508
28670
  const distance = node.distanceInKm ? `${Math.round(node.distanceInKm)} km` : '';
28671
+ // How long the hop takes and how far it is, the way the traject editor puts them.
28672
+ const journey = [getTrajectNodeDuration(node), distance].filter(Boolean).join(' · ');
28509
28673
  return (React__default["default"].createElement("div", { className: "search__filter__itinerary__transport", key: `connect-${node.nodeId}` },
28510
28674
  React__default["default"].createElement("div", { className: "search__filter__itinerary__transport-timeline" },
28511
28675
  React__default["default"].createElement("div", { className: "search__filter__itinerary__transport-timeline-line" })),
@@ -28514,8 +28678,8 @@ const TrajectItinerary = ({ trajectEntry, packagingEntry, translations }) => {
28514
28678
  React__default["default"].createElement("div", { className: badgeClass('transport', getTrajectNodeBadgeModifier(node)) },
28515
28679
  React__default["default"].createElement(Icon, { name: getTrajectNodeIcon(node), height: 15 })),
28516
28680
  React__default["default"].createElement("div", { className: "search__filter__itinerary__transport-details" },
28517
- React__default["default"].createElement("h6", null, node.name || distance),
28518
- !!node.name && !!distance && React__default["default"].createElement("p", { className: "search__filter__itinerary__transport-details-time" }, distance)))));
28681
+ React__default["default"].createElement("h6", null, node.name || journey),
28682
+ !!node.name && !!journey && React__default["default"].createElement("p", { className: "search__filter__itinerary__transport-details-time" }, journey)))));
28519
28683
  };
28520
28684
  const renderOvernight = (item, index) => {
28521
28685
  const { dayOrder } = item.node;
@@ -28659,7 +28823,7 @@ const getServiceTypePriority = (serviceType) => {
28659
28823
  const Itinerary = ({ isOpen, handleSetIsOpen, isLoading, onEditAccommodation }) => {
28660
28824
  const context = React.useContext(SearchResultsConfigurationContext);
28661
28825
  const translations = getTranslations(context?.languageCode ?? 'en-GB');
28662
- const { editablePackagingEntry, priceDetails } = reactRedux.useSelector((state) => state.searchResults);
28826
+ const { editablePackagingEntry, priceDetails, trajectItineraryView } = reactRedux.useSelector((state) => state.searchResults);
28663
28827
  const dispatch = reactRedux.useDispatch();
28664
28828
  const packagingEntry = editablePackagingEntry ?? context?.packagingEntry;
28665
28829
  const sortedLines = React.useMemo(() => {
@@ -28702,6 +28866,11 @@ const Itinerary = ({ isOpen, handleSetIsOpen, isLoading, onEditAccommodation })
28702
28866
  const handleConfirm = () => {
28703
28867
  dispatch(setBookPackagingEntry(true));
28704
28868
  };
28869
+ const handleTrajectView = (itinerary) => {
28870
+ dispatch(setTrajectItineraryView(itinerary));
28871
+ if (typeof window !== 'undefined')
28872
+ window.scrollTo(0, 0);
28873
+ };
28705
28874
  return (React__default["default"].createElement("div", { className: `search__filters--modal ${isOpen ? 'is-open' : ''}` },
28706
28875
  React__default["default"].createElement("div", { className: "search__filters--background", onClick: handleSetIsOpen }),
28707
28876
  React__default["default"].createElement("button", { className: "search__filters--close", onClick: handleSetIsOpen },
@@ -28729,7 +28898,8 @@ const Itinerary = ({ isOpen, handleSetIsOpen, isLoading, onEditAccommodation })
28729
28898
  " ",
28730
28899
  translations.SRP.TOTAL,
28731
28900
  ")"))),
28732
- React__default["default"].createElement("button", { className: "cta", onClick: handleConfirm }, translations.QSM.CONFIRM)))),
28901
+ React__default["default"].createElement("button", { className: "cta", onClick: handleConfirm }, translations.QSM.CONFIRM),
28902
+ !!context?.trajectEntry && (React__default["default"].createElement("button", { type: "button", className: "cta cta--secondary", onClick: () => handleTrajectView(!trajectItineraryView) }, trajectItineraryView ? translations.SRP.SHOW_SERVICES : translations.SRP.VIEW_FULL_ITINERARY))))),
28733
28903
  React__default["default"].createElement("div", { className: "search__filter__itinerary" },
28734
28904
  React__default["default"].createElement("p", null, translations.SRP.DAY_BY_DAY),
28735
28905
  firstEntryLine && (React__default["default"].createElement("div", { className: "search__filter__itinerary__country" },
@@ -33182,6 +33352,27 @@ const getRequestRoomsFromPackagingSegments = (entry, segments) => {
33182
33352
  }));
33183
33353
  };
33184
33354
 
33355
+ /**
33356
+ * The entry to send to the backend, as opposed to the one the page works with.
33357
+ *
33358
+ * They differ only for a traject: the page keeps the line guids the traject was rendered with,
33359
+ * because that is what joins its nodes to its lines, while pricing and booking need the guids the
33360
+ * searches cached their lines under. Anything else passes through untouched, so this can be used
33361
+ * wherever the entry leaves the component without checking which flow is running.
33362
+ */
33363
+ const useBookableEntry = () => {
33364
+ const context = React.useContext(SearchResultsConfigurationContext);
33365
+ const { editablePackagingEntry, trajectNodeAvailability, trajectFlightAvailability } = reactRedux.useSelector((state) => state.searchResults);
33366
+ const trajectEntry = context?.trajectEntry;
33367
+ return React.useMemo(() => {
33368
+ if (!editablePackagingEntry)
33369
+ return null;
33370
+ if (!trajectEntry)
33371
+ return editablePackagingEntry;
33372
+ return withCachedEntryLineGuids(editablePackagingEntry, trajectEntry, trajectNodeAvailability, trajectFlightAvailability);
33373
+ }, [editablePackagingEntry, trajectEntry, trajectNodeAvailability, trajectFlightAvailability]);
33374
+ };
33375
+
33185
33376
  const escapeHtml = (value) => {
33186
33377
  if (!value)
33187
33378
  return '';
@@ -33240,7 +33431,57 @@ const findAccommodationForDay = (nodes, currentDay) => {
33240
33431
  }
33241
33432
  return null;
33242
33433
  };
33243
- const buildItineraryHtml = (itinerary, translations) => {
33434
+ const formatTime = (time) => (time ? time.slice(0, 5) : '');
33435
+ /**
33436
+ * Whether generated content actually shows something.
33437
+ *
33438
+ * A content view whose data is missing still returns its wrapper markup, or the whitespace left by
33439
+ * a guard around its body — markup that renders as a blank gap. Only text or an image counts as
33440
+ * something the traveller can read.
33441
+ */
33442
+ const hasVisibleContent = (contents) => {
33443
+ if (!contents)
33444
+ return false;
33445
+ if (/<(img|svg|picture|video|iframe)\b/i.test(contents))
33446
+ return true;
33447
+ return (contents
33448
+ .replace(/<[^>]*>/g, ' ')
33449
+ .replace(/&nbsp;/gi, ' ')
33450
+ .trim().length > 0);
33451
+ };
33452
+ /** The legs of a flight, as the entry line records them. */
33453
+ const buildFlightHtml = (line) => {
33454
+ const flightLines = line.flightInformation?.flightLines ?? [];
33455
+ if (!flightLines.length)
33456
+ return '';
33457
+ const legs = flightLines
33458
+ .map((leg) => {
33459
+ const from = escapeHtml(leg.departureAirportDescription || leg.departureAirportCode);
33460
+ const to = escapeHtml(leg.arrivalAirportDescription || leg.arrivalAirportCode);
33461
+ const carrier = escapeHtml([leg.airlineDescription, leg.flightNumber].filter(Boolean).join(' '));
33462
+ return `<li>${from} ${formatTime(leg.departureTime)} &rarr; ${to} ${formatTime(leg.arrivalTime)}${carrier ? ` &middot; ${carrier}` : ''}</li>`;
33463
+ })
33464
+ .join('');
33465
+ return `<ul class="itinerary-item__legs">${legs}</ul>`;
33466
+ };
33467
+ /**
33468
+ * What to show for an item the generator returned no content for.
33469
+ *
33470
+ * An item comes back with a title and a template but an empty body whenever its template has no
33471
+ * content view of its own — flights, today. Rendering that empty body leaves a blank where a
33472
+ * service should be, so what the entry already knows about the line is drawn instead.
33473
+ */
33474
+ const buildFallbackHtml = (item, line) => {
33475
+ const details = [line?.accommodationName, line?.regimeName].filter(Boolean).join(' — ');
33476
+ return `
33477
+ <div class="itinerary-item__fallback">
33478
+ <h3 class="itinerary-item__fallback-title">${escapeHtml(item.title)}</h3>
33479
+ ${line ? buildFlightHtml(line) : ''}
33480
+ ${details ? `<p class="itinerary-item__fallback-details">${escapeHtml(details)}</p>` : ''}
33481
+ </div>
33482
+ `;
33483
+ };
33484
+ const buildItineraryHtml = (itinerary, translations, lines) => {
33244
33485
  if (!itinerary) {
33245
33486
  return `
33246
33487
  <div class="itinerary-shell">
@@ -33269,7 +33510,9 @@ const buildItineraryHtml = (itinerary, translations) => {
33269
33510
  ? uniqueItems
33270
33511
  .map((item) => `
33271
33512
  <article class="itinerary-item" data-template="${escapeHtml(item.templateName)}">
33272
- ${item.contents ?? ''}
33513
+ ${hasVisibleContent(item.contents)
33514
+ ? item.contents
33515
+ : buildFallbackHtml(item, lines.find((line) => line.guid === item.itemGuid))}
33273
33516
  </article>
33274
33517
  `)
33275
33518
  .join('')
@@ -33303,7 +33546,9 @@ const buildItineraryHtml = (itinerary, translations) => {
33303
33546
  ${getUniqueItems(itinerary.defaultItems)
33304
33547
  .map((item) => `
33305
33548
  <article class="itinerary-item" data-template="${escapeHtml(item.templateName)}">
33306
- ${item.contents ?? ''}
33549
+ ${hasVisibleContent(item.contents)
33550
+ ? item.contents
33551
+ : buildFallbackHtml(item, lines.find((line) => line.guid === item.itemGuid))}
33307
33552
  </article>
33308
33553
  `)
33309
33554
  .join('')}
@@ -33324,10 +33569,12 @@ const buildItineraryHtml = (itinerary, translations) => {
33324
33569
  const FullItinerary = ({ isLoading }) => {
33325
33570
  const { itinerary } = reactRedux.useSelector((state) => state.searchResults);
33326
33571
  const context = React.useContext(SearchResultsConfigurationContext);
33572
+ // The entry as it was sent: its line guids are the ones the itinerary items carry back.
33573
+ const bookableEntry = useBookableEntry();
33327
33574
  const translations = getTranslations(context?.languageCode ?? 'en-GB');
33328
33575
  const hostRef = React.useRef(null);
33329
33576
  const shadowRootRef = React.useRef(null);
33330
- const html = React.useMemo(() => buildItineraryHtml(itinerary, translations), [itinerary, translations]);
33577
+ const html = React.useMemo(() => buildItineraryHtml(itinerary, translations, bookableEntry?.lines ?? []), [itinerary, translations, bookableEntry]);
33331
33578
  React.useEffect(() => {
33332
33579
  if (isLoading) {
33333
33580
  return;
@@ -33441,6 +33688,35 @@ const FullItinerary = ({ isLoading }) => {
33441
33688
  background: #fff;
33442
33689
  }
33443
33690
 
33691
+ /* An item whose template has no content view of its own still has to read as a service. */
33692
+ .itinerary-item__fallback {
33693
+ padding: 16px 20px;
33694
+ border: 1px solid #e5e7eb;
33695
+ border-radius: 12px;
33696
+ }
33697
+
33698
+ .itinerary-item__fallback-title {
33699
+ margin: 0;
33700
+ font-size: 16px;
33701
+ font-weight: 600;
33702
+ color: #111827;
33703
+ }
33704
+
33705
+ .itinerary-item__legs {
33706
+ margin: 8px 0 0;
33707
+ padding-left: 18px;
33708
+ color: #374151;
33709
+ }
33710
+
33711
+ .itinerary-item__legs li + li {
33712
+ margin-top: 4px;
33713
+ }
33714
+
33715
+ .itinerary-item__fallback-details {
33716
+ margin: 8px 0 0;
33717
+ color: #6b7280;
33718
+ }
33719
+
33444
33720
  .itinerary-node__empty,
33445
33721
  .itinerary-empty {
33446
33722
  padding: 20px;
@@ -33576,27 +33852,6 @@ const DayByDayExcursions = () => {
33576
33852
  })));
33577
33853
  };
33578
33854
 
33579
- /**
33580
- * The entry to send to the backend, as opposed to the one the page works with.
33581
- *
33582
- * They differ only for a traject: the page keeps the line guids the traject was rendered with,
33583
- * because that is what joins its nodes to its lines, while pricing and booking need the guids the
33584
- * searches cached their lines under. Anything else passes through untouched, so this can be used
33585
- * wherever the entry leaves the component without checking which flow is running.
33586
- */
33587
- const useBookableEntry = () => {
33588
- const context = React.useContext(SearchResultsConfigurationContext);
33589
- const { editablePackagingEntry, trajectNodeAvailability, trajectFlightAvailability } = reactRedux.useSelector((state) => state.searchResults);
33590
- const trajectEntry = context?.trajectEntry;
33591
- return React.useMemo(() => {
33592
- if (!editablePackagingEntry)
33593
- return null;
33594
- if (!trajectEntry)
33595
- return editablePackagingEntry;
33596
- return withCachedEntryLineGuids(editablePackagingEntry, trajectEntry, trajectNodeAvailability, trajectFlightAvailability);
33597
- }, [editablePackagingEntry, trajectEntry, trajectNodeAvailability, trajectFlightAvailability]);
33598
- };
33599
-
33600
33855
  const renderEditablePackagingEntrySummaryOptions = (editablePackagingEntry, priceDetails, translations, currencyCode) => {
33601
33856
  const priceDetailsByProduct = lodash.groupBy(priceDetails?.details ?? [], (detail) => `${detail.productCode}|${detail.accommodationCode}`);
33602
33857
  return (editablePackagingEntry?.lines ?? []).map((line) => {
@@ -34093,7 +34348,7 @@ const TrajectResults = ({ isLoading }) => {
34093
34348
  const loadDay = useTrajectDayActivities();
34094
34349
  const translations = getTranslations(context?.languageCode ?? 'en-GB');
34095
34350
  const locale = getLocale(context?.languageCode ?? 'en-GB');
34096
- const { trajectNodeAvailability, trajectFlightAvailability, trajectDaySuggestions, editablePackagingEntry, trajectResolving } = reactRedux.useSelector((state) => state.searchResults);
34351
+ const { trajectNodeAvailability, trajectFlightAvailability, trajectDaySuggestions, editablePackagingEntry } = reactRedux.useSelector((state) => state.searchResults);
34097
34352
  const trajectEntry = context?.trajectEntry;
34098
34353
  const hideShowMoreResults = !!context?.hideShowMoreResults;
34099
34354
  const nodesWithLines = React.useMemo(() => (trajectEntry ? getTrajectNodesWithLines(trajectEntry) : []), [trajectEntry]);
@@ -34123,6 +34378,23 @@ const TrajectResults = ({ isLoading }) => {
34123
34378
  });
34124
34379
  return { stayByDay: byDay, stayStartByNodeId: byNodeId };
34125
34380
  }, [nodesWithLines]);
34381
+ /** Whether every node of the trip has an answer, one way or the other. */
34382
+ const tripResolved = React.useMemo(() => nodesWithLines
34383
+ .filter((x) => isSearchableTrajectNode(x.node) || isTrajectFlightNode(x.node))
34384
+ .every((x) => {
34385
+ const status = isTrajectFlightNode(x.node) ? trajectFlightAvailability[x.line.guid]?.status : trajectNodeAvailability[x.line.guid]?.status;
34386
+ return !!status && status !== 'loading';
34387
+ }), [nodesWithLines, trajectNodeAvailability, trajectFlightAvailability]);
34388
+ /**
34389
+ * Each day's activities are one more search, and a traject has as many as it has days. They go
34390
+ * out together, but only once the trip itself has resolved — sharing the connections with the
34391
+ * searches the trip cannot do without would only make those slower.
34392
+ */
34393
+ React.useEffect(() => {
34394
+ if (!trajectEntry || !tripResolved)
34395
+ return;
34396
+ (trajectEntry.traject.days ?? []).filter((day) => stayByDay.has(day.order)).forEach((day) => loadDay(day));
34397
+ }, [trajectEntry, tripResolved, stayByDay]);
34126
34398
  if (!context || !trajectEntry)
34127
34399
  return null;
34128
34400
  const handleSelect = (item, code) => {
@@ -34279,11 +34551,6 @@ const TrajectResults = ({ isLoading }) => {
34279
34551
  if (lines.length)
34280
34552
  dispatch(addEditableEntryLines(lines));
34281
34553
  };
34282
- const handleShowItinerary = () => {
34283
- dispatch(setTrajectItineraryView(true));
34284
- if (typeof window !== 'undefined')
34285
- window.scrollTo(0, 0);
34286
- };
34287
34554
  const handleShowMoreActivities = (dayOrder) => {
34288
34555
  dispatch(setTrajectActivitiesDayOrder(dayOrder));
34289
34556
  dispatch(setFlyInType('traject-day-activities'));
@@ -34308,16 +34575,16 @@ const TrajectResults = ({ isLoading }) => {
34308
34575
  const cards = buildDayActivityCards(items, trajectNodeAvailability, suggestions?.results ?? []);
34309
34576
  const nodesResolving = items.some((item) => (trajectNodeAvailability[item.line.guid]?.status ?? 'loading') === 'loading');
34310
34577
  const isSearching = suggestions?.status === 'loading';
34311
- const canSearch = !!getDayDestination(nodesWithLines, trajectNodeAvailability, day.order);
34578
+ // A day is worth filling only if the party is staying somewhere that night. The day they fly
34579
+ // home is covered by no stay — the last night was the night before — and nobody is looking for
34580
+ // something to do on it.
34581
+ const canSearch = stayByDay.has(day.order) && !!getDayDestination(nodesWithLines, trajectNodeAvailability, day.order);
34312
34582
  if (!items.length && !canSearch)
34313
34583
  return null;
34314
34584
  const visible = cards.slice(0, MAX_DAY_ACTIVITIES);
34315
34585
  const hidden = cards.length - visible.length;
34316
34586
  return (React__default["default"].createElement(React__default["default"].Fragment, { key: `activities-${day.order}` },
34317
- React__default["default"].createElement("div", { className: "search__results__label search__results__label--secondary" },
34318
- React__default["default"].createElement("div", { className: "search__results__label__date" },
34319
- React__default["default"].createElement("p", { className: "search__results__label__date-date" }, dateFns.format(new Date(day.date), 'd', { locale })),
34320
- React__default["default"].createElement("p", null, dateFns.format(new Date(day.date), 'MMM', { locale }))),
34587
+ React__default["default"].createElement("div", { className: "search__results__label search__results__label--sub" },
34321
34588
  React__default["default"].createElement("div", { className: "search__results__label__text" },
34322
34589
  React__default["default"].createElement(Icon, { name: "ui-excursion", height: 16 }),
34323
34590
  React__default["default"].createElement("h3", null,
@@ -34325,13 +34592,11 @@ const TrajectResults = ({ isLoading }) => {
34325
34592
  (nodesResolving || isSearching) && !cards.length && React__default["default"].createElement(Spinner, { label: translations.SRP.LOADING_ACTIVITIES }),
34326
34593
  !!cards.length && React__default["default"].createElement("div", { className: "search__results__cards search__results__cards--scroll" }, visible.map(renderActivityCard)),
34327
34594
  !nodesResolving && !isSearching && !cards.length && (!!items.length || !!suggestions) && (React__default["default"].createElement("div", { className: "no-results" }, translations.SRP.NO_RESULTS)),
34328
- React__default["default"].createElement("div", { className: "search__results__cards__actions" },
34329
- !suggestions && canSearch && !cards.length && !hideShowMoreResults && (React__default["default"].createElement("button", { type: "button", className: "cta cta--secondary", onClick: () => loadDay(day) }, translations.SRP.SHOW_ACTIVITIES)),
34330
- hidden > 0 && !hideShowMoreResults && (React__default["default"].createElement("button", { type: "button", className: "cta cta--secondary", onClick: () => handleShowMoreActivities(day.order) },
34331
- translations.SRP.SHOW_MORE,
34332
- " (",
34333
- hidden,
34334
- ")")))));
34595
+ React__default["default"].createElement("div", { className: "search__results__cards__actions" }, hidden > 0 && !hideShowMoreResults && (React__default["default"].createElement("button", { type: "button", className: "cta cta--secondary", onClick: () => handleShowMoreActivities(day.order) },
34596
+ translations.SRP.SHOW_MORE,
34597
+ " (",
34598
+ hidden,
34599
+ ")")))));
34335
34600
  };
34336
34601
  /**
34337
34602
  * An activity the traject books is swapped for another, the way a hotel is; one the traveller
@@ -34343,35 +34608,32 @@ const TrajectResults = ({ isLoading }) => {
34343
34608
  };
34344
34609
  if (isLoading)
34345
34610
  return React__default["default"].createElement(Spinner, { label: translations.SRP.LOADING_ITINERARY });
34346
- return (React__default["default"].createElement(React__default["default"].Fragment, null,
34347
- trajectEntry.traject.days.map((day) => {
34348
- const stay = stayByDay.get(day.order);
34349
- const dayNodes = (nodesByDay.get(day.order) ?? []).filter((node) => isRenderableNode(node) && lineByNodeId.has(node.nodeId));
34350
- const stayLeadsDay = !!stay && (stay.night > 0 || dayNodes[0]?.nodeId === stay.item.node.nodeId);
34351
- // Where the day's activities belong in the order: with the first excursion it carries.
34352
- const firstExcursionNodeId = dayNodes.find((node) => node.serviceType === EXCURSION_SERVICE_TYPE)?.nodeId;
34353
- const showDayHeader = stayLeadsDay || !!day.name?.trim();
34354
- return (React__default["default"].createElement(React__default["default"].Fragment, { key: `day-${day.order}` },
34355
- showDayHeader && renderDayHeader(day, stayLeadsDay ? stay : undefined),
34356
- dayNodes.map((node) => {
34357
- const item = lineByNodeId.get(node.nodeId);
34358
- if (node.serviceType === ACCOMMODATION_SERVICE_TYPE) {
34359
- const stayStart = stayStartByNodeId.get(node.nodeId);
34360
- const titledByDayHeader = stayLeadsDay && stay.item.node.nodeId === node.nodeId;
34361
- return renderServiceNode(item, !titledByDayHeader, stayStart && getStayTitle(stayStart));
34362
- }
34363
- // Every excursion the day holds is drawn once, in one block, where the first of
34364
- // them falls in the order — so the activities still sit between the stay and the
34365
- // flight that follow them.
34366
- if (node.serviceType === EXCURSION_SERVICE_TYPE) {
34367
- return node.nodeId === firstExcursionNodeId ? renderDayActivities(day) : null;
34368
- }
34369
- return renderFlightNode(item);
34370
- }),
34371
- firstExcursionNodeId === undefined && renderDayActivities(day)));
34372
- }),
34373
- React__default["default"].createElement("div", { className: "search__results__cards__actions" },
34374
- React__default["default"].createElement("button", { type: "button", className: "cta", disabled: trajectResolving, onClick: handleShowItinerary }, translations.STEPS.NEXT))));
34611
+ return (React__default["default"].createElement(React__default["default"].Fragment, null, trajectEntry.traject.days.map((day) => {
34612
+ const stay = stayByDay.get(day.order);
34613
+ const dayNodes = (nodesByDay.get(day.order) ?? []).filter((node) => isRenderableNode(node) && lineByNodeId.has(node.nodeId));
34614
+ const stayLeadsDay = !!stay && (stay.night > 0 || dayNodes[0]?.nodeId === stay.item.node.nodeId);
34615
+ // Where the day's activities belong in the order: with the first excursion it carries.
34616
+ const firstExcursionNodeId = dayNodes.find((node) => node.serviceType === EXCURSION_SERVICE_TYPE)?.nodeId;
34617
+ const showDayHeader = stayLeadsDay || !!day.name?.trim();
34618
+ return (React__default["default"].createElement(React__default["default"].Fragment, { key: `day-${day.order}` },
34619
+ showDayHeader && renderDayHeader(day, stayLeadsDay ? stay : undefined),
34620
+ dayNodes.map((node) => {
34621
+ const item = lineByNodeId.get(node.nodeId);
34622
+ if (node.serviceType === ACCOMMODATION_SERVICE_TYPE) {
34623
+ const stayStart = stayStartByNodeId.get(node.nodeId);
34624
+ const titledByDayHeader = stayLeadsDay && stay.item.node.nodeId === node.nodeId;
34625
+ return renderServiceNode(item, !titledByDayHeader, stayStart && getStayTitle(stayStart));
34626
+ }
34627
+ // Every excursion the day holds is drawn once, in one block, where the first of
34628
+ // them falls in the order so the activities still sit between the stay and the
34629
+ // flight that follow them.
34630
+ if (node.serviceType === EXCURSION_SERVICE_TYPE) {
34631
+ return node.nodeId === firstExcursionNodeId ? renderDayActivities(day) : null;
34632
+ }
34633
+ return renderFlightNode(item);
34634
+ }),
34635
+ firstExcursionNodeId === undefined && renderDayActivities(day)));
34636
+ })));
34375
34637
  };
34376
34638
 
34377
34639
  const useTrajectAvailability = () => {
@@ -35318,6 +35580,9 @@ const SearchResultsContainer = ({ onBookingStarted }) => {
35318
35580
  setPricesAreLoading(false);
35319
35581
  }
35320
35582
  };
35583
+ fetchPriceDetails();
35584
+ }, [editablePackagingEntry, bookableEntry, trajectResolving]);
35585
+ React.useEffect(() => {
35321
35586
  const fetchItinerary = async () => {
35322
35587
  if (!context || !context.packagingEntry || !editablePackagingEntry || lodash.isEmpty(editablePackagingEntry.lines))
35323
35588
  return;
@@ -35347,7 +35612,6 @@ const SearchResultsContainer = ({ onBookingStarted }) => {
35347
35612
  setItineraryIsLoading(false);
35348
35613
  }
35349
35614
  };
35350
- fetchPriceDetails();
35351
35615
  fetchItinerary();
35352
35616
  }, [editablePackagingEntry, bookableEntry, trajectResolving, trajectItineraryView]);
35353
35617
  // Flight selection
@@ -35698,11 +35962,6 @@ const SearchResultsContainer = ({ onBookingStarted }) => {
35698
35962
  address: {}
35699
35963
  };
35700
35964
  };
35701
- const handleComposeTraject = () => {
35702
- dispatch(setTrajectItineraryView(false));
35703
- if (typeof window !== 'undefined')
35704
- window.scrollTo(0, 0);
35705
- };
35706
35965
  const handleShowMoreFlights = (flyInType) => {
35707
35966
  dispatch(setFlyInType(flyInType));
35708
35967
  dispatch(setFlyInIsOpen(true));
@@ -35793,10 +36052,7 @@ const SearchResultsContainer = ({ onBookingStarted }) => {
35793
36052
  selectedReturnKey && selectedReturn && (React__default["default"].createElement(IndependentFlightOption, { key: `flight-${selectedReturnKey}`, item: selectedReturn.return, guid: selectedReturn.outwardGuid, selectedGuid: selectedReturn.outwardGuid, isOutward: false, showSelectedState: true, price: selectedReturn.price })),
35794
36053
  uniqueReturnFlights.map((result) => (React__default["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 })))))),
35795
36054
  context.searchConfiguration.qsmType === build.PortalQsmType.AccommodationAndFlight && context.trajectEntry && !trajectItineraryView && (React__default["default"].createElement(TrajectResults, { isLoading: isLoading })),
35796
- context.searchConfiguration.qsmType === build.PortalQsmType.AccommodationAndFlight && context.trajectEntry && trajectItineraryView && (React__default["default"].createElement(React__default["default"].Fragment, null,
35797
- React__default["default"].createElement("div", { className: "search__results__cards__actions" },
35798
- React__default["default"].createElement("button", { type: "button", className: "cta cta--secondary", onClick: handleComposeTraject }, translations.STEPS.PREVIOUS)),
35799
- React__default["default"].createElement(FullItinerary, { isLoading: itineraryIsLoading }))),
36055
+ context.searchConfiguration.qsmType === build.PortalQsmType.AccommodationAndFlight && context.trajectEntry && trajectItineraryView && (React__default["default"].createElement(FullItinerary, { isLoading: itineraryIsLoading })),
35800
36056
  context.searchConfiguration.qsmType === build.PortalQsmType.AccommodationAndFlight && context.packagingEntry && !context.trajectEntry && (React__default["default"].createElement(FullItinerary, { isLoading: itineraryIsLoading })))),
35801
36057
  React__default["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 })))))))));
35802
36058
  };