@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-cjs/index.js
CHANGED
|
@@ -417,6 +417,8 @@ var SRP$f = {
|
|
|
417
417
|
DEPARTURE_ASC: "تاريخ المغادرة (الأقرب أولاً)",
|
|
418
418
|
FILTERS: "عوامل التصفية",
|
|
419
419
|
SHOW_ITINERARY: "عرض خط سير الرحلة",
|
|
420
|
+
VIEW_FULL_ITINERARY: "عرض خط السير الكامل",
|
|
421
|
+
SHOW_SERVICES: "عرض الخدمات",
|
|
420
422
|
VIEW_BOOKING: "عرض الحجز",
|
|
421
423
|
ITINERARY_TITLE: "ملخص رحلتك",
|
|
422
424
|
DOSSIER_NUMBER: "رقم الملف",
|
|
@@ -458,6 +460,7 @@ var SRP$f = {
|
|
|
458
460
|
TRAVEL_GROUP: "مجموعة المسافرين",
|
|
459
461
|
EXCURSION: "رحلة",
|
|
460
462
|
LOADING_EXCURSIONS: "جارٍ تحميل الرحلات...",
|
|
463
|
+
LOADING_ACTIVITIES: "جارٍ تحميل الأنشطة...",
|
|
461
464
|
LOADING_FLIGHTS: "جارٍ تحميل الرحلات الجوية...",
|
|
462
465
|
LOADING_ACCOMMODATIONS: "جارٍ تحميل الإقامات...",
|
|
463
466
|
LOADING_ITINERARY: "جارٍ تحميل خط سير الرحلة...",
|
|
@@ -861,6 +864,8 @@ var SRP$e = {
|
|
|
861
864
|
DEPARTURE_ASC: "Afrejsedato (snarest først)",
|
|
862
865
|
FILTERS: "Filtre",
|
|
863
866
|
SHOW_ITINERARY: "Vis rejseplan",
|
|
867
|
+
VIEW_FULL_ITINERARY: "Se hele rejseplanen",
|
|
868
|
+
SHOW_SERVICES: "Vis ydelser",
|
|
864
869
|
VIEW_BOOKING: "Se booking",
|
|
865
870
|
ITINERARY_TITLE: "Din rejseoversigt",
|
|
866
871
|
DOSSIER_NUMBER: "Sagsnummer",
|
|
@@ -902,6 +907,7 @@ var SRP$e = {
|
|
|
902
907
|
TRAVEL_GROUP: "Rejseselskab",
|
|
903
908
|
EXCURSION: "Udflugt",
|
|
904
909
|
LOADING_EXCURSIONS: "Indlæser udflugter...",
|
|
910
|
+
LOADING_ACTIVITIES: "Indlæser aktiviteter...",
|
|
905
911
|
LOADING_FLIGHTS: "Indlæser fly...",
|
|
906
912
|
LOADING_ACCOMMODATIONS: "Indlæser indkvarteringer...",
|
|
907
913
|
LOADING_ITINERARY: "Indlæser rejseplan...",
|
|
@@ -1309,6 +1315,8 @@ var SRP$d = {
|
|
|
1309
1315
|
DEPARTURE_ASC: "Abreisedatum (früheste zuerst)",
|
|
1310
1316
|
FILTERS: "Filter",
|
|
1311
1317
|
SHOW_ITINERARY: "Reiseverlauf anzeigen",
|
|
1318
|
+
VIEW_FULL_ITINERARY: "Vollständigen Reiseverlauf ansehen",
|
|
1319
|
+
SHOW_SERVICES: "Leistungen anzeigen",
|
|
1312
1320
|
VIEW_BOOKING: "Buchung anzeigen",
|
|
1313
1321
|
ITINERARY_TITLE: "Ihre Reiseübersicht",
|
|
1314
1322
|
DOSSIER_NUMBER: "Vorgangsnummer",
|
|
@@ -1346,6 +1354,7 @@ var SRP$d = {
|
|
|
1346
1354
|
TRAVEL_GROUP: "Reisegruppe",
|
|
1347
1355
|
EXCURSION: "Ausflug",
|
|
1348
1356
|
LOADING_EXCURSIONS: "Lade Ausflüge...",
|
|
1357
|
+
LOADING_ACTIVITIES: "Lade Aktivitäten...",
|
|
1349
1358
|
LOADING_FLIGHTS: "Lade Flüge...",
|
|
1350
1359
|
LOADING_ACCOMMODATIONS: "Lade Unterkünfte...",
|
|
1351
1360
|
LOADING_ITINERARY: "Lade Reiseverlauf...",
|
|
@@ -1757,6 +1766,8 @@ var SRP$c = {
|
|
|
1757
1766
|
DEPARTURE_ASC: "Departure date (soonest first)",
|
|
1758
1767
|
FILTERS: "Filters",
|
|
1759
1768
|
SHOW_ITINERARY: "Show itinerary",
|
|
1769
|
+
VIEW_FULL_ITINERARY: "View full itinerary",
|
|
1770
|
+
SHOW_SERVICES: "Show services",
|
|
1760
1771
|
VIEW_BOOKING: "View booking",
|
|
1761
1772
|
ITINERARY_TITLE: "Your trip summary",
|
|
1762
1773
|
DOSSIER_NUMBER: "File number",
|
|
@@ -1794,6 +1805,7 @@ var SRP$c = {
|
|
|
1794
1805
|
TRAVEL_GROUP: "Travel group",
|
|
1795
1806
|
EXCURSION: "Excursion",
|
|
1796
1807
|
LOADING_EXCURSIONS: "Loading excursions...",
|
|
1808
|
+
LOADING_ACTIVITIES: "Loading activities...",
|
|
1797
1809
|
LOADING_FLIGHTS: "Loading flights...",
|
|
1798
1810
|
LOADING_ACCOMMODATIONS: "Loading accommodations...",
|
|
1799
1811
|
LOADING_ITINERARY: "Loading itinerary...",
|
|
@@ -2201,6 +2213,8 @@ var SRP$b = {
|
|
|
2201
2213
|
DEPARTURE_ASC: "Fecha de salida (más próxima primero)",
|
|
2202
2214
|
FILTERS: "Filtros",
|
|
2203
2215
|
SHOW_ITINERARY: "Mostrar itinerario",
|
|
2216
|
+
VIEW_FULL_ITINERARY: "Ver itinerario completo",
|
|
2217
|
+
SHOW_SERVICES: "Ver servicios",
|
|
2204
2218
|
VIEW_BOOKING: "Ver reserva",
|
|
2205
2219
|
ITINERARY_TITLE: "Resumen de tu viaje",
|
|
2206
2220
|
DOSSIER_NUMBER: "Número de expediente",
|
|
@@ -2238,6 +2252,7 @@ var SRP$b = {
|
|
|
2238
2252
|
TRAVEL_GROUP: "Grupo de viaje",
|
|
2239
2253
|
EXCURSION: "Excursión",
|
|
2240
2254
|
LOADING_EXCURSIONS: "Cargando excursiones...",
|
|
2255
|
+
LOADING_ACTIVITIES: "Cargando actividades...",
|
|
2241
2256
|
LOADING_FLIGHTS: "Cargando vuelos...",
|
|
2242
2257
|
LOADING_ACCOMMODATIONS: "Cargando alojamientos...",
|
|
2243
2258
|
LOADING_ITINERARY: "Cargando itinerario...",
|
|
@@ -2649,6 +2664,8 @@ var SRP$a = {
|
|
|
2649
2664
|
DEPARTURE_ASC: "Date de départ (la plus proche d’abord)",
|
|
2650
2665
|
FILTERS: "Filtres",
|
|
2651
2666
|
SHOW_ITINERARY: "Afficher l’itinéraire",
|
|
2667
|
+
VIEW_FULL_ITINERARY: "Voir l'itinéraire complet",
|
|
2668
|
+
SHOW_SERVICES: "Voir les services",
|
|
2652
2669
|
VIEW_BOOKING: "Voir la réservation",
|
|
2653
2670
|
ITINERARY_TITLE: "Résumé de votre voyage",
|
|
2654
2671
|
DOSSIER_NUMBER: "Numéro de dossier",
|
|
@@ -2686,6 +2703,7 @@ var SRP$a = {
|
|
|
2686
2703
|
TRAVEL_GROUP: "Groupe de voyageurs",
|
|
2687
2704
|
EXCURSION: "Excursion",
|
|
2688
2705
|
LOADING_EXCURSIONS: "Chargement des excursions...",
|
|
2706
|
+
LOADING_ACTIVITIES: "Chargement des activités...",
|
|
2689
2707
|
LOADING_FLIGHTS: "Chargement des vols...",
|
|
2690
2708
|
LOADING_ACCOMMODATIONS: "Chargement des hébergements...",
|
|
2691
2709
|
LOADING_ITINERARY: "Chargement de l’itinéraire...",
|
|
@@ -3093,6 +3111,8 @@ var SRP$9 = {
|
|
|
3093
3111
|
DEPARTURE_ASC: "Date de départ (la plus proche d’abord)",
|
|
3094
3112
|
FILTERS: "Filtres",
|
|
3095
3113
|
SHOW_ITINERARY: "Afficher l’itinéraire",
|
|
3114
|
+
VIEW_FULL_ITINERARY: "Voir l'itinéraire complet",
|
|
3115
|
+
SHOW_SERVICES: "Voir les services",
|
|
3096
3116
|
VIEW_BOOKING: "Voir la réservation",
|
|
3097
3117
|
ITINERARY_TITLE: "Résumé de votre voyage",
|
|
3098
3118
|
DOSSIER_NUMBER: "Numéro de dossier",
|
|
@@ -3130,6 +3150,7 @@ var SRP$9 = {
|
|
|
3130
3150
|
TRAVEL_GROUP: "Groupe de voyageurs",
|
|
3131
3151
|
EXCURSION: "Excursion",
|
|
3132
3152
|
LOADING_EXCURSIONS: "Chargement des excursions...",
|
|
3153
|
+
LOADING_ACTIVITIES: "Chargement des activités...",
|
|
3133
3154
|
LOADING_FLIGHTS: "Chargement des vols...",
|
|
3134
3155
|
LOADING_ACCOMMODATIONS: "Chargement des hébergements...",
|
|
3135
3156
|
LOADING_ITINERARY: "Chargement de l’itinéraire...",
|
|
@@ -3537,6 +3558,8 @@ var SRP$8 = {
|
|
|
3537
3558
|
DEPARTURE_ASC: "Brottfarardagur (nærst fyrst)",
|
|
3538
3559
|
FILTERS: "Síur",
|
|
3539
3560
|
SHOW_ITINERARY: "Sýna ferðaráætlun",
|
|
3561
|
+
VIEW_FULL_ITINERARY: "Skoða alla ferðaáætlunina",
|
|
3562
|
+
SHOW_SERVICES: "Sýna þjónustu",
|
|
3540
3563
|
VIEW_BOOKING: "Skoða bókun",
|
|
3541
3564
|
ITINERARY_TITLE: "Yfirlit ferðarinnar",
|
|
3542
3565
|
DOSSIER_NUMBER: "Skrárnúmer",
|
|
@@ -3574,6 +3597,7 @@ var SRP$8 = {
|
|
|
3574
3597
|
TRAVEL_GROUP: "Ferðahópur",
|
|
3575
3598
|
EXCURSION: "Útflutningur",
|
|
3576
3599
|
LOADING_EXCURSIONS: "Hleðst inn í útflutninga...",
|
|
3600
|
+
LOADING_ACTIVITIES: "Hleð inn afþreyingu...",
|
|
3577
3601
|
LOADING_FLIGHTS: "Hleðst inn í flug...",
|
|
3578
3602
|
LOADING_ACCOMMODATIONS: "Hleðst inn í gistingar...",
|
|
3579
3603
|
LOADING_ITINERARY: "Hleðst inn í ferðaráætlun...",
|
|
@@ -3981,6 +4005,8 @@ var SRP$7 = {
|
|
|
3981
4005
|
DEPARTURE_ASC: "Data di partenza (prima la più vicina)",
|
|
3982
4006
|
FILTERS: "Filtri",
|
|
3983
4007
|
SHOW_ITINERARY: "Mostra itinerario",
|
|
4008
|
+
VIEW_FULL_ITINERARY: "Vedi l'itinerario completo",
|
|
4009
|
+
SHOW_SERVICES: "Mostra i servizi",
|
|
3984
4010
|
VIEW_BOOKING: "Visualizza prenotazione",
|
|
3985
4011
|
ITINERARY_TITLE: "Riepilogo del tuo viaggio",
|
|
3986
4012
|
DOSSIER_NUMBER: "Numero di pratica",
|
|
@@ -4018,6 +4044,7 @@ var SRP$7 = {
|
|
|
4018
4044
|
TRAVEL_GROUP: "Gruppo di viaggio",
|
|
4019
4045
|
EXCURSION: "Escursione",
|
|
4020
4046
|
LOADING_EXCURSIONS: "Caricamento delle escursioni...",
|
|
4047
|
+
LOADING_ACTIVITIES: "Caricamento delle attività...",
|
|
4021
4048
|
LOADING_FLIGHTS: "Caricamento dei voli...",
|
|
4022
4049
|
LOADING_ACCOMMODATIONS: "Caricamento degli alloggi...",
|
|
4023
4050
|
LOADING_ITINERARY: "Caricamento dell'itinerario...",
|
|
@@ -4429,6 +4456,8 @@ var SRP$6 = {
|
|
|
4429
4456
|
DEPARTURE_ASC: "Vertrekdatum (dichtstbij eerst)",
|
|
4430
4457
|
FILTERS: "Filters",
|
|
4431
4458
|
SHOW_ITINERARY: "Toon reisplan",
|
|
4459
|
+
VIEW_FULL_ITINERARY: "Bekijk volledige reisroute",
|
|
4460
|
+
SHOW_SERVICES: "Toon diensten",
|
|
4432
4461
|
VIEW_BOOKING: "Bekijk boeking",
|
|
4433
4462
|
ITINERARY_TITLE: "Overzicht van je reis",
|
|
4434
4463
|
DOSSIER_NUMBER: "Dossiernummer",
|
|
@@ -4466,6 +4495,7 @@ var SRP$6 = {
|
|
|
4466
4495
|
TRAVEL_GROUP: "Reisgezelschap",
|
|
4467
4496
|
EXCURSION: "Excursie",
|
|
4468
4497
|
LOADING_EXCURSIONS: "Excursies laden...",
|
|
4498
|
+
LOADING_ACTIVITIES: "Activiteiten laden...",
|
|
4469
4499
|
LOADING_FLIGHTS: "Vluchten laden...",
|
|
4470
4500
|
LOADING_ACCOMMODATIONS: "Accommodaties laden...",
|
|
4471
4501
|
LOADING_ITINERARY: "Reisplan laden...",
|
|
@@ -4873,6 +4903,8 @@ var SRP$5 = {
|
|
|
4873
4903
|
DEPARTURE_ASC: "Vertrekdatum (dichtstbij eerst)",
|
|
4874
4904
|
FILTERS: "Filters",
|
|
4875
4905
|
SHOW_ITINERARY: "Toon reisplan",
|
|
4906
|
+
VIEW_FULL_ITINERARY: "Bekijk volledige reisroute",
|
|
4907
|
+
SHOW_SERVICES: "Toon diensten",
|
|
4876
4908
|
VIEW_BOOKING: "Bekijk boeking",
|
|
4877
4909
|
ITINERARY_TITLE: "Overzicht van je reis",
|
|
4878
4910
|
DOSSIER_NUMBER: "Dossiernummer",
|
|
@@ -4910,6 +4942,7 @@ var SRP$5 = {
|
|
|
4910
4942
|
TRAVEL_GROUP: "Reisgezelschap",
|
|
4911
4943
|
EXCURSION: "Excursie",
|
|
4912
4944
|
LOADING_EXCURSIONS: "Excursies laden...",
|
|
4945
|
+
LOADING_ACTIVITIES: "Activiteiten laden...",
|
|
4913
4946
|
LOADING_FLIGHTS: "Vluchten laden...",
|
|
4914
4947
|
LOADING_ACCOMMODATIONS: "Accommodaties laden...",
|
|
4915
4948
|
LOADING_ITINERARY: "Reisplan laden...",
|
|
@@ -5317,6 +5350,8 @@ var SRP$4 = {
|
|
|
5317
5350
|
DEPARTURE_ASC: "Avreisedato (nærmeste først)",
|
|
5318
5351
|
FILTERS: "Filtre",
|
|
5319
5352
|
SHOW_ITINERARY: "Vis reiserute",
|
|
5353
|
+
VIEW_FULL_ITINERARY: "Se hele reiseruten",
|
|
5354
|
+
SHOW_SERVICES: "Vis tjenester",
|
|
5320
5355
|
VIEW_BOOKING: "Se bestilling",
|
|
5321
5356
|
ITINERARY_TITLE: "Din reisemåloversikt",
|
|
5322
5357
|
DOSSIER_NUMBER: "Saksnummer",
|
|
@@ -5354,6 +5389,7 @@ var SRP$4 = {
|
|
|
5354
5389
|
TRAVEL_GROUP: "Reisefølge",
|
|
5355
5390
|
EXCURSION: "Utflukt",
|
|
5356
5391
|
LOADING_EXCURSIONS: "Laster utflukter...",
|
|
5392
|
+
LOADING_ACTIVITIES: "Laster aktiviteter...",
|
|
5357
5393
|
LOADING_FLIGHTS: "Laster flyvninger...",
|
|
5358
5394
|
LOADING_ACCOMMODATIONS: "Laster overnattinger...",
|
|
5359
5395
|
LOADING_ITINERARY: "Laster reiserute...",
|
|
@@ -5761,6 +5797,8 @@ var SRP$3 = {
|
|
|
5761
5797
|
DEPARTURE_ASC: "Data wyjazdu (najbliższa najpierw)",
|
|
5762
5798
|
FILTERS: "Filtry",
|
|
5763
5799
|
SHOW_ITINERARY: "Pokaż plan podróży",
|
|
5800
|
+
VIEW_FULL_ITINERARY: "Zobacz pełny plan podróży",
|
|
5801
|
+
SHOW_SERVICES: "Pokaż usługi",
|
|
5764
5802
|
VIEW_BOOKING: "Zobacz rezerwację",
|
|
5765
5803
|
ITINERARY_TITLE: "Podsumowanie Twojej podróży",
|
|
5766
5804
|
DOSSIER_NUMBER: "Numer pliku",
|
|
@@ -5798,6 +5836,7 @@ var SRP$3 = {
|
|
|
5798
5836
|
TRAVEL_GROUP: "Grupa podróżnych",
|
|
5799
5837
|
EXCURSION: "Wycieczka",
|
|
5800
5838
|
LOADING_EXCURSIONS: "Ładowanie wycieczek...",
|
|
5839
|
+
LOADING_ACTIVITIES: "Ładowanie atrakcji...",
|
|
5801
5840
|
LOADING_FLIGHTS: "Ładowanie lotów...",
|
|
5802
5841
|
LOADING_ACCOMMODATIONS: "Ładowanie zakwaterowań...",
|
|
5803
5842
|
LOADING_ITINERARY: "Ładowanie planu podróży...",
|
|
@@ -6205,6 +6244,8 @@ var SRP$2 = {
|
|
|
6205
6244
|
DEPARTURE_ASC: "Data de partida (mais próxima primeiro)",
|
|
6206
6245
|
FILTERS: "Filtros",
|
|
6207
6246
|
SHOW_ITINERARY: "Mostrar itinerário",
|
|
6247
|
+
VIEW_FULL_ITINERARY: "Ver itinerário completo",
|
|
6248
|
+
SHOW_SERVICES: "Ver serviços",
|
|
6208
6249
|
VIEW_BOOKING: "Ver reserva",
|
|
6209
6250
|
ITINERARY_TITLE: "Resumo da sua viagem",
|
|
6210
6251
|
DOSSIER_NUMBER: "Número do processo",
|
|
@@ -6242,6 +6283,7 @@ var SRP$2 = {
|
|
|
6242
6283
|
TRAVEL_GROUP: "Grupo de viajantes",
|
|
6243
6284
|
EXCURSION: "Excursão",
|
|
6244
6285
|
LOADING_EXCURSIONS: "A carregar excursões...",
|
|
6286
|
+
LOADING_ACTIVITIES: "A carregar atividades...",
|
|
6245
6287
|
LOADING_FLIGHTS: "A carregar voos...",
|
|
6246
6288
|
LOADING_ACCOMMODATIONS: "A carregar alojamentos...",
|
|
6247
6289
|
LOADING_ITINERARY: "A carregar itinerário...",
|
|
@@ -6649,6 +6691,8 @@ var SRP$1 = {
|
|
|
6649
6691
|
DEPARTURE_ASC: "Avresedatum (närmast först)",
|
|
6650
6692
|
FILTERS: "Filter",
|
|
6651
6693
|
SHOW_ITINERARY: "Visa resplan",
|
|
6694
|
+
VIEW_FULL_ITINERARY: "Visa hela resplanen",
|
|
6695
|
+
SHOW_SERVICES: "Visa tjänster",
|
|
6652
6696
|
VIEW_BOOKING: "Visa bokning",
|
|
6653
6697
|
ITINERARY_TITLE: "Din resesammanfattning",
|
|
6654
6698
|
DOSSIER_NUMBER: "Filnummer",
|
|
@@ -6686,6 +6730,7 @@ var SRP$1 = {
|
|
|
6686
6730
|
TRAVEL_GROUP: "Resesällskap",
|
|
6687
6731
|
EXCURSION: "Utflykt",
|
|
6688
6732
|
LOADING_EXCURSIONS: "Laddar utflykter...",
|
|
6733
|
+
LOADING_ACTIVITIES: "Laddar aktiviteter...",
|
|
6689
6734
|
LOADING_FLIGHTS: "Laddar flyg...",
|
|
6690
6735
|
LOADING_ACCOMMODATIONS: "Laddar boenden...",
|
|
6691
6736
|
LOADING_ITINERARY: "Laddar resplan...",
|
|
@@ -7093,6 +7138,8 @@ var SRP = {
|
|
|
7093
7138
|
DEPARTURE_ASC: "出発日(早い順)",
|
|
7094
7139
|
FILTERS: "フィルター",
|
|
7095
7140
|
SHOW_ITINERARY: "旅程を表示",
|
|
7141
|
+
VIEW_FULL_ITINERARY: "旅程の詳細を見る",
|
|
7142
|
+
SHOW_SERVICES: "サービスを表示",
|
|
7096
7143
|
VIEW_BOOKING: "予約を見る",
|
|
7097
7144
|
ITINERARY_TITLE: "あなたの旅行概要",
|
|
7098
7145
|
DOSSIER_NUMBER: "ファイル番号",
|
|
@@ -7130,6 +7177,7 @@ var SRP = {
|
|
|
7130
7177
|
TRAVEL_GROUP: "旅行グループ",
|
|
7131
7178
|
EXCURSION: "エクスカーション",
|
|
7132
7179
|
LOADING_EXCURSIONS: "エクスカーションを読み込み中...",
|
|
7180
|
+
LOADING_ACTIVITIES: "アクティビティを読み込み中...",
|
|
7133
7181
|
LOADING_FLIGHTS: "フライトを読み込み中...",
|
|
7134
7182
|
LOADING_ACCOMMODATIONS: "宿泊施設を読み込み中...",
|
|
7135
7183
|
LOADING_ITINERARY: "旅程を読み込み中...",
|
|
@@ -7801,6 +7849,33 @@ var TrajectNodeType = {
|
|
|
7801
7849
|
subroute: 3,
|
|
7802
7850
|
};
|
|
7803
7851
|
|
|
7852
|
+
var DurationType = {
|
|
7853
|
+
hours: 0,
|
|
7854
|
+
days: 1,
|
|
7855
|
+
nights: 2,
|
|
7856
|
+
minutes: 3,
|
|
7857
|
+
};
|
|
7858
|
+
|
|
7859
|
+
var RouteTransportType = {
|
|
7860
|
+
none: 0,
|
|
7861
|
+
privateTransport: 1,
|
|
7862
|
+
publicTransport: 2,
|
|
7863
|
+
rentalCar: 3,
|
|
7864
|
+
bus: 4,
|
|
7865
|
+
camper: 5,
|
|
7866
|
+
car: 6,
|
|
7867
|
+
charter: 7,
|
|
7868
|
+
cruise: 8,
|
|
7869
|
+
ferry: 9,
|
|
7870
|
+
flight: 10,
|
|
7871
|
+
taxi: 11,
|
|
7872
|
+
train: 12,
|
|
7873
|
+
transfer: 13,
|
|
7874
|
+
rocket: 14,
|
|
7875
|
+
walking: 15,
|
|
7876
|
+
cycling: 16,
|
|
7877
|
+
};
|
|
7878
|
+
|
|
7804
7879
|
/******************************************************************************
|
|
7805
7880
|
Copyright (c) Microsoft Corporation.
|
|
7806
7881
|
|
|
@@ -15152,6 +15227,7 @@ var submitForm = function (config, id, request, signal) {
|
|
|
15152
15227
|
exports.AllotmentType = AllotmentType;
|
|
15153
15228
|
exports.DataType = DataType;
|
|
15154
15229
|
exports.DossierType = DossierType;
|
|
15230
|
+
exports.DurationType = DurationType;
|
|
15155
15231
|
exports.EntryLineFlag = EntryLineFlag;
|
|
15156
15232
|
exports.EntryLineStatus = EntryLineStatus;
|
|
15157
15233
|
exports.ExportStatus = ExportStatus;
|
|
@@ -15164,6 +15240,7 @@ exports.PaymentType = PaymentType;
|
|
|
15164
15240
|
exports.PriceChartType = PriceChartType;
|
|
15165
15241
|
exports.RequirementType = RequirementType;
|
|
15166
15242
|
exports.RoomType = RoomType;
|
|
15243
|
+
exports.RouteTransportType = RouteTransportType;
|
|
15167
15244
|
exports.SaleType = SaleType;
|
|
15168
15245
|
exports.ServiceTokenError = ServiceTokenError;
|
|
15169
15246
|
exports.ServiceType = ServiceType;
|
|
@@ -16641,6 +16718,16 @@ const Icon = ({ name, className, title, width, height, fill }) => {
|
|
|
16641
16718
|
React__default["default"].createElement(HTMLComment, { text: "!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc." }),
|
|
16642
16719
|
title && React__default["default"].createElement("title", null, title),
|
|
16643
16720
|
React__default["default"].createElement("path", { d: "M135.2 117.4l-26.1 74.6 293.8 0-26.1-74.6C372.3 104.6 360.2 96 346.6 96L165.4 96c-13.6 0-25.7 8.6-30.2 21.4zM39.6 196.8L74.8 96.3C88.3 57.8 124.6 32 165.4 32l181.2 0c40.8 0 77.1 25.8 90.6 64.3l35.2 100.5c23.2 9.6 39.6 32.5 39.6 59.2l0 192c0 17.7-14.3 32-32 32l-32 0c-17.7 0-32-14.3-32-32l0-32-320 0 0 32c0 17.7-14.3 32-32 32l-32 0c-17.7 0-32-14.3-32-32L0 256c0-26.7 16.4-49.6 39.6-59.2zM128 304a32 32 0 1 0 -64 0 32 32 0 1 0 64 0zm288 32a32 32 0 1 0 0-64 32 32 0 1 0 0 64z", fill: "currentColor" })));
|
|
16721
|
+
case 'ui-cycling':
|
|
16722
|
+
return (React__default["default"].createElement("svg", { className: ['icon', `icon--${name}`, className].filter((className) => !lodash.isEmpty(className)).join(' '), width: width, height: height, viewBox: "0 0 640 512", fill: fill ?? 'currentColor' },
|
|
16723
|
+
React__default["default"].createElement(HTMLComment, { text: "!Font Awesome Free v5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc." }),
|
|
16724
|
+
title && React__default["default"].createElement("title", null, title),
|
|
16725
|
+
React__default["default"].createElement("path", { d: "M512.509 192.001c-16.373-.064-32.03 2.955-46.436 8.495l-77.68-125.153A24 24 0 0 0 368.001 64h-64c-8.837 0-16 7.163-16 16v16c0 8.837 7.163 16 16 16h50.649l14.896 24H256.002v-16c0-8.837-7.163-16-16-16h-87.459c-13.441 0-24.777 10.999-24.536 24.437.232 13.044 10.876 23.563 23.995 23.563h48.726l-29.417 47.52c-13.433-4.83-27.904-7.483-42.992-7.52C58.094 191.83.412 249.012.002 319.236-.413 390.279 57.055 448 128.002 448c59.642 0 109.758-40.793 123.967-96h52.033a24 24 0 0 0 20.406-11.367L410.37 201.77l14.938 24.067c-25.455 23.448-41.385 57.081-41.307 94.437.145 68.833 57.899 127.051 126.729 127.719 70.606.685 128.181-55.803 129.255-125.996 1.086-70.941-56.526-129.72-127.476-129.996zM186.75 265.772c9.727 10.529 16.673 23.661 19.642 38.228h-43.306l23.664-38.228zM128.002 400c-44.112 0-80-35.888-80-80s35.888-80 80-80c5.869 0 11.586.653 17.099 1.859l-45.505 73.509C89.715 331.327 101.213 352 120.002 352h81.3c-12.37 28.225-40.562 48-73.3 48zm162.63-96h-35.624c-3.96-31.756-19.556-59.894-42.383-80.026L237.371 184h127.547l-74.286 120zm217.057 95.886c-41.036-2.165-74.049-35.692-75.627-76.755-.812-21.121 6.633-40.518 19.335-55.263l44.433 71.586c4.66 7.508 14.524 9.816 22.032 5.156l13.594-8.437c7.508-4.66 9.817-14.524 5.156-22.032l-44.468-71.643a79.901 79.901 0 0 1 19.858-2.497c44.112 0 80 35.888 80 80-.001 45.54-38.252 82.316-84.313 79.885z", fill: "currentColor" })));
|
|
16726
|
+
case 'ui-walking':
|
|
16727
|
+
return (React__default["default"].createElement("svg", { className: ['icon', `icon--${name}`, className].filter((className) => !lodash.isEmpty(className)).join(' '), width: width, height: height, viewBox: "0 0 320 512", fill: fill ?? 'currentColor' },
|
|
16728
|
+
React__default["default"].createElement(HTMLComment, { text: "!Font Awesome Free v5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc." }),
|
|
16729
|
+
title && React__default["default"].createElement("title", null, title),
|
|
16730
|
+
React__default["default"].createElement("path", { d: "M208 96c26.5 0 48-21.5 48-48S234.5 0 208 0s-48 21.5-48 48 21.5 48 48 48zm94.5 149.1l-23.3-11.8-9.7-29.4c-14.7-44.6-55.7-75.8-102.2-75.9-36-.1-55.9 10.1-93.3 25.2-21.6 8.7-39.3 25.2-49.7 46.2L17.6 213c-7.8 15.8-1.5 35 14.2 42.9 15.6 7.9 34.6 1.5 42.5-14.3L81 228c3.5-7 9.3-12.5 16.5-15.4l26.8-10.8-15.2 60.7c-5.2 20.8.4 42.9 14.9 58.8l59.9 65.4c7.2 7.9 12.3 17.4 14.9 27.7l18.3 73.3c4.3 17.1 21.7 27.6 38.8 23.3 17.1-4.3 27.6-21.7 23.3-38.8l-22.2-89c-2.6-10.3-7.7-19.9-14.9-27.7l-45.5-49.7 17.2-68.7 5.5 16.5c5.3 16.1 16.7 29.4 31.7 37l23.3 11.8c15.6 7.9 34.6 1.5 42.5-14.3 7.7-15.7 1.4-35.1-14.3-43zM73.6 385.8c-3.2 8.1-8 15.4-14.2 21.5l-50 50.1c-12.5 12.5-12.5 32.8 0 45.3s32.7 12.5 45.2 0l59.4-59.4c6.1-6.1 10.9-13.4 14.2-21.5l13.5-33.8c-55.3-60.3-38.7-41.8-47.4-53.7l-20.7 51.5z", fill: "currentColor" })));
|
|
16644
16731
|
case 'ui-bus':
|
|
16645
16732
|
return (React__default["default"].createElement("svg", { className: ['icon', `icon--${name}`, className].filter((className) => !lodash.isEmpty(className)).join(' '), width: width, height: height, viewBox: "0 0 512 512", fill: fill ?? 'currentColor' },
|
|
16646
16733
|
title && React__default["default"].createElement("title", null, title),
|
|
@@ -16954,10 +17041,10 @@ const DateRangePicker$1 = (props) => {
|
|
|
16954
17041
|
month: dateFns.getMonth(new Date())
|
|
16955
17042
|
});
|
|
16956
17043
|
const [isWaitingForToDate, setWaitingForToDate] = React.useState(false);
|
|
16957
|
-
const { searchType = 0 } = React.useContext(SettingsContext$1);
|
|
17044
|
+
const { searchType = 0, allowSingleDayDateSelect = false } = React.useContext(SettingsContext$1);
|
|
16958
17045
|
const handleDayClick = (day) => {
|
|
16959
17046
|
const { onSelectionChange } = props;
|
|
16960
|
-
if (isWaitingForToDate && !lodash.isNil(fromDate) && dateFns.isAfter(day, fromDate)) {
|
|
17047
|
+
if (isWaitingForToDate && !lodash.isNil(fromDate) && (dateFns.isAfter(day, fromDate) || (allowSingleDayDateSelect && dateFns.isSameDay(day, fromDate)))) {
|
|
16961
17048
|
if (searchType === 1) {
|
|
16962
17049
|
// Only allow selecting the exact toDate
|
|
16963
17050
|
const expectedToDate = props.toDateByFromDate?.(fromDate);
|
|
@@ -16998,7 +17085,7 @@ const DateRangePicker$1 = (props) => {
|
|
|
16998
17085
|
}
|
|
16999
17086
|
};
|
|
17000
17087
|
const handleDayMouseOver = (day) => {
|
|
17001
|
-
if (isWaitingForToDate && (lodash.isNil(fromDate) || dateFns.
|
|
17088
|
+
if (isWaitingForToDate && (lodash.isNil(fromDate) || dateFns.isSameDay(day, fromDate) || dateFns.isAfter(day, fromDate))) {
|
|
17002
17089
|
setToDate(day);
|
|
17003
17090
|
}
|
|
17004
17091
|
};
|
|
@@ -27471,7 +27558,9 @@ const initialState$1 = {
|
|
|
27471
27558
|
trajectFlightAvailability: {},
|
|
27472
27559
|
trajectDaySuggestions: {},
|
|
27473
27560
|
trajectEditLineGuid: null,
|
|
27474
|
-
trajectActivitiesDayOrder: null
|
|
27561
|
+
trajectActivitiesDayOrder: null,
|
|
27562
|
+
trajectResolving: false,
|
|
27563
|
+
trajectItineraryView: false
|
|
27475
27564
|
};
|
|
27476
27565
|
const searchResultsSlice = toolkit.createSlice({
|
|
27477
27566
|
name: 'searchResults',
|
|
@@ -27741,6 +27830,12 @@ const searchResultsSlice = toolkit.createSlice({
|
|
|
27741
27830
|
setTrajectActivitiesDayOrder(state, action) {
|
|
27742
27831
|
state.trajectActivitiesDayOrder = action.payload;
|
|
27743
27832
|
},
|
|
27833
|
+
setTrajectResolving(state, action) {
|
|
27834
|
+
state.trajectResolving = action.payload;
|
|
27835
|
+
},
|
|
27836
|
+
setTrajectItineraryView(state, action) {
|
|
27837
|
+
state.trajectItineraryView = action.payload;
|
|
27838
|
+
},
|
|
27744
27839
|
updateTrajectNodeResult(state, action) {
|
|
27745
27840
|
const { lineGuid, result } = action.payload;
|
|
27746
27841
|
const node = state.trajectNodeAvailability[lineGuid];
|
|
@@ -27756,10 +27851,12 @@ const searchResultsSlice = toolkit.createSlice({
|
|
|
27756
27851
|
state.trajectDaySuggestions = {};
|
|
27757
27852
|
state.trajectEditLineGuid = null;
|
|
27758
27853
|
state.trajectActivitiesDayOrder = null;
|
|
27854
|
+
state.trajectResolving = false;
|
|
27855
|
+
state.trajectItineraryView = false;
|
|
27759
27856
|
}
|
|
27760
27857
|
}
|
|
27761
27858
|
});
|
|
27762
|
-
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;
|
|
27859
|
+
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;
|
|
27763
27860
|
var searchResultsReducer = searchResultsSlice.reducer;
|
|
27764
27861
|
|
|
27765
27862
|
const ItemPicker = ({ items, selection, selectedSortByType, label, placeholder, classModifier, onPick, valueFormatter }) => {
|
|
@@ -28007,8 +28104,97 @@ const getServiceTypeBadgeModifier = (serviceType) => {
|
|
|
28007
28104
|
return '';
|
|
28008
28105
|
}
|
|
28009
28106
|
};
|
|
28107
|
+
// DurationType, as the SDK numbers it.
|
|
28108
|
+
const DURATION_HOURS = 0;
|
|
28109
|
+
const DURATION_DAYS = 1;
|
|
28110
|
+
const DURATION_NIGHTS = 2;
|
|
28111
|
+
const DURATION_MINUTES = 3;
|
|
28112
|
+
/**
|
|
28113
|
+
* How long a node takes, in the same shape the flight legs are written in — an hour count only
|
|
28114
|
+
* once there is one, so a short drive reads as plain minutes.
|
|
28115
|
+
*/
|
|
28116
|
+
const getTrajectNodeDuration = (node) => {
|
|
28117
|
+
const minutes = toMinutes(node);
|
|
28118
|
+
if (!minutes)
|
|
28119
|
+
return '';
|
|
28120
|
+
const hours = Math.floor(minutes / 60);
|
|
28121
|
+
const rest = minutes % 60;
|
|
28122
|
+
return hours ? `${hours}h ${rest.toString().padStart(2, '0')}m` : `${rest} min`;
|
|
28123
|
+
};
|
|
28124
|
+
const toMinutes = (node) => {
|
|
28125
|
+
const duration = node.duration ?? 0;
|
|
28126
|
+
if (duration <= 0)
|
|
28127
|
+
return 0;
|
|
28128
|
+
switch (node.durationType) {
|
|
28129
|
+
case DURATION_MINUTES:
|
|
28130
|
+
return duration;
|
|
28131
|
+
case DURATION_HOURS:
|
|
28132
|
+
return duration * 60;
|
|
28133
|
+
case DURATION_DAYS:
|
|
28134
|
+
case DURATION_NIGHTS:
|
|
28135
|
+
return duration * 24 * 60;
|
|
28136
|
+
default:
|
|
28137
|
+
return 0;
|
|
28138
|
+
}
|
|
28139
|
+
};
|
|
28140
|
+
// RouteTransportType, as the SDK numbers it. It says how a leg is travelled, which is not the
|
|
28141
|
+
// same question as what was sold: cycling and walking have no service type of their own.
|
|
28142
|
+
const TRANSPORT_PRIVATE = 1;
|
|
28143
|
+
const TRANSPORT_PUBLIC = 2;
|
|
28144
|
+
const TRANSPORT_RENTAL_CAR = 3;
|
|
28145
|
+
const TRANSPORT_BUS = 4;
|
|
28146
|
+
const TRANSPORT_CAMPER = 5;
|
|
28147
|
+
const TRANSPORT_CAR = 6;
|
|
28148
|
+
const TRANSPORT_CHARTER = 7;
|
|
28149
|
+
const TRANSPORT_CRUISE = 8;
|
|
28150
|
+
const TRANSPORT_FERRY = 9;
|
|
28151
|
+
const TRANSPORT_FLIGHT = 10;
|
|
28152
|
+
const TRANSPORT_TAXI = 11;
|
|
28153
|
+
const TRANSPORT_TRAIN = 12;
|
|
28154
|
+
const TRANSPORT_TRANSFER = 13;
|
|
28155
|
+
const TRANSPORT_WALKING = 15;
|
|
28156
|
+
const TRANSPORT_CYCLING = 16;
|
|
28157
|
+
/** Icon per way of travelling, where the route records one. */
|
|
28158
|
+
const getTransportTypeIcon = (transportType) => {
|
|
28159
|
+
switch (transportType) {
|
|
28160
|
+
case TRANSPORT_WALKING:
|
|
28161
|
+
return 'ui-walking';
|
|
28162
|
+
case TRANSPORT_CYCLING:
|
|
28163
|
+
return 'ui-cycling';
|
|
28164
|
+
case TRANSPORT_PRIVATE:
|
|
28165
|
+
case TRANSPORT_RENTAL_CAR:
|
|
28166
|
+
case TRANSPORT_CAMPER:
|
|
28167
|
+
case TRANSPORT_CAR:
|
|
28168
|
+
return 'ui-car';
|
|
28169
|
+
case TRANSPORT_TAXI:
|
|
28170
|
+
case TRANSPORT_TRANSFER:
|
|
28171
|
+
return 'ui-transfer';
|
|
28172
|
+
case TRANSPORT_PUBLIC:
|
|
28173
|
+
case TRANSPORT_BUS:
|
|
28174
|
+
return 'ui-bus';
|
|
28175
|
+
case TRANSPORT_TRAIN:
|
|
28176
|
+
return 'ui-train';
|
|
28177
|
+
case TRANSPORT_CRUISE:
|
|
28178
|
+
case TRANSPORT_FERRY:
|
|
28179
|
+
return 'ui-ship';
|
|
28180
|
+
case TRANSPORT_FLIGHT:
|
|
28181
|
+
case TRANSPORT_CHARTER:
|
|
28182
|
+
return 'ui-plane';
|
|
28183
|
+
default:
|
|
28184
|
+
return null;
|
|
28185
|
+
}
|
|
28186
|
+
};
|
|
28010
28187
|
const POI_TRAJECT_NODE_TYPE = 2;
|
|
28011
|
-
|
|
28188
|
+
/**
|
|
28189
|
+
* A point of interest is a place on the map rather than a service, so it gets a marker. Anything
|
|
28190
|
+
* else goes by how it is travelled where the route says so — a leg on foot or by bike is sold as
|
|
28191
|
+
* nothing, and its service type falls back to a transfer — and by what it is where it does not.
|
|
28192
|
+
*/
|
|
28193
|
+
const getTrajectNodeIcon = (node) => {
|
|
28194
|
+
if (node.trajectNodeType === POI_TRAJECT_NODE_TYPE)
|
|
28195
|
+
return 'ui-location';
|
|
28196
|
+
return getTransportTypeIcon(node.transportType) ?? getServiceTypeIcon(node.serviceType);
|
|
28197
|
+
};
|
|
28012
28198
|
const getTrajectNodeBadgeModifier = (node) => node.trajectNodeType === POI_TRAJECT_NODE_TYPE ? 'poi' : getServiceTypeBadgeModifier(node.serviceType);
|
|
28013
28199
|
|
|
28014
28200
|
const REGULAR_TRAJECT_NODE_TYPE = 0;
|
|
@@ -28049,7 +28235,22 @@ const buildDestination = (line, node, result) => {
|
|
|
28049
28235
|
null;
|
|
28050
28236
|
return { id: 0, ...place, ...coordinates };
|
|
28051
28237
|
};
|
|
28052
|
-
const
|
|
28238
|
+
const hasDestination = (destination) => !!destination && (!!destination.id || (!!destination.latitude && !!destination.longitude));
|
|
28239
|
+
const buildTrajectClientConfig = (context) => ({
|
|
28240
|
+
host: context.tideConnection.host,
|
|
28241
|
+
apiKey: context.tideConnection.apiKey
|
|
28242
|
+
});
|
|
28243
|
+
const buildTrajectSearchContext = (context, trajectEntry) => ({
|
|
28244
|
+
transactionId: trajectEntry.entry.transactionId,
|
|
28245
|
+
officeId: context.tideConnection.officeId ?? 1,
|
|
28246
|
+
catalogueId: context.searchConfiguration.defaultCatalogueId ?? context.tideConnection.catalogueIds?.[0] ?? 1,
|
|
28247
|
+
searchConfigurationId: context.searchConfiguration.id,
|
|
28248
|
+
portalId: context.portalId ?? null,
|
|
28249
|
+
agentId: context.agentId ?? null,
|
|
28250
|
+
language: context.languageCode ?? 'en-GB',
|
|
28251
|
+
rooms: getPackagingRoomsFromEntry(trajectEntry.entry)
|
|
28252
|
+
});
|
|
28253
|
+
const buildTrajectNodeRequest = ({ node, line }, context, productCode, destination) => ({
|
|
28053
28254
|
transactionId: context.transactionId,
|
|
28054
28255
|
officeId: context.officeId,
|
|
28055
28256
|
agentId: context.agentId ?? null,
|
|
@@ -28060,25 +28261,69 @@ const buildTrajectNodeRequest = ({ node, line }, context, productCode) => ({
|
|
|
28060
28261
|
language: context.language,
|
|
28061
28262
|
serviceType: node.serviceType,
|
|
28062
28263
|
fromDate: toDateOnlyString(line.from),
|
|
28063
|
-
|
|
28064
|
-
|
|
28264
|
+
// An excursion happens on a day rather than over a span. The traject gives its line an end a day
|
|
28265
|
+
// out, the way a night is counted, which asks the search for a window it does not have.
|
|
28266
|
+
toDate: toDateOnlyString(node.serviceType === EXCURSION_SERVICE_TYPE ? line.from : line.to),
|
|
28267
|
+
destination: destination ?? buildDestination(line, node),
|
|
28065
28268
|
productCode,
|
|
28066
28269
|
rooms: context.rooms,
|
|
28067
28270
|
tagIds: []
|
|
28068
28271
|
});
|
|
28069
|
-
|
|
28070
|
-
|
|
28071
|
-
|
|
28072
|
-
|
|
28073
|
-
|
|
28074
|
-
|
|
28075
|
-
|
|
28076
|
-
|
|
28077
|
-
|
|
28078
|
-
|
|
28079
|
-
|
|
28080
|
-
|
|
28272
|
+
const coversDay = ({ node, line }, dayOrder) => {
|
|
28273
|
+
const nights = Math.max(dateFns.differenceInCalendarDays(new Date(line.to), new Date(line.from)), 1);
|
|
28274
|
+
return dayOrder >= node.dayOrder && dayOrder < node.dayOrder + nights;
|
|
28275
|
+
};
|
|
28276
|
+
const getDayDestinationItem = (items, dayOrder) => {
|
|
28277
|
+
const knowsWhereItIs = (x) => hasDestination(buildDestination(x.line, x.node));
|
|
28278
|
+
const stay = items.find((x) => x.node.serviceType === ACCOMMODATION_SERVICE_TYPE && coversDay(x, dayOrder) && knowsWhereItIs(x));
|
|
28279
|
+
if (stay)
|
|
28280
|
+
return stay;
|
|
28281
|
+
return ([...items]
|
|
28282
|
+
.filter((x) => x.node.dayOrder <= dayOrder && knowsWhereItIs(x))
|
|
28283
|
+
.sort((a, b) => a.node.dayOrder - b.node.dayOrder || a.node.order - b.node.order)
|
|
28284
|
+
.pop() ?? null);
|
|
28285
|
+
};
|
|
28286
|
+
const getDayDestination = (items, availability, dayOrder) => {
|
|
28287
|
+
const item = getDayDestinationItem(items, dayOrder);
|
|
28288
|
+
if (!item)
|
|
28289
|
+
return null;
|
|
28290
|
+
const resolved = availability[item.line.guid];
|
|
28291
|
+
const result = resolved?.results.find((x) => x.code === resolved.selectedCode) ?? null;
|
|
28292
|
+
const destination = buildDestination(item.line, item.node, result);
|
|
28293
|
+
return hasDestination(destination) ? destination : null;
|
|
28294
|
+
};
|
|
28295
|
+
const buildTrajectDayExcursionRequest = (date, destination, context) => ({
|
|
28296
|
+
transactionId: context.transactionId,
|
|
28297
|
+
officeId: context.officeId,
|
|
28298
|
+
agentId: context.agentId ?? null,
|
|
28299
|
+
catalogueId: context.catalogueId,
|
|
28300
|
+
searchConfigurationId: context.searchConfigurationId,
|
|
28301
|
+
portalId: context.portalId ?? null,
|
|
28302
|
+
vendorConfigurationId: null,
|
|
28303
|
+
language: context.language,
|
|
28304
|
+
serviceType: EXCURSION_SERVICE_TYPE,
|
|
28305
|
+
fromDate: toDateOnlyString(date),
|
|
28306
|
+
toDate: toDateOnlyString(date),
|
|
28307
|
+
destination,
|
|
28308
|
+
productCode: '',
|
|
28309
|
+
rooms: context.rooms,
|
|
28310
|
+
tagIds: []
|
|
28311
|
+
});
|
|
28312
|
+
const getExcursionOptionGroups = (result) => {
|
|
28313
|
+
const groups = new Map();
|
|
28314
|
+
(result.rooms ?? [])
|
|
28315
|
+
.flatMap((room) => room.options ?? [])
|
|
28316
|
+
.forEach((option) => {
|
|
28317
|
+
const key = [...(option.paxIds ?? [])].sort((a, b) => a - b).join(',');
|
|
28318
|
+
groups.set(key, [...(groups.get(key) ?? []), option]);
|
|
28081
28319
|
});
|
|
28320
|
+
return [...groups.values()];
|
|
28321
|
+
};
|
|
28322
|
+
const excursionNeedsChoice = (result) => getExcursionOptionGroups(result).some((group) => group.length > 1 && !group.some((option) => option.isSelected));
|
|
28323
|
+
const buildTrajectExcursionLines = (result) => {
|
|
28324
|
+
const options = getExcursionOptionGroups(result)
|
|
28325
|
+
.map((group) => group.find((x) => x.isSelected) ?? (group.length === 1 ? group[0] : null))
|
|
28326
|
+
.filter((x) => !!x);
|
|
28082
28327
|
if (!options.length)
|
|
28083
28328
|
return [];
|
|
28084
28329
|
const guids = options.map((option) => option.guid || crypto.randomUUID());
|
|
@@ -28107,6 +28352,21 @@ const buildTrajectExcursionLines = (result) => {
|
|
|
28107
28352
|
flightInformation: null
|
|
28108
28353
|
}));
|
|
28109
28354
|
};
|
|
28355
|
+
const buildExcursionSearchParams = (result) => ({
|
|
28356
|
+
date: result.fromDate,
|
|
28357
|
+
fromDate: result.fromDate,
|
|
28358
|
+
toDate: result.toDate,
|
|
28359
|
+
countryId: result.countryId,
|
|
28360
|
+
regionId: result.regionId,
|
|
28361
|
+
oordId: result.oordId,
|
|
28362
|
+
locationId: result.locationId,
|
|
28363
|
+
locationName: result.locationName,
|
|
28364
|
+
accommodationCode: null,
|
|
28365
|
+
accommodationName: null,
|
|
28366
|
+
hotelCode: result.code,
|
|
28367
|
+
hotelName: result.name,
|
|
28368
|
+
rooms: result.rooms ?? []
|
|
28369
|
+
});
|
|
28110
28370
|
const buildDayActivityCards = (items, availability, suggestions) => {
|
|
28111
28371
|
const cards = [];
|
|
28112
28372
|
const seen = new Set();
|
|
@@ -28123,11 +28383,27 @@ const buildDayActivityCards = (items, availability, suggestions) => {
|
|
|
28123
28383
|
results.filter((x) => !!findCandidateForResult(item.node, x)).forEach((result) => push(result, item));
|
|
28124
28384
|
});
|
|
28125
28385
|
suggestions.forEach((result) => push(result));
|
|
28126
|
-
|
|
28127
|
-
// nothing, as they are the same search, but a node can resolve where a day has no place to look.
|
|
28128
|
-
items.forEach((item) => (availability[item.line.guid]?.results ?? []).forEach((result) => push(result, item)));
|
|
28386
|
+
items.forEach((item) => (availability[item.line.guid]?.results ?? []).forEach((result) => push(result)));
|
|
28129
28387
|
return cards;
|
|
28130
28388
|
};
|
|
28389
|
+
/**
|
|
28390
|
+
* Where to search for a node.
|
|
28391
|
+
*
|
|
28392
|
+
* Normally the node says so itself, through the product it was configured with. An excursion slot
|
|
28393
|
+
* left empty on the traject says nothing at all — no code, no alternatives, and a line with no
|
|
28394
|
+
* place on it — so it falls back to wherever the party is staying that day, the same anchor the
|
|
28395
|
+
* day's own activity search uses.
|
|
28396
|
+
*/
|
|
28397
|
+
const getNodeSearchDestination = (items, item) => {
|
|
28398
|
+
const own = buildDestination(item.line, item.node);
|
|
28399
|
+
if (hasDestination(own))
|
|
28400
|
+
return own;
|
|
28401
|
+
return getDayDestination(items, {}, item.node.dayOrder) ?? own;
|
|
28402
|
+
};
|
|
28403
|
+
const getConfiguredExcursionCodes = (items, dayOrder) => new Set(items
|
|
28404
|
+
.filter((x) => x.node.dayOrder === dayOrder && x.node.serviceType === EXCURSION_SERVICE_TYPE)
|
|
28405
|
+
.flatMap((x) => [x.node.code, ...getNodeCandidates(x.node).map((candidate) => candidate.code)])
|
|
28406
|
+
.filter((code) => !!code));
|
|
28131
28407
|
const getNodeCandidates = (node) => {
|
|
28132
28408
|
const configured = (node.alternatives ?? []).filter((x) => !!x.code && x.code.trim().length > 0);
|
|
28133
28409
|
if (configured.length)
|
|
@@ -28236,6 +28512,51 @@ const buildTrajectFlightRequest = ({ node, line }, context, pax) => ({
|
|
|
28236
28512
|
outward: { date: dateToDateStruct(new Date(line.from)) },
|
|
28237
28513
|
return: null
|
|
28238
28514
|
});
|
|
28515
|
+
/**
|
|
28516
|
+
* The entry as the backend can price it.
|
|
28517
|
+
*
|
|
28518
|
+
* A search caches an EntryLine per option and hands back that line's guid on the option. The
|
|
28519
|
+
* backend then rebuilds the entry by looking each line up in that cache on its guid, so a line
|
|
28520
|
+
* it cannot find is dropped — PackagingEntryBuilder.SyncEntryLines skips it, and an entry whose
|
|
28521
|
+
* lines all came off a traject prices as nothing at all.
|
|
28522
|
+
*
|
|
28523
|
+
* A traject entry is rendered before any search runs, so its lines carry the guids the traject
|
|
28524
|
+
* mapper gave them. This swaps in the guid of whatever each line resolved onto, leaving the
|
|
28525
|
+
* lines the traveller added themselves alone — those were built from a search result and already
|
|
28526
|
+
* carry the right one.
|
|
28527
|
+
*/
|
|
28528
|
+
const withCachedEntryLineGuids = (entry, trajectEntry, nodeAvailability, flightAvailability) => {
|
|
28529
|
+
const cachedGuids = new Map();
|
|
28530
|
+
getTrajectNodesWithLines(trajectEntry).forEach((item) => {
|
|
28531
|
+
if (isTrajectFlightNode(item.node)) {
|
|
28532
|
+
// A flight search caches the outward line under the guid it reports as outwardGuid.
|
|
28533
|
+
const guid = flightAvailability[item.line.guid]?.selectedGuid;
|
|
28534
|
+
if (guid)
|
|
28535
|
+
cachedGuids.set(item.line.guid, guid);
|
|
28536
|
+
return;
|
|
28537
|
+
}
|
|
28538
|
+
const availability = nodeAvailability[item.line.guid];
|
|
28539
|
+
const result = availability?.results.find((x) => x.code === availability.selectedCode);
|
|
28540
|
+
if (!result)
|
|
28541
|
+
return;
|
|
28542
|
+
// Room i took option i when the result was stamped on, so it takes option i here too.
|
|
28543
|
+
item.lines.forEach((line, index) => {
|
|
28544
|
+
const guid = getOptionForRoom(result, index)?.guid;
|
|
28545
|
+
if (guid)
|
|
28546
|
+
cachedGuids.set(line.guid, guid);
|
|
28547
|
+
});
|
|
28548
|
+
});
|
|
28549
|
+
if (!cachedGuids.size)
|
|
28550
|
+
return entry;
|
|
28551
|
+
return {
|
|
28552
|
+
...entry,
|
|
28553
|
+
lines: (entry.lines ?? []).map((line) => ({
|
|
28554
|
+
...line,
|
|
28555
|
+
guid: cachedGuids.get(line.guid) ?? line.guid,
|
|
28556
|
+
parentGuid: (line.parentGuid && cachedGuids.get(line.parentGuid)) ?? line.parentGuid
|
|
28557
|
+
}))
|
|
28558
|
+
};
|
|
28559
|
+
};
|
|
28239
28560
|
const pickCheapestFlight = (flights) => [...flights].sort((a, b) => a.price - b.price)[0] ?? null;
|
|
28240
28561
|
const applyFlightToLine = (line, flight) => {
|
|
28241
28562
|
const segments = flight.outward?.segments ?? [];
|
|
@@ -28312,6 +28633,8 @@ const TrajectItinerary = ({ trajectEntry, packagingEntry, translations }) => {
|
|
|
28312
28633
|
trajectNodeType: REGULAR_TRAJECT_NODE_TYPE,
|
|
28313
28634
|
requirementType: 0,
|
|
28314
28635
|
contentSource: 0,
|
|
28636
|
+
duration: 0,
|
|
28637
|
+
durationType: 0,
|
|
28315
28638
|
alternatives: [],
|
|
28316
28639
|
from: line.from,
|
|
28317
28640
|
to: line.to
|
|
@@ -28345,6 +28668,8 @@ const TrajectItinerary = ({ trajectEntry, packagingEntry, translations }) => {
|
|
|
28345
28668
|
React__default["default"].createElement("p", null, dateFns.format(new Date(value), 'MMM'))));
|
|
28346
28669
|
const renderConnectingNode = ({ node }) => {
|
|
28347
28670
|
const distance = node.distanceInKm ? `${Math.round(node.distanceInKm)} km` : '';
|
|
28671
|
+
// How long the hop takes and how far it is, the way the traject editor puts them.
|
|
28672
|
+
const journey = [getTrajectNodeDuration(node), distance].filter(Boolean).join(' · ');
|
|
28348
28673
|
return (React__default["default"].createElement("div", { className: "search__filter__itinerary__transport", key: `connect-${node.nodeId}` },
|
|
28349
28674
|
React__default["default"].createElement("div", { className: "search__filter__itinerary__transport-timeline" },
|
|
28350
28675
|
React__default["default"].createElement("div", { className: "search__filter__itinerary__transport-timeline-line" })),
|
|
@@ -28353,8 +28678,8 @@ const TrajectItinerary = ({ trajectEntry, packagingEntry, translations }) => {
|
|
|
28353
28678
|
React__default["default"].createElement("div", { className: badgeClass('transport', getTrajectNodeBadgeModifier(node)) },
|
|
28354
28679
|
React__default["default"].createElement(Icon, { name: getTrajectNodeIcon(node), height: 15 })),
|
|
28355
28680
|
React__default["default"].createElement("div", { className: "search__filter__itinerary__transport-details" },
|
|
28356
|
-
React__default["default"].createElement("h6", null, node.name ||
|
|
28357
|
-
!!node.name && !!
|
|
28681
|
+
React__default["default"].createElement("h6", null, node.name || journey),
|
|
28682
|
+
!!node.name && !!journey && React__default["default"].createElement("p", { className: "search__filter__itinerary__transport-details-time" }, journey)))));
|
|
28358
28683
|
};
|
|
28359
28684
|
const renderOvernight = (item, index) => {
|
|
28360
28685
|
const { dayOrder } = item.node;
|
|
@@ -28498,7 +28823,7 @@ const getServiceTypePriority = (serviceType) => {
|
|
|
28498
28823
|
const Itinerary = ({ isOpen, handleSetIsOpen, isLoading, onEditAccommodation }) => {
|
|
28499
28824
|
const context = React.useContext(SearchResultsConfigurationContext);
|
|
28500
28825
|
const translations = getTranslations(context?.languageCode ?? 'en-GB');
|
|
28501
|
-
const { editablePackagingEntry, priceDetails } = reactRedux.useSelector((state) => state.searchResults);
|
|
28826
|
+
const { editablePackagingEntry, priceDetails, trajectItineraryView } = reactRedux.useSelector((state) => state.searchResults);
|
|
28502
28827
|
const dispatch = reactRedux.useDispatch();
|
|
28503
28828
|
const packagingEntry = editablePackagingEntry ?? context?.packagingEntry;
|
|
28504
28829
|
const sortedLines = React.useMemo(() => {
|
|
@@ -28541,6 +28866,11 @@ const Itinerary = ({ isOpen, handleSetIsOpen, isLoading, onEditAccommodation })
|
|
|
28541
28866
|
const handleConfirm = () => {
|
|
28542
28867
|
dispatch(setBookPackagingEntry(true));
|
|
28543
28868
|
};
|
|
28869
|
+
const handleTrajectView = (itinerary) => {
|
|
28870
|
+
dispatch(setTrajectItineraryView(itinerary));
|
|
28871
|
+
if (typeof window !== 'undefined')
|
|
28872
|
+
window.scrollTo(0, 0);
|
|
28873
|
+
};
|
|
28544
28874
|
return (React__default["default"].createElement("div", { className: `search__filters--modal ${isOpen ? 'is-open' : ''}` },
|
|
28545
28875
|
React__default["default"].createElement("div", { className: "search__filters--background", onClick: handleSetIsOpen }),
|
|
28546
28876
|
React__default["default"].createElement("button", { className: "search__filters--close", onClick: handleSetIsOpen },
|
|
@@ -28568,7 +28898,8 @@ const Itinerary = ({ isOpen, handleSetIsOpen, isLoading, onEditAccommodation })
|
|
|
28568
28898
|
" ",
|
|
28569
28899
|
translations.SRP.TOTAL,
|
|
28570
28900
|
")"))),
|
|
28571
|
-
React__default["default"].createElement("button", { className: "cta", onClick: handleConfirm }, translations.QSM.CONFIRM)
|
|
28901
|
+
React__default["default"].createElement("button", { className: "cta", onClick: handleConfirm }, translations.QSM.CONFIRM),
|
|
28902
|
+
!!context?.trajectEntry && (React__default["default"].createElement("button", { type: "button", className: "cta cta--secondary", onClick: () => handleTrajectView(!trajectItineraryView) }, trajectItineraryView ? translations.SRP.SHOW_SERVICES : translations.SRP.VIEW_FULL_ITINERARY))))),
|
|
28572
28903
|
React__default["default"].createElement("div", { className: "search__filter__itinerary" },
|
|
28573
28904
|
React__default["default"].createElement("p", null, translations.SRP.DAY_BY_DAY),
|
|
28574
28905
|
firstEntryLine && (React__default["default"].createElement("div", { className: "search__filter__itinerary__country" },
|
|
@@ -31062,10 +31393,9 @@ const getOptionPaxIds = (option) => {
|
|
|
31062
31393
|
const optionAppliesToPax = (option, paxId) => {
|
|
31063
31394
|
return getOptionPaxIds(option).includes(paxId);
|
|
31064
31395
|
};
|
|
31065
|
-
const optionAppliesToAllTravellers = (option,
|
|
31396
|
+
const optionAppliesToAllTravellers = (option, travellerIds) => {
|
|
31066
31397
|
const paxIds = getOptionPaxIds(option);
|
|
31067
|
-
|
|
31068
|
-
return paxIds.length === expected.length && paxIds.every((id, index) => id === expected[index]);
|
|
31398
|
+
return paxIds.length === travellerIds.length && travellerIds.every((id) => paxIds.includes(id));
|
|
31069
31399
|
};
|
|
31070
31400
|
const groupOptionsByExcursion = (options) => {
|
|
31071
31401
|
const groupedMap = new Map();
|
|
@@ -31090,26 +31420,26 @@ const ExcursionDetails = () => {
|
|
|
31090
31420
|
return null;
|
|
31091
31421
|
}
|
|
31092
31422
|
const translations = getTranslations(context.languageCode ?? 'en-GB');
|
|
31093
|
-
const
|
|
31423
|
+
const travellerIds = editablePackagingEntry.pax.map((pax) => pax.id);
|
|
31094
31424
|
const allOptions = React.useMemo(() => {
|
|
31095
31425
|
return selectedExcursionSearchResult.rooms.flatMap((room) => room.options ?? []);
|
|
31096
31426
|
}, [selectedExcursionSearchResult]);
|
|
31097
31427
|
const sharedOptions = React.useMemo(() => {
|
|
31098
|
-
return allOptions.filter((option) => optionAppliesToAllTravellers(option,
|
|
31099
|
-
}, [allOptions,
|
|
31428
|
+
return allOptions.filter((option) => optionAppliesToAllTravellers(option, travellerIds));
|
|
31429
|
+
}, [allOptions, travellerIds]);
|
|
31100
31430
|
const sharedExcursions = React.useMemo(() => {
|
|
31101
31431
|
return groupOptionsByExcursion(sharedOptions);
|
|
31102
31432
|
}, [sharedOptions]);
|
|
31103
31433
|
const paxGroups = React.useMemo(() => {
|
|
31104
31434
|
return editablePackagingEntry.pax.map((pax) => {
|
|
31105
|
-
const paxOptions = allOptions.filter((option) => optionAppliesToPax(option, pax.id) && !optionAppliesToAllTravellers(option,
|
|
31435
|
+
const paxOptions = allOptions.filter((option) => optionAppliesToPax(option, pax.id) && !optionAppliesToAllTravellers(option, travellerIds));
|
|
31106
31436
|
return {
|
|
31107
31437
|
pax,
|
|
31108
31438
|
paxId: pax.id,
|
|
31109
31439
|
excursions: groupOptionsByExcursion(paxOptions)
|
|
31110
31440
|
};
|
|
31111
31441
|
});
|
|
31112
|
-
}, [editablePackagingEntry.pax, allOptions,
|
|
31442
|
+
}, [editablePackagingEntry.pax, allOptions, travellerIds]);
|
|
31113
31443
|
const getSelectedSharedOption = () => {
|
|
31114
31444
|
return sharedOptions.find((option) => option.isSelected);
|
|
31115
31445
|
};
|
|
@@ -31117,11 +31447,11 @@ const ExcursionDetails = () => {
|
|
|
31117
31447
|
return sharedOptions.find((option) => option.accommodationCode === accommodationCode && option.isSelected);
|
|
31118
31448
|
};
|
|
31119
31449
|
const getSelectedOptionForPax = (paxId) => {
|
|
31120
|
-
return allOptions.find((option) => optionAppliesToPax(option, paxId) && !optionAppliesToAllTravellers(option,
|
|
31450
|
+
return allOptions.find((option) => optionAppliesToPax(option, paxId) && !optionAppliesToAllTravellers(option, travellerIds) && option.isSelected);
|
|
31121
31451
|
};
|
|
31122
31452
|
const getSelectedOptionForExcursion = (paxId, accommodationCode) => {
|
|
31123
31453
|
return allOptions.find((option) => optionAppliesToPax(option, paxId) &&
|
|
31124
|
-
!optionAppliesToAllTravellers(option,
|
|
31454
|
+
!optionAppliesToAllTravellers(option, travellerIds) &&
|
|
31125
31455
|
option.accommodationCode === accommodationCode &&
|
|
31126
31456
|
option.isSelected);
|
|
31127
31457
|
};
|
|
@@ -31131,7 +31461,7 @@ const ExcursionDetails = () => {
|
|
|
31131
31461
|
rooms: selectedExcursionSearchResult.rooms.map((room) => ({
|
|
31132
31462
|
...room,
|
|
31133
31463
|
options: room.options.map((option) => {
|
|
31134
|
-
const isSharedOption = optionAppliesToAllTravellers(option,
|
|
31464
|
+
const isSharedOption = optionAppliesToAllTravellers(option, travellerIds);
|
|
31135
31465
|
if (paxId === undefined) {
|
|
31136
31466
|
if (!isSharedOption) {
|
|
31137
31467
|
return option;
|
|
@@ -31178,7 +31508,7 @@ const ExcursionDetails = () => {
|
|
|
31178
31508
|
targetPrice = selectedOption.price;
|
|
31179
31509
|
}
|
|
31180
31510
|
else {
|
|
31181
|
-
const firstOption = allOptions.find((option) => optionAppliesToPax(option, paxId) && !optionAppliesToAllTravellers(option,
|
|
31511
|
+
const firstOption = allOptions.find((option) => optionAppliesToPax(option, paxId) && !optionAppliesToAllTravellers(option, travellerIds) && option.accommodationCode === accommodationCode);
|
|
31182
31512
|
targetPrice = firstOption?.price || 0;
|
|
31183
31513
|
}
|
|
31184
31514
|
return targetPrice - (currentSelectedPrice || 0);
|
|
@@ -31200,6 +31530,13 @@ const ExcursionDetails = () => {
|
|
|
31200
31530
|
return 'flyin__acco__price';
|
|
31201
31531
|
};
|
|
31202
31532
|
const handleConfirm = () => {
|
|
31533
|
+
if (context.trajectEntry) {
|
|
31534
|
+
const lines = buildTrajectExcursionLines(selectedExcursionSearchResult);
|
|
31535
|
+
if (lines.length)
|
|
31536
|
+
dispatch(addEditableEntryLines(lines));
|
|
31537
|
+
dispatch(setFlyInIsOpen(false));
|
|
31538
|
+
return;
|
|
31539
|
+
}
|
|
31203
31540
|
const dayKey = getExcursionDayKey(excursionSearchParams.date);
|
|
31204
31541
|
dispatch(confirmExcursionForDay({
|
|
31205
31542
|
dayKey,
|
|
@@ -31441,6 +31778,12 @@ const TrajectDayActivitiesFlyIn = ({ isLoading }) => {
|
|
|
31441
31778
|
dispatch(removeEditableEntryLines(added.map((line) => line.guid)));
|
|
31442
31779
|
return;
|
|
31443
31780
|
}
|
|
31781
|
+
if (excursionNeedsChoice(result)) {
|
|
31782
|
+
dispatch(setExcursionSearchParams(buildExcursionSearchParams(result)));
|
|
31783
|
+
dispatch(setSelectedExcursionSearchResult(result));
|
|
31784
|
+
dispatch(setFlyInType('excursion-details'));
|
|
31785
|
+
return;
|
|
31786
|
+
}
|
|
31444
31787
|
const lines = buildTrajectExcursionLines(result);
|
|
31445
31788
|
if (lines.length)
|
|
31446
31789
|
dispatch(addEditableEntryLines(lines));
|
|
@@ -31455,7 +31798,7 @@ const TrajectDayActivitiesFlyIn = ({ isLoading }) => {
|
|
|
31455
31798
|
dispatch(setFlyInIsOpen(false));
|
|
31456
31799
|
};
|
|
31457
31800
|
if (isLoading || suggestions?.status === 'loading') {
|
|
31458
|
-
return React__default["default"].createElement(React__default["default"].Fragment, null, context.customSpinner ?? React__default["default"].createElement(Spinner, { label: translations.SRP.
|
|
31801
|
+
return React__default["default"].createElement(React__default["default"].Fragment, null, context.customSpinner ?? React__default["default"].createElement(Spinner, { label: translations.SRP.LOADING_ACTIVITIES }));
|
|
31459
31802
|
}
|
|
31460
31803
|
return (React__default["default"].createElement("div", { className: "flyin__content" },
|
|
31461
31804
|
React__default["default"].createElement("div", { className: "search__result-row" },
|
|
@@ -33009,6 +33352,27 @@ const getRequestRoomsFromPackagingSegments = (entry, segments) => {
|
|
|
33009
33352
|
}));
|
|
33010
33353
|
};
|
|
33011
33354
|
|
|
33355
|
+
/**
|
|
33356
|
+
* The entry to send to the backend, as opposed to the one the page works with.
|
|
33357
|
+
*
|
|
33358
|
+
* They differ only for a traject: the page keeps the line guids the traject was rendered with,
|
|
33359
|
+
* because that is what joins its nodes to its lines, while pricing and booking need the guids the
|
|
33360
|
+
* searches cached their lines under. Anything else passes through untouched, so this can be used
|
|
33361
|
+
* wherever the entry leaves the component without checking which flow is running.
|
|
33362
|
+
*/
|
|
33363
|
+
const useBookableEntry = () => {
|
|
33364
|
+
const context = React.useContext(SearchResultsConfigurationContext);
|
|
33365
|
+
const { editablePackagingEntry, trajectNodeAvailability, trajectFlightAvailability } = reactRedux.useSelector((state) => state.searchResults);
|
|
33366
|
+
const trajectEntry = context?.trajectEntry;
|
|
33367
|
+
return React.useMemo(() => {
|
|
33368
|
+
if (!editablePackagingEntry)
|
|
33369
|
+
return null;
|
|
33370
|
+
if (!trajectEntry)
|
|
33371
|
+
return editablePackagingEntry;
|
|
33372
|
+
return withCachedEntryLineGuids(editablePackagingEntry, trajectEntry, trajectNodeAvailability, trajectFlightAvailability);
|
|
33373
|
+
}, [editablePackagingEntry, trajectEntry, trajectNodeAvailability, trajectFlightAvailability]);
|
|
33374
|
+
};
|
|
33375
|
+
|
|
33012
33376
|
const escapeHtml = (value) => {
|
|
33013
33377
|
if (!value)
|
|
33014
33378
|
return '';
|
|
@@ -33067,7 +33431,57 @@ const findAccommodationForDay = (nodes, currentDay) => {
|
|
|
33067
33431
|
}
|
|
33068
33432
|
return null;
|
|
33069
33433
|
};
|
|
33070
|
-
const
|
|
33434
|
+
const formatTime = (time) => (time ? time.slice(0, 5) : '');
|
|
33435
|
+
/**
|
|
33436
|
+
* Whether generated content actually shows something.
|
|
33437
|
+
*
|
|
33438
|
+
* A content view whose data is missing still returns its wrapper markup, or the whitespace left by
|
|
33439
|
+
* a guard around its body — markup that renders as a blank gap. Only text or an image counts as
|
|
33440
|
+
* something the traveller can read.
|
|
33441
|
+
*/
|
|
33442
|
+
const hasVisibleContent = (contents) => {
|
|
33443
|
+
if (!contents)
|
|
33444
|
+
return false;
|
|
33445
|
+
if (/<(img|svg|picture|video|iframe)\b/i.test(contents))
|
|
33446
|
+
return true;
|
|
33447
|
+
return (contents
|
|
33448
|
+
.replace(/<[^>]*>/g, ' ')
|
|
33449
|
+
.replace(/ /gi, ' ')
|
|
33450
|
+
.trim().length > 0);
|
|
33451
|
+
};
|
|
33452
|
+
/** The legs of a flight, as the entry line records them. */
|
|
33453
|
+
const buildFlightHtml = (line) => {
|
|
33454
|
+
const flightLines = line.flightInformation?.flightLines ?? [];
|
|
33455
|
+
if (!flightLines.length)
|
|
33456
|
+
return '';
|
|
33457
|
+
const legs = flightLines
|
|
33458
|
+
.map((leg) => {
|
|
33459
|
+
const from = escapeHtml(leg.departureAirportDescription || leg.departureAirportCode);
|
|
33460
|
+
const to = escapeHtml(leg.arrivalAirportDescription || leg.arrivalAirportCode);
|
|
33461
|
+
const carrier = escapeHtml([leg.airlineDescription, leg.flightNumber].filter(Boolean).join(' '));
|
|
33462
|
+
return `<li>${from} ${formatTime(leg.departureTime)} → ${to} ${formatTime(leg.arrivalTime)}${carrier ? ` · ${carrier}` : ''}</li>`;
|
|
33463
|
+
})
|
|
33464
|
+
.join('');
|
|
33465
|
+
return `<ul class="itinerary-item__legs">${legs}</ul>`;
|
|
33466
|
+
};
|
|
33467
|
+
/**
|
|
33468
|
+
* What to show for an item the generator returned no content for.
|
|
33469
|
+
*
|
|
33470
|
+
* An item comes back with a title and a template but an empty body whenever its template has no
|
|
33471
|
+
* content view of its own — flights, today. Rendering that empty body leaves a blank where a
|
|
33472
|
+
* service should be, so what the entry already knows about the line is drawn instead.
|
|
33473
|
+
*/
|
|
33474
|
+
const buildFallbackHtml = (item, line) => {
|
|
33475
|
+
const details = [line?.accommodationName, line?.regimeName].filter(Boolean).join(' — ');
|
|
33476
|
+
return `
|
|
33477
|
+
<div class="itinerary-item__fallback">
|
|
33478
|
+
<h3 class="itinerary-item__fallback-title">${escapeHtml(item.title)}</h3>
|
|
33479
|
+
${line ? buildFlightHtml(line) : ''}
|
|
33480
|
+
${details ? `<p class="itinerary-item__fallback-details">${escapeHtml(details)}</p>` : ''}
|
|
33481
|
+
</div>
|
|
33482
|
+
`;
|
|
33483
|
+
};
|
|
33484
|
+
const buildItineraryHtml = (itinerary, translations, lines) => {
|
|
33071
33485
|
if (!itinerary) {
|
|
33072
33486
|
return `
|
|
33073
33487
|
<div class="itinerary-shell">
|
|
@@ -33096,7 +33510,9 @@ const buildItineraryHtml = (itinerary, translations) => {
|
|
|
33096
33510
|
? uniqueItems
|
|
33097
33511
|
.map((item) => `
|
|
33098
33512
|
<article class="itinerary-item" data-template="${escapeHtml(item.templateName)}">
|
|
33099
|
-
${item.contents
|
|
33513
|
+
${hasVisibleContent(item.contents)
|
|
33514
|
+
? item.contents
|
|
33515
|
+
: buildFallbackHtml(item, lines.find((line) => line.guid === item.itemGuid))}
|
|
33100
33516
|
</article>
|
|
33101
33517
|
`)
|
|
33102
33518
|
.join('')
|
|
@@ -33130,7 +33546,9 @@ const buildItineraryHtml = (itinerary, translations) => {
|
|
|
33130
33546
|
${getUniqueItems(itinerary.defaultItems)
|
|
33131
33547
|
.map((item) => `
|
|
33132
33548
|
<article class="itinerary-item" data-template="${escapeHtml(item.templateName)}">
|
|
33133
|
-
${item.contents
|
|
33549
|
+
${hasVisibleContent(item.contents)
|
|
33550
|
+
? item.contents
|
|
33551
|
+
: buildFallbackHtml(item, lines.find((line) => line.guid === item.itemGuid))}
|
|
33134
33552
|
</article>
|
|
33135
33553
|
`)
|
|
33136
33554
|
.join('')}
|
|
@@ -33151,10 +33569,12 @@ const buildItineraryHtml = (itinerary, translations) => {
|
|
|
33151
33569
|
const FullItinerary = ({ isLoading }) => {
|
|
33152
33570
|
const { itinerary } = reactRedux.useSelector((state) => state.searchResults);
|
|
33153
33571
|
const context = React.useContext(SearchResultsConfigurationContext);
|
|
33572
|
+
// The entry as it was sent: its line guids are the ones the itinerary items carry back.
|
|
33573
|
+
const bookableEntry = useBookableEntry();
|
|
33154
33574
|
const translations = getTranslations(context?.languageCode ?? 'en-GB');
|
|
33155
33575
|
const hostRef = React.useRef(null);
|
|
33156
33576
|
const shadowRootRef = React.useRef(null);
|
|
33157
|
-
const html = React.useMemo(() => buildItineraryHtml(itinerary, translations), [itinerary, translations]);
|
|
33577
|
+
const html = React.useMemo(() => buildItineraryHtml(itinerary, translations, bookableEntry?.lines ?? []), [itinerary, translations, bookableEntry]);
|
|
33158
33578
|
React.useEffect(() => {
|
|
33159
33579
|
if (isLoading) {
|
|
33160
33580
|
return;
|
|
@@ -33268,6 +33688,35 @@ const FullItinerary = ({ isLoading }) => {
|
|
|
33268
33688
|
background: #fff;
|
|
33269
33689
|
}
|
|
33270
33690
|
|
|
33691
|
+
/* An item whose template has no content view of its own still has to read as a service. */
|
|
33692
|
+
.itinerary-item__fallback {
|
|
33693
|
+
padding: 16px 20px;
|
|
33694
|
+
border: 1px solid #e5e7eb;
|
|
33695
|
+
border-radius: 12px;
|
|
33696
|
+
}
|
|
33697
|
+
|
|
33698
|
+
.itinerary-item__fallback-title {
|
|
33699
|
+
margin: 0;
|
|
33700
|
+
font-size: 16px;
|
|
33701
|
+
font-weight: 600;
|
|
33702
|
+
color: #111827;
|
|
33703
|
+
}
|
|
33704
|
+
|
|
33705
|
+
.itinerary-item__legs {
|
|
33706
|
+
margin: 8px 0 0;
|
|
33707
|
+
padding-left: 18px;
|
|
33708
|
+
color: #374151;
|
|
33709
|
+
}
|
|
33710
|
+
|
|
33711
|
+
.itinerary-item__legs li + li {
|
|
33712
|
+
margin-top: 4px;
|
|
33713
|
+
}
|
|
33714
|
+
|
|
33715
|
+
.itinerary-item__fallback-details {
|
|
33716
|
+
margin: 8px 0 0;
|
|
33717
|
+
color: #6b7280;
|
|
33718
|
+
}
|
|
33719
|
+
|
|
33271
33720
|
.itinerary-node__empty,
|
|
33272
33721
|
.itinerary-empty {
|
|
33273
33722
|
padding: 20px;
|
|
@@ -33787,6 +34236,8 @@ const BookPackagingEntry = ({ activeSearchSeed, isLoading, isConfirmationPage })
|
|
|
33787
34236
|
setSelectedPackagingAccoResult(selectedPackagingAccoResult);
|
|
33788
34237
|
}
|
|
33789
34238
|
}, [selectedPackagingAccoResultCode, packagingAccoResults]);
|
|
34239
|
+
// What the page holds versus what the backend can look up; they differ for a traject.
|
|
34240
|
+
const bookableEntry = useBookableEntry();
|
|
33790
34241
|
if (!context || !editablePackagingEntry || !priceDetails || !config)
|
|
33791
34242
|
return React__default["default"].createElement(Spinner, { label: translations.SUMMARY.PROCESS_BOOKING });
|
|
33792
34243
|
const handleSummarySubmit = async (e) => {
|
|
@@ -33795,20 +34246,15 @@ const BookPackagingEntry = ({ activeSearchSeed, isLoading, isConfirmationPage })
|
|
|
33795
34246
|
if (typeof window !== 'undefined') {
|
|
33796
34247
|
window.scrollTo(0, 0);
|
|
33797
34248
|
}
|
|
33798
|
-
|
|
33799
|
-
|
|
33800
|
-
remarks
|
|
33801
|
-
};
|
|
34249
|
+
// Held apart from the entry itself so they can be put on both copies of it.
|
|
34250
|
+
let bookingFields = { remarks };
|
|
33802
34251
|
if (context.generatePaymentUrl && typeof window !== 'undefined') {
|
|
33803
34252
|
const redirectUrl = new URL(window.location.href);
|
|
33804
34253
|
redirectUrl.searchParams.set('bookingConfirmation', 'true');
|
|
33805
34254
|
redirectUrl.searchParams.set('link', '');
|
|
33806
|
-
|
|
33807
|
-
...updatedEditablePackagingEntry,
|
|
33808
|
-
redirectUrl: redirectUrl.toString(),
|
|
33809
|
-
returnPaymentUrl: true
|
|
33810
|
-
};
|
|
34255
|
+
bookingFields = { ...bookingFields, redirectUrl: redirectUrl.toString(), returnPaymentUrl: true };
|
|
33811
34256
|
}
|
|
34257
|
+
const updatedEditablePackagingEntry = { ...editablePackagingEntry, ...bookingFields };
|
|
33812
34258
|
dispatch(setEditablePackagingEntry(updatedEditablePackagingEntry));
|
|
33813
34259
|
try {
|
|
33814
34260
|
const request = {
|
|
@@ -33816,7 +34262,8 @@ const BookPackagingEntry = ({ activeSearchSeed, isLoading, isConfirmationPage })
|
|
|
33816
34262
|
officeId: context.tideConnection.officeId,
|
|
33817
34263
|
catalogueId: context.searchConfiguration.defaultCatalogueId ?? 0,
|
|
33818
34264
|
agentId: context.agentId,
|
|
33819
|
-
|
|
34265
|
+
// The page keeps the entry it works with; the backend gets the one it can look up.
|
|
34266
|
+
payload: { ...(bookableEntry ?? editablePackagingEntry), ...bookingFields }
|
|
33820
34267
|
};
|
|
33821
34268
|
const bookingResponse = await build.bookPackagingEntry(config, request);
|
|
33822
34269
|
dispatch(setBookingNumber(bookingResponse.number));
|
|
@@ -33848,6 +34295,41 @@ const BookPackagingEntry = ({ activeSearchSeed, isLoading, isConfirmationPage })
|
|
|
33848
34295
|
React__default["default"].createElement(WLSidebar, { activeSearchSeed: activeSearchSeed, packagingAccoResult: selectedPackagingAccoResult }))));
|
|
33849
34296
|
};
|
|
33850
34297
|
|
|
34298
|
+
const useTrajectDayActivities = () => {
|
|
34299
|
+
const context = React.useContext(SearchResultsConfigurationContext);
|
|
34300
|
+
const dispatch = reactRedux.useDispatch();
|
|
34301
|
+
const { trajectDaySuggestions, trajectNodeAvailability } = reactRedux.useSelector((state) => state.searchResults);
|
|
34302
|
+
const trajectEntry = context?.trajectEntry;
|
|
34303
|
+
const showApiItems = !!context?.showApiItems;
|
|
34304
|
+
const loadDay = React.useCallback(async (day) => {
|
|
34305
|
+
if (!context || !trajectEntry)
|
|
34306
|
+
return;
|
|
34307
|
+
// Already asked for, or already answered: leave it be.
|
|
34308
|
+
if (trajectDaySuggestions[day.order])
|
|
34309
|
+
return;
|
|
34310
|
+
const nodesWithLines = getTrajectNodesWithLines(trajectEntry);
|
|
34311
|
+
const destination = getDayDestination(nodesWithLines, trajectNodeAvailability, day.order);
|
|
34312
|
+
if (!destination)
|
|
34313
|
+
return;
|
|
34314
|
+
dispatch(setTrajectDaySuggestionsLoading(day.order));
|
|
34315
|
+
try {
|
|
34316
|
+
const results = (await build.searchPackagingExcursions(buildTrajectClientConfig(context), buildTrajectDayExcursionRequest(day.date, destination, buildTrajectSearchContext(context, trajectEntry)))) ?? [];
|
|
34317
|
+
// With external items switched off a day only offers what Tide holds itself, and whatever
|
|
34318
|
+
// the traject already books that day has a card of its own further up the block.
|
|
34319
|
+
const configured = getConfiguredExcursionCodes(nodesWithLines, day.order);
|
|
34320
|
+
dispatch(setTrajectDaySuggestions({
|
|
34321
|
+
dayOrder: day.order,
|
|
34322
|
+
results: results.filter((x) => showApiItems || !isExternalResult(x)).filter((x) => !configured.has(x.code))
|
|
34323
|
+
}));
|
|
34324
|
+
}
|
|
34325
|
+
catch (error) {
|
|
34326
|
+
console.error('Failed to load traject day activities', day.order, error);
|
|
34327
|
+
dispatch(setTrajectDaySuggestions({ dayOrder: day.order, results: [] }));
|
|
34328
|
+
}
|
|
34329
|
+
}, [context, trajectEntry, showApiItems, trajectDaySuggestions, trajectNodeAvailability, dispatch]);
|
|
34330
|
+
return loadDay;
|
|
34331
|
+
};
|
|
34332
|
+
|
|
33851
34333
|
const getNodeIcon = (node) => {
|
|
33852
34334
|
if (isTrajectFlightNode(node))
|
|
33853
34335
|
return 'ui-flight';
|
|
@@ -33863,6 +34345,7 @@ const MAX_DAY_ACTIVITIES = 6;
|
|
|
33863
34345
|
const TrajectResults = ({ isLoading }) => {
|
|
33864
34346
|
const context = React.useContext(SearchResultsConfigurationContext);
|
|
33865
34347
|
const dispatch = reactRedux.useDispatch();
|
|
34348
|
+
const loadDay = useTrajectDayActivities();
|
|
33866
34349
|
const translations = getTranslations(context?.languageCode ?? 'en-GB');
|
|
33867
34350
|
const locale = getLocale(context?.languageCode ?? 'en-GB');
|
|
33868
34351
|
const { trajectNodeAvailability, trajectFlightAvailability, trajectDaySuggestions, editablePackagingEntry } = reactRedux.useSelector((state) => state.searchResults);
|
|
@@ -33895,6 +34378,23 @@ const TrajectResults = ({ isLoading }) => {
|
|
|
33895
34378
|
});
|
|
33896
34379
|
return { stayByDay: byDay, stayStartByNodeId: byNodeId };
|
|
33897
34380
|
}, [nodesWithLines]);
|
|
34381
|
+
/** Whether every node of the trip has an answer, one way or the other. */
|
|
34382
|
+
const tripResolved = React.useMemo(() => nodesWithLines
|
|
34383
|
+
.filter((x) => isSearchableTrajectNode(x.node) || isTrajectFlightNode(x.node))
|
|
34384
|
+
.every((x) => {
|
|
34385
|
+
const status = isTrajectFlightNode(x.node) ? trajectFlightAvailability[x.line.guid]?.status : trajectNodeAvailability[x.line.guid]?.status;
|
|
34386
|
+
return !!status && status !== 'loading';
|
|
34387
|
+
}), [nodesWithLines, trajectNodeAvailability, trajectFlightAvailability]);
|
|
34388
|
+
/**
|
|
34389
|
+
* Each day's activities are one more search, and a traject has as many as it has days. They go
|
|
34390
|
+
* out together, but only once the trip itself has resolved — sharing the connections with the
|
|
34391
|
+
* searches the trip cannot do without would only make those slower.
|
|
34392
|
+
*/
|
|
34393
|
+
React.useEffect(() => {
|
|
34394
|
+
if (!trajectEntry || !tripResolved)
|
|
34395
|
+
return;
|
|
34396
|
+
(trajectEntry.traject.days ?? []).filter((day) => stayByDay.has(day.order)).forEach((day) => loadDay(day));
|
|
34397
|
+
}, [trajectEntry, tripResolved, stayByDay]);
|
|
33898
34398
|
if (!context || !trajectEntry)
|
|
33899
34399
|
return null;
|
|
33900
34400
|
const handleSelect = (item, code) => {
|
|
@@ -33979,7 +34479,7 @@ const TrajectResults = ({ isLoading }) => {
|
|
|
33979
34479
|
React__default["default"].createElement(Icon, { name: getNodeIcon(node), height: 16 }),
|
|
33980
34480
|
React__default["default"].createElement("h3", null,
|
|
33981
34481
|
React__default["default"].createElement("strong", null, headerTitle || node.name))))),
|
|
33982
|
-
status === 'loading' &&
|
|
34482
|
+
status === 'loading' && React__default["default"].createElement(Spinner, { label: translations.SRP.LOADING_ACCOMMODATIONS }),
|
|
33983
34483
|
status === 'unavailable' && React__default["default"].createElement("div", { className: "no-results" }, translations.SRP.NO_RESULTS),
|
|
33984
34484
|
status === 'resolved' && selected && (React__default["default"].createElement(React__default["default"].Fragment, null,
|
|
33985
34485
|
availability.preferredUnavailable && node.preferredAccommodationName && (React__default["default"].createElement("div", { className: "search__results__cards__actions" },
|
|
@@ -34015,8 +34515,6 @@ const TrajectResults = ({ isLoading }) => {
|
|
|
34015
34515
|
React__default["default"].createElement("div", { className: "search__results__label__text" },
|
|
34016
34516
|
React__default["default"].createElement(Icon, { name: "ui-flight", height: 16 }),
|
|
34017
34517
|
React__default["default"].createElement("h3", null,
|
|
34018
|
-
translations.SRP.SELECT,
|
|
34019
|
-
' ',
|
|
34020
34518
|
React__default["default"].createElement("strong", null,
|
|
34021
34519
|
node.departureAirportCode,
|
|
34022
34520
|
" - ",
|
|
@@ -34042,6 +34540,13 @@ const TrajectResults = ({ isLoading }) => {
|
|
|
34042
34540
|
dispatch(removeEditableEntryLines(added.map((line) => line.guid)));
|
|
34043
34541
|
return;
|
|
34044
34542
|
}
|
|
34543
|
+
if (excursionNeedsChoice(result)) {
|
|
34544
|
+
dispatch(setExcursionSearchParams(buildExcursionSearchParams(result)));
|
|
34545
|
+
dispatch(setSelectedExcursionSearchResult(result));
|
|
34546
|
+
dispatch(setFlyInType('excursion-details'));
|
|
34547
|
+
dispatch(setFlyInIsOpen(true));
|
|
34548
|
+
return;
|
|
34549
|
+
}
|
|
34045
34550
|
const lines = buildTrajectExcursionLines(result);
|
|
34046
34551
|
if (lines.length)
|
|
34047
34552
|
dispatch(addEditableEntryLines(lines));
|
|
@@ -34068,32 +34573,30 @@ const TrajectResults = ({ isLoading }) => {
|
|
|
34068
34573
|
const items = getDayExcursionItems(day.order);
|
|
34069
34574
|
const suggestions = trajectDaySuggestions[day.order];
|
|
34070
34575
|
const cards = buildDayActivityCards(items, trajectNodeAvailability, suggestions?.results ?? []);
|
|
34071
|
-
const
|
|
34072
|
-
|
|
34073
|
-
//
|
|
34074
|
-
|
|
34576
|
+
const nodesResolving = items.some((item) => (trajectNodeAvailability[item.line.guid]?.status ?? 'loading') === 'loading');
|
|
34577
|
+
const isSearching = suggestions?.status === 'loading';
|
|
34578
|
+
// A day is worth filling only if the party is staying somewhere that night. The day they fly
|
|
34579
|
+
// home is covered by no stay — the last night was the night before — and nobody is looking for
|
|
34580
|
+
// something to do on it.
|
|
34581
|
+
const canSearch = stayByDay.has(day.order) && !!getDayDestination(nodesWithLines, trajectNodeAvailability, day.order);
|
|
34582
|
+
if (!items.length && !canSearch)
|
|
34075
34583
|
return null;
|
|
34076
34584
|
const visible = cards.slice(0, MAX_DAY_ACTIVITIES);
|
|
34077
34585
|
const hidden = cards.length - visible.length;
|
|
34078
34586
|
return (React__default["default"].createElement(React__default["default"].Fragment, { key: `activities-${day.order}` },
|
|
34079
|
-
React__default["default"].createElement("div", { className: "search__results__label search__results__label--
|
|
34080
|
-
React__default["default"].createElement("div", { className: "search__results__label__date" },
|
|
34081
|
-
React__default["default"].createElement("p", { className: "search__results__label__date-date" }, dateFns.format(new Date(day.date), 'd', { locale })),
|
|
34082
|
-
React__default["default"].createElement("p", null, dateFns.format(new Date(day.date), 'MMM', { locale }))),
|
|
34587
|
+
React__default["default"].createElement("div", { className: "search__results__label search__results__label--sub" },
|
|
34083
34588
|
React__default["default"].createElement("div", { className: "search__results__label__text" },
|
|
34084
34589
|
React__default["default"].createElement(Icon, { name: "ui-excursion", height: 16 }),
|
|
34085
34590
|
React__default["default"].createElement("h3", null,
|
|
34086
34591
|
React__default["default"].createElement("strong", null, translations.SRP.ACTIVITIES)))),
|
|
34087
|
-
|
|
34088
|
-
|
|
34089
|
-
|
|
34090
|
-
|
|
34091
|
-
|
|
34092
|
-
|
|
34093
|
-
|
|
34094
|
-
|
|
34095
|
-
hidden,
|
|
34096
|
-
")")))))));
|
|
34592
|
+
(nodesResolving || isSearching) && !cards.length && React__default["default"].createElement(Spinner, { label: translations.SRP.LOADING_ACTIVITIES }),
|
|
34593
|
+
!!cards.length && React__default["default"].createElement("div", { className: "search__results__cards search__results__cards--scroll" }, visible.map(renderActivityCard)),
|
|
34594
|
+
!nodesResolving && !isSearching && !cards.length && (!!items.length || !!suggestions) && (React__default["default"].createElement("div", { className: "no-results" }, translations.SRP.NO_RESULTS)),
|
|
34595
|
+
React__default["default"].createElement("div", { className: "search__results__cards__actions" }, hidden > 0 && !hideShowMoreResults && (React__default["default"].createElement("button", { type: "button", className: "cta cta--secondary", onClick: () => handleShowMoreActivities(day.order) },
|
|
34596
|
+
translations.SRP.SHOW_MORE,
|
|
34597
|
+
" (",
|
|
34598
|
+
hidden,
|
|
34599
|
+
")")))));
|
|
34097
34600
|
};
|
|
34098
34601
|
/**
|
|
34099
34602
|
* An activity the traject books is swapped for another, the way a hotel is; one the traveller
|
|
@@ -34144,6 +34647,7 @@ const useTrajectAvailability = () => {
|
|
|
34144
34647
|
return;
|
|
34145
34648
|
const controller = new AbortController();
|
|
34146
34649
|
dispatch(resetTrajectAvailability());
|
|
34650
|
+
dispatch(setTrajectResolving(true));
|
|
34147
34651
|
const config = {
|
|
34148
34652
|
host: context.tideConnection.host,
|
|
34149
34653
|
apiKey: context.tideConnection.apiKey
|
|
@@ -34170,42 +34674,51 @@ const useTrajectAvailability = () => {
|
|
|
34170
34674
|
const lineGuid = item.line.guid;
|
|
34171
34675
|
const search = item.node.serviceType === EXCURSION_SERVICE_TYPE ? build.searchPackagingExcursions : build.searchPackagingAccommodations;
|
|
34172
34676
|
dispatch(setTrajectNodeLoading(lineGuid));
|
|
34677
|
+
const destination = getNodeSearchDestination(nodesWithLines, item);
|
|
34678
|
+
const searchFor = async (productCode) => {
|
|
34679
|
+
try {
|
|
34680
|
+
const results = (await search(config, buildTrajectNodeRequest(item, searchContext, productCode, destination), controller.signal)) ?? [];
|
|
34681
|
+
return results.filter((x) => showApiItems || !isExternalResult(x));
|
|
34682
|
+
}
|
|
34683
|
+
catch (error) {
|
|
34684
|
+
if (!controller.signal.aborted)
|
|
34685
|
+
console.error('Traject product unavailable', item.node.name, productCode, error);
|
|
34686
|
+
return [];
|
|
34687
|
+
}
|
|
34688
|
+
};
|
|
34173
34689
|
try {
|
|
34174
34690
|
// With external items switched off the node only looks at products held inside Tide.
|
|
34175
34691
|
const candidates = getNodeCandidates(item.node).filter((x) => showApiItems || !isExternalCandidate(x));
|
|
34176
|
-
const
|
|
34177
|
-
|
|
34178
|
-
|
|
34179
|
-
|
|
34180
|
-
|
|
34181
|
-
|
|
34182
|
-
|
|
34183
|
-
|
|
34184
|
-
|
|
34185
|
-
}
|
|
34692
|
+
const areaResults = hideShowMoreResults ? [] : await searchFor('');
|
|
34693
|
+
if (controller.signal.aborted)
|
|
34694
|
+
return [];
|
|
34695
|
+
const configuredResults = [];
|
|
34696
|
+
const found = await Promise.all(candidates.map(async (candidate) => {
|
|
34697
|
+
const known = areaResults.find((x) => matchesCode(x, candidate.code));
|
|
34698
|
+
if (known)
|
|
34699
|
+
return known;
|
|
34700
|
+
return (await searchFor(candidate.code)).find((x) => matchesCode(x, candidate.code)) ?? null;
|
|
34186
34701
|
}));
|
|
34187
|
-
|
|
34188
|
-
|
|
34189
|
-
|
|
34702
|
+
if (controller.signal.aborted)
|
|
34703
|
+
return [];
|
|
34704
|
+
// Two configured codes can land on the same product — a hotel and one of its room types.
|
|
34705
|
+
found.forEach((result) => {
|
|
34706
|
+
if (!result || configuredResults.some((x) => x.code === result.code))
|
|
34707
|
+
return;
|
|
34708
|
+
configuredResults.push(result);
|
|
34709
|
+
});
|
|
34710
|
+
const configuredCodes = new Set(configuredResults.map((x) => x.code));
|
|
34711
|
+
const destinationResults = areaResults.filter((x) => !configuredCodes.has(x.code));
|
|
34190
34712
|
const preferredCandidate = candidates.find((x) => x.isPreferred) ?? candidates[0];
|
|
34191
|
-
|
|
34192
|
-
|
|
34193
|
-
|
|
34194
|
-
// rather than quietly substituting a hotel nobody configured.
|
|
34195
|
-
const destinationResults = hideShowMoreResults
|
|
34196
|
-
? []
|
|
34197
|
-
: ((await search(config, buildTrajectNodeRequest(item, searchContext, ''), controller.signal)) ?? [])
|
|
34198
|
-
.filter((x) => showApiItems || !isExternalResult(x))
|
|
34199
|
-
.filter((x) => !configuredResults.some((configured) => configured.code === x.code));
|
|
34713
|
+
const preferredUnavailable = configuredResults.length
|
|
34714
|
+
? !!preferredCandidate && !configuredResults.some((x) => matchesCode(x, preferredCandidate.code))
|
|
34715
|
+
: candidates.length > 0;
|
|
34200
34716
|
const picked = configuredResults[0] ?? pickCheapest(destinationResults);
|
|
34201
|
-
const results = [...configuredResults, ...destinationResults];
|
|
34202
|
-
if (!configuredResults.length)
|
|
34203
|
-
preferredUnavailable = candidates.length > 0;
|
|
34204
34717
|
if (controller.signal.aborted)
|
|
34205
34718
|
return [];
|
|
34206
34719
|
dispatch(setTrajectNodeAvailability({
|
|
34207
34720
|
lineGuid,
|
|
34208
|
-
results,
|
|
34721
|
+
results: [...configuredResults, ...destinationResults],
|
|
34209
34722
|
selectedCode: picked?.code ?? null,
|
|
34210
34723
|
preferredUnavailable
|
|
34211
34724
|
}));
|
|
@@ -34249,11 +34762,11 @@ const useTrajectAvailability = () => {
|
|
|
34249
34762
|
}
|
|
34250
34763
|
};
|
|
34251
34764
|
(async () => {
|
|
34252
|
-
const
|
|
34253
|
-
const
|
|
34254
|
-
const unresolved = (await Promise.all([...searchable.map(resolveNode), ...flightNodes.map(resolveFlightNode)])).flat();
|
|
34765
|
+
const resolvable = nodesWithLines.filter((x) => isSearchableTrajectNode(x.node) || isTrajectFlightNode(x.node));
|
|
34766
|
+
const unresolved = (await Promise.all(resolvable.map((item) => (isTrajectFlightNode(item.node) ? resolveFlightNode(item) : resolveNode(item))))).flat();
|
|
34255
34767
|
if (controller.signal.aborted)
|
|
34256
34768
|
return;
|
|
34769
|
+
dispatch(setTrajectResolving(false));
|
|
34257
34770
|
// A node with no availability anywhere cannot be priced or booked, so its line comes off
|
|
34258
34771
|
// the entry. The card stays visible and reports the gap.
|
|
34259
34772
|
const emptyLineGuids = unresolved.filter(Boolean);
|
|
@@ -34269,10 +34782,12 @@ const SearchResultsContainer = ({ onBookingStarted }) => {
|
|
|
34269
34782
|
const dispatch = reactRedux.useDispatch();
|
|
34270
34783
|
const context = React.useContext(SearchResultsConfigurationContext);
|
|
34271
34784
|
const translations = getTranslations(context?.languageCode ?? 'en-GB');
|
|
34272
|
-
const { results, filteredResults, packagingAccoResults, filteredPackagingAccoResults, isLoading, flightsLoading, initialFilters, filters, flightFilters, selectedSortType, selectedFlightSortType, selectedSearchResult, selectedPackagingAccoResultCode, flyInIsOpen, packagingAccoSearchDetails, editablePackagingEntry, transactionId, flyInType, packagingFlightResults, confirmedExcursionsByDay, bookPackagingEntry, trajectEditLineGuid } = reactRedux.useSelector((state) => state.searchResults);
|
|
34785
|
+
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 } = reactRedux.useSelector((state) => state.searchResults);
|
|
34273
34786
|
const isMobile = useMediaQuery('(max-width: 1200px)');
|
|
34274
34787
|
// Resolves live availability per traject node; no-op unless a traject entry is configured.
|
|
34275
34788
|
useTrajectAvailability();
|
|
34789
|
+
// The entry as the backend can price it: a traject swaps in the guids its searches cached.
|
|
34790
|
+
const bookableEntry = useBookableEntry();
|
|
34276
34791
|
const [initialFiltersSet, setInitialFiltersSet] = React.useState(false);
|
|
34277
34792
|
const [initialFlightFiltersSet, setInitialFlightFiltersSet] = React.useState(false);
|
|
34278
34793
|
const [filtersOpen, setFiltersOpen] = React.useState(false);
|
|
@@ -35039,6 +35554,10 @@ const SearchResultsContainer = ({ onBookingStarted }) => {
|
|
|
35039
35554
|
const fetchPriceDetails = async () => {
|
|
35040
35555
|
if (!context || !editablePackagingEntry || lodash.isEmpty(editablePackagingEntry.lines))
|
|
35041
35556
|
return;
|
|
35557
|
+
// A traject resolves one node at a time. Pricing each step would show a total that climbs
|
|
35558
|
+
// as the trip fills in, so the whole trip is priced once, when the last node is in.
|
|
35559
|
+
if (context.trajectEntry && trajectResolving)
|
|
35560
|
+
return;
|
|
35042
35561
|
setPricesAreLoading(true);
|
|
35043
35562
|
try {
|
|
35044
35563
|
const config = {
|
|
@@ -35050,7 +35569,7 @@ const SearchResultsContainer = ({ onBookingStarted }) => {
|
|
|
35050
35569
|
officeId: context.tideConnection.officeId,
|
|
35051
35570
|
catalogueId: context.searchConfiguration.defaultCatalogueId ?? lodash.first(context?.tideConnection?.catalogueIds) ?? 1,
|
|
35052
35571
|
agentId: context.agentId,
|
|
35053
|
-
payload: editablePackagingEntry
|
|
35572
|
+
payload: bookableEntry ?? editablePackagingEntry
|
|
35054
35573
|
};
|
|
35055
35574
|
const priceDetails = await build.getPriceDetails(config, request);
|
|
35056
35575
|
dispatch(setPriceDetails(priceDetails));
|
|
@@ -35061,11 +35580,15 @@ const SearchResultsContainer = ({ onBookingStarted }) => {
|
|
|
35061
35580
|
setPricesAreLoading(false);
|
|
35062
35581
|
}
|
|
35063
35582
|
};
|
|
35583
|
+
fetchPriceDetails();
|
|
35584
|
+
}, [editablePackagingEntry, bookableEntry, trajectResolving]);
|
|
35585
|
+
React.useEffect(() => {
|
|
35064
35586
|
const fetchItinerary = async () => {
|
|
35065
35587
|
if (!context || !context.packagingEntry || !editablePackagingEntry || lodash.isEmpty(editablePackagingEntry.lines))
|
|
35066
35588
|
return;
|
|
35067
|
-
//
|
|
35068
|
-
|
|
35589
|
+
// While a traject is being composed its own day-by-day column is the itinerary; the generated
|
|
35590
|
+
// one is what the traveller reads the finished trip back from, so it waits until then.
|
|
35591
|
+
if (context.trajectEntry && (trajectResolving || !trajectItineraryView))
|
|
35069
35592
|
return;
|
|
35070
35593
|
setItineraryIsLoading(true);
|
|
35071
35594
|
try {
|
|
@@ -35078,7 +35601,7 @@ const SearchResultsContainer = ({ onBookingStarted }) => {
|
|
|
35078
35601
|
officeId: context.tideConnection.officeId,
|
|
35079
35602
|
catalogueId: context.searchConfiguration.defaultCatalogueId ?? lodash.first(context?.tideConnection?.catalogueIds) ?? 1,
|
|
35080
35603
|
agentId: context.agentId,
|
|
35081
|
-
payload: editablePackagingEntry
|
|
35604
|
+
payload: bookableEntry ?? editablePackagingEntry
|
|
35082
35605
|
};
|
|
35083
35606
|
const itinerary = await build.getItinerary(config, request);
|
|
35084
35607
|
dispatch(setItinerary(itinerary));
|
|
@@ -35089,9 +35612,8 @@ const SearchResultsContainer = ({ onBookingStarted }) => {
|
|
|
35089
35612
|
setItineraryIsLoading(false);
|
|
35090
35613
|
}
|
|
35091
35614
|
};
|
|
35092
|
-
fetchPriceDetails();
|
|
35093
35615
|
fetchItinerary();
|
|
35094
|
-
}, [editablePackagingEntry]);
|
|
35616
|
+
}, [editablePackagingEntry, bookableEntry, trajectResolving, trajectItineraryView]);
|
|
35095
35617
|
// Flight selection
|
|
35096
35618
|
// const [selectedOutwardKey, setSelectedOutwardKey] = useState<string | null>(null);
|
|
35097
35619
|
// const [selectedReturnKey, setSelectedReturnKey] = useState<string | null>(null);
|
|
@@ -35455,7 +35977,7 @@ const SearchResultsContainer = ({ onBookingStarted }) => {
|
|
|
35455
35977
|
context.searchConfiguration.qsmType != build.PortalQsmType.AccommodationAndFlight && context.showFilters && (React__default["default"].createElement(Filters, { initialFilters: initialFilters, filters: filters, isOpen: filtersOpen, handleSetIsOpen: () => setFiltersOpen(!filtersOpen),
|
|
35456
35978
|
// handleApplyFilters={() => setSearchTrigger((prev) => prev + 1)}
|
|
35457
35979
|
isLoading: isLoading, setFilters: (filters) => dispatch(setFilters(filters)), resetFilters: (filters) => dispatch(resetFilters(filters)) })),
|
|
35458
|
-
context.searchConfiguration.qsmType === build.PortalQsmType.AccommodationAndFlight && (React__default["default"].createElement(Itinerary, { isOpen: itineraryOpen, handleSetIsOpen: () => setItineraryOpen(!itineraryOpen), isLoading: isLoading || pricesAreLoading || flightsLoading || itineraryIsLoading, onEditAccommodation: handleEditAccommodation })),
|
|
35980
|
+
context.searchConfiguration.qsmType === build.PortalQsmType.AccommodationAndFlight && (React__default["default"].createElement(Itinerary, { isOpen: itineraryOpen, handleSetIsOpen: () => setItineraryOpen(!itineraryOpen), isLoading: isLoading || pricesAreLoading || flightsLoading || itineraryIsLoading || trajectResolving, onEditAccommodation: handleEditAccommodation })),
|
|
35459
35981
|
React__default["default"].createElement("div", { className: "search__results" },
|
|
35460
35982
|
isMobile && (React__default["default"].createElement("div", { className: "search__result-row" },
|
|
35461
35983
|
React__default["default"].createElement("div", { className: "search__results__actions" },
|
|
@@ -35529,7 +36051,8 @@ const SearchResultsContainer = ({ onBookingStarted }) => {
|
|
|
35529
36051
|
React__default["default"].createElement("div", { className: "search__results__cards search__results__cards--extended" },
|
|
35530
36052
|
selectedReturnKey && selectedReturn && (React__default["default"].createElement(IndependentFlightOption, { key: `flight-${selectedReturnKey}`, item: selectedReturn.return, guid: selectedReturn.outwardGuid, selectedGuid: selectedReturn.outwardGuid, isOutward: false, showSelectedState: true, price: selectedReturn.price })),
|
|
35531
36053
|
uniqueReturnFlights.map((result) => (React__default["default"].createElement(IndependentFlightOption, { key: `flight-${result.outwardGuid}`, item: result.return, onSelect: () => dispatch(setSelectedReturnKey(getFlightKey(result.return.segments))), guid: result.outwardGuid, isOutward: false, currentSelectedPrice: selectedReturn?.price, price: result.price })))))),
|
|
35532
|
-
context.searchConfiguration.qsmType === build.PortalQsmType.AccommodationAndFlight && context.trajectEntry && (React__default["default"].createElement(TrajectResults, { isLoading: isLoading })),
|
|
36054
|
+
context.searchConfiguration.qsmType === build.PortalQsmType.AccommodationAndFlight && context.trajectEntry && !trajectItineraryView && (React__default["default"].createElement(TrajectResults, { isLoading: isLoading })),
|
|
36055
|
+
context.searchConfiguration.qsmType === build.PortalQsmType.AccommodationAndFlight && context.trajectEntry && trajectItineraryView && (React__default["default"].createElement(FullItinerary, { isLoading: itineraryIsLoading })),
|
|
35533
36056
|
context.searchConfiguration.qsmType === build.PortalQsmType.AccommodationAndFlight && context.packagingEntry && !context.trajectEntry && (React__default["default"].createElement(FullItinerary, { isLoading: itineraryIsLoading })))),
|
|
35534
36057
|
React__default["default"].createElement(FlyIn, { srpType: context.searchConfiguration.qsmType, isOpen: flyInIsOpen, setIsOpen: handleFlyInToggle, handleConfirm: () => handleConfirmHotelSwap(), onPanelRef: (el) => (panelRef.current = el), detailsLoading: detailsIsLoading, flyInType: flyInType, isPackageEditFlow: !!context.packagingEntry, sortByTypes: sortByTypes, activeSearchSeed: activeSearchSeed, toggleFilters: () => setFiltersOpen(!filtersOpen), filtersOpen: filtersOpen })))))))));
|
|
35535
36058
|
};
|