@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.
- package/build/build-cjs/index.js +346 -90
- package/build/build-cjs/src/booking-product/types.d.ts +10 -0
- package/build/build-cjs/src/search-results/components/itinerary/itinerary-utils.d.ts +12 -0
- package/build/build-esm/index.js +347 -91
- package/build/build-esm/src/booking-product/types.d.ts +10 -0
- package/build/build-esm/src/search-results/components/itinerary/itinerary-utils.d.ts +12 -0
- package/package.json +2 -2
- package/styles/components/_search.scss +20 -1
package/build/build-cjs/index.js
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
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 ||
|
|
28518
|
-
!!node.name && !!
|
|
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
|
|
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(/ /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)} → ${to} ${formatTime(leg.arrivalTime)}${carrier ? ` · ${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
|
|
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
|
-
|
|
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--
|
|
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
|
-
|
|
34330
|
-
|
|
34331
|
-
|
|
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
|
-
|
|
34348
|
-
|
|
34349
|
-
|
|
34350
|
-
|
|
34351
|
-
|
|
34352
|
-
|
|
34353
|
-
|
|
34354
|
-
|
|
34355
|
-
|
|
34356
|
-
|
|
34357
|
-
|
|
34358
|
-
|
|
34359
|
-
|
|
34360
|
-
|
|
34361
|
-
|
|
34362
|
-
|
|
34363
|
-
|
|
34364
|
-
|
|
34365
|
-
|
|
34366
|
-
|
|
34367
|
-
|
|
34368
|
-
|
|
34369
|
-
|
|
34370
|
-
|
|
34371
|
-
|
|
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(
|
|
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
|
};
|