@qite/tide-components 1.0.7 → 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 +637 -114
- 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-cjs/src/search-results/hooks/use-bookable-entry.d.ts +11 -0
- package/build/build-cjs/src/search-results/store/search-results-slice.d.ts +3 -1
- package/build/build-cjs/src/search-results/utils/traject-utils.d.ts +48 -10
- package/build/build-esm/index.js +638 -115
- 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/build/build-esm/src/search-results/hooks/use-bookable-entry.d.ts +11 -0
- package/build/build-esm/src/search-results/store/search-results-slice.d.ts +3 -1
- package/build/build-esm/src/search-results/utils/traject-utils.d.ts +48 -10
- 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: "رقم الملف",
|
|
@@ -429,6 +431,7 @@ var SRP$f = {
|
|
|
429
431
|
TRAVEL_GROUP: "مجموعة المسافرين",
|
|
430
432
|
EXCURSION: "رحلة",
|
|
431
433
|
LOADING_EXCURSIONS: "جارٍ تحميل الرحلات...",
|
|
434
|
+
LOADING_ACTIVITIES: "جارٍ تحميل الأنشطة...",
|
|
432
435
|
LOADING_FLIGHTS: "جارٍ تحميل الرحلات الجوية...",
|
|
433
436
|
LOADING_ACCOMMODATIONS: "جارٍ تحميل الإقامات...",
|
|
434
437
|
LOADING_ITINERARY: "جارٍ تحميل خط سير الرحلة...",
|
|
@@ -832,6 +835,8 @@ var SRP$e = {
|
|
|
832
835
|
DEPARTURE_ASC: "Afrejsedato (snarest først)",
|
|
833
836
|
FILTERS: "Filtre",
|
|
834
837
|
SHOW_ITINERARY: "Vis rejseplan",
|
|
838
|
+
VIEW_FULL_ITINERARY: "Se hele rejseplanen",
|
|
839
|
+
SHOW_SERVICES: "Vis ydelser",
|
|
835
840
|
VIEW_BOOKING: "Se booking",
|
|
836
841
|
ITINERARY_TITLE: "Din rejseoversigt",
|
|
837
842
|
DOSSIER_NUMBER: "Sagsnummer",
|
|
@@ -873,6 +878,7 @@ var SRP$e = {
|
|
|
873
878
|
TRAVEL_GROUP: "Rejseselskab",
|
|
874
879
|
EXCURSION: "Udflugt",
|
|
875
880
|
LOADING_EXCURSIONS: "Indlæser udflugter...",
|
|
881
|
+
LOADING_ACTIVITIES: "Indlæser aktiviteter...",
|
|
876
882
|
LOADING_FLIGHTS: "Indlæser fly...",
|
|
877
883
|
LOADING_ACCOMMODATIONS: "Indlæser indkvarteringer...",
|
|
878
884
|
LOADING_ITINERARY: "Indlæser rejseplan...",
|
|
@@ -1280,6 +1286,8 @@ var SRP$d = {
|
|
|
1280
1286
|
DEPARTURE_ASC: "Abreisedatum (früheste zuerst)",
|
|
1281
1287
|
FILTERS: "Filter",
|
|
1282
1288
|
SHOW_ITINERARY: "Reiseverlauf anzeigen",
|
|
1289
|
+
VIEW_FULL_ITINERARY: "Vollständigen Reiseverlauf ansehen",
|
|
1290
|
+
SHOW_SERVICES: "Leistungen anzeigen",
|
|
1283
1291
|
VIEW_BOOKING: "Buchung anzeigen",
|
|
1284
1292
|
ITINERARY_TITLE: "Ihre Reiseübersicht",
|
|
1285
1293
|
DOSSIER_NUMBER: "Vorgangsnummer",
|
|
@@ -1317,6 +1325,7 @@ var SRP$d = {
|
|
|
1317
1325
|
TRAVEL_GROUP: "Reisegruppe",
|
|
1318
1326
|
EXCURSION: "Ausflug",
|
|
1319
1327
|
LOADING_EXCURSIONS: "Lade Ausflüge...",
|
|
1328
|
+
LOADING_ACTIVITIES: "Lade Aktivitäten...",
|
|
1320
1329
|
LOADING_FLIGHTS: "Lade Flüge...",
|
|
1321
1330
|
LOADING_ACCOMMODATIONS: "Lade Unterkünfte...",
|
|
1322
1331
|
LOADING_ITINERARY: "Lade Reiseverlauf...",
|
|
@@ -1728,6 +1737,8 @@ var SRP$c = {
|
|
|
1728
1737
|
DEPARTURE_ASC: "Departure date (soonest first)",
|
|
1729
1738
|
FILTERS: "Filters",
|
|
1730
1739
|
SHOW_ITINERARY: "Show itinerary",
|
|
1740
|
+
VIEW_FULL_ITINERARY: "View full itinerary",
|
|
1741
|
+
SHOW_SERVICES: "Show services",
|
|
1731
1742
|
VIEW_BOOKING: "View booking",
|
|
1732
1743
|
ITINERARY_TITLE: "Your trip summary",
|
|
1733
1744
|
DOSSIER_NUMBER: "File number",
|
|
@@ -1765,6 +1776,7 @@ var SRP$c = {
|
|
|
1765
1776
|
TRAVEL_GROUP: "Travel group",
|
|
1766
1777
|
EXCURSION: "Excursion",
|
|
1767
1778
|
LOADING_EXCURSIONS: "Loading excursions...",
|
|
1779
|
+
LOADING_ACTIVITIES: "Loading activities...",
|
|
1768
1780
|
LOADING_FLIGHTS: "Loading flights...",
|
|
1769
1781
|
LOADING_ACCOMMODATIONS: "Loading accommodations...",
|
|
1770
1782
|
LOADING_ITINERARY: "Loading itinerary...",
|
|
@@ -2172,6 +2184,8 @@ var SRP$b = {
|
|
|
2172
2184
|
DEPARTURE_ASC: "Fecha de salida (más próxima primero)",
|
|
2173
2185
|
FILTERS: "Filtros",
|
|
2174
2186
|
SHOW_ITINERARY: "Mostrar itinerario",
|
|
2187
|
+
VIEW_FULL_ITINERARY: "Ver itinerario completo",
|
|
2188
|
+
SHOW_SERVICES: "Ver servicios",
|
|
2175
2189
|
VIEW_BOOKING: "Ver reserva",
|
|
2176
2190
|
ITINERARY_TITLE: "Resumen de tu viaje",
|
|
2177
2191
|
DOSSIER_NUMBER: "Número de expediente",
|
|
@@ -2209,6 +2223,7 @@ var SRP$b = {
|
|
|
2209
2223
|
TRAVEL_GROUP: "Grupo de viaje",
|
|
2210
2224
|
EXCURSION: "Excursión",
|
|
2211
2225
|
LOADING_EXCURSIONS: "Cargando excursiones...",
|
|
2226
|
+
LOADING_ACTIVITIES: "Cargando actividades...",
|
|
2212
2227
|
LOADING_FLIGHTS: "Cargando vuelos...",
|
|
2213
2228
|
LOADING_ACCOMMODATIONS: "Cargando alojamientos...",
|
|
2214
2229
|
LOADING_ITINERARY: "Cargando itinerario...",
|
|
@@ -2620,6 +2635,8 @@ var SRP$a = {
|
|
|
2620
2635
|
DEPARTURE_ASC: "Date de départ (la plus proche d’abord)",
|
|
2621
2636
|
FILTERS: "Filtres",
|
|
2622
2637
|
SHOW_ITINERARY: "Afficher l’itinéraire",
|
|
2638
|
+
VIEW_FULL_ITINERARY: "Voir l'itinéraire complet",
|
|
2639
|
+
SHOW_SERVICES: "Voir les services",
|
|
2623
2640
|
VIEW_BOOKING: "Voir la réservation",
|
|
2624
2641
|
ITINERARY_TITLE: "Résumé de votre voyage",
|
|
2625
2642
|
DOSSIER_NUMBER: "Numéro de dossier",
|
|
@@ -2657,6 +2674,7 @@ var SRP$a = {
|
|
|
2657
2674
|
TRAVEL_GROUP: "Groupe de voyageurs",
|
|
2658
2675
|
EXCURSION: "Excursion",
|
|
2659
2676
|
LOADING_EXCURSIONS: "Chargement des excursions...",
|
|
2677
|
+
LOADING_ACTIVITIES: "Chargement des activités...",
|
|
2660
2678
|
LOADING_FLIGHTS: "Chargement des vols...",
|
|
2661
2679
|
LOADING_ACCOMMODATIONS: "Chargement des hébergements...",
|
|
2662
2680
|
LOADING_ITINERARY: "Chargement de l’itinéraire...",
|
|
@@ -3064,6 +3082,8 @@ var SRP$9 = {
|
|
|
3064
3082
|
DEPARTURE_ASC: "Date de départ (la plus proche d’abord)",
|
|
3065
3083
|
FILTERS: "Filtres",
|
|
3066
3084
|
SHOW_ITINERARY: "Afficher l’itinéraire",
|
|
3085
|
+
VIEW_FULL_ITINERARY: "Voir l'itinéraire complet",
|
|
3086
|
+
SHOW_SERVICES: "Voir les services",
|
|
3067
3087
|
VIEW_BOOKING: "Voir la réservation",
|
|
3068
3088
|
ITINERARY_TITLE: "Résumé de votre voyage",
|
|
3069
3089
|
DOSSIER_NUMBER: "Numéro de dossier",
|
|
@@ -3101,6 +3121,7 @@ var SRP$9 = {
|
|
|
3101
3121
|
TRAVEL_GROUP: "Groupe de voyageurs",
|
|
3102
3122
|
EXCURSION: "Excursion",
|
|
3103
3123
|
LOADING_EXCURSIONS: "Chargement des excursions...",
|
|
3124
|
+
LOADING_ACTIVITIES: "Chargement des activités...",
|
|
3104
3125
|
LOADING_FLIGHTS: "Chargement des vols...",
|
|
3105
3126
|
LOADING_ACCOMMODATIONS: "Chargement des hébergements...",
|
|
3106
3127
|
LOADING_ITINERARY: "Chargement de l’itinéraire...",
|
|
@@ -3508,6 +3529,8 @@ var SRP$8 = {
|
|
|
3508
3529
|
DEPARTURE_ASC: "Brottfarardagur (nærst fyrst)",
|
|
3509
3530
|
FILTERS: "Síur",
|
|
3510
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",
|
|
3511
3534
|
VIEW_BOOKING: "Skoða bókun",
|
|
3512
3535
|
ITINERARY_TITLE: "Yfirlit ferðarinnar",
|
|
3513
3536
|
DOSSIER_NUMBER: "Skrárnúmer",
|
|
@@ -3545,6 +3568,7 @@ var SRP$8 = {
|
|
|
3545
3568
|
TRAVEL_GROUP: "Ferðahópur",
|
|
3546
3569
|
EXCURSION: "Útflutningur",
|
|
3547
3570
|
LOADING_EXCURSIONS: "Hleðst inn í útflutninga...",
|
|
3571
|
+
LOADING_ACTIVITIES: "Hleð inn afþreyingu...",
|
|
3548
3572
|
LOADING_FLIGHTS: "Hleðst inn í flug...",
|
|
3549
3573
|
LOADING_ACCOMMODATIONS: "Hleðst inn í gistingar...",
|
|
3550
3574
|
LOADING_ITINERARY: "Hleðst inn í ferðaráætlun...",
|
|
@@ -3952,6 +3976,8 @@ var SRP$7 = {
|
|
|
3952
3976
|
DEPARTURE_ASC: "Data di partenza (prima la più vicina)",
|
|
3953
3977
|
FILTERS: "Filtri",
|
|
3954
3978
|
SHOW_ITINERARY: "Mostra itinerario",
|
|
3979
|
+
VIEW_FULL_ITINERARY: "Vedi l'itinerario completo",
|
|
3980
|
+
SHOW_SERVICES: "Mostra i servizi",
|
|
3955
3981
|
VIEW_BOOKING: "Visualizza prenotazione",
|
|
3956
3982
|
ITINERARY_TITLE: "Riepilogo del tuo viaggio",
|
|
3957
3983
|
DOSSIER_NUMBER: "Numero di pratica",
|
|
@@ -3989,6 +4015,7 @@ var SRP$7 = {
|
|
|
3989
4015
|
TRAVEL_GROUP: "Gruppo di viaggio",
|
|
3990
4016
|
EXCURSION: "Escursione",
|
|
3991
4017
|
LOADING_EXCURSIONS: "Caricamento delle escursioni...",
|
|
4018
|
+
LOADING_ACTIVITIES: "Caricamento delle attività...",
|
|
3992
4019
|
LOADING_FLIGHTS: "Caricamento dei voli...",
|
|
3993
4020
|
LOADING_ACCOMMODATIONS: "Caricamento degli alloggi...",
|
|
3994
4021
|
LOADING_ITINERARY: "Caricamento dell'itinerario...",
|
|
@@ -4400,6 +4427,8 @@ var SRP$6 = {
|
|
|
4400
4427
|
DEPARTURE_ASC: "Vertrekdatum (dichtstbij eerst)",
|
|
4401
4428
|
FILTERS: "Filters",
|
|
4402
4429
|
SHOW_ITINERARY: "Toon reisplan",
|
|
4430
|
+
VIEW_FULL_ITINERARY: "Bekijk volledige reisroute",
|
|
4431
|
+
SHOW_SERVICES: "Toon diensten",
|
|
4403
4432
|
VIEW_BOOKING: "Bekijk boeking",
|
|
4404
4433
|
ITINERARY_TITLE: "Overzicht van je reis",
|
|
4405
4434
|
DOSSIER_NUMBER: "Dossiernummer",
|
|
@@ -4437,6 +4466,7 @@ var SRP$6 = {
|
|
|
4437
4466
|
TRAVEL_GROUP: "Reisgezelschap",
|
|
4438
4467
|
EXCURSION: "Excursie",
|
|
4439
4468
|
LOADING_EXCURSIONS: "Excursies laden...",
|
|
4469
|
+
LOADING_ACTIVITIES: "Activiteiten laden...",
|
|
4440
4470
|
LOADING_FLIGHTS: "Vluchten laden...",
|
|
4441
4471
|
LOADING_ACCOMMODATIONS: "Accommodaties laden...",
|
|
4442
4472
|
LOADING_ITINERARY: "Reisplan laden...",
|
|
@@ -4844,6 +4874,8 @@ var SRP$5 = {
|
|
|
4844
4874
|
DEPARTURE_ASC: "Vertrekdatum (dichtstbij eerst)",
|
|
4845
4875
|
FILTERS: "Filters",
|
|
4846
4876
|
SHOW_ITINERARY: "Toon reisplan",
|
|
4877
|
+
VIEW_FULL_ITINERARY: "Bekijk volledige reisroute",
|
|
4878
|
+
SHOW_SERVICES: "Toon diensten",
|
|
4847
4879
|
VIEW_BOOKING: "Bekijk boeking",
|
|
4848
4880
|
ITINERARY_TITLE: "Overzicht van je reis",
|
|
4849
4881
|
DOSSIER_NUMBER: "Dossiernummer",
|
|
@@ -4881,6 +4913,7 @@ var SRP$5 = {
|
|
|
4881
4913
|
TRAVEL_GROUP: "Reisgezelschap",
|
|
4882
4914
|
EXCURSION: "Excursie",
|
|
4883
4915
|
LOADING_EXCURSIONS: "Excursies laden...",
|
|
4916
|
+
LOADING_ACTIVITIES: "Activiteiten laden...",
|
|
4884
4917
|
LOADING_FLIGHTS: "Vluchten laden...",
|
|
4885
4918
|
LOADING_ACCOMMODATIONS: "Accommodaties laden...",
|
|
4886
4919
|
LOADING_ITINERARY: "Reisplan laden...",
|
|
@@ -5288,6 +5321,8 @@ var SRP$4 = {
|
|
|
5288
5321
|
DEPARTURE_ASC: "Avreisedato (nærmeste først)",
|
|
5289
5322
|
FILTERS: "Filtre",
|
|
5290
5323
|
SHOW_ITINERARY: "Vis reiserute",
|
|
5324
|
+
VIEW_FULL_ITINERARY: "Se hele reiseruten",
|
|
5325
|
+
SHOW_SERVICES: "Vis tjenester",
|
|
5291
5326
|
VIEW_BOOKING: "Se bestilling",
|
|
5292
5327
|
ITINERARY_TITLE: "Din reisemåloversikt",
|
|
5293
5328
|
DOSSIER_NUMBER: "Saksnummer",
|
|
@@ -5325,6 +5360,7 @@ var SRP$4 = {
|
|
|
5325
5360
|
TRAVEL_GROUP: "Reisefølge",
|
|
5326
5361
|
EXCURSION: "Utflukt",
|
|
5327
5362
|
LOADING_EXCURSIONS: "Laster utflukter...",
|
|
5363
|
+
LOADING_ACTIVITIES: "Laster aktiviteter...",
|
|
5328
5364
|
LOADING_FLIGHTS: "Laster flyvninger...",
|
|
5329
5365
|
LOADING_ACCOMMODATIONS: "Laster overnattinger...",
|
|
5330
5366
|
LOADING_ITINERARY: "Laster reiserute...",
|
|
@@ -5732,6 +5768,8 @@ var SRP$3 = {
|
|
|
5732
5768
|
DEPARTURE_ASC: "Data wyjazdu (najbliższa najpierw)",
|
|
5733
5769
|
FILTERS: "Filtry",
|
|
5734
5770
|
SHOW_ITINERARY: "Pokaż plan podróży",
|
|
5771
|
+
VIEW_FULL_ITINERARY: "Zobacz pełny plan podróży",
|
|
5772
|
+
SHOW_SERVICES: "Pokaż usługi",
|
|
5735
5773
|
VIEW_BOOKING: "Zobacz rezerwację",
|
|
5736
5774
|
ITINERARY_TITLE: "Podsumowanie Twojej podróży",
|
|
5737
5775
|
DOSSIER_NUMBER: "Numer pliku",
|
|
@@ -5769,6 +5807,7 @@ var SRP$3 = {
|
|
|
5769
5807
|
TRAVEL_GROUP: "Grupa podróżnych",
|
|
5770
5808
|
EXCURSION: "Wycieczka",
|
|
5771
5809
|
LOADING_EXCURSIONS: "Ładowanie wycieczek...",
|
|
5810
|
+
LOADING_ACTIVITIES: "Ładowanie atrakcji...",
|
|
5772
5811
|
LOADING_FLIGHTS: "Ładowanie lotów...",
|
|
5773
5812
|
LOADING_ACCOMMODATIONS: "Ładowanie zakwaterowań...",
|
|
5774
5813
|
LOADING_ITINERARY: "Ładowanie planu podróży...",
|
|
@@ -6176,6 +6215,8 @@ var SRP$2 = {
|
|
|
6176
6215
|
DEPARTURE_ASC: "Data de partida (mais próxima primeiro)",
|
|
6177
6216
|
FILTERS: "Filtros",
|
|
6178
6217
|
SHOW_ITINERARY: "Mostrar itinerário",
|
|
6218
|
+
VIEW_FULL_ITINERARY: "Ver itinerário completo",
|
|
6219
|
+
SHOW_SERVICES: "Ver serviços",
|
|
6179
6220
|
VIEW_BOOKING: "Ver reserva",
|
|
6180
6221
|
ITINERARY_TITLE: "Resumo da sua viagem",
|
|
6181
6222
|
DOSSIER_NUMBER: "Número do processo",
|
|
@@ -6213,6 +6254,7 @@ var SRP$2 = {
|
|
|
6213
6254
|
TRAVEL_GROUP: "Grupo de viajantes",
|
|
6214
6255
|
EXCURSION: "Excursão",
|
|
6215
6256
|
LOADING_EXCURSIONS: "A carregar excursões...",
|
|
6257
|
+
LOADING_ACTIVITIES: "A carregar atividades...",
|
|
6216
6258
|
LOADING_FLIGHTS: "A carregar voos...",
|
|
6217
6259
|
LOADING_ACCOMMODATIONS: "A carregar alojamentos...",
|
|
6218
6260
|
LOADING_ITINERARY: "A carregar itinerário...",
|
|
@@ -6620,6 +6662,8 @@ var SRP$1 = {
|
|
|
6620
6662
|
DEPARTURE_ASC: "Avresedatum (närmast först)",
|
|
6621
6663
|
FILTERS: "Filter",
|
|
6622
6664
|
SHOW_ITINERARY: "Visa resplan",
|
|
6665
|
+
VIEW_FULL_ITINERARY: "Visa hela resplanen",
|
|
6666
|
+
SHOW_SERVICES: "Visa tjänster",
|
|
6623
6667
|
VIEW_BOOKING: "Visa bokning",
|
|
6624
6668
|
ITINERARY_TITLE: "Din resesammanfattning",
|
|
6625
6669
|
DOSSIER_NUMBER: "Filnummer",
|
|
@@ -6657,6 +6701,7 @@ var SRP$1 = {
|
|
|
6657
6701
|
TRAVEL_GROUP: "Resesällskap",
|
|
6658
6702
|
EXCURSION: "Utflykt",
|
|
6659
6703
|
LOADING_EXCURSIONS: "Laddar utflykter...",
|
|
6704
|
+
LOADING_ACTIVITIES: "Laddar aktiviteter...",
|
|
6660
6705
|
LOADING_FLIGHTS: "Laddar flyg...",
|
|
6661
6706
|
LOADING_ACCOMMODATIONS: "Laddar boenden...",
|
|
6662
6707
|
LOADING_ITINERARY: "Laddar resplan...",
|
|
@@ -7064,6 +7109,8 @@ var SRP = {
|
|
|
7064
7109
|
DEPARTURE_ASC: "出発日(早い順)",
|
|
7065
7110
|
FILTERS: "フィルター",
|
|
7066
7111
|
SHOW_ITINERARY: "旅程を表示",
|
|
7112
|
+
VIEW_FULL_ITINERARY: "旅程の詳細を見る",
|
|
7113
|
+
SHOW_SERVICES: "サービスを表示",
|
|
7067
7114
|
VIEW_BOOKING: "予約を見る",
|
|
7068
7115
|
ITINERARY_TITLE: "あなたの旅行概要",
|
|
7069
7116
|
DOSSIER_NUMBER: "ファイル番号",
|
|
@@ -7101,6 +7148,7 @@ var SRP = {
|
|
|
7101
7148
|
TRAVEL_GROUP: "旅行グループ",
|
|
7102
7149
|
EXCURSION: "エクスカーション",
|
|
7103
7150
|
LOADING_EXCURSIONS: "エクスカーションを読み込み中...",
|
|
7151
|
+
LOADING_ACTIVITIES: "アクティビティを読み込み中...",
|
|
7104
7152
|
LOADING_FLIGHTS: "フライトを読み込み中...",
|
|
7105
7153
|
LOADING_ACCOMMODATIONS: "宿泊施設を読み込み中...",
|
|
7106
7154
|
LOADING_ITINERARY: "旅程を読み込み中...",
|
|
@@ -7772,6 +7820,33 @@ var TrajectNodeType = {
|
|
|
7772
7820
|
subroute: 3,
|
|
7773
7821
|
};
|
|
7774
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
|
+
|
|
7775
7850
|
/******************************************************************************
|
|
7776
7851
|
Copyright (c) Microsoft Corporation.
|
|
7777
7852
|
|
|
@@ -15123,6 +15198,7 @@ var submitForm = function (config, id, request, signal) {
|
|
|
15123
15198
|
exports.AllotmentType = AllotmentType;
|
|
15124
15199
|
exports.DataType = DataType;
|
|
15125
15200
|
exports.DossierType = DossierType;
|
|
15201
|
+
exports.DurationType = DurationType;
|
|
15126
15202
|
exports.EntryLineFlag = EntryLineFlag;
|
|
15127
15203
|
exports.EntryLineStatus = EntryLineStatus;
|
|
15128
15204
|
exports.ExportStatus = ExportStatus;
|
|
@@ -15135,6 +15211,7 @@ exports.PaymentType = PaymentType;
|
|
|
15135
15211
|
exports.PriceChartType = PriceChartType;
|
|
15136
15212
|
exports.RequirementType = RequirementType;
|
|
15137
15213
|
exports.RoomType = RoomType;
|
|
15214
|
+
exports.RouteTransportType = RouteTransportType;
|
|
15138
15215
|
exports.SaleType = SaleType;
|
|
15139
15216
|
exports.ServiceTokenError = ServiceTokenError;
|
|
15140
15217
|
exports.ServiceType = ServiceType;
|
|
@@ -16612,6 +16689,16 @@ const Icon = ({ name, className, title, width, height, fill }) => {
|
|
|
16612
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." }),
|
|
16613
16690
|
title && React__default.createElement("title", null, title),
|
|
16614
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" })));
|
|
16615
16702
|
case 'ui-bus':
|
|
16616
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' },
|
|
16617
16704
|
title && React__default.createElement("title", null, title),
|
|
@@ -16925,10 +17012,10 @@ const DateRangePicker$1 = (props) => {
|
|
|
16925
17012
|
month: getMonth(new Date())
|
|
16926
17013
|
});
|
|
16927
17014
|
const [isWaitingForToDate, setWaitingForToDate] = useState(false);
|
|
16928
|
-
const { searchType = 0 } = useContext(SettingsContext$1);
|
|
17015
|
+
const { searchType = 0, allowSingleDayDateSelect = false } = useContext(SettingsContext$1);
|
|
16929
17016
|
const handleDayClick = (day) => {
|
|
16930
17017
|
const { onSelectionChange } = props;
|
|
16931
|
-
if (isWaitingForToDate && !isNil(fromDate) && isAfter(day, fromDate)) {
|
|
17018
|
+
if (isWaitingForToDate && !isNil(fromDate) && (isAfter(day, fromDate) || (allowSingleDayDateSelect && isSameDay(day, fromDate)))) {
|
|
16932
17019
|
if (searchType === 1) {
|
|
16933
17020
|
// Only allow selecting the exact toDate
|
|
16934
17021
|
const expectedToDate = props.toDateByFromDate?.(fromDate);
|
|
@@ -16969,7 +17056,7 @@ const DateRangePicker$1 = (props) => {
|
|
|
16969
17056
|
}
|
|
16970
17057
|
};
|
|
16971
17058
|
const handleDayMouseOver = (day) => {
|
|
16972
|
-
if (isWaitingForToDate && (isNil(fromDate) ||
|
|
17059
|
+
if (isWaitingForToDate && (isNil(fromDate) || isSameDay(day, fromDate) || isAfter(day, fromDate))) {
|
|
16973
17060
|
setToDate(day);
|
|
16974
17061
|
}
|
|
16975
17062
|
};
|
|
@@ -27442,7 +27529,9 @@ const initialState$1 = {
|
|
|
27442
27529
|
trajectFlightAvailability: {},
|
|
27443
27530
|
trajectDaySuggestions: {},
|
|
27444
27531
|
trajectEditLineGuid: null,
|
|
27445
|
-
trajectActivitiesDayOrder: null
|
|
27532
|
+
trajectActivitiesDayOrder: null,
|
|
27533
|
+
trajectResolving: false,
|
|
27534
|
+
trajectItineraryView: false
|
|
27446
27535
|
};
|
|
27447
27536
|
const searchResultsSlice = createSlice({
|
|
27448
27537
|
name: 'searchResults',
|
|
@@ -27712,6 +27801,12 @@ const searchResultsSlice = createSlice({
|
|
|
27712
27801
|
setTrajectActivitiesDayOrder(state, action) {
|
|
27713
27802
|
state.trajectActivitiesDayOrder = action.payload;
|
|
27714
27803
|
},
|
|
27804
|
+
setTrajectResolving(state, action) {
|
|
27805
|
+
state.trajectResolving = action.payload;
|
|
27806
|
+
},
|
|
27807
|
+
setTrajectItineraryView(state, action) {
|
|
27808
|
+
state.trajectItineraryView = action.payload;
|
|
27809
|
+
},
|
|
27715
27810
|
updateTrajectNodeResult(state, action) {
|
|
27716
27811
|
const { lineGuid, result } = action.payload;
|
|
27717
27812
|
const node = state.trajectNodeAvailability[lineGuid];
|
|
@@ -27727,10 +27822,12 @@ const searchResultsSlice = createSlice({
|
|
|
27727
27822
|
state.trajectDaySuggestions = {};
|
|
27728
27823
|
state.trajectEditLineGuid = null;
|
|
27729
27824
|
state.trajectActivitiesDayOrder = null;
|
|
27825
|
+
state.trajectResolving = false;
|
|
27826
|
+
state.trajectItineraryView = false;
|
|
27730
27827
|
}
|
|
27731
27828
|
}
|
|
27732
27829
|
});
|
|
27733
|
-
const { setResults, setFilteredResults, setSelectedSearchResult, setPackagingAccoResults, setFilteredPackagingAccoResults, setFilteredPackagingFlightResults, setPackagingAccoSearchDetails, setSelectedPackagingAccoResult, setPackagingFlightResults, setSelectedPackagingFlight, setSelectedFlight, setSelectedFlightDetails, setBookingPackageDetails, selectFlight, setIsLoading, setFlightsLoading, setInitialFilters, setFilters, resetFilters, setInitialFlightFilters, setFlightFilters, resetFlightFilters, setSortType, setFlightSortType, setActiveTab, setCurrentPage, resetSearchState, setFlyInIsOpen, setEditablePackagingEntry, setTransactionId, setFlyInType, setPriceDetails, setItinerary, setSelectedOutwardKey, setSelectedReturnKey, resetFlightSelection, setExcursionSearchParams, setSelectedExcursionSearchResult, confirmExcursionForDay, removeConfirmedExcursionForDay, clearConfirmedExcursionsForDay, setBookPackagingEntry, setCurrentStep, setBookingNumber, setTrajectNodeLoading, setTrajectNodeAvailability, setTrajectNodeSelection, setTrajectFlightLoading, setTrajectFlightAvailability, setTrajectFlightSelection, setTrajectEditLineGuid, setTrajectActivitiesDayOrder, setTrajectDaySuggestionsLoading, setTrajectDaySuggestions, addEditableEntryLines, updateTrajectNodeResult, resetTrajectAvailability, updateEditableEntryLine, removeEditableEntryLines } = searchResultsSlice.actions;
|
|
27830
|
+
const { setResults, setFilteredResults, setSelectedSearchResult, setPackagingAccoResults, setFilteredPackagingAccoResults, setFilteredPackagingFlightResults, setPackagingAccoSearchDetails, setSelectedPackagingAccoResult, setPackagingFlightResults, setSelectedPackagingFlight, setSelectedFlight, setSelectedFlightDetails, setBookingPackageDetails, selectFlight, setIsLoading, setFlightsLoading, setInitialFilters, setFilters, resetFilters, setInitialFlightFilters, setFlightFilters, resetFlightFilters, setSortType, setFlightSortType, setActiveTab, setCurrentPage, resetSearchState, setFlyInIsOpen, setEditablePackagingEntry, setTransactionId, setFlyInType, setPriceDetails, setItinerary, setSelectedOutwardKey, setSelectedReturnKey, resetFlightSelection, setExcursionSearchParams, setSelectedExcursionSearchResult, confirmExcursionForDay, removeConfirmedExcursionForDay, clearConfirmedExcursionsForDay, setBookPackagingEntry, setCurrentStep, setBookingNumber, setTrajectNodeLoading, setTrajectNodeAvailability, setTrajectNodeSelection, setTrajectResolving, setTrajectItineraryView, setTrajectFlightLoading, setTrajectFlightAvailability, setTrajectFlightSelection, setTrajectEditLineGuid, setTrajectActivitiesDayOrder, setTrajectDaySuggestionsLoading, setTrajectDaySuggestions, addEditableEntryLines, updateTrajectNodeResult, resetTrajectAvailability, updateEditableEntryLine, removeEditableEntryLines } = searchResultsSlice.actions;
|
|
27734
27831
|
var searchResultsReducer = searchResultsSlice.reducer;
|
|
27735
27832
|
|
|
27736
27833
|
const ItemPicker = ({ items, selection, selectedSortByType, label, placeholder, classModifier, onPick, valueFormatter }) => {
|
|
@@ -27978,8 +28075,97 @@ const getServiceTypeBadgeModifier = (serviceType) => {
|
|
|
27978
28075
|
return '';
|
|
27979
28076
|
}
|
|
27980
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
|
+
};
|
|
27981
28158
|
const POI_TRAJECT_NODE_TYPE = 2;
|
|
27982
|
-
|
|
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
|
+
};
|
|
27983
28169
|
const getTrajectNodeBadgeModifier = (node) => node.trajectNodeType === POI_TRAJECT_NODE_TYPE ? 'poi' : getServiceTypeBadgeModifier(node.serviceType);
|
|
27984
28170
|
|
|
27985
28171
|
const REGULAR_TRAJECT_NODE_TYPE = 0;
|
|
@@ -28020,7 +28206,22 @@ const buildDestination = (line, node, result) => {
|
|
|
28020
28206
|
null;
|
|
28021
28207
|
return { id: 0, ...place, ...coordinates };
|
|
28022
28208
|
};
|
|
28023
|
-
const
|
|
28209
|
+
const hasDestination = (destination) => !!destination && (!!destination.id || (!!destination.latitude && !!destination.longitude));
|
|
28210
|
+
const buildTrajectClientConfig = (context) => ({
|
|
28211
|
+
host: context.tideConnection.host,
|
|
28212
|
+
apiKey: context.tideConnection.apiKey
|
|
28213
|
+
});
|
|
28214
|
+
const buildTrajectSearchContext = (context, trajectEntry) => ({
|
|
28215
|
+
transactionId: trajectEntry.entry.transactionId,
|
|
28216
|
+
officeId: context.tideConnection.officeId ?? 1,
|
|
28217
|
+
catalogueId: context.searchConfiguration.defaultCatalogueId ?? context.tideConnection.catalogueIds?.[0] ?? 1,
|
|
28218
|
+
searchConfigurationId: context.searchConfiguration.id,
|
|
28219
|
+
portalId: context.portalId ?? null,
|
|
28220
|
+
agentId: context.agentId ?? null,
|
|
28221
|
+
language: context.languageCode ?? 'en-GB',
|
|
28222
|
+
rooms: getPackagingRoomsFromEntry(trajectEntry.entry)
|
|
28223
|
+
});
|
|
28224
|
+
const buildTrajectNodeRequest = ({ node, line }, context, productCode, destination) => ({
|
|
28024
28225
|
transactionId: context.transactionId,
|
|
28025
28226
|
officeId: context.officeId,
|
|
28026
28227
|
agentId: context.agentId ?? null,
|
|
@@ -28031,25 +28232,69 @@ const buildTrajectNodeRequest = ({ node, line }, context, productCode) => ({
|
|
|
28031
28232
|
language: context.language,
|
|
28032
28233
|
serviceType: node.serviceType,
|
|
28033
28234
|
fromDate: toDateOnlyString(line.from),
|
|
28034
|
-
|
|
28035
|
-
|
|
28235
|
+
// An excursion happens on a day rather than over a span. The traject gives its line an end a day
|
|
28236
|
+
// out, the way a night is counted, which asks the search for a window it does not have.
|
|
28237
|
+
toDate: toDateOnlyString(node.serviceType === EXCURSION_SERVICE_TYPE ? line.from : line.to),
|
|
28238
|
+
destination: destination ?? buildDestination(line, node),
|
|
28036
28239
|
productCode,
|
|
28037
28240
|
rooms: context.rooms,
|
|
28038
28241
|
tagIds: []
|
|
28039
28242
|
});
|
|
28040
|
-
|
|
28041
|
-
|
|
28042
|
-
|
|
28043
|
-
|
|
28044
|
-
|
|
28045
|
-
|
|
28046
|
-
|
|
28047
|
-
|
|
28048
|
-
|
|
28049
|
-
|
|
28050
|
-
|
|
28051
|
-
|
|
28243
|
+
const coversDay = ({ node, line }, dayOrder) => {
|
|
28244
|
+
const nights = Math.max(differenceInCalendarDays(new Date(line.to), new Date(line.from)), 1);
|
|
28245
|
+
return dayOrder >= node.dayOrder && dayOrder < node.dayOrder + nights;
|
|
28246
|
+
};
|
|
28247
|
+
const getDayDestinationItem = (items, dayOrder) => {
|
|
28248
|
+
const knowsWhereItIs = (x) => hasDestination(buildDestination(x.line, x.node));
|
|
28249
|
+
const stay = items.find((x) => x.node.serviceType === ACCOMMODATION_SERVICE_TYPE && coversDay(x, dayOrder) && knowsWhereItIs(x));
|
|
28250
|
+
if (stay)
|
|
28251
|
+
return stay;
|
|
28252
|
+
return ([...items]
|
|
28253
|
+
.filter((x) => x.node.dayOrder <= dayOrder && knowsWhereItIs(x))
|
|
28254
|
+
.sort((a, b) => a.node.dayOrder - b.node.dayOrder || a.node.order - b.node.order)
|
|
28255
|
+
.pop() ?? null);
|
|
28256
|
+
};
|
|
28257
|
+
const getDayDestination = (items, availability, dayOrder) => {
|
|
28258
|
+
const item = getDayDestinationItem(items, dayOrder);
|
|
28259
|
+
if (!item)
|
|
28260
|
+
return null;
|
|
28261
|
+
const resolved = availability[item.line.guid];
|
|
28262
|
+
const result = resolved?.results.find((x) => x.code === resolved.selectedCode) ?? null;
|
|
28263
|
+
const destination = buildDestination(item.line, item.node, result);
|
|
28264
|
+
return hasDestination(destination) ? destination : null;
|
|
28265
|
+
};
|
|
28266
|
+
const buildTrajectDayExcursionRequest = (date, destination, context) => ({
|
|
28267
|
+
transactionId: context.transactionId,
|
|
28268
|
+
officeId: context.officeId,
|
|
28269
|
+
agentId: context.agentId ?? null,
|
|
28270
|
+
catalogueId: context.catalogueId,
|
|
28271
|
+
searchConfigurationId: context.searchConfigurationId,
|
|
28272
|
+
portalId: context.portalId ?? null,
|
|
28273
|
+
vendorConfigurationId: null,
|
|
28274
|
+
language: context.language,
|
|
28275
|
+
serviceType: EXCURSION_SERVICE_TYPE,
|
|
28276
|
+
fromDate: toDateOnlyString(date),
|
|
28277
|
+
toDate: toDateOnlyString(date),
|
|
28278
|
+
destination,
|
|
28279
|
+
productCode: '',
|
|
28280
|
+
rooms: context.rooms,
|
|
28281
|
+
tagIds: []
|
|
28282
|
+
});
|
|
28283
|
+
const getExcursionOptionGroups = (result) => {
|
|
28284
|
+
const groups = new Map();
|
|
28285
|
+
(result.rooms ?? [])
|
|
28286
|
+
.flatMap((room) => room.options ?? [])
|
|
28287
|
+
.forEach((option) => {
|
|
28288
|
+
const key = [...(option.paxIds ?? [])].sort((a, b) => a - b).join(',');
|
|
28289
|
+
groups.set(key, [...(groups.get(key) ?? []), option]);
|
|
28052
28290
|
});
|
|
28291
|
+
return [...groups.values()];
|
|
28292
|
+
};
|
|
28293
|
+
const excursionNeedsChoice = (result) => getExcursionOptionGroups(result).some((group) => group.length > 1 && !group.some((option) => option.isSelected));
|
|
28294
|
+
const buildTrajectExcursionLines = (result) => {
|
|
28295
|
+
const options = getExcursionOptionGroups(result)
|
|
28296
|
+
.map((group) => group.find((x) => x.isSelected) ?? (group.length === 1 ? group[0] : null))
|
|
28297
|
+
.filter((x) => !!x);
|
|
28053
28298
|
if (!options.length)
|
|
28054
28299
|
return [];
|
|
28055
28300
|
const guids = options.map((option) => option.guid || crypto.randomUUID());
|
|
@@ -28078,6 +28323,21 @@ const buildTrajectExcursionLines = (result) => {
|
|
|
28078
28323
|
flightInformation: null
|
|
28079
28324
|
}));
|
|
28080
28325
|
};
|
|
28326
|
+
const buildExcursionSearchParams = (result) => ({
|
|
28327
|
+
date: result.fromDate,
|
|
28328
|
+
fromDate: result.fromDate,
|
|
28329
|
+
toDate: result.toDate,
|
|
28330
|
+
countryId: result.countryId,
|
|
28331
|
+
regionId: result.regionId,
|
|
28332
|
+
oordId: result.oordId,
|
|
28333
|
+
locationId: result.locationId,
|
|
28334
|
+
locationName: result.locationName,
|
|
28335
|
+
accommodationCode: null,
|
|
28336
|
+
accommodationName: null,
|
|
28337
|
+
hotelCode: result.code,
|
|
28338
|
+
hotelName: result.name,
|
|
28339
|
+
rooms: result.rooms ?? []
|
|
28340
|
+
});
|
|
28081
28341
|
const buildDayActivityCards = (items, availability, suggestions) => {
|
|
28082
28342
|
const cards = [];
|
|
28083
28343
|
const seen = new Set();
|
|
@@ -28094,11 +28354,27 @@ const buildDayActivityCards = (items, availability, suggestions) => {
|
|
|
28094
28354
|
results.filter((x) => !!findCandidateForResult(item.node, x)).forEach((result) => push(result, item));
|
|
28095
28355
|
});
|
|
28096
28356
|
suggestions.forEach((result) => push(result));
|
|
28097
|
-
|
|
28098
|
-
// nothing, as they are the same search, but a node can resolve where a day has no place to look.
|
|
28099
|
-
items.forEach((item) => (availability[item.line.guid]?.results ?? []).forEach((result) => push(result, item)));
|
|
28357
|
+
items.forEach((item) => (availability[item.line.guid]?.results ?? []).forEach((result) => push(result)));
|
|
28100
28358
|
return cards;
|
|
28101
28359
|
};
|
|
28360
|
+
/**
|
|
28361
|
+
* Where to search for a node.
|
|
28362
|
+
*
|
|
28363
|
+
* Normally the node says so itself, through the product it was configured with. An excursion slot
|
|
28364
|
+
* left empty on the traject says nothing at all — no code, no alternatives, and a line with no
|
|
28365
|
+
* place on it — so it falls back to wherever the party is staying that day, the same anchor the
|
|
28366
|
+
* day's own activity search uses.
|
|
28367
|
+
*/
|
|
28368
|
+
const getNodeSearchDestination = (items, item) => {
|
|
28369
|
+
const own = buildDestination(item.line, item.node);
|
|
28370
|
+
if (hasDestination(own))
|
|
28371
|
+
return own;
|
|
28372
|
+
return getDayDestination(items, {}, item.node.dayOrder) ?? own;
|
|
28373
|
+
};
|
|
28374
|
+
const getConfiguredExcursionCodes = (items, dayOrder) => new Set(items
|
|
28375
|
+
.filter((x) => x.node.dayOrder === dayOrder && x.node.serviceType === EXCURSION_SERVICE_TYPE)
|
|
28376
|
+
.flatMap((x) => [x.node.code, ...getNodeCandidates(x.node).map((candidate) => candidate.code)])
|
|
28377
|
+
.filter((code) => !!code));
|
|
28102
28378
|
const getNodeCandidates = (node) => {
|
|
28103
28379
|
const configured = (node.alternatives ?? []).filter((x) => !!x.code && x.code.trim().length > 0);
|
|
28104
28380
|
if (configured.length)
|
|
@@ -28207,6 +28483,51 @@ const buildTrajectFlightRequest = ({ node, line }, context, pax) => ({
|
|
|
28207
28483
|
outward: { date: dateToDateStruct(new Date(line.from)) },
|
|
28208
28484
|
return: null
|
|
28209
28485
|
});
|
|
28486
|
+
/**
|
|
28487
|
+
* The entry as the backend can price it.
|
|
28488
|
+
*
|
|
28489
|
+
* A search caches an EntryLine per option and hands back that line's guid on the option. The
|
|
28490
|
+
* backend then rebuilds the entry by looking each line up in that cache on its guid, so a line
|
|
28491
|
+
* it cannot find is dropped — PackagingEntryBuilder.SyncEntryLines skips it, and an entry whose
|
|
28492
|
+
* lines all came off a traject prices as nothing at all.
|
|
28493
|
+
*
|
|
28494
|
+
* A traject entry is rendered before any search runs, so its lines carry the guids the traject
|
|
28495
|
+
* mapper gave them. This swaps in the guid of whatever each line resolved onto, leaving the
|
|
28496
|
+
* lines the traveller added themselves alone — those were built from a search result and already
|
|
28497
|
+
* carry the right one.
|
|
28498
|
+
*/
|
|
28499
|
+
const withCachedEntryLineGuids = (entry, trajectEntry, nodeAvailability, flightAvailability) => {
|
|
28500
|
+
const cachedGuids = new Map();
|
|
28501
|
+
getTrajectNodesWithLines(trajectEntry).forEach((item) => {
|
|
28502
|
+
if (isTrajectFlightNode(item.node)) {
|
|
28503
|
+
// A flight search caches the outward line under the guid it reports as outwardGuid.
|
|
28504
|
+
const guid = flightAvailability[item.line.guid]?.selectedGuid;
|
|
28505
|
+
if (guid)
|
|
28506
|
+
cachedGuids.set(item.line.guid, guid);
|
|
28507
|
+
return;
|
|
28508
|
+
}
|
|
28509
|
+
const availability = nodeAvailability[item.line.guid];
|
|
28510
|
+
const result = availability?.results.find((x) => x.code === availability.selectedCode);
|
|
28511
|
+
if (!result)
|
|
28512
|
+
return;
|
|
28513
|
+
// Room i took option i when the result was stamped on, so it takes option i here too.
|
|
28514
|
+
item.lines.forEach((line, index) => {
|
|
28515
|
+
const guid = getOptionForRoom(result, index)?.guid;
|
|
28516
|
+
if (guid)
|
|
28517
|
+
cachedGuids.set(line.guid, guid);
|
|
28518
|
+
});
|
|
28519
|
+
});
|
|
28520
|
+
if (!cachedGuids.size)
|
|
28521
|
+
return entry;
|
|
28522
|
+
return {
|
|
28523
|
+
...entry,
|
|
28524
|
+
lines: (entry.lines ?? []).map((line) => ({
|
|
28525
|
+
...line,
|
|
28526
|
+
guid: cachedGuids.get(line.guid) ?? line.guid,
|
|
28527
|
+
parentGuid: (line.parentGuid && cachedGuids.get(line.parentGuid)) ?? line.parentGuid
|
|
28528
|
+
}))
|
|
28529
|
+
};
|
|
28530
|
+
};
|
|
28210
28531
|
const pickCheapestFlight = (flights) => [...flights].sort((a, b) => a.price - b.price)[0] ?? null;
|
|
28211
28532
|
const applyFlightToLine = (line, flight) => {
|
|
28212
28533
|
const segments = flight.outward?.segments ?? [];
|
|
@@ -28283,6 +28604,8 @@ const TrajectItinerary = ({ trajectEntry, packagingEntry, translations }) => {
|
|
|
28283
28604
|
trajectNodeType: REGULAR_TRAJECT_NODE_TYPE,
|
|
28284
28605
|
requirementType: 0,
|
|
28285
28606
|
contentSource: 0,
|
|
28607
|
+
duration: 0,
|
|
28608
|
+
durationType: 0,
|
|
28286
28609
|
alternatives: [],
|
|
28287
28610
|
from: line.from,
|
|
28288
28611
|
to: line.to
|
|
@@ -28316,6 +28639,8 @@ const TrajectItinerary = ({ trajectEntry, packagingEntry, translations }) => {
|
|
|
28316
28639
|
React__default.createElement("p", null, format$2(new Date(value), 'MMM'))));
|
|
28317
28640
|
const renderConnectingNode = ({ node }) => {
|
|
28318
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(' · ');
|
|
28319
28644
|
return (React__default.createElement("div", { className: "search__filter__itinerary__transport", key: `connect-${node.nodeId}` },
|
|
28320
28645
|
React__default.createElement("div", { className: "search__filter__itinerary__transport-timeline" },
|
|
28321
28646
|
React__default.createElement("div", { className: "search__filter__itinerary__transport-timeline-line" })),
|
|
@@ -28324,8 +28649,8 @@ const TrajectItinerary = ({ trajectEntry, packagingEntry, translations }) => {
|
|
|
28324
28649
|
React__default.createElement("div", { className: badgeClass('transport', getTrajectNodeBadgeModifier(node)) },
|
|
28325
28650
|
React__default.createElement(Icon, { name: getTrajectNodeIcon(node), height: 15 })),
|
|
28326
28651
|
React__default.createElement("div", { className: "search__filter__itinerary__transport-details" },
|
|
28327
|
-
React__default.createElement("h6", null, node.name ||
|
|
28328
|
-
!!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)))));
|
|
28329
28654
|
};
|
|
28330
28655
|
const renderOvernight = (item, index) => {
|
|
28331
28656
|
const { dayOrder } = item.node;
|
|
@@ -28469,7 +28794,7 @@ const getServiceTypePriority = (serviceType) => {
|
|
|
28469
28794
|
const Itinerary = ({ isOpen, handleSetIsOpen, isLoading, onEditAccommodation }) => {
|
|
28470
28795
|
const context = useContext(SearchResultsConfigurationContext);
|
|
28471
28796
|
const translations = getTranslations(context?.languageCode ?? 'en-GB');
|
|
28472
|
-
const { editablePackagingEntry, priceDetails } = useSelector((state) => state.searchResults);
|
|
28797
|
+
const { editablePackagingEntry, priceDetails, trajectItineraryView } = useSelector((state) => state.searchResults);
|
|
28473
28798
|
const dispatch = useDispatch();
|
|
28474
28799
|
const packagingEntry = editablePackagingEntry ?? context?.packagingEntry;
|
|
28475
28800
|
const sortedLines = useMemo(() => {
|
|
@@ -28512,6 +28837,11 @@ const Itinerary = ({ isOpen, handleSetIsOpen, isLoading, onEditAccommodation })
|
|
|
28512
28837
|
const handleConfirm = () => {
|
|
28513
28838
|
dispatch(setBookPackagingEntry(true));
|
|
28514
28839
|
};
|
|
28840
|
+
const handleTrajectView = (itinerary) => {
|
|
28841
|
+
dispatch(setTrajectItineraryView(itinerary));
|
|
28842
|
+
if (typeof window !== 'undefined')
|
|
28843
|
+
window.scrollTo(0, 0);
|
|
28844
|
+
};
|
|
28515
28845
|
return (React__default.createElement("div", { className: `search__filters--modal ${isOpen ? 'is-open' : ''}` },
|
|
28516
28846
|
React__default.createElement("div", { className: "search__filters--background", onClick: handleSetIsOpen }),
|
|
28517
28847
|
React__default.createElement("button", { className: "search__filters--close", onClick: handleSetIsOpen },
|
|
@@ -28539,7 +28869,8 @@ const Itinerary = ({ isOpen, handleSetIsOpen, isLoading, onEditAccommodation })
|
|
|
28539
28869
|
" ",
|
|
28540
28870
|
translations.SRP.TOTAL,
|
|
28541
28871
|
")"))),
|
|
28542
|
-
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))))),
|
|
28543
28874
|
React__default.createElement("div", { className: "search__filter__itinerary" },
|
|
28544
28875
|
React__default.createElement("p", null, translations.SRP.DAY_BY_DAY),
|
|
28545
28876
|
firstEntryLine && (React__default.createElement("div", { className: "search__filter__itinerary__country" },
|
|
@@ -31033,10 +31364,9 @@ const getOptionPaxIds = (option) => {
|
|
|
31033
31364
|
const optionAppliesToPax = (option, paxId) => {
|
|
31034
31365
|
return getOptionPaxIds(option).includes(paxId);
|
|
31035
31366
|
};
|
|
31036
|
-
const optionAppliesToAllTravellers = (option,
|
|
31367
|
+
const optionAppliesToAllTravellers = (option, travellerIds) => {
|
|
31037
31368
|
const paxIds = getOptionPaxIds(option);
|
|
31038
|
-
|
|
31039
|
-
return paxIds.length === expected.length && paxIds.every((id, index) => id === expected[index]);
|
|
31369
|
+
return paxIds.length === travellerIds.length && travellerIds.every((id) => paxIds.includes(id));
|
|
31040
31370
|
};
|
|
31041
31371
|
const groupOptionsByExcursion = (options) => {
|
|
31042
31372
|
const groupedMap = new Map();
|
|
@@ -31061,26 +31391,26 @@ const ExcursionDetails = () => {
|
|
|
31061
31391
|
return null;
|
|
31062
31392
|
}
|
|
31063
31393
|
const translations = getTranslations(context.languageCode ?? 'en-GB');
|
|
31064
|
-
const
|
|
31394
|
+
const travellerIds = editablePackagingEntry.pax.map((pax) => pax.id);
|
|
31065
31395
|
const allOptions = useMemo(() => {
|
|
31066
31396
|
return selectedExcursionSearchResult.rooms.flatMap((room) => room.options ?? []);
|
|
31067
31397
|
}, [selectedExcursionSearchResult]);
|
|
31068
31398
|
const sharedOptions = useMemo(() => {
|
|
31069
|
-
return allOptions.filter((option) => optionAppliesToAllTravellers(option,
|
|
31070
|
-
}, [allOptions,
|
|
31399
|
+
return allOptions.filter((option) => optionAppliesToAllTravellers(option, travellerIds));
|
|
31400
|
+
}, [allOptions, travellerIds]);
|
|
31071
31401
|
const sharedExcursions = useMemo(() => {
|
|
31072
31402
|
return groupOptionsByExcursion(sharedOptions);
|
|
31073
31403
|
}, [sharedOptions]);
|
|
31074
31404
|
const paxGroups = useMemo(() => {
|
|
31075
31405
|
return editablePackagingEntry.pax.map((pax) => {
|
|
31076
|
-
const paxOptions = allOptions.filter((option) => optionAppliesToPax(option, pax.id) && !optionAppliesToAllTravellers(option,
|
|
31406
|
+
const paxOptions = allOptions.filter((option) => optionAppliesToPax(option, pax.id) && !optionAppliesToAllTravellers(option, travellerIds));
|
|
31077
31407
|
return {
|
|
31078
31408
|
pax,
|
|
31079
31409
|
paxId: pax.id,
|
|
31080
31410
|
excursions: groupOptionsByExcursion(paxOptions)
|
|
31081
31411
|
};
|
|
31082
31412
|
});
|
|
31083
|
-
}, [editablePackagingEntry.pax, allOptions,
|
|
31413
|
+
}, [editablePackagingEntry.pax, allOptions, travellerIds]);
|
|
31084
31414
|
const getSelectedSharedOption = () => {
|
|
31085
31415
|
return sharedOptions.find((option) => option.isSelected);
|
|
31086
31416
|
};
|
|
@@ -31088,11 +31418,11 @@ const ExcursionDetails = () => {
|
|
|
31088
31418
|
return sharedOptions.find((option) => option.accommodationCode === accommodationCode && option.isSelected);
|
|
31089
31419
|
};
|
|
31090
31420
|
const getSelectedOptionForPax = (paxId) => {
|
|
31091
|
-
return allOptions.find((option) => optionAppliesToPax(option, paxId) && !optionAppliesToAllTravellers(option,
|
|
31421
|
+
return allOptions.find((option) => optionAppliesToPax(option, paxId) && !optionAppliesToAllTravellers(option, travellerIds) && option.isSelected);
|
|
31092
31422
|
};
|
|
31093
31423
|
const getSelectedOptionForExcursion = (paxId, accommodationCode) => {
|
|
31094
31424
|
return allOptions.find((option) => optionAppliesToPax(option, paxId) &&
|
|
31095
|
-
!optionAppliesToAllTravellers(option,
|
|
31425
|
+
!optionAppliesToAllTravellers(option, travellerIds) &&
|
|
31096
31426
|
option.accommodationCode === accommodationCode &&
|
|
31097
31427
|
option.isSelected);
|
|
31098
31428
|
};
|
|
@@ -31102,7 +31432,7 @@ const ExcursionDetails = () => {
|
|
|
31102
31432
|
rooms: selectedExcursionSearchResult.rooms.map((room) => ({
|
|
31103
31433
|
...room,
|
|
31104
31434
|
options: room.options.map((option) => {
|
|
31105
|
-
const isSharedOption = optionAppliesToAllTravellers(option,
|
|
31435
|
+
const isSharedOption = optionAppliesToAllTravellers(option, travellerIds);
|
|
31106
31436
|
if (paxId === undefined) {
|
|
31107
31437
|
if (!isSharedOption) {
|
|
31108
31438
|
return option;
|
|
@@ -31149,7 +31479,7 @@ const ExcursionDetails = () => {
|
|
|
31149
31479
|
targetPrice = selectedOption.price;
|
|
31150
31480
|
}
|
|
31151
31481
|
else {
|
|
31152
|
-
const firstOption = allOptions.find((option) => optionAppliesToPax(option, paxId) && !optionAppliesToAllTravellers(option,
|
|
31482
|
+
const firstOption = allOptions.find((option) => optionAppliesToPax(option, paxId) && !optionAppliesToAllTravellers(option, travellerIds) && option.accommodationCode === accommodationCode);
|
|
31153
31483
|
targetPrice = firstOption?.price || 0;
|
|
31154
31484
|
}
|
|
31155
31485
|
return targetPrice - (currentSelectedPrice || 0);
|
|
@@ -31171,6 +31501,13 @@ const ExcursionDetails = () => {
|
|
|
31171
31501
|
return 'flyin__acco__price';
|
|
31172
31502
|
};
|
|
31173
31503
|
const handleConfirm = () => {
|
|
31504
|
+
if (context.trajectEntry) {
|
|
31505
|
+
const lines = buildTrajectExcursionLines(selectedExcursionSearchResult);
|
|
31506
|
+
if (lines.length)
|
|
31507
|
+
dispatch(addEditableEntryLines(lines));
|
|
31508
|
+
dispatch(setFlyInIsOpen(false));
|
|
31509
|
+
return;
|
|
31510
|
+
}
|
|
31174
31511
|
const dayKey = getExcursionDayKey(excursionSearchParams.date);
|
|
31175
31512
|
dispatch(confirmExcursionForDay({
|
|
31176
31513
|
dayKey,
|
|
@@ -31412,6 +31749,12 @@ const TrajectDayActivitiesFlyIn = ({ isLoading }) => {
|
|
|
31412
31749
|
dispatch(removeEditableEntryLines(added.map((line) => line.guid)));
|
|
31413
31750
|
return;
|
|
31414
31751
|
}
|
|
31752
|
+
if (excursionNeedsChoice(result)) {
|
|
31753
|
+
dispatch(setExcursionSearchParams(buildExcursionSearchParams(result)));
|
|
31754
|
+
dispatch(setSelectedExcursionSearchResult(result));
|
|
31755
|
+
dispatch(setFlyInType('excursion-details'));
|
|
31756
|
+
return;
|
|
31757
|
+
}
|
|
31415
31758
|
const lines = buildTrajectExcursionLines(result);
|
|
31416
31759
|
if (lines.length)
|
|
31417
31760
|
dispatch(addEditableEntryLines(lines));
|
|
@@ -31426,7 +31769,7 @@ const TrajectDayActivitiesFlyIn = ({ isLoading }) => {
|
|
|
31426
31769
|
dispatch(setFlyInIsOpen(false));
|
|
31427
31770
|
};
|
|
31428
31771
|
if (isLoading || suggestions?.status === 'loading') {
|
|
31429
|
-
return React__default.createElement(React__default.Fragment, null, context.customSpinner ?? React__default.createElement(Spinner, { label: translations.SRP.
|
|
31772
|
+
return React__default.createElement(React__default.Fragment, null, context.customSpinner ?? React__default.createElement(Spinner, { label: translations.SRP.LOADING_ACTIVITIES }));
|
|
31430
31773
|
}
|
|
31431
31774
|
return (React__default.createElement("div", { className: "flyin__content" },
|
|
31432
31775
|
React__default.createElement("div", { className: "search__result-row" },
|
|
@@ -32980,6 +33323,27 @@ const getRequestRoomsFromPackagingSegments = (entry, segments) => {
|
|
|
32980
33323
|
}));
|
|
32981
33324
|
};
|
|
32982
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
|
+
|
|
32983
33347
|
const escapeHtml = (value) => {
|
|
32984
33348
|
if (!value)
|
|
32985
33349
|
return '';
|
|
@@ -33038,7 +33402,57 @@ const findAccommodationForDay = (nodes, currentDay) => {
|
|
|
33038
33402
|
}
|
|
33039
33403
|
return null;
|
|
33040
33404
|
};
|
|
33041
|
-
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) => {
|
|
33042
33456
|
if (!itinerary) {
|
|
33043
33457
|
return `
|
|
33044
33458
|
<div class="itinerary-shell">
|
|
@@ -33067,7 +33481,9 @@ const buildItineraryHtml = (itinerary, translations) => {
|
|
|
33067
33481
|
? uniqueItems
|
|
33068
33482
|
.map((item) => `
|
|
33069
33483
|
<article class="itinerary-item" data-template="${escapeHtml(item.templateName)}">
|
|
33070
|
-
${item.contents
|
|
33484
|
+
${hasVisibleContent(item.contents)
|
|
33485
|
+
? item.contents
|
|
33486
|
+
: buildFallbackHtml(item, lines.find((line) => line.guid === item.itemGuid))}
|
|
33071
33487
|
</article>
|
|
33072
33488
|
`)
|
|
33073
33489
|
.join('')
|
|
@@ -33101,7 +33517,9 @@ const buildItineraryHtml = (itinerary, translations) => {
|
|
|
33101
33517
|
${getUniqueItems(itinerary.defaultItems)
|
|
33102
33518
|
.map((item) => `
|
|
33103
33519
|
<article class="itinerary-item" data-template="${escapeHtml(item.templateName)}">
|
|
33104
|
-
${item.contents
|
|
33520
|
+
${hasVisibleContent(item.contents)
|
|
33521
|
+
? item.contents
|
|
33522
|
+
: buildFallbackHtml(item, lines.find((line) => line.guid === item.itemGuid))}
|
|
33105
33523
|
</article>
|
|
33106
33524
|
`)
|
|
33107
33525
|
.join('')}
|
|
@@ -33122,10 +33540,12 @@ const buildItineraryHtml = (itinerary, translations) => {
|
|
|
33122
33540
|
const FullItinerary = ({ isLoading }) => {
|
|
33123
33541
|
const { itinerary } = useSelector((state) => state.searchResults);
|
|
33124
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();
|
|
33125
33545
|
const translations = getTranslations(context?.languageCode ?? 'en-GB');
|
|
33126
33546
|
const hostRef = useRef(null);
|
|
33127
33547
|
const shadowRootRef = useRef(null);
|
|
33128
|
-
const html = useMemo(() => buildItineraryHtml(itinerary, translations), [itinerary, translations]);
|
|
33548
|
+
const html = useMemo(() => buildItineraryHtml(itinerary, translations, bookableEntry?.lines ?? []), [itinerary, translations, bookableEntry]);
|
|
33129
33549
|
useEffect(() => {
|
|
33130
33550
|
if (isLoading) {
|
|
33131
33551
|
return;
|
|
@@ -33239,6 +33659,35 @@ const FullItinerary = ({ isLoading }) => {
|
|
|
33239
33659
|
background: #fff;
|
|
33240
33660
|
}
|
|
33241
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
|
+
|
|
33242
33691
|
.itinerary-node__empty,
|
|
33243
33692
|
.itinerary-empty {
|
|
33244
33693
|
padding: 20px;
|
|
@@ -33758,6 +34207,8 @@ const BookPackagingEntry = ({ activeSearchSeed, isLoading, isConfirmationPage })
|
|
|
33758
34207
|
setSelectedPackagingAccoResult(selectedPackagingAccoResult);
|
|
33759
34208
|
}
|
|
33760
34209
|
}, [selectedPackagingAccoResultCode, packagingAccoResults]);
|
|
34210
|
+
// What the page holds versus what the backend can look up; they differ for a traject.
|
|
34211
|
+
const bookableEntry = useBookableEntry();
|
|
33761
34212
|
if (!context || !editablePackagingEntry || !priceDetails || !config)
|
|
33762
34213
|
return React__default.createElement(Spinner, { label: translations.SUMMARY.PROCESS_BOOKING });
|
|
33763
34214
|
const handleSummarySubmit = async (e) => {
|
|
@@ -33766,20 +34217,15 @@ const BookPackagingEntry = ({ activeSearchSeed, isLoading, isConfirmationPage })
|
|
|
33766
34217
|
if (typeof window !== 'undefined') {
|
|
33767
34218
|
window.scrollTo(0, 0);
|
|
33768
34219
|
}
|
|
33769
|
-
|
|
33770
|
-
|
|
33771
|
-
remarks
|
|
33772
|
-
};
|
|
34220
|
+
// Held apart from the entry itself so they can be put on both copies of it.
|
|
34221
|
+
let bookingFields = { remarks };
|
|
33773
34222
|
if (context.generatePaymentUrl && typeof window !== 'undefined') {
|
|
33774
34223
|
const redirectUrl = new URL(window.location.href);
|
|
33775
34224
|
redirectUrl.searchParams.set('bookingConfirmation', 'true');
|
|
33776
34225
|
redirectUrl.searchParams.set('link', '');
|
|
33777
|
-
|
|
33778
|
-
...updatedEditablePackagingEntry,
|
|
33779
|
-
redirectUrl: redirectUrl.toString(),
|
|
33780
|
-
returnPaymentUrl: true
|
|
33781
|
-
};
|
|
34226
|
+
bookingFields = { ...bookingFields, redirectUrl: redirectUrl.toString(), returnPaymentUrl: true };
|
|
33782
34227
|
}
|
|
34228
|
+
const updatedEditablePackagingEntry = { ...editablePackagingEntry, ...bookingFields };
|
|
33783
34229
|
dispatch(setEditablePackagingEntry(updatedEditablePackagingEntry));
|
|
33784
34230
|
try {
|
|
33785
34231
|
const request = {
|
|
@@ -33787,7 +34233,8 @@ const BookPackagingEntry = ({ activeSearchSeed, isLoading, isConfirmationPage })
|
|
|
33787
34233
|
officeId: context.tideConnection.officeId,
|
|
33788
34234
|
catalogueId: context.searchConfiguration.defaultCatalogueId ?? 0,
|
|
33789
34235
|
agentId: context.agentId,
|
|
33790
|
-
|
|
34236
|
+
// The page keeps the entry it works with; the backend gets the one it can look up.
|
|
34237
|
+
payload: { ...(bookableEntry ?? editablePackagingEntry), ...bookingFields }
|
|
33791
34238
|
};
|
|
33792
34239
|
const bookingResponse = await build.bookPackagingEntry(config, request);
|
|
33793
34240
|
dispatch(setBookingNumber(bookingResponse.number));
|
|
@@ -33819,6 +34266,41 @@ const BookPackagingEntry = ({ activeSearchSeed, isLoading, isConfirmationPage })
|
|
|
33819
34266
|
React__default.createElement(WLSidebar, { activeSearchSeed: activeSearchSeed, packagingAccoResult: selectedPackagingAccoResult }))));
|
|
33820
34267
|
};
|
|
33821
34268
|
|
|
34269
|
+
const useTrajectDayActivities = () => {
|
|
34270
|
+
const context = useContext(SearchResultsConfigurationContext);
|
|
34271
|
+
const dispatch = useDispatch();
|
|
34272
|
+
const { trajectDaySuggestions, trajectNodeAvailability } = useSelector((state) => state.searchResults);
|
|
34273
|
+
const trajectEntry = context?.trajectEntry;
|
|
34274
|
+
const showApiItems = !!context?.showApiItems;
|
|
34275
|
+
const loadDay = useCallback(async (day) => {
|
|
34276
|
+
if (!context || !trajectEntry)
|
|
34277
|
+
return;
|
|
34278
|
+
// Already asked for, or already answered: leave it be.
|
|
34279
|
+
if (trajectDaySuggestions[day.order])
|
|
34280
|
+
return;
|
|
34281
|
+
const nodesWithLines = getTrajectNodesWithLines(trajectEntry);
|
|
34282
|
+
const destination = getDayDestination(nodesWithLines, trajectNodeAvailability, day.order);
|
|
34283
|
+
if (!destination)
|
|
34284
|
+
return;
|
|
34285
|
+
dispatch(setTrajectDaySuggestionsLoading(day.order));
|
|
34286
|
+
try {
|
|
34287
|
+
const results = (await build.searchPackagingExcursions(buildTrajectClientConfig(context), buildTrajectDayExcursionRequest(day.date, destination, buildTrajectSearchContext(context, trajectEntry)))) ?? [];
|
|
34288
|
+
// With external items switched off a day only offers what Tide holds itself, and whatever
|
|
34289
|
+
// the traject already books that day has a card of its own further up the block.
|
|
34290
|
+
const configured = getConfiguredExcursionCodes(nodesWithLines, day.order);
|
|
34291
|
+
dispatch(setTrajectDaySuggestions({
|
|
34292
|
+
dayOrder: day.order,
|
|
34293
|
+
results: results.filter((x) => showApiItems || !isExternalResult(x)).filter((x) => !configured.has(x.code))
|
|
34294
|
+
}));
|
|
34295
|
+
}
|
|
34296
|
+
catch (error) {
|
|
34297
|
+
console.error('Failed to load traject day activities', day.order, error);
|
|
34298
|
+
dispatch(setTrajectDaySuggestions({ dayOrder: day.order, results: [] }));
|
|
34299
|
+
}
|
|
34300
|
+
}, [context, trajectEntry, showApiItems, trajectDaySuggestions, trajectNodeAvailability, dispatch]);
|
|
34301
|
+
return loadDay;
|
|
34302
|
+
};
|
|
34303
|
+
|
|
33822
34304
|
const getNodeIcon = (node) => {
|
|
33823
34305
|
if (isTrajectFlightNode(node))
|
|
33824
34306
|
return 'ui-flight';
|
|
@@ -33834,6 +34316,7 @@ const MAX_DAY_ACTIVITIES = 6;
|
|
|
33834
34316
|
const TrajectResults = ({ isLoading }) => {
|
|
33835
34317
|
const context = useContext(SearchResultsConfigurationContext);
|
|
33836
34318
|
const dispatch = useDispatch();
|
|
34319
|
+
const loadDay = useTrajectDayActivities();
|
|
33837
34320
|
const translations = getTranslations(context?.languageCode ?? 'en-GB');
|
|
33838
34321
|
const locale = getLocale(context?.languageCode ?? 'en-GB');
|
|
33839
34322
|
const { trajectNodeAvailability, trajectFlightAvailability, trajectDaySuggestions, editablePackagingEntry } = useSelector((state) => state.searchResults);
|
|
@@ -33866,6 +34349,23 @@ const TrajectResults = ({ isLoading }) => {
|
|
|
33866
34349
|
});
|
|
33867
34350
|
return { stayByDay: byDay, stayStartByNodeId: byNodeId };
|
|
33868
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]);
|
|
33869
34369
|
if (!context || !trajectEntry)
|
|
33870
34370
|
return null;
|
|
33871
34371
|
const handleSelect = (item, code) => {
|
|
@@ -33950,7 +34450,7 @@ const TrajectResults = ({ isLoading }) => {
|
|
|
33950
34450
|
React__default.createElement(Icon, { name: getNodeIcon(node), height: 16 }),
|
|
33951
34451
|
React__default.createElement("h3", null,
|
|
33952
34452
|
React__default.createElement("strong", null, headerTitle || node.name))))),
|
|
33953
|
-
status === 'loading' &&
|
|
34453
|
+
status === 'loading' && React__default.createElement(Spinner, { label: translations.SRP.LOADING_ACCOMMODATIONS }),
|
|
33954
34454
|
status === 'unavailable' && React__default.createElement("div", { className: "no-results" }, translations.SRP.NO_RESULTS),
|
|
33955
34455
|
status === 'resolved' && selected && (React__default.createElement(React__default.Fragment, null,
|
|
33956
34456
|
availability.preferredUnavailable && node.preferredAccommodationName && (React__default.createElement("div", { className: "search__results__cards__actions" },
|
|
@@ -33986,8 +34486,6 @@ const TrajectResults = ({ isLoading }) => {
|
|
|
33986
34486
|
React__default.createElement("div", { className: "search__results__label__text" },
|
|
33987
34487
|
React__default.createElement(Icon, { name: "ui-flight", height: 16 }),
|
|
33988
34488
|
React__default.createElement("h3", null,
|
|
33989
|
-
translations.SRP.SELECT,
|
|
33990
|
-
' ',
|
|
33991
34489
|
React__default.createElement("strong", null,
|
|
33992
34490
|
node.departureAirportCode,
|
|
33993
34491
|
" - ",
|
|
@@ -34013,6 +34511,13 @@ const TrajectResults = ({ isLoading }) => {
|
|
|
34013
34511
|
dispatch(removeEditableEntryLines(added.map((line) => line.guid)));
|
|
34014
34512
|
return;
|
|
34015
34513
|
}
|
|
34514
|
+
if (excursionNeedsChoice(result)) {
|
|
34515
|
+
dispatch(setExcursionSearchParams(buildExcursionSearchParams(result)));
|
|
34516
|
+
dispatch(setSelectedExcursionSearchResult(result));
|
|
34517
|
+
dispatch(setFlyInType('excursion-details'));
|
|
34518
|
+
dispatch(setFlyInIsOpen(true));
|
|
34519
|
+
return;
|
|
34520
|
+
}
|
|
34016
34521
|
const lines = buildTrajectExcursionLines(result);
|
|
34017
34522
|
if (lines.length)
|
|
34018
34523
|
dispatch(addEditableEntryLines(lines));
|
|
@@ -34039,32 +34544,30 @@ const TrajectResults = ({ isLoading }) => {
|
|
|
34039
34544
|
const items = getDayExcursionItems(day.order);
|
|
34040
34545
|
const suggestions = trajectDaySuggestions[day.order];
|
|
34041
34546
|
const cards = buildDayActivityCards(items, trajectNodeAvailability, suggestions?.results ?? []);
|
|
34042
|
-
const
|
|
34043
|
-
|
|
34044
|
-
//
|
|
34045
|
-
|
|
34547
|
+
const nodesResolving = items.some((item) => (trajectNodeAvailability[item.line.guid]?.status ?? 'loading') === 'loading');
|
|
34548
|
+
const isSearching = suggestions?.status === 'loading';
|
|
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);
|
|
34553
|
+
if (!items.length && !canSearch)
|
|
34046
34554
|
return null;
|
|
34047
34555
|
const visible = cards.slice(0, MAX_DAY_ACTIVITIES);
|
|
34048
34556
|
const hidden = cards.length - visible.length;
|
|
34049
34557
|
return (React__default.createElement(React__default.Fragment, { key: `activities-${day.order}` },
|
|
34050
|
-
React__default.createElement("div", { className: "search__results__label search__results__label--
|
|
34051
|
-
React__default.createElement("div", { className: "search__results__label__date" },
|
|
34052
|
-
React__default.createElement("p", { className: "search__results__label__date-date" }, format$2(new Date(day.date), 'd', { locale })),
|
|
34053
|
-
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" },
|
|
34054
34559
|
React__default.createElement("div", { className: "search__results__label__text" },
|
|
34055
34560
|
React__default.createElement(Icon, { name: "ui-excursion", height: 16 }),
|
|
34056
34561
|
React__default.createElement("h3", null,
|
|
34057
34562
|
React__default.createElement("strong", null, translations.SRP.ACTIVITIES)))),
|
|
34058
|
-
|
|
34059
|
-
|
|
34060
|
-
|
|
34061
|
-
|
|
34062
|
-
|
|
34063
|
-
|
|
34064
|
-
|
|
34065
|
-
|
|
34066
|
-
hidden,
|
|
34067
|
-
")")))))));
|
|
34563
|
+
(nodesResolving || isSearching) && !cards.length && React__default.createElement(Spinner, { label: translations.SRP.LOADING_ACTIVITIES }),
|
|
34564
|
+
!!cards.length && React__default.createElement("div", { className: "search__results__cards search__results__cards--scroll" }, visible.map(renderActivityCard)),
|
|
34565
|
+
!nodesResolving && !isSearching && !cards.length && (!!items.length || !!suggestions) && (React__default.createElement("div", { className: "no-results" }, translations.SRP.NO_RESULTS)),
|
|
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
|
+
")")))));
|
|
34068
34571
|
};
|
|
34069
34572
|
/**
|
|
34070
34573
|
* An activity the traject books is swapped for another, the way a hotel is; one the traveller
|
|
@@ -34115,6 +34618,7 @@ const useTrajectAvailability = () => {
|
|
|
34115
34618
|
return;
|
|
34116
34619
|
const controller = new AbortController();
|
|
34117
34620
|
dispatch(resetTrajectAvailability());
|
|
34621
|
+
dispatch(setTrajectResolving(true));
|
|
34118
34622
|
const config = {
|
|
34119
34623
|
host: context.tideConnection.host,
|
|
34120
34624
|
apiKey: context.tideConnection.apiKey
|
|
@@ -34141,42 +34645,51 @@ const useTrajectAvailability = () => {
|
|
|
34141
34645
|
const lineGuid = item.line.guid;
|
|
34142
34646
|
const search = item.node.serviceType === EXCURSION_SERVICE_TYPE ? build.searchPackagingExcursions : build.searchPackagingAccommodations;
|
|
34143
34647
|
dispatch(setTrajectNodeLoading(lineGuid));
|
|
34648
|
+
const destination = getNodeSearchDestination(nodesWithLines, item);
|
|
34649
|
+
const searchFor = async (productCode) => {
|
|
34650
|
+
try {
|
|
34651
|
+
const results = (await search(config, buildTrajectNodeRequest(item, searchContext, productCode, destination), controller.signal)) ?? [];
|
|
34652
|
+
return results.filter((x) => showApiItems || !isExternalResult(x));
|
|
34653
|
+
}
|
|
34654
|
+
catch (error) {
|
|
34655
|
+
if (!controller.signal.aborted)
|
|
34656
|
+
console.error('Traject product unavailable', item.node.name, productCode, error);
|
|
34657
|
+
return [];
|
|
34658
|
+
}
|
|
34659
|
+
};
|
|
34144
34660
|
try {
|
|
34145
34661
|
// With external items switched off the node only looks at products held inside Tide.
|
|
34146
34662
|
const candidates = getNodeCandidates(item.node).filter((x) => showApiItems || !isExternalCandidate(x));
|
|
34147
|
-
const
|
|
34148
|
-
|
|
34149
|
-
|
|
34150
|
-
|
|
34151
|
-
|
|
34152
|
-
|
|
34153
|
-
|
|
34154
|
-
|
|
34155
|
-
|
|
34156
|
-
}
|
|
34663
|
+
const areaResults = hideShowMoreResults ? [] : await searchFor('');
|
|
34664
|
+
if (controller.signal.aborted)
|
|
34665
|
+
return [];
|
|
34666
|
+
const configuredResults = [];
|
|
34667
|
+
const found = await Promise.all(candidates.map(async (candidate) => {
|
|
34668
|
+
const known = areaResults.find((x) => matchesCode(x, candidate.code));
|
|
34669
|
+
if (known)
|
|
34670
|
+
return known;
|
|
34671
|
+
return (await searchFor(candidate.code)).find((x) => matchesCode(x, candidate.code)) ?? null;
|
|
34157
34672
|
}));
|
|
34158
|
-
|
|
34159
|
-
|
|
34160
|
-
|
|
34673
|
+
if (controller.signal.aborted)
|
|
34674
|
+
return [];
|
|
34675
|
+
// Two configured codes can land on the same product — a hotel and one of its room types.
|
|
34676
|
+
found.forEach((result) => {
|
|
34677
|
+
if (!result || configuredResults.some((x) => x.code === result.code))
|
|
34678
|
+
return;
|
|
34679
|
+
configuredResults.push(result);
|
|
34680
|
+
});
|
|
34681
|
+
const configuredCodes = new Set(configuredResults.map((x) => x.code));
|
|
34682
|
+
const destinationResults = areaResults.filter((x) => !configuredCodes.has(x.code));
|
|
34161
34683
|
const preferredCandidate = candidates.find((x) => x.isPreferred) ?? candidates[0];
|
|
34162
|
-
|
|
34163
|
-
|
|
34164
|
-
|
|
34165
|
-
// rather than quietly substituting a hotel nobody configured.
|
|
34166
|
-
const destinationResults = hideShowMoreResults
|
|
34167
|
-
? []
|
|
34168
|
-
: ((await search(config, buildTrajectNodeRequest(item, searchContext, ''), controller.signal)) ?? [])
|
|
34169
|
-
.filter((x) => showApiItems || !isExternalResult(x))
|
|
34170
|
-
.filter((x) => !configuredResults.some((configured) => configured.code === x.code));
|
|
34684
|
+
const preferredUnavailable = configuredResults.length
|
|
34685
|
+
? !!preferredCandidate && !configuredResults.some((x) => matchesCode(x, preferredCandidate.code))
|
|
34686
|
+
: candidates.length > 0;
|
|
34171
34687
|
const picked = configuredResults[0] ?? pickCheapest(destinationResults);
|
|
34172
|
-
const results = [...configuredResults, ...destinationResults];
|
|
34173
|
-
if (!configuredResults.length)
|
|
34174
|
-
preferredUnavailable = candidates.length > 0;
|
|
34175
34688
|
if (controller.signal.aborted)
|
|
34176
34689
|
return [];
|
|
34177
34690
|
dispatch(setTrajectNodeAvailability({
|
|
34178
34691
|
lineGuid,
|
|
34179
|
-
results,
|
|
34692
|
+
results: [...configuredResults, ...destinationResults],
|
|
34180
34693
|
selectedCode: picked?.code ?? null,
|
|
34181
34694
|
preferredUnavailable
|
|
34182
34695
|
}));
|
|
@@ -34220,11 +34733,11 @@ const useTrajectAvailability = () => {
|
|
|
34220
34733
|
}
|
|
34221
34734
|
};
|
|
34222
34735
|
(async () => {
|
|
34223
|
-
const
|
|
34224
|
-
const
|
|
34225
|
-
const unresolved = (await Promise.all([...searchable.map(resolveNode), ...flightNodes.map(resolveFlightNode)])).flat();
|
|
34736
|
+
const resolvable = nodesWithLines.filter((x) => isSearchableTrajectNode(x.node) || isTrajectFlightNode(x.node));
|
|
34737
|
+
const unresolved = (await Promise.all(resolvable.map((item) => (isTrajectFlightNode(item.node) ? resolveFlightNode(item) : resolveNode(item))))).flat();
|
|
34226
34738
|
if (controller.signal.aborted)
|
|
34227
34739
|
return;
|
|
34740
|
+
dispatch(setTrajectResolving(false));
|
|
34228
34741
|
// A node with no availability anywhere cannot be priced or booked, so its line comes off
|
|
34229
34742
|
// the entry. The card stays visible and reports the gap.
|
|
34230
34743
|
const emptyLineGuids = unresolved.filter(Boolean);
|
|
@@ -34240,10 +34753,12 @@ const SearchResultsContainer = ({ onBookingStarted }) => {
|
|
|
34240
34753
|
const dispatch = useDispatch();
|
|
34241
34754
|
const context = useContext(SearchResultsConfigurationContext);
|
|
34242
34755
|
const translations = getTranslations(context?.languageCode ?? 'en-GB');
|
|
34243
|
-
const { results, filteredResults, packagingAccoResults, filteredPackagingAccoResults, isLoading, flightsLoading, initialFilters, filters, flightFilters, selectedSortType, selectedFlightSortType, selectedSearchResult, selectedPackagingAccoResultCode, flyInIsOpen, packagingAccoSearchDetails, editablePackagingEntry, transactionId, flyInType, packagingFlightResults, confirmedExcursionsByDay, bookPackagingEntry, trajectEditLineGuid } = useSelector((state) => state.searchResults);
|
|
34756
|
+
const { results, filteredResults, packagingAccoResults, filteredPackagingAccoResults, isLoading, flightsLoading, initialFilters, filters, flightFilters, selectedSortType, selectedFlightSortType, selectedSearchResult, selectedPackagingAccoResultCode, flyInIsOpen, packagingAccoSearchDetails, editablePackagingEntry, transactionId, flyInType, packagingFlightResults, confirmedExcursionsByDay, bookPackagingEntry, trajectEditLineGuid, trajectResolving, trajectItineraryView } = useSelector((state) => state.searchResults);
|
|
34244
34757
|
const isMobile = useMediaQuery('(max-width: 1200px)');
|
|
34245
34758
|
// Resolves live availability per traject node; no-op unless a traject entry is configured.
|
|
34246
34759
|
useTrajectAvailability();
|
|
34760
|
+
// The entry as the backend can price it: a traject swaps in the guids its searches cached.
|
|
34761
|
+
const bookableEntry = useBookableEntry();
|
|
34247
34762
|
const [initialFiltersSet, setInitialFiltersSet] = useState(false);
|
|
34248
34763
|
const [initialFlightFiltersSet, setInitialFlightFiltersSet] = useState(false);
|
|
34249
34764
|
const [filtersOpen, setFiltersOpen] = useState(false);
|
|
@@ -35010,6 +35525,10 @@ const SearchResultsContainer = ({ onBookingStarted }) => {
|
|
|
35010
35525
|
const fetchPriceDetails = async () => {
|
|
35011
35526
|
if (!context || !editablePackagingEntry || isEmpty$1(editablePackagingEntry.lines))
|
|
35012
35527
|
return;
|
|
35528
|
+
// A traject resolves one node at a time. Pricing each step would show a total that climbs
|
|
35529
|
+
// as the trip fills in, so the whole trip is priced once, when the last node is in.
|
|
35530
|
+
if (context.trajectEntry && trajectResolving)
|
|
35531
|
+
return;
|
|
35013
35532
|
setPricesAreLoading(true);
|
|
35014
35533
|
try {
|
|
35015
35534
|
const config = {
|
|
@@ -35021,7 +35540,7 @@ const SearchResultsContainer = ({ onBookingStarted }) => {
|
|
|
35021
35540
|
officeId: context.tideConnection.officeId,
|
|
35022
35541
|
catalogueId: context.searchConfiguration.defaultCatalogueId ?? first(context?.tideConnection?.catalogueIds) ?? 1,
|
|
35023
35542
|
agentId: context.agentId,
|
|
35024
|
-
payload: editablePackagingEntry
|
|
35543
|
+
payload: bookableEntry ?? editablePackagingEntry
|
|
35025
35544
|
};
|
|
35026
35545
|
const priceDetails = await build.getPriceDetails(config, request);
|
|
35027
35546
|
dispatch(setPriceDetails(priceDetails));
|
|
@@ -35032,11 +35551,15 @@ const SearchResultsContainer = ({ onBookingStarted }) => {
|
|
|
35032
35551
|
setPricesAreLoading(false);
|
|
35033
35552
|
}
|
|
35034
35553
|
};
|
|
35554
|
+
fetchPriceDetails();
|
|
35555
|
+
}, [editablePackagingEntry, bookableEntry, trajectResolving]);
|
|
35556
|
+
useEffect(() => {
|
|
35035
35557
|
const fetchItinerary = async () => {
|
|
35036
35558
|
if (!context || !context.packagingEntry || !editablePackagingEntry || isEmpty$1(editablePackagingEntry.lines))
|
|
35037
35559
|
return;
|
|
35038
|
-
//
|
|
35039
|
-
|
|
35560
|
+
// While a traject is being composed its own day-by-day column is the itinerary; the generated
|
|
35561
|
+
// one is what the traveller reads the finished trip back from, so it waits until then.
|
|
35562
|
+
if (context.trajectEntry && (trajectResolving || !trajectItineraryView))
|
|
35040
35563
|
return;
|
|
35041
35564
|
setItineraryIsLoading(true);
|
|
35042
35565
|
try {
|
|
@@ -35049,7 +35572,7 @@ const SearchResultsContainer = ({ onBookingStarted }) => {
|
|
|
35049
35572
|
officeId: context.tideConnection.officeId,
|
|
35050
35573
|
catalogueId: context.searchConfiguration.defaultCatalogueId ?? first(context?.tideConnection?.catalogueIds) ?? 1,
|
|
35051
35574
|
agentId: context.agentId,
|
|
35052
|
-
payload: editablePackagingEntry
|
|
35575
|
+
payload: bookableEntry ?? editablePackagingEntry
|
|
35053
35576
|
};
|
|
35054
35577
|
const itinerary = await build.getItinerary(config, request);
|
|
35055
35578
|
dispatch(setItinerary(itinerary));
|
|
@@ -35060,9 +35583,8 @@ const SearchResultsContainer = ({ onBookingStarted }) => {
|
|
|
35060
35583
|
setItineraryIsLoading(false);
|
|
35061
35584
|
}
|
|
35062
35585
|
};
|
|
35063
|
-
fetchPriceDetails();
|
|
35064
35586
|
fetchItinerary();
|
|
35065
|
-
}, [editablePackagingEntry]);
|
|
35587
|
+
}, [editablePackagingEntry, bookableEntry, trajectResolving, trajectItineraryView]);
|
|
35066
35588
|
// Flight selection
|
|
35067
35589
|
// const [selectedOutwardKey, setSelectedOutwardKey] = useState<string | null>(null);
|
|
35068
35590
|
// const [selectedReturnKey, setSelectedReturnKey] = useState<string | null>(null);
|
|
@@ -35426,7 +35948,7 @@ const SearchResultsContainer = ({ onBookingStarted }) => {
|
|
|
35426
35948
|
context.searchConfiguration.qsmType != build.PortalQsmType.AccommodationAndFlight && context.showFilters && (React__default.createElement(Filters, { initialFilters: initialFilters, filters: filters, isOpen: filtersOpen, handleSetIsOpen: () => setFiltersOpen(!filtersOpen),
|
|
35427
35949
|
// handleApplyFilters={() => setSearchTrigger((prev) => prev + 1)}
|
|
35428
35950
|
isLoading: isLoading, setFilters: (filters) => dispatch(setFilters(filters)), resetFilters: (filters) => dispatch(resetFilters(filters)) })),
|
|
35429
|
-
context.searchConfiguration.qsmType === build.PortalQsmType.AccommodationAndFlight && (React__default.createElement(Itinerary, { isOpen: itineraryOpen, handleSetIsOpen: () => setItineraryOpen(!itineraryOpen), isLoading: isLoading || pricesAreLoading || flightsLoading || itineraryIsLoading, onEditAccommodation: handleEditAccommodation })),
|
|
35951
|
+
context.searchConfiguration.qsmType === build.PortalQsmType.AccommodationAndFlight && (React__default.createElement(Itinerary, { isOpen: itineraryOpen, handleSetIsOpen: () => setItineraryOpen(!itineraryOpen), isLoading: isLoading || pricesAreLoading || flightsLoading || itineraryIsLoading || trajectResolving, onEditAccommodation: handleEditAccommodation })),
|
|
35430
35952
|
React__default.createElement("div", { className: "search__results" },
|
|
35431
35953
|
isMobile && (React__default.createElement("div", { className: "search__result-row" },
|
|
35432
35954
|
React__default.createElement("div", { className: "search__results__actions" },
|
|
@@ -35500,7 +36022,8 @@ const SearchResultsContainer = ({ onBookingStarted }) => {
|
|
|
35500
36022
|
React__default.createElement("div", { className: "search__results__cards search__results__cards--extended" },
|
|
35501
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 })),
|
|
35502
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 })))))),
|
|
35503
|
-
context.searchConfiguration.qsmType === build.PortalQsmType.AccommodationAndFlight && context.trajectEntry && (React__default.createElement(TrajectResults, { isLoading: isLoading })),
|
|
36025
|
+
context.searchConfiguration.qsmType === build.PortalQsmType.AccommodationAndFlight && context.trajectEntry && !trajectItineraryView && (React__default.createElement(TrajectResults, { isLoading: isLoading })),
|
|
36026
|
+
context.searchConfiguration.qsmType === build.PortalQsmType.AccommodationAndFlight && context.trajectEntry && trajectItineraryView && (React__default.createElement(FullItinerary, { isLoading: itineraryIsLoading })),
|
|
35504
36027
|
context.searchConfiguration.qsmType === build.PortalQsmType.AccommodationAndFlight && context.packagingEntry && !context.trajectEntry && (React__default.createElement(FullItinerary, { isLoading: itineraryIsLoading })))),
|
|
35505
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 })))))))));
|
|
35506
36029
|
};
|