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