@qite/tide-components 1.0.6 → 1.0.8
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 +1368 -395
- package/build/build-cjs/src/search-results/components/itinerary/itinerary-utils.d.ts +12 -0
- package/build/build-cjs/src/search-results/components/itinerary/traject-itinerary.d.ts +9 -0
- package/build/build-cjs/src/search-results/components/traject/traject-day-activities-flyin.d.ts +6 -0
- package/build/build-cjs/src/search-results/components/traject/traject-flights-flyin.d.ts +13 -0
- package/build/build-cjs/src/search-results/components/traject/traject-node-card.d.ts +6 -3
- package/build/build-cjs/src/search-results/hooks/use-bookable-entry.d.ts +11 -0
- package/build/build-cjs/src/search-results/hooks/use-traject-day-activities.d.ts +6 -0
- package/build/build-cjs/src/search-results/store/search-results-slice.d.ts +14 -2
- package/build/build-cjs/src/search-results/types.d.ts +12 -2
- package/build/build-cjs/src/search-results/utils/traject-utils.d.ts +76 -2
- package/build/build-esm/index.js +1368 -395
- package/build/build-esm/src/search-results/components/itinerary/itinerary-utils.d.ts +12 -0
- package/build/build-esm/src/search-results/components/itinerary/traject-itinerary.d.ts +9 -0
- package/build/build-esm/src/search-results/components/traject/traject-day-activities-flyin.d.ts +6 -0
- package/build/build-esm/src/search-results/components/traject/traject-flights-flyin.d.ts +13 -0
- package/build/build-esm/src/search-results/components/traject/traject-node-card.d.ts +6 -3
- package/build/build-esm/src/search-results/hooks/use-bookable-entry.d.ts +11 -0
- package/build/build-esm/src/search-results/hooks/use-traject-day-activities.d.ts +6 -0
- package/build/build-esm/src/search-results/store/search-results-slice.d.ts +14 -2
- package/build/build-esm/src/search-results/types.d.ts +12 -2
- package/build/build-esm/src/search-results/utils/traject-utils.d.ts +76 -2
- package/package.json +2 -2
- package/styles/booking-search-results-variables.scss +6 -0
- package/styles/components/_search.scss +94 -5
package/build/build-esm/index.js
CHANGED
|
@@ -409,6 +409,11 @@ var SRP$f = {
|
|
|
409
409
|
ACCOMMODATION: "الإقامة",
|
|
410
410
|
VIEW_DETAILS: "عرض التفاصيل",
|
|
411
411
|
NIGHTS: "ليالٍ",
|
|
412
|
+
OF: "من",
|
|
413
|
+
ADD: "إضافة",
|
|
414
|
+
REMOVE: "إزالة",
|
|
415
|
+
ACTIVITIES: "الأنشطة",
|
|
416
|
+
SHOW_ACTIVITIES: "عرض الأنشطة",
|
|
412
417
|
MORNING_RANGE: "الصباح",
|
|
413
418
|
AFTERNOON_RANGE: "بعد الظهر",
|
|
414
419
|
EVENING_RANGE: "المساء",
|
|
@@ -424,6 +429,7 @@ var SRP$f = {
|
|
|
424
429
|
TRAVEL_GROUP: "مجموعة المسافرين",
|
|
425
430
|
EXCURSION: "رحلة",
|
|
426
431
|
LOADING_EXCURSIONS: "جارٍ تحميل الرحلات...",
|
|
432
|
+
LOADING_ACTIVITIES: "جارٍ تحميل الأنشطة...",
|
|
427
433
|
LOADING_FLIGHTS: "جارٍ تحميل الرحلات الجوية...",
|
|
428
434
|
LOADING_ACCOMMODATIONS: "جارٍ تحميل الإقامات...",
|
|
429
435
|
LOADING_ITINERARY: "جارٍ تحميل خط سير الرحلة...",
|
|
@@ -848,6 +854,11 @@ var SRP$e = {
|
|
|
848
854
|
ACCOMMODATION: "Indkvartering",
|
|
849
855
|
VIEW_DETAILS: "Se detaljer",
|
|
850
856
|
NIGHTS: "nætter",
|
|
857
|
+
OF: "af",
|
|
858
|
+
ADD: "Tilføj",
|
|
859
|
+
REMOVE: "Fjern",
|
|
860
|
+
ACTIVITIES: "Aktiviteter",
|
|
861
|
+
SHOW_ACTIVITIES: "Vis aktiviteter",
|
|
851
862
|
MORNING_RANGE: "Morgen",
|
|
852
863
|
AFTERNOON_RANGE: "Eftermiddag",
|
|
853
864
|
EVENING_RANGE: "Aften",
|
|
@@ -863,6 +874,7 @@ var SRP$e = {
|
|
|
863
874
|
TRAVEL_GROUP: "Rejseselskab",
|
|
864
875
|
EXCURSION: "Udflugt",
|
|
865
876
|
LOADING_EXCURSIONS: "Indlæser udflugter...",
|
|
877
|
+
LOADING_ACTIVITIES: "Indlæser aktiviteter...",
|
|
866
878
|
LOADING_FLIGHTS: "Indlæser fly...",
|
|
867
879
|
LOADING_ACCOMMODATIONS: "Indlæser indkvarteringer...",
|
|
868
880
|
LOADING_ITINERARY: "Indlæser rejseplan...",
|
|
@@ -1291,6 +1303,11 @@ var SRP$d = {
|
|
|
1291
1303
|
ACCOMMODATION: "Unterkunft",
|
|
1292
1304
|
VIEW_DETAILS: "Details anzeigen",
|
|
1293
1305
|
NIGHTS: "Nächte",
|
|
1306
|
+
OF: "von",
|
|
1307
|
+
ADD: "Hinzufügen",
|
|
1308
|
+
REMOVE: "Entfernen",
|
|
1309
|
+
ACTIVITIES: "Aktivitäten",
|
|
1310
|
+
SHOW_ACTIVITIES: "Aktivitäten anzeigen",
|
|
1294
1311
|
MORNING_RANGE: "Morgen",
|
|
1295
1312
|
AFTERNOON_RANGE: "Nachmittag",
|
|
1296
1313
|
EVENING_RANGE: "Abend",
|
|
@@ -1302,6 +1319,7 @@ var SRP$d = {
|
|
|
1302
1319
|
TRAVEL_GROUP: "Reisegruppe",
|
|
1303
1320
|
EXCURSION: "Ausflug",
|
|
1304
1321
|
LOADING_EXCURSIONS: "Lade Ausflüge...",
|
|
1322
|
+
LOADING_ACTIVITIES: "Lade Aktivitäten...",
|
|
1305
1323
|
LOADING_FLIGHTS: "Lade Flüge...",
|
|
1306
1324
|
LOADING_ACCOMMODATIONS: "Lade Unterkünfte...",
|
|
1307
1325
|
LOADING_ITINERARY: "Lade Reiseverlauf...",
|
|
@@ -1734,6 +1752,11 @@ var SRP$c = {
|
|
|
1734
1752
|
ACCOMMODATION: "Accommodation",
|
|
1735
1753
|
VIEW_DETAILS: "View details",
|
|
1736
1754
|
NIGHTS: "nights",
|
|
1755
|
+
OF: "of",
|
|
1756
|
+
ADD: "Add",
|
|
1757
|
+
REMOVE: "Remove",
|
|
1758
|
+
ACTIVITIES: "Activities",
|
|
1759
|
+
SHOW_ACTIVITIES: "Show activities",
|
|
1737
1760
|
PRICE: "Price",
|
|
1738
1761
|
MORNING_RANGE: "Morning",
|
|
1739
1762
|
AFTERNOON_RANGE: "Afternoon",
|
|
@@ -1745,6 +1768,7 @@ var SRP$c = {
|
|
|
1745
1768
|
TRAVEL_GROUP: "Travel group",
|
|
1746
1769
|
EXCURSION: "Excursion",
|
|
1747
1770
|
LOADING_EXCURSIONS: "Loading excursions...",
|
|
1771
|
+
LOADING_ACTIVITIES: "Loading activities...",
|
|
1748
1772
|
LOADING_FLIGHTS: "Loading flights...",
|
|
1749
1773
|
LOADING_ACCOMMODATIONS: "Loading accommodations...",
|
|
1750
1774
|
LOADING_ITINERARY: "Loading itinerary...",
|
|
@@ -2173,6 +2197,11 @@ var SRP$b = {
|
|
|
2173
2197
|
ACCOMMODATION: "Alojamiento",
|
|
2174
2198
|
VIEW_DETAILS: "Ver detalles",
|
|
2175
2199
|
NIGHTS: "noches",
|
|
2200
|
+
OF: "de",
|
|
2201
|
+
ADD: "Añadir",
|
|
2202
|
+
REMOVE: "Eliminar",
|
|
2203
|
+
ACTIVITIES: "Actividades",
|
|
2204
|
+
SHOW_ACTIVITIES: "Ver actividades",
|
|
2176
2205
|
MORNING_RANGE: "Mañana",
|
|
2177
2206
|
AFTERNOON_RANGE: "Tarde",
|
|
2178
2207
|
EVENING_RANGE: "Noche",
|
|
@@ -2184,6 +2213,7 @@ var SRP$b = {
|
|
|
2184
2213
|
TRAVEL_GROUP: "Grupo de viaje",
|
|
2185
2214
|
EXCURSION: "Excursión",
|
|
2186
2215
|
LOADING_EXCURSIONS: "Cargando excursiones...",
|
|
2216
|
+
LOADING_ACTIVITIES: "Cargando actividades...",
|
|
2187
2217
|
LOADING_FLIGHTS: "Cargando vuelos...",
|
|
2188
2218
|
LOADING_ACCOMMODATIONS: "Cargando alojamientos...",
|
|
2189
2219
|
LOADING_ITINERARY: "Cargando itinerario...",
|
|
@@ -2616,6 +2646,11 @@ var SRP$a = {
|
|
|
2616
2646
|
ACCOMMODATION: "Hébergement",
|
|
2617
2647
|
VIEW_DETAILS: "Voir les détails",
|
|
2618
2648
|
NIGHTS: "nuits",
|
|
2649
|
+
OF: "sur",
|
|
2650
|
+
ADD: "Ajouter",
|
|
2651
|
+
REMOVE: "Supprimer",
|
|
2652
|
+
ACTIVITIES: "Activités",
|
|
2653
|
+
SHOW_ACTIVITIES: "Voir les activités",
|
|
2619
2654
|
MORNING_RANGE: "Matin",
|
|
2620
2655
|
AFTERNOON_RANGE: "Après-midi",
|
|
2621
2656
|
EVENING_RANGE: "Soir",
|
|
@@ -2627,6 +2662,7 @@ var SRP$a = {
|
|
|
2627
2662
|
TRAVEL_GROUP: "Groupe de voyageurs",
|
|
2628
2663
|
EXCURSION: "Excursion",
|
|
2629
2664
|
LOADING_EXCURSIONS: "Chargement des excursions...",
|
|
2665
|
+
LOADING_ACTIVITIES: "Chargement des activités...",
|
|
2630
2666
|
LOADING_FLIGHTS: "Chargement des vols...",
|
|
2631
2667
|
LOADING_ACCOMMODATIONS: "Chargement des hébergements...",
|
|
2632
2668
|
LOADING_ITINERARY: "Chargement de l’itinéraire...",
|
|
@@ -3055,6 +3091,11 @@ var SRP$9 = {
|
|
|
3055
3091
|
ACCOMMODATION: "Hébergement",
|
|
3056
3092
|
VIEW_DETAILS: "Voir les détails",
|
|
3057
3093
|
NIGHTS: "nuits",
|
|
3094
|
+
OF: "sur",
|
|
3095
|
+
ADD: "Ajouter",
|
|
3096
|
+
REMOVE: "Supprimer",
|
|
3097
|
+
ACTIVITIES: "Activités",
|
|
3098
|
+
SHOW_ACTIVITIES: "Voir les activités",
|
|
3058
3099
|
MORNING_RANGE: "Matin",
|
|
3059
3100
|
AFTERNOON_RANGE: "Après-midi",
|
|
3060
3101
|
EVENING_RANGE: "Soir",
|
|
@@ -3066,6 +3107,7 @@ var SRP$9 = {
|
|
|
3066
3107
|
TRAVEL_GROUP: "Groupe de voyageurs",
|
|
3067
3108
|
EXCURSION: "Excursion",
|
|
3068
3109
|
LOADING_EXCURSIONS: "Chargement des excursions...",
|
|
3110
|
+
LOADING_ACTIVITIES: "Chargement des activités...",
|
|
3069
3111
|
LOADING_FLIGHTS: "Chargement des vols...",
|
|
3070
3112
|
LOADING_ACCOMMODATIONS: "Chargement des hébergements...",
|
|
3071
3113
|
LOADING_ITINERARY: "Chargement de l’itinéraire...",
|
|
@@ -3494,6 +3536,11 @@ var SRP$8 = {
|
|
|
3494
3536
|
ACCOMMODATION: "Gisting",
|
|
3495
3537
|
VIEW_DETAILS: "Skoða upplýsingar",
|
|
3496
3538
|
NIGHTS: "nætur",
|
|
3539
|
+
OF: "af",
|
|
3540
|
+
ADD: "Bæta við",
|
|
3541
|
+
REMOVE: "Fjarlægja",
|
|
3542
|
+
ACTIVITIES: "Afþreying",
|
|
3543
|
+
SHOW_ACTIVITIES: "Sýna afþreyingu",
|
|
3497
3544
|
MORNING_RANGE: "Morgunn",
|
|
3498
3545
|
AFTERNOON_RANGE: "Síðdegi",
|
|
3499
3546
|
EVENING_RANGE: "Kvöld",
|
|
@@ -3505,6 +3552,7 @@ var SRP$8 = {
|
|
|
3505
3552
|
TRAVEL_GROUP: "Ferðahópur",
|
|
3506
3553
|
EXCURSION: "Útflutningur",
|
|
3507
3554
|
LOADING_EXCURSIONS: "Hleðst inn í útflutninga...",
|
|
3555
|
+
LOADING_ACTIVITIES: "Hleð inn afþreyingu...",
|
|
3508
3556
|
LOADING_FLIGHTS: "Hleðst inn í flug...",
|
|
3509
3557
|
LOADING_ACCOMMODATIONS: "Hleðst inn í gistingar...",
|
|
3510
3558
|
LOADING_ITINERARY: "Hleðst inn í ferðaráætlun...",
|
|
@@ -3933,6 +3981,11 @@ var SRP$7 = {
|
|
|
3933
3981
|
ACCOMMODATION: "Alloggio",
|
|
3934
3982
|
VIEW_DETAILS: "Visualizza dettagli",
|
|
3935
3983
|
NIGHTS: "notti",
|
|
3984
|
+
OF: "di",
|
|
3985
|
+
ADD: "Aggiungi",
|
|
3986
|
+
REMOVE: "Rimuovi",
|
|
3987
|
+
ACTIVITIES: "Attività",
|
|
3988
|
+
SHOW_ACTIVITIES: "Mostra attività",
|
|
3936
3989
|
MORNING_RANGE: "Mattina",
|
|
3937
3990
|
AFTERNOON_RANGE: "Pomeriggio",
|
|
3938
3991
|
EVENING_RANGE: "Sera",
|
|
@@ -3944,6 +3997,7 @@ var SRP$7 = {
|
|
|
3944
3997
|
TRAVEL_GROUP: "Gruppo di viaggio",
|
|
3945
3998
|
EXCURSION: "Escursione",
|
|
3946
3999
|
LOADING_EXCURSIONS: "Caricamento delle escursioni...",
|
|
4000
|
+
LOADING_ACTIVITIES: "Caricamento delle attività...",
|
|
3947
4001
|
LOADING_FLIGHTS: "Caricamento dei voli...",
|
|
3948
4002
|
LOADING_ACCOMMODATIONS: "Caricamento degli alloggi...",
|
|
3949
4003
|
LOADING_ITINERARY: "Caricamento dell'itinerario...",
|
|
@@ -4376,6 +4430,11 @@ var SRP$6 = {
|
|
|
4376
4430
|
ACCOMMODATION: "Accommodatie",
|
|
4377
4431
|
VIEW_DETAILS: "Bekijk details",
|
|
4378
4432
|
NIGHTS: "nachten",
|
|
4433
|
+
OF: "van",
|
|
4434
|
+
ADD: "Toevoegen",
|
|
4435
|
+
REMOVE: "Verwijderen",
|
|
4436
|
+
ACTIVITIES: "Activiteiten",
|
|
4437
|
+
SHOW_ACTIVITIES: "Toon activiteiten",
|
|
4379
4438
|
MORNING_RANGE: "Ochtend",
|
|
4380
4439
|
AFTERNOON_RANGE: "Namiddag",
|
|
4381
4440
|
EVENING_RANGE: "Avond",
|
|
@@ -4387,6 +4446,7 @@ var SRP$6 = {
|
|
|
4387
4446
|
TRAVEL_GROUP: "Reisgezelschap",
|
|
4388
4447
|
EXCURSION: "Excursie",
|
|
4389
4448
|
LOADING_EXCURSIONS: "Excursies laden...",
|
|
4449
|
+
LOADING_ACTIVITIES: "Activiteiten laden...",
|
|
4390
4450
|
LOADING_FLIGHTS: "Vluchten laden...",
|
|
4391
4451
|
LOADING_ACCOMMODATIONS: "Accommodaties laden...",
|
|
4392
4452
|
LOADING_ITINERARY: "Reisplan laden...",
|
|
@@ -4815,6 +4875,11 @@ var SRP$5 = {
|
|
|
4815
4875
|
ACCOMMODATION: "Accommodatie",
|
|
4816
4876
|
VIEW_DETAILS: "Bekijk details",
|
|
4817
4877
|
NIGHTS: "nachten",
|
|
4878
|
+
OF: "van",
|
|
4879
|
+
ADD: "Toevoegen",
|
|
4880
|
+
REMOVE: "Verwijderen",
|
|
4881
|
+
ACTIVITIES: "Activiteiten",
|
|
4882
|
+
SHOW_ACTIVITIES: "Toon activiteiten",
|
|
4818
4883
|
MORNING_RANGE: "Ochtend",
|
|
4819
4884
|
AFTERNOON_RANGE: "Namiddag",
|
|
4820
4885
|
EVENING_RANGE: "Avond",
|
|
@@ -4826,6 +4891,7 @@ var SRP$5 = {
|
|
|
4826
4891
|
TRAVEL_GROUP: "Reisgezelschap",
|
|
4827
4892
|
EXCURSION: "Excursie",
|
|
4828
4893
|
LOADING_EXCURSIONS: "Excursies laden...",
|
|
4894
|
+
LOADING_ACTIVITIES: "Activiteiten laden...",
|
|
4829
4895
|
LOADING_FLIGHTS: "Vluchten laden...",
|
|
4830
4896
|
LOADING_ACCOMMODATIONS: "Accommodaties laden...",
|
|
4831
4897
|
LOADING_ITINERARY: "Reisplan laden...",
|
|
@@ -5254,6 +5320,11 @@ var SRP$4 = {
|
|
|
5254
5320
|
ACCOMMODATION: "Overnatting",
|
|
5255
5321
|
VIEW_DETAILS: "Se detaljer",
|
|
5256
5322
|
NIGHTS: "netter",
|
|
5323
|
+
OF: "av",
|
|
5324
|
+
ADD: "Legg til",
|
|
5325
|
+
REMOVE: "Fjern",
|
|
5326
|
+
ACTIVITIES: "Aktiviteter",
|
|
5327
|
+
SHOW_ACTIVITIES: "Vis aktiviteter",
|
|
5257
5328
|
MORNING_RANGE: "Morgen",
|
|
5258
5329
|
AFTERNOON_RANGE: "Ettermiddag",
|
|
5259
5330
|
EVENING_RANGE: "Kveld",
|
|
@@ -5265,6 +5336,7 @@ var SRP$4 = {
|
|
|
5265
5336
|
TRAVEL_GROUP: "Reisefølge",
|
|
5266
5337
|
EXCURSION: "Utflukt",
|
|
5267
5338
|
LOADING_EXCURSIONS: "Laster utflukter...",
|
|
5339
|
+
LOADING_ACTIVITIES: "Laster aktiviteter...",
|
|
5268
5340
|
LOADING_FLIGHTS: "Laster flyvninger...",
|
|
5269
5341
|
LOADING_ACCOMMODATIONS: "Laster overnattinger...",
|
|
5270
5342
|
LOADING_ITINERARY: "Laster reiserute...",
|
|
@@ -5693,6 +5765,11 @@ var SRP$3 = {
|
|
|
5693
5765
|
ACCOMMODATION: "Zakwaterowanie",
|
|
5694
5766
|
VIEW_DETAILS: "Zobacz szczegóły",
|
|
5695
5767
|
NIGHTS: "noce",
|
|
5768
|
+
OF: "z",
|
|
5769
|
+
ADD: "Dodaj",
|
|
5770
|
+
REMOVE: "Usuń",
|
|
5771
|
+
ACTIVITIES: "Atrakcje",
|
|
5772
|
+
SHOW_ACTIVITIES: "Pokaż atrakcje",
|
|
5696
5773
|
MORNING_RANGE: "Poranek",
|
|
5697
5774
|
AFTERNOON_RANGE: "Popołudnie",
|
|
5698
5775
|
EVENING_RANGE: "Wieczór",
|
|
@@ -5704,6 +5781,7 @@ var SRP$3 = {
|
|
|
5704
5781
|
TRAVEL_GROUP: "Grupa podróżnych",
|
|
5705
5782
|
EXCURSION: "Wycieczka",
|
|
5706
5783
|
LOADING_EXCURSIONS: "Ładowanie wycieczek...",
|
|
5784
|
+
LOADING_ACTIVITIES: "Ładowanie atrakcji...",
|
|
5707
5785
|
LOADING_FLIGHTS: "Ładowanie lotów...",
|
|
5708
5786
|
LOADING_ACCOMMODATIONS: "Ładowanie zakwaterowań...",
|
|
5709
5787
|
LOADING_ITINERARY: "Ładowanie planu podróży...",
|
|
@@ -6132,6 +6210,11 @@ var SRP$2 = {
|
|
|
6132
6210
|
ACCOMMODATION: "Alojamento",
|
|
6133
6211
|
VIEW_DETAILS: "Ver detalhes",
|
|
6134
6212
|
NIGHTS: "noites",
|
|
6213
|
+
OF: "de",
|
|
6214
|
+
ADD: "Adicionar",
|
|
6215
|
+
REMOVE: "Remover",
|
|
6216
|
+
ACTIVITIES: "Atividades",
|
|
6217
|
+
SHOW_ACTIVITIES: "Ver atividades",
|
|
6135
6218
|
MORNING_RANGE: "Manhã",
|
|
6136
6219
|
AFTERNOON_RANGE: "Tarde",
|
|
6137
6220
|
EVENING_RANGE: "Noite",
|
|
@@ -6143,6 +6226,7 @@ var SRP$2 = {
|
|
|
6143
6226
|
TRAVEL_GROUP: "Grupo de viajantes",
|
|
6144
6227
|
EXCURSION: "Excursão",
|
|
6145
6228
|
LOADING_EXCURSIONS: "A carregar excursões...",
|
|
6229
|
+
LOADING_ACTIVITIES: "A carregar atividades...",
|
|
6146
6230
|
LOADING_FLIGHTS: "A carregar voos...",
|
|
6147
6231
|
LOADING_ACCOMMODATIONS: "A carregar alojamentos...",
|
|
6148
6232
|
LOADING_ITINERARY: "A carregar itinerário...",
|
|
@@ -6571,6 +6655,11 @@ var SRP$1 = {
|
|
|
6571
6655
|
ACCOMMODATION: "Boende",
|
|
6572
6656
|
VIEW_DETAILS: "Visa detaljer",
|
|
6573
6657
|
NIGHTS: "nätter",
|
|
6658
|
+
OF: "av",
|
|
6659
|
+
ADD: "Lägg till",
|
|
6660
|
+
REMOVE: "Ta bort",
|
|
6661
|
+
ACTIVITIES: "Aktiviteter",
|
|
6662
|
+
SHOW_ACTIVITIES: "Visa aktiviteter",
|
|
6574
6663
|
MORNING_RANGE: "Morgon",
|
|
6575
6664
|
AFTERNOON_RANGE: "Eftermiddag",
|
|
6576
6665
|
EVENING_RANGE: "Kväll",
|
|
@@ -6582,6 +6671,7 @@ var SRP$1 = {
|
|
|
6582
6671
|
TRAVEL_GROUP: "Resesällskap",
|
|
6583
6672
|
EXCURSION: "Utflykt",
|
|
6584
6673
|
LOADING_EXCURSIONS: "Laddar utflykter...",
|
|
6674
|
+
LOADING_ACTIVITIES: "Laddar aktiviteter...",
|
|
6585
6675
|
LOADING_FLIGHTS: "Laddar flyg...",
|
|
6586
6676
|
LOADING_ACCOMMODATIONS: "Laddar boenden...",
|
|
6587
6677
|
LOADING_ITINERARY: "Laddar resplan...",
|
|
@@ -7010,6 +7100,11 @@ var SRP = {
|
|
|
7010
7100
|
ACCOMMODATION: "宿泊",
|
|
7011
7101
|
VIEW_DETAILS: "詳細を見る",
|
|
7012
7102
|
NIGHTS: "泊",
|
|
7103
|
+
OF: "/",
|
|
7104
|
+
ADD: "追加",
|
|
7105
|
+
REMOVE: "削除",
|
|
7106
|
+
ACTIVITIES: "アクティビティ",
|
|
7107
|
+
SHOW_ACTIVITIES: "アクティビティを表示",
|
|
7013
7108
|
MORNING_RANGE: "朝",
|
|
7014
7109
|
AFTERNOON_RANGE: "午後",
|
|
7015
7110
|
EVENING_RANGE: "夕方",
|
|
@@ -7021,6 +7116,7 @@ var SRP = {
|
|
|
7021
7116
|
TRAVEL_GROUP: "旅行グループ",
|
|
7022
7117
|
EXCURSION: "エクスカーション",
|
|
7023
7118
|
LOADING_EXCURSIONS: "エクスカーションを読み込み中...",
|
|
7119
|
+
LOADING_ACTIVITIES: "アクティビティを読み込み中...",
|
|
7024
7120
|
LOADING_FLIGHTS: "フライトを読み込み中...",
|
|
7025
7121
|
LOADING_ACCOMMODATIONS: "宿泊施設を読み込み中...",
|
|
7026
7122
|
LOADING_ITINERARY: "旅程を読み込み中...",
|
|
@@ -16503,7 +16599,7 @@ const Icon = ({ name, className, title, width, height, fill }) => {
|
|
|
16503
16599
|
title && React__default.createElement("title", null, title),
|
|
16504
16600
|
React__default.createElement("path", { d: "M438.6 105.4c12.5 12.5 12.5 32.8 0 45.3l-256 256c-12.5 12.5-32.8 12.5-45.3 0l-128-128c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0L160 338.7 393.4 105.4c12.5-12.5 32.8-12.5 45.3 0z" })));
|
|
16505
16601
|
case 'ui-hotel':
|
|
16506
|
-
return (React__default.createElement("svg", { className: ['icon', `icon--${name}`, className].filter((className) => !isEmpty$1(className)).join(' '), width: width, height: height, viewBox: "0 0
|
|
16602
|
+
return (React__default.createElement("svg", { className: ['icon', `icon--${name}`, className].filter((className) => !isEmpty$1(className)).join(' '), width: width, height: height, viewBox: "0 0 512 512", fill: fill ?? 'currentColor' },
|
|
16507
16603
|
React__default.createElement(HTMLComment, { text: "!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc." }),
|
|
16508
16604
|
title && React__default.createElement("title", null, title),
|
|
16509
16605
|
React__default.createElement("path", { d: "M16 24C16 10.7 26.7 0 40 0L472 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-8 0 0 416 8 0c13.3 0 24 10.7 24 24s-10.7 24-24 24L40 512c-13.3 0-24-10.7-24-24s10.7-24 24-24l8 0 0-416-8 0C26.7 48 16 37.3 16 24zm208 88l0 32c0 8.8 7.2 16 16 16l32 0c8.8 0 16-7.2 16-16l0-32c0-8.8-7.2-16-16-16l-32 0c-8.8 0-16 7.2-16 16zM128 96c-8.8 0-16 7.2-16 16l0 32c0 8.8 7.2 16 16 16l32 0c8.8 0 16-7.2 16-16l0-32c0-8.8-7.2-16-16-16l-32 0zm96 112l0 32c0 8.8 7.2 16 16 16l32 0c8.8 0 16-7.2 16-16l0-32c0-8.8-7.2-16-16-16l-32 0c-8.8 0-16 7.2-16 16zM352 96c-8.8 0-16 7.2-16 16l0 32c0 8.8 7.2 16 16 16l32 0c8.8 0 16-7.2 16-16l0-32c0-8.8-7.2-16-16-16l-32 0zM112 208l0 32c0 8.8 7.2 16 16 16l32 0c8.8 0 16-7.2 16-16l0-32c0-8.8-7.2-16-16-16l-32 0c-8.8 0-16 7.2-16 16zm240-16c-8.8 0-16 7.2-16 16l0 32c0 8.8 7.2 16 16 16l32 0c8.8 0 16-7.2 16-16l0-32c0-8.8-7.2-16-16-16l-32 0zM288 384l43.8 0c9.9 0 17.5-9 14-18.2-13.8-36.1-48.8-61.8-89.7-61.8s-75.9 25.7-89.7 61.8c-3.5 9.2 4.1 18.2 14 18.2l43.8 0 0 80 64 0 0-80z", fill: "currentColor" })));
|
|
@@ -16532,6 +16628,16 @@ const Icon = ({ name, className, title, width, height, fill }) => {
|
|
|
16532
16628
|
React__default.createElement(HTMLComment, { text: "!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc." }),
|
|
16533
16629
|
title && React__default.createElement("title", null, title),
|
|
16534
16630
|
React__default.createElement("path", { d: "M135.2 117.4l-26.1 74.6 293.8 0-26.1-74.6C372.3 104.6 360.2 96 346.6 96L165.4 96c-13.6 0-25.7 8.6-30.2 21.4zM39.6 196.8L74.8 96.3C88.3 57.8 124.6 32 165.4 32l181.2 0c40.8 0 77.1 25.8 90.6 64.3l35.2 100.5c23.2 9.6 39.6 32.5 39.6 59.2l0 192c0 17.7-14.3 32-32 32l-32 0c-17.7 0-32-14.3-32-32l0-32-320 0 0 32c0 17.7-14.3 32-32 32l-32 0c-17.7 0-32-14.3-32-32L0 256c0-26.7 16.4-49.6 39.6-59.2zM128 304a32 32 0 1 0 -64 0 32 32 0 1 0 64 0zm288 32a32 32 0 1 0 0-64 32 32 0 1 0 0 64z", fill: "currentColor" })));
|
|
16631
|
+
case 'ui-bus':
|
|
16632
|
+
return (React__default.createElement("svg", { className: ['icon', `icon--${name}`, className].filter((className) => !isEmpty$1(className)).join(' '), width: width, height: height, viewBox: "0 0 512 512", fill: fill ?? 'currentColor' },
|
|
16633
|
+
title && React__default.createElement("title", null, title),
|
|
16634
|
+
React__default.createElement("path", { d: "M112 32h288a64 64 0 0 1 64 64v256a64 64 0 0 1-64 64h-288a64 64 0 0 1-64-64V96a64 64 0 0 1 64-64zM96 112v104h136V112H96zm184 0v104h136V112H280zM160 352a36 36 0 1 0-72 0 36 36 0 1 0 72 0zm264 0a36 36 0 1 0-72 0 36 36 0 1 0 72 0z", fill: "currentColor", fillRule: "evenodd" }),
|
|
16635
|
+
React__default.createElement("path", { d: "M112 416h64v48a24 24 0 0 1-24 24h-16a24 24 0 0 1-24-24v-48zm224 0h64v48a24 24 0 0 1-24 24h-16a24 24 0 0 1-24-24v-48z", fill: "currentColor" })));
|
|
16636
|
+
case 'ui-train':
|
|
16637
|
+
return (React__default.createElement("svg", { className: ['icon', `icon--${name}`, className].filter((className) => !isEmpty$1(className)).join(' '), width: width, height: height, viewBox: "0 0 512 512", fill: fill ?? 'currentColor' },
|
|
16638
|
+
title && React__default.createElement("title", null, title),
|
|
16639
|
+
React__default.createElement("path", { d: "M160 32h192a64 64 0 0 1 64 64v224a64 64 0 0 1-64 64h-192a64 64 0 0 1-64-64V96a64 64 0 0 1 64-64zm-24 80v104h240V112H136zm56 168a28 28 0 1 0-56 0 28 28 0 1 0 56 0zm192 0a28 28 0 1 0-56 0 28 28 0 1 0 56 0z", fill: "currentColor", fillRule: "evenodd" }),
|
|
16640
|
+
React__default.createElement("path", { d: "M168 416h72l-48 64h-72l48-64zm104 0h72l48 64h-72l-48-64z", fill: "currentColor" })));
|
|
16535
16641
|
case 'ui-ship':
|
|
16536
16642
|
return (React__default.createElement("svg", { className: ['icon', `icon--${name}`, className].filter((className) => !isEmpty$1(className)).join(' '), width: width, height: height, viewBox: "0 0 640 512", fill: fill ?? 'currentColor' },
|
|
16537
16643
|
React__default.createElement(HTMLComment, { text: "!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc." }),
|
|
@@ -27350,7 +27456,11 @@ const initialState$1 = {
|
|
|
27350
27456
|
bookingNumber: undefined,
|
|
27351
27457
|
trajectNodeAvailability: {},
|
|
27352
27458
|
trajectFlightAvailability: {},
|
|
27353
|
-
|
|
27459
|
+
trajectDaySuggestions: {},
|
|
27460
|
+
trajectEditLineGuid: null,
|
|
27461
|
+
trajectActivitiesDayOrder: null,
|
|
27462
|
+
trajectResolving: false,
|
|
27463
|
+
trajectItineraryView: false
|
|
27354
27464
|
};
|
|
27355
27465
|
const searchResultsSlice = createSlice({
|
|
27356
27466
|
name: 'searchResults',
|
|
@@ -27566,6 +27676,11 @@ const searchResultsSlice = createSlice({
|
|
|
27566
27676
|
return;
|
|
27567
27677
|
state.editablePackagingEntry.lines[index] = action.payload;
|
|
27568
27678
|
},
|
|
27679
|
+
addEditableEntryLines(state, action) {
|
|
27680
|
+
if (!state.editablePackagingEntry)
|
|
27681
|
+
return;
|
|
27682
|
+
state.editablePackagingEntry.lines = [...(state.editablePackagingEntry.lines ?? []), ...action.payload];
|
|
27683
|
+
},
|
|
27569
27684
|
removeEditableEntryLines(state, action) {
|
|
27570
27685
|
if (!state.editablePackagingEntry)
|
|
27571
27686
|
return;
|
|
@@ -27597,9 +27712,30 @@ const searchResultsSlice = createSlice({
|
|
|
27597
27712
|
flight.selectedGuid = action.payload.guid;
|
|
27598
27713
|
flight.status = 'resolved';
|
|
27599
27714
|
},
|
|
27715
|
+
setTrajectDaySuggestionsLoading(state, action) {
|
|
27716
|
+
const dayOrder = action.payload;
|
|
27717
|
+
state.trajectDaySuggestions[dayOrder] = {
|
|
27718
|
+
dayOrder,
|
|
27719
|
+
status: 'loading',
|
|
27720
|
+
results: state.trajectDaySuggestions[dayOrder]?.results ?? []
|
|
27721
|
+
};
|
|
27722
|
+
},
|
|
27723
|
+
setTrajectDaySuggestions(state, action) {
|
|
27724
|
+
const { dayOrder, results } = action.payload;
|
|
27725
|
+
state.trajectDaySuggestions[dayOrder] = { dayOrder, status: 'resolved', results };
|
|
27726
|
+
},
|
|
27600
27727
|
setTrajectEditLineGuid(state, action) {
|
|
27601
27728
|
state.trajectEditLineGuid = action.payload;
|
|
27602
27729
|
},
|
|
27730
|
+
setTrajectActivitiesDayOrder(state, action) {
|
|
27731
|
+
state.trajectActivitiesDayOrder = action.payload;
|
|
27732
|
+
},
|
|
27733
|
+
setTrajectResolving(state, action) {
|
|
27734
|
+
state.trajectResolving = action.payload;
|
|
27735
|
+
},
|
|
27736
|
+
setTrajectItineraryView(state, action) {
|
|
27737
|
+
state.trajectItineraryView = action.payload;
|
|
27738
|
+
},
|
|
27603
27739
|
updateTrajectNodeResult(state, action) {
|
|
27604
27740
|
const { lineGuid, result } = action.payload;
|
|
27605
27741
|
const node = state.trajectNodeAvailability[lineGuid];
|
|
@@ -27612,11 +27748,15 @@ const searchResultsSlice = createSlice({
|
|
|
27612
27748
|
resetTrajectAvailability(state) {
|
|
27613
27749
|
state.trajectNodeAvailability = {};
|
|
27614
27750
|
state.trajectFlightAvailability = {};
|
|
27751
|
+
state.trajectDaySuggestions = {};
|
|
27615
27752
|
state.trajectEditLineGuid = null;
|
|
27753
|
+
state.trajectActivitiesDayOrder = null;
|
|
27754
|
+
state.trajectResolving = false;
|
|
27755
|
+
state.trajectItineraryView = false;
|
|
27616
27756
|
}
|
|
27617
27757
|
}
|
|
27618
27758
|
});
|
|
27619
|
-
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, updateTrajectNodeResult, resetTrajectAvailability, updateEditableEntryLine, removeEditableEntryLines } = searchResultsSlice.actions;
|
|
27759
|
+
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;
|
|
27620
27760
|
var searchResultsReducer = searchResultsSlice.reducer;
|
|
27621
27761
|
|
|
27622
27762
|
const ItemPicker = ({ items, selection, selectedSortByType, label, placeholder, classModifier, onPick, valueFormatter }) => {
|
|
@@ -27801,6 +27941,653 @@ const getDuration = (flight) => {
|
|
|
27801
27941
|
const numberOfNights = (segment) => {
|
|
27802
27942
|
return differenceInCalendarDays(new Date(segment.to), new Date(segment.from));
|
|
27803
27943
|
};
|
|
27944
|
+
const CHARTER_SERVICE_TYPE = 10;
|
|
27945
|
+
const CRUISE_SERVICE_TYPE = 11;
|
|
27946
|
+
const FERRY_SERVICE_TYPE = 12;
|
|
27947
|
+
const TRANSFER_SERVICE_TYPE = 13;
|
|
27948
|
+
const BUS_SERVICE_TYPE = 14;
|
|
27949
|
+
const CAR_SERVICE_TYPE = 17;
|
|
27950
|
+
const CAMPER_SERVICE_TYPE = 18;
|
|
27951
|
+
const TRAIN_SERVICE_TYPE = 20;
|
|
27952
|
+
const RENTAL_SERVICE_TYPE = 22;
|
|
27953
|
+
const RESTAURANT_SERVICE_TYPE = 23;
|
|
27954
|
+
const EVENT_SERVICE_TYPE = 24;
|
|
27955
|
+
const TRAINING_SERVICE_TYPE = 25;
|
|
27956
|
+
/** Icon per service type, so every node on the itinerary reads at a glance. */
|
|
27957
|
+
const getServiceTypeIcon = (serviceType) => {
|
|
27958
|
+
switch (serviceType) {
|
|
27959
|
+
case ACCOMMODATION_SERVICE_TYPE:
|
|
27960
|
+
return 'ui-hotel';
|
|
27961
|
+
case EXCURSION_SERVICE_TYPE:
|
|
27962
|
+
return 'ui-excursion';
|
|
27963
|
+
case FLIGHT_SERVICE_TYPE:
|
|
27964
|
+
case CHARTER_SERVICE_TYPE:
|
|
27965
|
+
return 'ui-plane';
|
|
27966
|
+
case RESTAURANT_SERVICE_TYPE:
|
|
27967
|
+
return 'ui-utensils';
|
|
27968
|
+
case CRUISE_SERVICE_TYPE:
|
|
27969
|
+
case FERRY_SERVICE_TYPE:
|
|
27970
|
+
return 'ui-ship';
|
|
27971
|
+
case CAR_SERVICE_TYPE:
|
|
27972
|
+
case CAMPER_SERVICE_TYPE:
|
|
27973
|
+
case RENTAL_SERVICE_TYPE:
|
|
27974
|
+
return 'ui-car';
|
|
27975
|
+
case BUS_SERVICE_TYPE:
|
|
27976
|
+
return 'ui-bus';
|
|
27977
|
+
case TRAIN_SERVICE_TYPE:
|
|
27978
|
+
return 'ui-train';
|
|
27979
|
+
case TRANSFER_SERVICE_TYPE:
|
|
27980
|
+
return 'ui-transfer';
|
|
27981
|
+
case EVENT_SERVICE_TYPE:
|
|
27982
|
+
return 'ui-ticket';
|
|
27983
|
+
case TRAINING_SERVICE_TYPE:
|
|
27984
|
+
return 'ui-group';
|
|
27985
|
+
default:
|
|
27986
|
+
return 'ui-location';
|
|
27987
|
+
}
|
|
27988
|
+
};
|
|
27989
|
+
const getServiceTypeBadgeModifier = (serviceType) => {
|
|
27990
|
+
switch (serviceType) {
|
|
27991
|
+
case FLIGHT_SERVICE_TYPE:
|
|
27992
|
+
case CHARTER_SERVICE_TYPE:
|
|
27993
|
+
case CRUISE_SERVICE_TYPE:
|
|
27994
|
+
case FERRY_SERVICE_TYPE:
|
|
27995
|
+
return 'flight';
|
|
27996
|
+
case ACCOMMODATION_SERVICE_TYPE:
|
|
27997
|
+
return 'hotel';
|
|
27998
|
+
case EXCURSION_SERVICE_TYPE:
|
|
27999
|
+
case EVENT_SERVICE_TYPE:
|
|
28000
|
+
return 'excursion';
|
|
28001
|
+
case RESTAURANT_SERVICE_TYPE:
|
|
28002
|
+
return 'restaurant';
|
|
28003
|
+
default:
|
|
28004
|
+
return '';
|
|
28005
|
+
}
|
|
28006
|
+
};
|
|
28007
|
+
const POI_TRAJECT_NODE_TYPE = 2;
|
|
28008
|
+
const getTrajectNodeIcon = (node) => node.trajectNodeType === POI_TRAJECT_NODE_TYPE ? 'ui-location' : getServiceTypeIcon(node.serviceType);
|
|
28009
|
+
const getTrajectNodeBadgeModifier = (node) => node.trajectNodeType === POI_TRAJECT_NODE_TYPE ? 'poi' : getServiceTypeBadgeModifier(node.serviceType);
|
|
28010
|
+
|
|
28011
|
+
const REGULAR_TRAJECT_NODE_TYPE = 0;
|
|
28012
|
+
const CONNECTING_TRAJECT_NODE_TYPE = 1;
|
|
28013
|
+
const isSearchableTrajectNode = (node) => node.serviceType === ACCOMMODATION_SERVICE_TYPE || node.serviceType === EXCURSION_SERVICE_TYPE;
|
|
28014
|
+
const isTrajectFlightNode = (node) => node.serviceType === FLIGHT_SERVICE_TYPE;
|
|
28015
|
+
const getTrajectNodesWithLines = (trajectEntry) => {
|
|
28016
|
+
const linesByGuid = new Map((trajectEntry.entry.lines ?? []).map((line) => [line.guid, line]));
|
|
28017
|
+
return [...(trajectEntry.nodes ?? [])]
|
|
28018
|
+
.sort((a, b) => a.dayOrder - b.dayOrder || a.order - b.order)
|
|
28019
|
+
.map((node) => {
|
|
28020
|
+
const guids = node.lineGuids?.length ? node.lineGuids : [node.lineGuid];
|
|
28021
|
+
const lines = guids.map((guid) => linesByGuid.get(guid)).filter((x) => !!x);
|
|
28022
|
+
return { node, line: lines[0], lines };
|
|
28023
|
+
})
|
|
28024
|
+
.filter((x) => !!x.line);
|
|
28025
|
+
};
|
|
28026
|
+
const getCoordinates = (line, node, result) => {
|
|
28027
|
+
const sources = [
|
|
28028
|
+
...(result ? [result] : []),
|
|
28029
|
+
...(line ? [line] : []),
|
|
28030
|
+
...(node?.alternatives ?? []).filter((x) => x.isPreferred),
|
|
28031
|
+
...(node?.alternatives ?? [])
|
|
28032
|
+
];
|
|
28033
|
+
const found = sources.find((x) => !!x.latitude && !!x.longitude);
|
|
28034
|
+
return found ? { latitude: found.latitude, longitude: found.longitude } : null;
|
|
28035
|
+
};
|
|
28036
|
+
const buildDestination = (line, node, result) => {
|
|
28037
|
+
const coordinates = getCoordinates(line, node, result);
|
|
28038
|
+
const place = (result?.locationId && { id: result.locationId, isLocation: true }) ||
|
|
28039
|
+
(result?.oordId && { id: result.oordId, isOord: true }) ||
|
|
28040
|
+
(result?.regionId && { id: result.regionId, isRegion: true }) ||
|
|
28041
|
+
(result?.countryId && { id: result.countryId, isCountry: true }) ||
|
|
28042
|
+
(line.location?.id && { id: line.location.id, isLocation: true }) ||
|
|
28043
|
+
(line.oord?.id && { id: line.oord.id, isOord: true }) ||
|
|
28044
|
+
(line.region?.id && { id: line.region.id, isRegion: true }) ||
|
|
28045
|
+
(line.country?.id && { id: line.country.id, isCountry: true }) ||
|
|
28046
|
+
null;
|
|
28047
|
+
return { id: 0, ...place, ...coordinates };
|
|
28048
|
+
};
|
|
28049
|
+
const hasDestination = (destination) => !!destination && (!!destination.id || (!!destination.latitude && !!destination.longitude));
|
|
28050
|
+
const buildTrajectClientConfig = (context) => ({
|
|
28051
|
+
host: context.tideConnection.host,
|
|
28052
|
+
apiKey: context.tideConnection.apiKey
|
|
28053
|
+
});
|
|
28054
|
+
const buildTrajectSearchContext = (context, trajectEntry) => ({
|
|
28055
|
+
transactionId: trajectEntry.entry.transactionId,
|
|
28056
|
+
officeId: context.tideConnection.officeId ?? 1,
|
|
28057
|
+
catalogueId: context.searchConfiguration.defaultCatalogueId ?? context.tideConnection.catalogueIds?.[0] ?? 1,
|
|
28058
|
+
searchConfigurationId: context.searchConfiguration.id,
|
|
28059
|
+
portalId: context.portalId ?? null,
|
|
28060
|
+
agentId: context.agentId ?? null,
|
|
28061
|
+
language: context.languageCode ?? 'en-GB',
|
|
28062
|
+
rooms: getPackagingRoomsFromEntry(trajectEntry.entry)
|
|
28063
|
+
});
|
|
28064
|
+
const buildTrajectNodeRequest = ({ node, line }, context, productCode, destination) => ({
|
|
28065
|
+
transactionId: context.transactionId,
|
|
28066
|
+
officeId: context.officeId,
|
|
28067
|
+
agentId: context.agentId ?? null,
|
|
28068
|
+
catalogueId: context.catalogueId,
|
|
28069
|
+
searchConfigurationId: context.searchConfigurationId,
|
|
28070
|
+
portalId: context.portalId ?? null,
|
|
28071
|
+
vendorConfigurationId: node.externalVendorId ?? null,
|
|
28072
|
+
language: context.language,
|
|
28073
|
+
serviceType: node.serviceType,
|
|
28074
|
+
fromDate: toDateOnlyString(line.from),
|
|
28075
|
+
// An excursion happens on a day rather than over a span. The traject gives its line an end a day
|
|
28076
|
+
// out, the way a night is counted, which asks the search for a window it does not have.
|
|
28077
|
+
toDate: toDateOnlyString(node.serviceType === EXCURSION_SERVICE_TYPE ? line.from : line.to),
|
|
28078
|
+
destination: destination ?? buildDestination(line, node),
|
|
28079
|
+
productCode,
|
|
28080
|
+
rooms: context.rooms,
|
|
28081
|
+
tagIds: []
|
|
28082
|
+
});
|
|
28083
|
+
const coversDay = ({ node, line }, dayOrder) => {
|
|
28084
|
+
const nights = Math.max(differenceInCalendarDays(new Date(line.to), new Date(line.from)), 1);
|
|
28085
|
+
return dayOrder >= node.dayOrder && dayOrder < node.dayOrder + nights;
|
|
28086
|
+
};
|
|
28087
|
+
const getDayDestinationItem = (items, dayOrder) => {
|
|
28088
|
+
const knowsWhereItIs = (x) => hasDestination(buildDestination(x.line, x.node));
|
|
28089
|
+
const stay = items.find((x) => x.node.serviceType === ACCOMMODATION_SERVICE_TYPE && coversDay(x, dayOrder) && knowsWhereItIs(x));
|
|
28090
|
+
if (stay)
|
|
28091
|
+
return stay;
|
|
28092
|
+
return ([...items]
|
|
28093
|
+
.filter((x) => x.node.dayOrder <= dayOrder && knowsWhereItIs(x))
|
|
28094
|
+
.sort((a, b) => a.node.dayOrder - b.node.dayOrder || a.node.order - b.node.order)
|
|
28095
|
+
.pop() ?? null);
|
|
28096
|
+
};
|
|
28097
|
+
const getDayDestination = (items, availability, dayOrder) => {
|
|
28098
|
+
const item = getDayDestinationItem(items, dayOrder);
|
|
28099
|
+
if (!item)
|
|
28100
|
+
return null;
|
|
28101
|
+
const resolved = availability[item.line.guid];
|
|
28102
|
+
const result = resolved?.results.find((x) => x.code === resolved.selectedCode) ?? null;
|
|
28103
|
+
const destination = buildDestination(item.line, item.node, result);
|
|
28104
|
+
return hasDestination(destination) ? destination : null;
|
|
28105
|
+
};
|
|
28106
|
+
const buildTrajectDayExcursionRequest = (date, destination, context) => ({
|
|
28107
|
+
transactionId: context.transactionId,
|
|
28108
|
+
officeId: context.officeId,
|
|
28109
|
+
agentId: context.agentId ?? null,
|
|
28110
|
+
catalogueId: context.catalogueId,
|
|
28111
|
+
searchConfigurationId: context.searchConfigurationId,
|
|
28112
|
+
portalId: context.portalId ?? null,
|
|
28113
|
+
vendorConfigurationId: null,
|
|
28114
|
+
language: context.language,
|
|
28115
|
+
serviceType: EXCURSION_SERVICE_TYPE,
|
|
28116
|
+
fromDate: toDateOnlyString(date),
|
|
28117
|
+
toDate: toDateOnlyString(date),
|
|
28118
|
+
destination,
|
|
28119
|
+
productCode: '',
|
|
28120
|
+
rooms: context.rooms,
|
|
28121
|
+
tagIds: []
|
|
28122
|
+
});
|
|
28123
|
+
const getExcursionOptionGroups = (result) => {
|
|
28124
|
+
const groups = new Map();
|
|
28125
|
+
(result.rooms ?? [])
|
|
28126
|
+
.flatMap((room) => room.options ?? [])
|
|
28127
|
+
.forEach((option) => {
|
|
28128
|
+
const key = [...(option.paxIds ?? [])].sort((a, b) => a - b).join(',');
|
|
28129
|
+
groups.set(key, [...(groups.get(key) ?? []), option]);
|
|
28130
|
+
});
|
|
28131
|
+
return [...groups.values()];
|
|
28132
|
+
};
|
|
28133
|
+
const excursionNeedsChoice = (result) => getExcursionOptionGroups(result).some((group) => group.length > 1 && !group.some((option) => option.isSelected));
|
|
28134
|
+
const buildTrajectExcursionLines = (result) => {
|
|
28135
|
+
const options = getExcursionOptionGroups(result)
|
|
28136
|
+
.map((group) => group.find((x) => x.isSelected) ?? (group.length === 1 ? group[0] : null))
|
|
28137
|
+
.filter((x) => !!x);
|
|
28138
|
+
if (!options.length)
|
|
28139
|
+
return [];
|
|
28140
|
+
const guids = options.map((option) => option.guid || crypto.randomUUID());
|
|
28141
|
+
return options.map((option, index) => ({
|
|
28142
|
+
guid: guids[index],
|
|
28143
|
+
moment: '',
|
|
28144
|
+
parentGuid: index === 0 ? null : guids[0],
|
|
28145
|
+
order: index,
|
|
28146
|
+
isChanged: true,
|
|
28147
|
+
from: result.fromDate,
|
|
28148
|
+
to: result.toDate,
|
|
28149
|
+
serviceType: EXCURSION_SERVICE_TYPE,
|
|
28150
|
+
productName: result.name,
|
|
28151
|
+
productCode: result.code,
|
|
28152
|
+
accommodationName: option.accommodationName,
|
|
28153
|
+
accommodationCode: option.accommodationCode,
|
|
28154
|
+
regimeName: option.regimeName,
|
|
28155
|
+
regimeCode: option.regimeCode,
|
|
28156
|
+
country: result.countryId ? { id: result.countryId, name: result.countryName, localizations: [] } : null,
|
|
28157
|
+
region: result.regionId ? { id: result.regionId, name: result.regionName, localizations: [] } : null,
|
|
28158
|
+
oord: result.oordId ? { id: result.oordId, name: result.oordName, localizations: [] } : null,
|
|
28159
|
+
location: result.locationId ? { id: result.locationId, name: result.locationName, localizations: [] } : null,
|
|
28160
|
+
longitude: result.longitude ?? null,
|
|
28161
|
+
latitude: result.latitude ?? null,
|
|
28162
|
+
pax: (option.paxIds ?? []).map((paxId, paxIndex) => ({ paxId, room: 0, order: paxIndex })),
|
|
28163
|
+
flightInformation: null
|
|
28164
|
+
}));
|
|
28165
|
+
};
|
|
28166
|
+
const buildExcursionSearchParams = (result) => ({
|
|
28167
|
+
date: result.fromDate,
|
|
28168
|
+
fromDate: result.fromDate,
|
|
28169
|
+
toDate: result.toDate,
|
|
28170
|
+
countryId: result.countryId,
|
|
28171
|
+
regionId: result.regionId,
|
|
28172
|
+
oordId: result.oordId,
|
|
28173
|
+
locationId: result.locationId,
|
|
28174
|
+
locationName: result.locationName,
|
|
28175
|
+
accommodationCode: null,
|
|
28176
|
+
accommodationName: null,
|
|
28177
|
+
hotelCode: result.code,
|
|
28178
|
+
hotelName: result.name,
|
|
28179
|
+
rooms: result.rooms ?? []
|
|
28180
|
+
});
|
|
28181
|
+
const buildDayActivityCards = (items, availability, suggestions) => {
|
|
28182
|
+
const cards = [];
|
|
28183
|
+
const seen = new Set();
|
|
28184
|
+
const push = (result, item) => {
|
|
28185
|
+
if (!result || seen.has(result.code))
|
|
28186
|
+
return;
|
|
28187
|
+
seen.add(result.code);
|
|
28188
|
+
cards.push({ result, item });
|
|
28189
|
+
};
|
|
28190
|
+
items.forEach((item) => {
|
|
28191
|
+
const results = availability[item.line.guid]?.results ?? [];
|
|
28192
|
+
const selected = results.find((x) => x.code === availability[item.line.guid]?.selectedCode);
|
|
28193
|
+
push(selected, item);
|
|
28194
|
+
results.filter((x) => !!findCandidateForResult(item.node, x)).forEach((result) => push(result, item));
|
|
28195
|
+
});
|
|
28196
|
+
suggestions.forEach((result) => push(result));
|
|
28197
|
+
items.forEach((item) => (availability[item.line.guid]?.results ?? []).forEach((result) => push(result)));
|
|
28198
|
+
return cards;
|
|
28199
|
+
};
|
|
28200
|
+
/**
|
|
28201
|
+
* Where to search for a node.
|
|
28202
|
+
*
|
|
28203
|
+
* Normally the node says so itself, through the product it was configured with. An excursion slot
|
|
28204
|
+
* left empty on the traject says nothing at all — no code, no alternatives, and a line with no
|
|
28205
|
+
* place on it — so it falls back to wherever the party is staying that day, the same anchor the
|
|
28206
|
+
* day's own activity search uses.
|
|
28207
|
+
*/
|
|
28208
|
+
const getNodeSearchDestination = (items, item) => {
|
|
28209
|
+
const own = buildDestination(item.line, item.node);
|
|
28210
|
+
if (hasDestination(own))
|
|
28211
|
+
return own;
|
|
28212
|
+
return getDayDestination(items, {}, item.node.dayOrder) ?? own;
|
|
28213
|
+
};
|
|
28214
|
+
const getConfiguredExcursionCodes = (items, dayOrder) => new Set(items
|
|
28215
|
+
.filter((x) => x.node.dayOrder === dayOrder && x.node.serviceType === EXCURSION_SERVICE_TYPE)
|
|
28216
|
+
.flatMap((x) => [x.node.code, ...getNodeCandidates(x.node).map((candidate) => candidate.code)])
|
|
28217
|
+
.filter((code) => !!code));
|
|
28218
|
+
const getNodeCandidates = (node) => {
|
|
28219
|
+
const configured = (node.alternatives ?? []).filter((x) => !!x.code && x.code.trim().length > 0);
|
|
28220
|
+
if (configured.length)
|
|
28221
|
+
return configured;
|
|
28222
|
+
const fallbackCode = node.code ?? node.preferredAccommodationCode;
|
|
28223
|
+
if (!fallbackCode || !fallbackCode.trim().length)
|
|
28224
|
+
return [];
|
|
28225
|
+
return [
|
|
28226
|
+
{
|
|
28227
|
+
code: fallbackCode,
|
|
28228
|
+
name: node.name,
|
|
28229
|
+
description: node.description,
|
|
28230
|
+
imageUrl: node.imageUrl,
|
|
28231
|
+
contentSource: node.contentSource,
|
|
28232
|
+
vendor: node.vendor,
|
|
28233
|
+
externalVendorId: node.externalVendorId,
|
|
28234
|
+
isPreferred: true
|
|
28235
|
+
}
|
|
28236
|
+
];
|
|
28237
|
+
};
|
|
28238
|
+
const findCandidateForResult = (node, result) => getNodeCandidates(node).find((candidate) => matchesCode(result, candidate.code)) ?? null;
|
|
28239
|
+
const matchesCode = (result, code) => {
|
|
28240
|
+
if (result.code === code)
|
|
28241
|
+
return true;
|
|
28242
|
+
return (result.rooms ?? []).some((room) => (room.options ?? []).some((option) => option.accommodationCode === code));
|
|
28243
|
+
};
|
|
28244
|
+
const getPackagingRoomsFromEntry = (entry) => {
|
|
28245
|
+
const paxById = new Map((entry.pax ?? []).map((p) => [p.id, p]));
|
|
28246
|
+
const rooms = (entry.rooms ?? []).map((room) => ({
|
|
28247
|
+
travellers: (room.paxIds ?? []).map((paxId) => {
|
|
28248
|
+
const pax = paxById.get(paxId);
|
|
28249
|
+
return {
|
|
28250
|
+
id: paxId,
|
|
28251
|
+
age: pax?.age ?? null,
|
|
28252
|
+
dateOfBirth: pax?.dateOfBirth ?? null
|
|
28253
|
+
};
|
|
28254
|
+
})
|
|
28255
|
+
}));
|
|
28256
|
+
return rooms.filter((room) => room.travellers.length > 0);
|
|
28257
|
+
};
|
|
28258
|
+
const pickCheapest = (results) => [...results].sort((a, b) => a.price - b.price)[0] ?? null;
|
|
28259
|
+
const applyResultToLines = (lines, result) => lines.map((line, index) => applyResultToLine(line, result, index));
|
|
28260
|
+
const getOptionForRoom = (result, roomIndex) => {
|
|
28261
|
+
const room = (result.rooms ?? [])[roomIndex];
|
|
28262
|
+
if (room)
|
|
28263
|
+
return (room.options ?? []).find((x) => x.isSelected) ?? (room.options ?? [])[0];
|
|
28264
|
+
return (result.rooms ?? []).flatMap((x) => x.options ?? []).find((x) => x.isSelected) ?? (result.rooms ?? [])[0]?.options?.[0];
|
|
28265
|
+
};
|
|
28266
|
+
const applyResultToLine = (line, result, roomIndex = 0) => {
|
|
28267
|
+
const option = getOptionForRoom(result, roomIndex);
|
|
28268
|
+
return {
|
|
28269
|
+
...line,
|
|
28270
|
+
productName: result.name ?? line.productName,
|
|
28271
|
+
productCode: result.code ?? line.productCode,
|
|
28272
|
+
accommodationCode: option?.accommodationCode ?? line.accommodationCode,
|
|
28273
|
+
accommodationName: option?.accommodationName ?? line.accommodationName,
|
|
28274
|
+
regimeCode: option?.regimeCode ?? line.regimeCode,
|
|
28275
|
+
regimeName: option?.regimeName ?? line.regimeName,
|
|
28276
|
+
latitude: result.latitude ?? line.latitude,
|
|
28277
|
+
longitude: result.longitude ?? line.longitude,
|
|
28278
|
+
isChanged: true
|
|
28279
|
+
};
|
|
28280
|
+
};
|
|
28281
|
+
const toDateOnlyUtcString = (value) => {
|
|
28282
|
+
const date = new Date(value);
|
|
28283
|
+
return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate())).toISOString();
|
|
28284
|
+
};
|
|
28285
|
+
const toTimeOnlyString = (value) => {
|
|
28286
|
+
const date = new Date(value);
|
|
28287
|
+
const hh = String(date.getUTCHours()).padStart(2, '0');
|
|
28288
|
+
const mm = String(date.getUTCMinutes()).padStart(2, '0');
|
|
28289
|
+
const ss = String(date.getUTCSeconds()).padStart(2, '0');
|
|
28290
|
+
return `${hh}:${mm}:${ss}`;
|
|
28291
|
+
};
|
|
28292
|
+
const mapFlightSegmentsToFlightLines = (segments) => segments.map((segment) => ({
|
|
28293
|
+
airlineCode: segment.marketingAirlineCode,
|
|
28294
|
+
airlineDescription: segment.marketingAirlineName,
|
|
28295
|
+
operatingAirlineCode: segment.operatingAirlineCode,
|
|
28296
|
+
operatingAirlineDescription: segment.operatingAirlineName,
|
|
28297
|
+
flightNumber: segment.flightNumber,
|
|
28298
|
+
operatingFlightNumber: segment.operatingFlightNumber ?? null,
|
|
28299
|
+
departureDate: toDateOnlyUtcString(segment.departureDateTime),
|
|
28300
|
+
departureTime: toTimeOnlyString(segment.departureDateTime),
|
|
28301
|
+
departureAirportCode: segment.departureAirportCode,
|
|
28302
|
+
departureAirportDescription: segment.departureAirportName,
|
|
28303
|
+
arrivalDate: toDateOnlyUtcString(segment.arrivalDateTime),
|
|
28304
|
+
arrivalTime: toTimeOnlyString(segment.arrivalDateTime),
|
|
28305
|
+
arrivalAirportCode: segment.arrivalAirportCode,
|
|
28306
|
+
arrivalAirportDescription: segment.arrivalAirportName,
|
|
28307
|
+
durationInTicks: segment.durationInTicks
|
|
28308
|
+
}));
|
|
28309
|
+
const buildTrajectFlightRequest = ({ node, line }, context, pax) => ({
|
|
28310
|
+
transactionId: context.transactionId,
|
|
28311
|
+
officeId: context.officeId,
|
|
28312
|
+
catalogueId: context.catalogueId,
|
|
28313
|
+
agentId: context.agentId ?? null,
|
|
28314
|
+
language: context.language,
|
|
28315
|
+
departureAirportCode: node.departureAirportCode ?? '',
|
|
28316
|
+
arrivalAirportCode: node.arrivalAirportCode ?? '',
|
|
28317
|
+
returnAirportCode: null,
|
|
28318
|
+
luggageIncluded: null,
|
|
28319
|
+
maxStops: null,
|
|
28320
|
+
travelClass: null,
|
|
28321
|
+
vendorConfigurationId: node.externalVendorId ?? null,
|
|
28322
|
+
pax,
|
|
28323
|
+
outward: { date: dateToDateStruct(new Date(line.from)) },
|
|
28324
|
+
return: null
|
|
28325
|
+
});
|
|
28326
|
+
/**
|
|
28327
|
+
* The entry as the backend can price it.
|
|
28328
|
+
*
|
|
28329
|
+
* A search caches an EntryLine per option and hands back that line's guid on the option. The
|
|
28330
|
+
* backend then rebuilds the entry by looking each line up in that cache on its guid, so a line
|
|
28331
|
+
* it cannot find is dropped — PackagingEntryBuilder.SyncEntryLines skips it, and an entry whose
|
|
28332
|
+
* lines all came off a traject prices as nothing at all.
|
|
28333
|
+
*
|
|
28334
|
+
* A traject entry is rendered before any search runs, so its lines carry the guids the traject
|
|
28335
|
+
* mapper gave them. This swaps in the guid of whatever each line resolved onto, leaving the
|
|
28336
|
+
* lines the traveller added themselves alone — those were built from a search result and already
|
|
28337
|
+
* carry the right one.
|
|
28338
|
+
*/
|
|
28339
|
+
const withCachedEntryLineGuids = (entry, trajectEntry, nodeAvailability, flightAvailability) => {
|
|
28340
|
+
const cachedGuids = new Map();
|
|
28341
|
+
getTrajectNodesWithLines(trajectEntry).forEach((item) => {
|
|
28342
|
+
if (isTrajectFlightNode(item.node)) {
|
|
28343
|
+
// A flight search caches the outward line under the guid it reports as outwardGuid.
|
|
28344
|
+
const guid = flightAvailability[item.line.guid]?.selectedGuid;
|
|
28345
|
+
if (guid)
|
|
28346
|
+
cachedGuids.set(item.line.guid, guid);
|
|
28347
|
+
return;
|
|
28348
|
+
}
|
|
28349
|
+
const availability = nodeAvailability[item.line.guid];
|
|
28350
|
+
const result = availability?.results.find((x) => x.code === availability.selectedCode);
|
|
28351
|
+
if (!result)
|
|
28352
|
+
return;
|
|
28353
|
+
// Room i took option i when the result was stamped on, so it takes option i here too.
|
|
28354
|
+
item.lines.forEach((line, index) => {
|
|
28355
|
+
const guid = getOptionForRoom(result, index)?.guid;
|
|
28356
|
+
if (guid)
|
|
28357
|
+
cachedGuids.set(line.guid, guid);
|
|
28358
|
+
});
|
|
28359
|
+
});
|
|
28360
|
+
if (!cachedGuids.size)
|
|
28361
|
+
return entry;
|
|
28362
|
+
return {
|
|
28363
|
+
...entry,
|
|
28364
|
+
lines: (entry.lines ?? []).map((line) => ({
|
|
28365
|
+
...line,
|
|
28366
|
+
guid: cachedGuids.get(line.guid) ?? line.guid,
|
|
28367
|
+
parentGuid: (line.parentGuid && cachedGuids.get(line.parentGuid)) ?? line.parentGuid
|
|
28368
|
+
}))
|
|
28369
|
+
};
|
|
28370
|
+
};
|
|
28371
|
+
const pickCheapestFlight = (flights) => [...flights].sort((a, b) => a.price - b.price)[0] ?? null;
|
|
28372
|
+
const applyFlightToLine = (line, flight) => {
|
|
28373
|
+
const segments = flight.outward?.segments ?? [];
|
|
28374
|
+
if (!segments.length)
|
|
28375
|
+
return line;
|
|
28376
|
+
const firstSegment = segments[0];
|
|
28377
|
+
const lastSegment = segments[segments.length - 1];
|
|
28378
|
+
return {
|
|
28379
|
+
...line,
|
|
28380
|
+
from: new Date(firstSegment.departureDateTime).toISOString(),
|
|
28381
|
+
to: new Date(lastSegment.arrivalDateTime).toISOString(),
|
|
28382
|
+
productName: `${firstSegment.departureAirportName} - ${lastSegment.arrivalAirportName} (${firstSegment.marketingAirlineName})`,
|
|
28383
|
+
productCode: `${firstSegment.departureAirportCode} ${lastSegment.arrivalAirportCode}/${firstSegment.marketingAirlineCode}`,
|
|
28384
|
+
accommodationName: firstSegment.metaData?.farePriceClassName ?? line.accommodationName,
|
|
28385
|
+
accommodationCode: firstSegment.metaData?.fareCode ?? line.accommodationCode,
|
|
28386
|
+
flightInformation: {
|
|
28387
|
+
pnr: '',
|
|
28388
|
+
flightLines: mapFlightSegmentsToFlightLines(segments)
|
|
28389
|
+
},
|
|
28390
|
+
isChanged: true
|
|
28391
|
+
};
|
|
28392
|
+
};
|
|
28393
|
+
/**
|
|
28394
|
+
* Externally sourced products carry a vendor configuration; products held internally in Tide have
|
|
28395
|
+
* none. `vendorType` is not a reliable signal — the packaging service nulls it out when the search
|
|
28396
|
+
* configuration has ExposeProvider off — so the vendor ids are what we go on.
|
|
28397
|
+
*/
|
|
28398
|
+
const isExternalResult = (result) => result.vendorId != null || result.vendorConfigurationId != null;
|
|
28399
|
+
const isExternalCandidate = (candidate) => candidate.externalVendorId != null || candidate.vendor != null;
|
|
28400
|
+
|
|
28401
|
+
const badgeClass = (kind, modifier) => {
|
|
28402
|
+
const base = `search__filter__itinerary__${kind}-badge`;
|
|
28403
|
+
const fallback = kind === 'segment' ? `${base}--secondary` : `${base}--default`;
|
|
28404
|
+
return `${base} ${modifier ? `${base}--${modifier}` : fallback}`;
|
|
28405
|
+
};
|
|
28406
|
+
const nightsOf = (node, line) => differenceInCalendarDays(new Date(line?.to ?? node.to), new Date(line?.from ?? node.from));
|
|
28407
|
+
const TrajectItinerary = ({ trajectEntry, packagingEntry, translations }) => {
|
|
28408
|
+
const timeline = useMemo(() => {
|
|
28409
|
+
const linesByGuid = new Map((packagingEntry.lines ?? []).map((line) => [line.guid, line]));
|
|
28410
|
+
const nodes = [...(trajectEntry.nodes ?? [])]
|
|
28411
|
+
.sort((a, b) => a.dayOrder - b.dayOrder || a.order - b.order)
|
|
28412
|
+
.map((node) => {
|
|
28413
|
+
const guids = node.lineGuids?.length ? node.lineGuids : [node.lineGuid];
|
|
28414
|
+
const lines = guids.map((guid) => linesByGuid.get(guid)).filter((x) => !!x);
|
|
28415
|
+
return { node, lines };
|
|
28416
|
+
});
|
|
28417
|
+
const claimed = new Set(nodes.flatMap((item) => item.lines.map((line) => line.guid)));
|
|
28418
|
+
const extras = (packagingEntry.lines ?? []).filter((line) => !claimed.has(line.guid));
|
|
28419
|
+
if (!extras.length)
|
|
28420
|
+
return nodes;
|
|
28421
|
+
const dayOrderOf = (date) => trajectEntry.traject.days.find((day) => isSameDay(new Date(day.date), new Date(date)))?.order ??
|
|
28422
|
+
Math.max(differenceInCalendarDays(new Date(date), new Date(trajectEntry.traject.startDate)), 0);
|
|
28423
|
+
// One product bought once, however many option lines it took (an adult and a child ticket).
|
|
28424
|
+
const grouped = new Map();
|
|
28425
|
+
extras.forEach((line) => {
|
|
28426
|
+
const key = `${line.productCode}|${line.from}`;
|
|
28427
|
+
grouped.set(key, [...(grouped.get(key) ?? []), line]);
|
|
28428
|
+
});
|
|
28429
|
+
const byDay = new Map();
|
|
28430
|
+
nodes.forEach((item) => byDay.set(item.node.dayOrder, [...(byDay.get(item.node.dayOrder) ?? []), item]));
|
|
28431
|
+
[...grouped.values()].forEach((lines, index) => {
|
|
28432
|
+
const line = lines[0];
|
|
28433
|
+
const dayOrder = dayOrderOf(line.from);
|
|
28434
|
+
const item = {
|
|
28435
|
+
lines,
|
|
28436
|
+
node: {
|
|
28437
|
+
nodeId: -(index + 1),
|
|
28438
|
+
lineGuid: line.guid,
|
|
28439
|
+
lineGuids: lines.map((x) => x.guid),
|
|
28440
|
+
dayOrder,
|
|
28441
|
+
order: Number.MAX_SAFE_INTEGER,
|
|
28442
|
+
name: line.productName,
|
|
28443
|
+
serviceType: line.serviceType,
|
|
28444
|
+
trajectNodeType: REGULAR_TRAJECT_NODE_TYPE,
|
|
28445
|
+
requirementType: 0,
|
|
28446
|
+
contentSource: 0,
|
|
28447
|
+
alternatives: [],
|
|
28448
|
+
from: line.from,
|
|
28449
|
+
to: line.to
|
|
28450
|
+
}
|
|
28451
|
+
};
|
|
28452
|
+
const dayItems = [...(byDay.get(dayOrder) ?? [])];
|
|
28453
|
+
let at = dayItems.length;
|
|
28454
|
+
while (at > 0 && dayItems[at - 1].node.trajectNodeType === CONNECTING_TRAJECT_NODE_TYPE)
|
|
28455
|
+
at--;
|
|
28456
|
+
dayItems.splice(at, 0, item);
|
|
28457
|
+
byDay.set(dayOrder, dayItems);
|
|
28458
|
+
});
|
|
28459
|
+
return [...byDay.keys()].sort((a, b) => a - b).flatMap((dayOrder) => byDay.get(dayOrder));
|
|
28460
|
+
}, [trajectEntry, packagingEntry]);
|
|
28461
|
+
const stayByDay = useMemo(() => {
|
|
28462
|
+
const map = new Map();
|
|
28463
|
+
timeline
|
|
28464
|
+
.filter(({ node }) => node.trajectNodeType === REGULAR_TRAJECT_NODE_TYPE && node.serviceType === ACCOMMODATION_SERVICE_TYPE)
|
|
28465
|
+
.forEach(({ node, lines }) => {
|
|
28466
|
+
const line = lines[0];
|
|
28467
|
+
const nights = Math.max(nightsOf(node, line), 1);
|
|
28468
|
+
const name = line?.productName || node.preferredAccommodationName || node.name || '';
|
|
28469
|
+
for (let night = 0; night < nights; night++)
|
|
28470
|
+
map.set(node.dayOrder + night, { name, night, nights });
|
|
28471
|
+
});
|
|
28472
|
+
return map;
|
|
28473
|
+
}, [timeline]);
|
|
28474
|
+
const renderDate = (value) => (React__default.createElement("div", { className: "search__filter__itinerary__transport-date" },
|
|
28475
|
+
React__default.createElement("p", { className: "search__filter__itinerary__transport-date-date" },
|
|
28476
|
+
React__default.createElement("strong", null, format$2(new Date(value), 'd'))),
|
|
28477
|
+
React__default.createElement("p", null, format$2(new Date(value), 'MMM'))));
|
|
28478
|
+
const renderConnectingNode = ({ node }) => {
|
|
28479
|
+
const distance = node.distanceInKm ? `${Math.round(node.distanceInKm)} km` : '';
|
|
28480
|
+
return (React__default.createElement("div", { className: "search__filter__itinerary__transport", key: `connect-${node.nodeId}` },
|
|
28481
|
+
React__default.createElement("div", { className: "search__filter__itinerary__transport-timeline" },
|
|
28482
|
+
React__default.createElement("div", { className: "search__filter__itinerary__transport-timeline-line" })),
|
|
28483
|
+
React__default.createElement("div", { className: "search__filter__itinerary__transport-item" },
|
|
28484
|
+
React__default.createElement("div", { className: "search__filter__itinerary__transport-date" }),
|
|
28485
|
+
React__default.createElement("div", { className: badgeClass('transport', getTrajectNodeBadgeModifier(node)) },
|
|
28486
|
+
React__default.createElement(Icon, { name: getTrajectNodeIcon(node), height: 15 })),
|
|
28487
|
+
React__default.createElement("div", { className: "search__filter__itinerary__transport-details" },
|
|
28488
|
+
React__default.createElement("h6", null, node.name || distance),
|
|
28489
|
+
!!node.name && !!distance && React__default.createElement("p", { className: "search__filter__itinerary__transport-details-time" }, distance)))));
|
|
28490
|
+
};
|
|
28491
|
+
const renderOvernight = (item, index) => {
|
|
28492
|
+
const { dayOrder } = item.node;
|
|
28493
|
+
const closesTheDay = timeline[index + 1]?.node.dayOrder !== dayOrder;
|
|
28494
|
+
const stay = stayByDay.get(dayOrder);
|
|
28495
|
+
if (!closesTheDay || !stay)
|
|
28496
|
+
return null;
|
|
28497
|
+
return (React__default.createElement("div", { className: "search__filter__itinerary__transport", key: `overnight-${item.node.nodeId}` },
|
|
28498
|
+
React__default.createElement("div", { className: "search__filter__itinerary__transport-timeline" },
|
|
28499
|
+
React__default.createElement("div", { className: "search__filter__itinerary__transport-timeline-line" })),
|
|
28500
|
+
React__default.createElement("div", { className: "search__filter__itinerary__transport-item" },
|
|
28501
|
+
renderDate(item.node.from),
|
|
28502
|
+
React__default.createElement("div", { className: badgeClass('transport', getServiceTypeBadgeModifier(ACCOMMODATION_SERVICE_TYPE)) },
|
|
28503
|
+
React__default.createElement(Icon, { name: "ui-bed", height: 15 })),
|
|
28504
|
+
React__default.createElement("div", { className: "search__filter__itinerary__transport-details" },
|
|
28505
|
+
React__default.createElement("h6", null, stay.name),
|
|
28506
|
+
React__default.createElement("p", { className: "search__filter__itinerary__transport-details-time" },
|
|
28507
|
+
stay.night + 1,
|
|
28508
|
+
" ",
|
|
28509
|
+
translations.SRP.OF,
|
|
28510
|
+
" ",
|
|
28511
|
+
stay.nights,
|
|
28512
|
+
" ",
|
|
28513
|
+
translations.SRP.NIGHTS)))));
|
|
28514
|
+
};
|
|
28515
|
+
const renderFlightNode = ({ node, lines }) => {
|
|
28516
|
+
const line = lines[0];
|
|
28517
|
+
return (React__default.createElement("div", { className: "search__filter__itinerary__transport", key: `flight-${node.nodeId}` },
|
|
28518
|
+
React__default.createElement("div", { className: "search__filter__itinerary__transport-timeline" },
|
|
28519
|
+
React__default.createElement("div", { className: "search__filter__itinerary__transport-timeline-line" })),
|
|
28520
|
+
React__default.createElement("div", { className: "search__filter__itinerary__transport-item" },
|
|
28521
|
+
renderDate(line?.from ?? node.from),
|
|
28522
|
+
React__default.createElement("div", { className: badgeClass('transport', getServiceTypeBadgeModifier(FLIGHT_SERVICE_TYPE)) },
|
|
28523
|
+
React__default.createElement(Icon, { name: "ui-plane", height: 15 })),
|
|
28524
|
+
React__default.createElement("div", { className: "search__filter__itinerary__transport-details" },
|
|
28525
|
+
React__default.createElement("h6", null, line?.productName || node.name),
|
|
28526
|
+
!!getDepartureTime(line) && (React__default.createElement("p", { className: "search__filter__itinerary__transport-details-plane" },
|
|
28527
|
+
React__default.createElement("span", null,
|
|
28528
|
+
React__default.createElement(Icon, { name: "ui-plane-depart", height: 14 }),
|
|
28529
|
+
" ",
|
|
28530
|
+
React__default.createElement("strong", null, getDepartureTime(line))),
|
|
28531
|
+
' ',
|
|
28532
|
+
"-",
|
|
28533
|
+
' ',
|
|
28534
|
+
React__default.createElement("span", null,
|
|
28535
|
+
React__default.createElement(Icon, { name: "ui-plane-arrive", height: 14 }),
|
|
28536
|
+
" ",
|
|
28537
|
+
React__default.createElement("strong", null, getArrivalTime(line))))),
|
|
28538
|
+
!!getDuration(line) && (React__default.createElement("p", { className: "search__filter__itinerary__transport-details-time" },
|
|
28539
|
+
React__default.createElement("span", null,
|
|
28540
|
+
React__default.createElement(Icon, { name: "ui-clock", height: 20 }),
|
|
28541
|
+
" ",
|
|
28542
|
+
getDuration(line))))))));
|
|
28543
|
+
};
|
|
28544
|
+
const renderServiceNode = ({ node, lines }) => {
|
|
28545
|
+
const line = lines[0];
|
|
28546
|
+
const isStay = node.serviceType === ACCOMMODATION_SERVICE_TYPE;
|
|
28547
|
+
const nights = isStay ? nightsOf(node, line) : 0;
|
|
28548
|
+
return (React__default.createElement("div", { className: "search__filter__itinerary__segments", key: `node-${node.nodeId}` },
|
|
28549
|
+
React__default.createElement("div", { className: "search__filter__itinerary__segments__wrapper" },
|
|
28550
|
+
React__default.createElement("div", { className: "search__filter__itinerary__segments-timeline" },
|
|
28551
|
+
React__default.createElement("div", { className: "search__filter__itinerary__segments-timeline-line" })),
|
|
28552
|
+
React__default.createElement("div", { className: "search__filter__itinerary__segment" },
|
|
28553
|
+
React__default.createElement("div", { className: "search__filter__itinerary__segment-item" },
|
|
28554
|
+
React__default.createElement("div", { className: `search__filter__itinerary__segment-date ${isStay && nights > 0 ? 'search__filter__itinerary__segment-nights' : ''}` }, isStay && nights > 0 ? (React__default.createElement(React__default.Fragment, null,
|
|
28555
|
+
React__default.createElement("p", { className: "search__filter__itinerary__segment-date-date" },
|
|
28556
|
+
React__default.createElement("strong", null, nights)),
|
|
28557
|
+
React__default.createElement(Icon, { name: "ui-moon", width: 16, height: 16 }))) : (React__default.createElement(React__default.Fragment, null,
|
|
28558
|
+
React__default.createElement("p", { className: "search__filter__itinerary__segment-date-date" },
|
|
28559
|
+
React__default.createElement("strong", null, format$2(new Date(line?.from ?? node.from), 'd'))),
|
|
28560
|
+
React__default.createElement("p", null, format$2(new Date(line?.from ?? node.from), 'MMM'))))),
|
|
28561
|
+
React__default.createElement("div", { className: badgeClass('segment', getTrajectNodeBadgeModifier(node)) },
|
|
28562
|
+
React__default.createElement(Icon, { name: getTrajectNodeIcon(node), width: 15, height: 15 })),
|
|
28563
|
+
React__default.createElement("div", { className: "search__filter__itinerary__segment-details" },
|
|
28564
|
+
React__default.createElement("h6", null, line?.productName || node.preferredAccommodationName || node.name),
|
|
28565
|
+
isStay &&
|
|
28566
|
+
lines.map((roomLine, index) => (React__default.createElement(React__default.Fragment, { key: roomLine.guid },
|
|
28567
|
+
lines.length > 1 && (React__default.createElement("strong", null,
|
|
28568
|
+
translations.SHARED.ROOM,
|
|
28569
|
+
"\u00A0",
|
|
28570
|
+
index + 1)),
|
|
28571
|
+
roomLine.accommodationName && (React__default.createElement("div", { className: "search__filter__itinerary__segment-details__room" },
|
|
28572
|
+
React__default.createElement(Icon, { name: "ui-bed", width: 12, height: 12 }),
|
|
28573
|
+
roomLine.accommodationName)),
|
|
28574
|
+
roomLine.regimeName && (React__default.createElement("div", { className: "search__filter__itinerary__segment-details__room" },
|
|
28575
|
+
React__default.createElement(Icon, { name: "ui-utensils", width: 12, height: 12 }),
|
|
28576
|
+
roomLine.regimeName))))),
|
|
28577
|
+
!isStay && React__default.createElement("p", { className: "search__filter__itinerary__segment-details-text" }, format$2(new Date(line?.from ?? node.from), 'EEE. d MMM yyyy'))))))));
|
|
28578
|
+
};
|
|
28579
|
+
return (React__default.createElement(React__default.Fragment, null, timeline.map((item, index) => {
|
|
28580
|
+
if (item.node.trajectNodeType === CONNECTING_TRAJECT_NODE_TYPE) {
|
|
28581
|
+
return (React__default.createElement(React__default.Fragment, { key: `connect-${item.node.nodeId}` },
|
|
28582
|
+
renderConnectingNode(item),
|
|
28583
|
+
renderOvernight(item, index)));
|
|
28584
|
+
}
|
|
28585
|
+
if (item.node.trajectNodeType === REGULAR_TRAJECT_NODE_TYPE && item.node.serviceType === FLIGHT_SERVICE_TYPE)
|
|
28586
|
+
return renderFlightNode(item);
|
|
28587
|
+
return renderServiceNode(item);
|
|
28588
|
+
})));
|
|
28589
|
+
};
|
|
28590
|
+
|
|
27804
28591
|
const getSegmentIcon = (segment) => {
|
|
27805
28592
|
switch (segment.serviceType) {
|
|
27806
28593
|
case 3:
|
|
@@ -27924,116 +28711,119 @@ const Itinerary = ({ isOpen, handleSetIsOpen, isLoading, onEditAccommodation })
|
|
|
27924
28711
|
format$2(new Date(firstEntryLine.from), 'EEE. d MMM yyyy'),
|
|
27925
28712
|
" - ",
|
|
27926
28713
|
React__default.createElement("strong", null, translations.SRP.START))))),
|
|
27927
|
-
|
|
27928
|
-
React__default.createElement("div", { className: "search__filter__itinerary__transport
|
|
27929
|
-
React__default.createElement("div", { className: "search__filter__itinerary__transport-timeline
|
|
27930
|
-
|
|
27931
|
-
React__default.createElement("div", { className: "search__filter__itinerary__transport-
|
|
27932
|
-
React__default.createElement("
|
|
27933
|
-
React__default.createElement("
|
|
27934
|
-
|
|
27935
|
-
|
|
27936
|
-
React__default.createElement(
|
|
27937
|
-
|
|
27938
|
-
React__default.createElement("
|
|
27939
|
-
|
|
27940
|
-
React__default.createElement("
|
|
27941
|
-
React__default.createElement(
|
|
27942
|
-
|
|
27943
|
-
|
|
27944
|
-
|
|
27945
|
-
|
|
27946
|
-
|
|
27947
|
-
|
|
27948
|
-
React__default.createElement(
|
|
27949
|
-
|
|
27950
|
-
|
|
27951
|
-
|
|
27952
|
-
React__default.createElement("
|
|
27953
|
-
React__default.createElement(
|
|
27954
|
-
|
|
27955
|
-
|
|
27956
|
-
|
|
27957
|
-
groupedOtherSegments
|
|
27958
|
-
|
|
27959
|
-
|
|
27960
|
-
|
|
27961
|
-
|
|
27962
|
-
React__default.createElement("div", { className: "
|
|
27963
|
-
React__default.createElement("div", { className: "
|
|
27964
|
-
React__default.createElement("div", { className: "search__filter__itinerary__segments-timeline
|
|
27965
|
-
|
|
27966
|
-
React__default.createElement("div", { className: "search__filter__itinerary__segment
|
|
27967
|
-
React__default.createElement("div", { className: "
|
|
27968
|
-
React__default.createElement("
|
|
27969
|
-
React__default.createElement("
|
|
27970
|
-
|
|
27971
|
-
|
|
27972
|
-
React__default.createElement(
|
|
27973
|
-
|
|
27974
|
-
React__default.createElement("
|
|
27975
|
-
|
|
27976
|
-
|
|
27977
|
-
|
|
27978
|
-
|
|
27979
|
-
|
|
27980
|
-
|
|
27981
|
-
|
|
27982
|
-
|
|
27983
|
-
|
|
27984
|
-
|
|
27985
|
-
|
|
27986
|
-
|
|
27987
|
-
|
|
27988
|
-
|
|
27989
|
-
|
|
27990
|
-
|
|
27991
|
-
React__default.createElement(
|
|
27992
|
-
|
|
27993
|
-
|
|
27994
|
-
|
|
27995
|
-
firstSegment
|
|
27996
|
-
|
|
27997
|
-
|
|
27998
|
-
|
|
27999
|
-
|
|
28000
|
-
|
|
28001
|
-
|
|
28002
|
-
|
|
28003
|
-
|
|
28004
|
-
|
|
28005
|
-
|
|
28006
|
-
|
|
28007
|
-
|
|
28008
|
-
|
|
28009
|
-
|
|
28010
|
-
|
|
28011
|
-
React__default.createElement("div", { className: "search__filter__itinerary__transport
|
|
28012
|
-
React__default.createElement("div", { className: "search__filter__itinerary__transport-
|
|
28013
|
-
React__default.createElement("
|
|
28014
|
-
|
|
28015
|
-
React__default.createElement("
|
|
28016
|
-
|
|
28017
|
-
|
|
28018
|
-
|
|
28019
|
-
React__default.createElement("
|
|
28020
|
-
|
|
28021
|
-
|
|
28022
|
-
|
|
28023
|
-
|
|
28024
|
-
React__default.createElement("
|
|
28025
|
-
|
|
28026
|
-
|
|
28027
|
-
|
|
28028
|
-
|
|
28029
|
-
|
|
28030
|
-
|
|
28031
|
-
React__default.createElement("
|
|
28032
|
-
|
|
28033
|
-
|
|
28034
|
-
|
|
28035
|
-
|
|
28036
|
-
|
|
28714
|
+
context?.trajectEntry ? (React__default.createElement(TrajectItinerary, { trajectEntry: context.trajectEntry, packagingEntry: packagingEntry, translations: translations })) : (React__default.createElement(React__default.Fragment, null,
|
|
28715
|
+
outboundFlight && (React__default.createElement("div", { className: "search__filter__itinerary__transport" },
|
|
28716
|
+
React__default.createElement("div", { className: "search__filter__itinerary__transport-timeline" },
|
|
28717
|
+
React__default.createElement("div", { className: "search__filter__itinerary__transport-timeline-line" })),
|
|
28718
|
+
React__default.createElement("div", { className: "search__filter__itinerary__transport-item" },
|
|
28719
|
+
React__default.createElement("div", { className: "search__filter__itinerary__transport-date" },
|
|
28720
|
+
React__default.createElement("p", { className: "search__filter__itinerary__transport-date-date" },
|
|
28721
|
+
React__default.createElement("strong", null, format$2(new Date(outboundFlight.from), 'd'))),
|
|
28722
|
+
React__default.createElement("p", null, format$2(new Date(outboundFlight.from), 'MMM'))),
|
|
28723
|
+
React__default.createElement("div", { className: "search__filter__itinerary__transport-badge" },
|
|
28724
|
+
React__default.createElement(Icon, { name: "ui-plane", height: 15 })),
|
|
28725
|
+
React__default.createElement("div", { className: "search__filter__itinerary__transport-details" },
|
|
28726
|
+
React__default.createElement("h6", null, outboundFlight.productName),
|
|
28727
|
+
React__default.createElement("p", { className: "search__filter__itinerary__transport-details-plane" },
|
|
28728
|
+
React__default.createElement("span", null,
|
|
28729
|
+
React__default.createElement(Icon, { name: "ui-plane-depart", height: 14 }),
|
|
28730
|
+
" ",
|
|
28731
|
+
React__default.createElement("strong", null, getDepartureTime(outboundFlight))),
|
|
28732
|
+
' ',
|
|
28733
|
+
"-",
|
|
28734
|
+
' ',
|
|
28735
|
+
React__default.createElement("span", null,
|
|
28736
|
+
React__default.createElement(Icon, { name: "ui-plane-arrive", height: 14 }),
|
|
28737
|
+
" ",
|
|
28738
|
+
React__default.createElement("strong", null, getArrivalTime(outboundFlight)))),
|
|
28739
|
+
React__default.createElement("p", { className: "search__filter__itinerary__transport-details-time" },
|
|
28740
|
+
React__default.createElement("span", null,
|
|
28741
|
+
React__default.createElement(Icon, { name: "ui-clock", height: 20 }),
|
|
28742
|
+
" ",
|
|
28743
|
+
getDuration(outboundFlight))))))),
|
|
28744
|
+
!isEmpty$1(groupedOtherSegments) &&
|
|
28745
|
+
groupedOtherSegments.map((group) => {
|
|
28746
|
+
const firstSegment = first(group.segments);
|
|
28747
|
+
if (!firstSegment)
|
|
28748
|
+
return null;
|
|
28749
|
+
return (React__default.createElement("div", { className: "search__filter__itinerary__segments" },
|
|
28750
|
+
React__default.createElement("div", { className: "search__filter__itinerary__segments__wrapper", key: group.key },
|
|
28751
|
+
React__default.createElement("div", { className: "search__filter__itinerary__segments-timeline" },
|
|
28752
|
+
React__default.createElement("div", { className: "search__filter__itinerary__segments-timeline-line" })),
|
|
28753
|
+
React__default.createElement("div", { className: "search__filter__itinerary__segment" },
|
|
28754
|
+
React__default.createElement("div", { className: "search__filter__itinerary__segment-item search__filter__itinerary__segment-item--start" },
|
|
28755
|
+
React__default.createElement("div", { className: "search__filter__itinerary__transport-date" },
|
|
28756
|
+
React__default.createElement("p", { className: "search__filter__itinerary__transport-date-date" },
|
|
28757
|
+
React__default.createElement("strong", null, format$2(new Date(firstSegment.from), 'd'))),
|
|
28758
|
+
React__default.createElement("p", null, format$2(new Date(firstSegment.from), 'MMM'))),
|
|
28759
|
+
React__default.createElement("div", { className: "search__filter__itinerary__segment-badge" },
|
|
28760
|
+
React__default.createElement(Icon, { name: "ui-location", width: 15, height: 15 })),
|
|
28761
|
+
React__default.createElement("div", { className: "search__filter__itinerary__segment-details" },
|
|
28762
|
+
React__default.createElement("h6", null,
|
|
28763
|
+
firstSegment.location?.name || firstSegment.oord?.name || firstSegment.region?.name,
|
|
28764
|
+
(firstSegment.location?.name || firstSegment.oord?.name || firstSegment.region?.name) && firstSegment.country?.name
|
|
28765
|
+
? ', '
|
|
28766
|
+
: '',
|
|
28767
|
+
firstSegment.country?.name),
|
|
28768
|
+
React__default.createElement("p", { className: "search__filter__itinerary__segment-details-text" },
|
|
28769
|
+
format$2(new Date(firstSegment.from), 'EEE. d MMM yyyy'),
|
|
28770
|
+
"> ",
|
|
28771
|
+
format$2(new Date(firstSegment.to), 'EEE. d MMM yyyy'))))),
|
|
28772
|
+
React__default.createElement("div", { className: "search__filter__itinerary__segment" },
|
|
28773
|
+
React__default.createElement("div", { className: `search__filter__itinerary__segment-item ${canEdit(firstSegment) ? 'search__filter__itinerary__segment-item--editable' : ''}`, onClick: () => {
|
|
28774
|
+
if (canEdit(firstSegment) && onEditAccommodation) {
|
|
28775
|
+
onEditAccommodation(group.segments);
|
|
28776
|
+
}
|
|
28777
|
+
} },
|
|
28778
|
+
React__default.createElement("div", { className: "search__filter__itinerary__segment-date search__filter__itinerary__segment-nights" }, firstSegment.serviceType === ACCOMMODATION_SERVICE_TYPE && (React__default.createElement(React__default.Fragment, null,
|
|
28779
|
+
React__default.createElement("p", { className: "search__filter__itinerary__segment-date-date" },
|
|
28780
|
+
React__default.createElement("strong", null, numberOfNights(firstSegment))),
|
|
28781
|
+
React__default.createElement(Icon, { name: "ui-moon", width: 16, height: 16 })))),
|
|
28782
|
+
getSegmentIcon(firstSegment),
|
|
28783
|
+
React__default.createElement("div", { className: "search__filter__itinerary__segment-details" },
|
|
28784
|
+
React__default.createElement("h6", null, getSegmentTitle(firstSegment)),
|
|
28785
|
+
firstSegment.serviceType === ACCOMMODATION_SERVICE_TYPE &&
|
|
28786
|
+
group.segments.map((segment, index) => (React__default.createElement(React__default.Fragment, { key: segment.guid },
|
|
28787
|
+
React__default.createElement("strong", null,
|
|
28788
|
+
translations.SHARED.ROOM,
|
|
28789
|
+
"\u00A0",
|
|
28790
|
+
index + 1),
|
|
28791
|
+
segment.productName && segment.accommodationName && (React__default.createElement("div", { className: "search__filter__itinerary__segment-details__room" },
|
|
28792
|
+
React__default.createElement(Icon, { name: "ui-bed", width: 12, height: 12 }),
|
|
28793
|
+
segment.accommodationName)),
|
|
28794
|
+
segment.regimeName && (React__default.createElement("div", { className: "search__filter__itinerary__segment-details__room" },
|
|
28795
|
+
React__default.createElement(Icon, { name: "ui-utensils", width: 12, height: 12 }),
|
|
28796
|
+
segment.regimeName)))))))))));
|
|
28797
|
+
}),
|
|
28798
|
+
returnFlight && returnFlight !== outboundFlight && (React__default.createElement("div", { className: "search__filter__itinerary__transport" },
|
|
28799
|
+
React__default.createElement("div", { className: "search__filter__itinerary__transport-timeline" },
|
|
28800
|
+
React__default.createElement("div", { className: "search__filter__itinerary__transport-timeline-line" })),
|
|
28801
|
+
React__default.createElement("div", { className: "search__filter__itinerary__transport-item" },
|
|
28802
|
+
React__default.createElement("div", { className: "search__filter__itinerary__transport-date" },
|
|
28803
|
+
React__default.createElement("p", { className: "search__filter__itinerary__transport-date-date" },
|
|
28804
|
+
React__default.createElement("strong", null, format$2(new Date(returnFlight.from), 'd'))),
|
|
28805
|
+
React__default.createElement("p", null, format$2(new Date(returnFlight.from), 'MMM'))),
|
|
28806
|
+
React__default.createElement("div", { className: "search__filter__itinerary__transport-badge" },
|
|
28807
|
+
React__default.createElement(Icon, { name: "ui-plane", height: 15 })),
|
|
28808
|
+
React__default.createElement("div", { className: "search__filter__itinerary__transport-details" },
|
|
28809
|
+
React__default.createElement("h6", null, returnFlight.productName),
|
|
28810
|
+
React__default.createElement("p", { className: "search__filter__itinerary__transport-details-plane" },
|
|
28811
|
+
React__default.createElement("span", null,
|
|
28812
|
+
React__default.createElement(Icon, { name: "ui-plane-depart", height: 14 }),
|
|
28813
|
+
" ",
|
|
28814
|
+
React__default.createElement("strong", null, getDepartureTime(returnFlight))),
|
|
28815
|
+
' ',
|
|
28816
|
+
"-",
|
|
28817
|
+
' ',
|
|
28818
|
+
React__default.createElement("span", null,
|
|
28819
|
+
React__default.createElement(Icon, { name: "ui-plane-arrive", height: 14 }),
|
|
28820
|
+
" ",
|
|
28821
|
+
React__default.createElement("strong", null, getArrivalTime(returnFlight)))),
|
|
28822
|
+
React__default.createElement("p", { className: "search__filter__itinerary__transport-details-time" },
|
|
28823
|
+
React__default.createElement("span", null,
|
|
28824
|
+
React__default.createElement(Icon, { name: "ui-clock", height: 20 }),
|
|
28825
|
+
" ",
|
|
28826
|
+
getDuration(returnFlight))))))))),
|
|
28037
28827
|
lastEntryLine && (React__default.createElement("div", { className: "search__filter__itinerary__country" },
|
|
28038
28828
|
React__default.createElement("div", { className: "search__filter__itinerary__country-icon" },
|
|
28039
28829
|
React__default.createElement(Icon, { name: "ui-flag", width: 17.5, height: 20 })),
|
|
@@ -30404,10 +31194,9 @@ const getOptionPaxIds = (option) => {
|
|
|
30404
31194
|
const optionAppliesToPax = (option, paxId) => {
|
|
30405
31195
|
return getOptionPaxIds(option).includes(paxId);
|
|
30406
31196
|
};
|
|
30407
|
-
const optionAppliesToAllTravellers = (option,
|
|
31197
|
+
const optionAppliesToAllTravellers = (option, travellerIds) => {
|
|
30408
31198
|
const paxIds = getOptionPaxIds(option);
|
|
30409
|
-
|
|
30410
|
-
return paxIds.length === expected.length && paxIds.every((id, index) => id === expected[index]);
|
|
31199
|
+
return paxIds.length === travellerIds.length && travellerIds.every((id) => paxIds.includes(id));
|
|
30411
31200
|
};
|
|
30412
31201
|
const groupOptionsByExcursion = (options) => {
|
|
30413
31202
|
const groupedMap = new Map();
|
|
@@ -30432,26 +31221,26 @@ const ExcursionDetails = () => {
|
|
|
30432
31221
|
return null;
|
|
30433
31222
|
}
|
|
30434
31223
|
const translations = getTranslations(context.languageCode ?? 'en-GB');
|
|
30435
|
-
const
|
|
31224
|
+
const travellerIds = editablePackagingEntry.pax.map((pax) => pax.id);
|
|
30436
31225
|
const allOptions = useMemo(() => {
|
|
30437
31226
|
return selectedExcursionSearchResult.rooms.flatMap((room) => room.options ?? []);
|
|
30438
31227
|
}, [selectedExcursionSearchResult]);
|
|
30439
31228
|
const sharedOptions = useMemo(() => {
|
|
30440
|
-
return allOptions.filter((option) => optionAppliesToAllTravellers(option,
|
|
30441
|
-
}, [allOptions,
|
|
31229
|
+
return allOptions.filter((option) => optionAppliesToAllTravellers(option, travellerIds));
|
|
31230
|
+
}, [allOptions, travellerIds]);
|
|
30442
31231
|
const sharedExcursions = useMemo(() => {
|
|
30443
31232
|
return groupOptionsByExcursion(sharedOptions);
|
|
30444
31233
|
}, [sharedOptions]);
|
|
30445
31234
|
const paxGroups = useMemo(() => {
|
|
30446
31235
|
return editablePackagingEntry.pax.map((pax) => {
|
|
30447
|
-
const paxOptions = allOptions.filter((option) => optionAppliesToPax(option, pax.id) && !optionAppliesToAllTravellers(option,
|
|
31236
|
+
const paxOptions = allOptions.filter((option) => optionAppliesToPax(option, pax.id) && !optionAppliesToAllTravellers(option, travellerIds));
|
|
30448
31237
|
return {
|
|
30449
31238
|
pax,
|
|
30450
31239
|
paxId: pax.id,
|
|
30451
31240
|
excursions: groupOptionsByExcursion(paxOptions)
|
|
30452
31241
|
};
|
|
30453
31242
|
});
|
|
30454
|
-
}, [editablePackagingEntry.pax, allOptions,
|
|
31243
|
+
}, [editablePackagingEntry.pax, allOptions, travellerIds]);
|
|
30455
31244
|
const getSelectedSharedOption = () => {
|
|
30456
31245
|
return sharedOptions.find((option) => option.isSelected);
|
|
30457
31246
|
};
|
|
@@ -30459,11 +31248,11 @@ const ExcursionDetails = () => {
|
|
|
30459
31248
|
return sharedOptions.find((option) => option.accommodationCode === accommodationCode && option.isSelected);
|
|
30460
31249
|
};
|
|
30461
31250
|
const getSelectedOptionForPax = (paxId) => {
|
|
30462
|
-
return allOptions.find((option) => optionAppliesToPax(option, paxId) && !optionAppliesToAllTravellers(option,
|
|
31251
|
+
return allOptions.find((option) => optionAppliesToPax(option, paxId) && !optionAppliesToAllTravellers(option, travellerIds) && option.isSelected);
|
|
30463
31252
|
};
|
|
30464
31253
|
const getSelectedOptionForExcursion = (paxId, accommodationCode) => {
|
|
30465
31254
|
return allOptions.find((option) => optionAppliesToPax(option, paxId) &&
|
|
30466
|
-
!optionAppliesToAllTravellers(option,
|
|
31255
|
+
!optionAppliesToAllTravellers(option, travellerIds) &&
|
|
30467
31256
|
option.accommodationCode === accommodationCode &&
|
|
30468
31257
|
option.isSelected);
|
|
30469
31258
|
};
|
|
@@ -30473,7 +31262,7 @@ const ExcursionDetails = () => {
|
|
|
30473
31262
|
rooms: selectedExcursionSearchResult.rooms.map((room) => ({
|
|
30474
31263
|
...room,
|
|
30475
31264
|
options: room.options.map((option) => {
|
|
30476
|
-
const isSharedOption = optionAppliesToAllTravellers(option,
|
|
31265
|
+
const isSharedOption = optionAppliesToAllTravellers(option, travellerIds);
|
|
30477
31266
|
if (paxId === undefined) {
|
|
30478
31267
|
if (!isSharedOption) {
|
|
30479
31268
|
return option;
|
|
@@ -30520,7 +31309,7 @@ const ExcursionDetails = () => {
|
|
|
30520
31309
|
targetPrice = selectedOption.price;
|
|
30521
31310
|
}
|
|
30522
31311
|
else {
|
|
30523
|
-
const firstOption = allOptions.find((option) => optionAppliesToPax(option, paxId) && !optionAppliesToAllTravellers(option,
|
|
31312
|
+
const firstOption = allOptions.find((option) => optionAppliesToPax(option, paxId) && !optionAppliesToAllTravellers(option, travellerIds) && option.accommodationCode === accommodationCode);
|
|
30524
31313
|
targetPrice = firstOption?.price || 0;
|
|
30525
31314
|
}
|
|
30526
31315
|
return targetPrice - (currentSelectedPrice || 0);
|
|
@@ -30542,6 +31331,13 @@ const ExcursionDetails = () => {
|
|
|
30542
31331
|
return 'flyin__acco__price';
|
|
30543
31332
|
};
|
|
30544
31333
|
const handleConfirm = () => {
|
|
31334
|
+
if (context.trajectEntry) {
|
|
31335
|
+
const lines = buildTrajectExcursionLines(selectedExcursionSearchResult);
|
|
31336
|
+
if (lines.length)
|
|
31337
|
+
dispatch(addEditableEntryLines(lines));
|
|
31338
|
+
dispatch(setFlyInIsOpen(false));
|
|
31339
|
+
return;
|
|
31340
|
+
}
|
|
30545
31341
|
const dayKey = getExcursionDayKey(excursionSearchParams.date);
|
|
30546
31342
|
dispatch(confirmExcursionForDay({
|
|
30547
31343
|
dayKey,
|
|
@@ -30596,181 +31392,6 @@ const ExcursionDetails = () => {
|
|
|
30596
31392
|
React__default.createElement("button", { type: "button", className: "cta cta--primary", onClick: handleConfirm }, translations?.QSM.CONFIRM))));
|
|
30597
31393
|
};
|
|
30598
31394
|
|
|
30599
|
-
const REGULAR_TRAJECT_NODE_TYPE = 0;
|
|
30600
|
-
const isSearchableTrajectNode = (node) => node.serviceType === ACCOMMODATION_SERVICE_TYPE || node.serviceType === EXCURSION_SERVICE_TYPE;
|
|
30601
|
-
const isTrajectFlightNode = (node) => node.serviceType === FLIGHT_SERVICE_TYPE;
|
|
30602
|
-
const getTrajectNodesWithLines = (trajectEntry) => {
|
|
30603
|
-
const linesByGuid = new Map((trajectEntry.entry.lines ?? []).map((line) => [line.guid, line]));
|
|
30604
|
-
return [...(trajectEntry.nodes ?? [])]
|
|
30605
|
-
.sort((a, b) => a.dayOrder - b.dayOrder || a.order - b.order)
|
|
30606
|
-
.map((node) => {
|
|
30607
|
-
const guids = node.lineGuids?.length ? node.lineGuids : [node.lineGuid];
|
|
30608
|
-
const lines = guids.map((guid) => linesByGuid.get(guid)).filter((x) => !!x);
|
|
30609
|
-
return { node, line: lines[0], lines };
|
|
30610
|
-
})
|
|
30611
|
-
.filter((x) => !!x.line);
|
|
30612
|
-
};
|
|
30613
|
-
const buildDestination = (line) => {
|
|
30614
|
-
if (line.location?.id)
|
|
30615
|
-
return { id: line.location.id, isLocation: true };
|
|
30616
|
-
if (line.oord?.id)
|
|
30617
|
-
return { id: line.oord.id, isOord: true };
|
|
30618
|
-
if (line.region?.id)
|
|
30619
|
-
return { id: line.region.id, isRegion: true };
|
|
30620
|
-
if (line.country?.id)
|
|
30621
|
-
return { id: line.country.id, isCountry: true };
|
|
30622
|
-
if (line.latitude && line.longitude)
|
|
30623
|
-
return { latitude: line.latitude, longitude: line.longitude };
|
|
30624
|
-
return { id: 0 };
|
|
30625
|
-
};
|
|
30626
|
-
const buildTrajectNodeRequest = ({ node, line }, context, productCode) => ({
|
|
30627
|
-
transactionId: context.transactionId,
|
|
30628
|
-
officeId: context.officeId,
|
|
30629
|
-
agentId: context.agentId ?? null,
|
|
30630
|
-
catalogueId: context.catalogueId,
|
|
30631
|
-
searchConfigurationId: context.searchConfigurationId,
|
|
30632
|
-
portalId: context.portalId ?? null,
|
|
30633
|
-
vendorConfigurationId: node.externalVendorId ?? null,
|
|
30634
|
-
language: context.language,
|
|
30635
|
-
serviceType: node.serviceType,
|
|
30636
|
-
fromDate: toDateOnlyString(line.from),
|
|
30637
|
-
toDate: toDateOnlyString(line.to),
|
|
30638
|
-
destination: buildDestination(line),
|
|
30639
|
-
productCode,
|
|
30640
|
-
rooms: context.rooms,
|
|
30641
|
-
tagIds: []
|
|
30642
|
-
});
|
|
30643
|
-
const getNodeCandidates = (node) => {
|
|
30644
|
-
const configured = (node.alternatives ?? []).filter((x) => !!x.code && x.code.trim().length > 0);
|
|
30645
|
-
if (configured.length)
|
|
30646
|
-
return configured;
|
|
30647
|
-
const fallbackCode = node.code ?? node.preferredAccommodationCode;
|
|
30648
|
-
if (!fallbackCode || !fallbackCode.trim().length)
|
|
30649
|
-
return [];
|
|
30650
|
-
return [
|
|
30651
|
-
{
|
|
30652
|
-
code: fallbackCode,
|
|
30653
|
-
name: node.name,
|
|
30654
|
-
description: node.description,
|
|
30655
|
-
imageUrl: node.imageUrl,
|
|
30656
|
-
contentSource: node.contentSource,
|
|
30657
|
-
vendor: node.vendor,
|
|
30658
|
-
externalVendorId: node.externalVendorId,
|
|
30659
|
-
isPreferred: true
|
|
30660
|
-
}
|
|
30661
|
-
];
|
|
30662
|
-
};
|
|
30663
|
-
const findCandidateForResult = (node, result) => getNodeCandidates(node).find((candidate) => matchesCode(result, candidate.code)) ?? null;
|
|
30664
|
-
const matchesCode = (result, code) => {
|
|
30665
|
-
if (result.code === code)
|
|
30666
|
-
return true;
|
|
30667
|
-
return (result.rooms ?? []).some((room) => (room.options ?? []).some((option) => option.accommodationCode === code));
|
|
30668
|
-
};
|
|
30669
|
-
const getPackagingRoomsFromEntry = (entry) => {
|
|
30670
|
-
const paxById = new Map((entry.pax ?? []).map((p) => [p.id, p]));
|
|
30671
|
-
const rooms = (entry.rooms ?? []).map((room) => ({
|
|
30672
|
-
travellers: (room.paxIds ?? []).map((paxId) => {
|
|
30673
|
-
const pax = paxById.get(paxId);
|
|
30674
|
-
return {
|
|
30675
|
-
id: paxId,
|
|
30676
|
-
age: pax?.age ?? null,
|
|
30677
|
-
dateOfBirth: pax?.dateOfBirth ?? null
|
|
30678
|
-
};
|
|
30679
|
-
})
|
|
30680
|
-
}));
|
|
30681
|
-
return rooms.filter((room) => room.travellers.length > 0);
|
|
30682
|
-
};
|
|
30683
|
-
const pickCheapest = (results) => [...results].sort((a, b) => a.price - b.price)[0] ?? null;
|
|
30684
|
-
const applyResultToLines = (lines, result) => lines.map((line, index) => applyResultToLine(line, result, index));
|
|
30685
|
-
const getOptionForRoom = (result, roomIndex) => {
|
|
30686
|
-
const room = (result.rooms ?? [])[roomIndex];
|
|
30687
|
-
if (room)
|
|
30688
|
-
return (room.options ?? []).find((x) => x.isSelected) ?? (room.options ?? [])[0];
|
|
30689
|
-
return (result.rooms ?? []).flatMap((x) => x.options ?? []).find((x) => x.isSelected) ?? (result.rooms ?? [])[0]?.options?.[0];
|
|
30690
|
-
};
|
|
30691
|
-
const applyResultToLine = (line, result, roomIndex = 0) => {
|
|
30692
|
-
const option = getOptionForRoom(result, roomIndex);
|
|
30693
|
-
return {
|
|
30694
|
-
...line,
|
|
30695
|
-
productName: result.name ?? line.productName,
|
|
30696
|
-
productCode: result.code ?? line.productCode,
|
|
30697
|
-
accommodationCode: option?.accommodationCode ?? line.accommodationCode,
|
|
30698
|
-
accommodationName: option?.accommodationName ?? line.accommodationName,
|
|
30699
|
-
regimeCode: option?.regimeCode ?? line.regimeCode,
|
|
30700
|
-
regimeName: option?.regimeName ?? line.regimeName,
|
|
30701
|
-
latitude: result.latitude ?? line.latitude,
|
|
30702
|
-
longitude: result.longitude ?? line.longitude,
|
|
30703
|
-
isChanged: true
|
|
30704
|
-
};
|
|
30705
|
-
};
|
|
30706
|
-
const toDateOnlyUtcString = (value) => {
|
|
30707
|
-
const date = new Date(value);
|
|
30708
|
-
return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate())).toISOString();
|
|
30709
|
-
};
|
|
30710
|
-
const toTimeOnlyString = (value) => {
|
|
30711
|
-
const date = new Date(value);
|
|
30712
|
-
const hh = String(date.getUTCHours()).padStart(2, '0');
|
|
30713
|
-
const mm = String(date.getUTCMinutes()).padStart(2, '0');
|
|
30714
|
-
const ss = String(date.getUTCSeconds()).padStart(2, '0');
|
|
30715
|
-
return `${hh}:${mm}:${ss}`;
|
|
30716
|
-
};
|
|
30717
|
-
const mapFlightSegmentsToFlightLines = (segments) => segments.map((segment) => ({
|
|
30718
|
-
airlineCode: segment.marketingAirlineCode,
|
|
30719
|
-
airlineDescription: segment.marketingAirlineName,
|
|
30720
|
-
operatingAirlineCode: segment.operatingAirlineCode,
|
|
30721
|
-
operatingAirlineDescription: segment.operatingAirlineName,
|
|
30722
|
-
flightNumber: segment.flightNumber,
|
|
30723
|
-
operatingFlightNumber: segment.operatingFlightNumber ?? null,
|
|
30724
|
-
departureDate: toDateOnlyUtcString(segment.departureDateTime),
|
|
30725
|
-
departureTime: toTimeOnlyString(segment.departureDateTime),
|
|
30726
|
-
departureAirportCode: segment.departureAirportCode,
|
|
30727
|
-
departureAirportDescription: segment.departureAirportName,
|
|
30728
|
-
arrivalDate: toDateOnlyUtcString(segment.arrivalDateTime),
|
|
30729
|
-
arrivalTime: toTimeOnlyString(segment.arrivalDateTime),
|
|
30730
|
-
arrivalAirportCode: segment.arrivalAirportCode,
|
|
30731
|
-
arrivalAirportDescription: segment.arrivalAirportName,
|
|
30732
|
-
durationInTicks: segment.durationInTicks
|
|
30733
|
-
}));
|
|
30734
|
-
const buildTrajectFlightRequest = ({ node, line }, context, pax) => ({
|
|
30735
|
-
transactionId: context.transactionId,
|
|
30736
|
-
officeId: context.officeId,
|
|
30737
|
-
catalogueId: context.catalogueId,
|
|
30738
|
-
agentId: context.agentId ?? null,
|
|
30739
|
-
language: context.language,
|
|
30740
|
-
departureAirportCode: node.departureAirportCode ?? '',
|
|
30741
|
-
arrivalAirportCode: node.arrivalAirportCode ?? '',
|
|
30742
|
-
returnAirportCode: null,
|
|
30743
|
-
luggageIncluded: null,
|
|
30744
|
-
maxStops: null,
|
|
30745
|
-
travelClass: null,
|
|
30746
|
-
vendorConfigurationId: node.externalVendorId ?? null,
|
|
30747
|
-
pax,
|
|
30748
|
-
outward: { date: dateToDateStruct(new Date(line.from)) },
|
|
30749
|
-
return: null
|
|
30750
|
-
});
|
|
30751
|
-
const pickCheapestFlight = (flights) => [...flights].sort((a, b) => a.price - b.price)[0] ?? null;
|
|
30752
|
-
const applyFlightToLine = (line, flight) => {
|
|
30753
|
-
const segments = flight.outward?.segments ?? [];
|
|
30754
|
-
if (!segments.length)
|
|
30755
|
-
return line;
|
|
30756
|
-
const firstSegment = segments[0];
|
|
30757
|
-
const lastSegment = segments[segments.length - 1];
|
|
30758
|
-
return {
|
|
30759
|
-
...line,
|
|
30760
|
-
from: new Date(firstSegment.departureDateTime).toISOString(),
|
|
30761
|
-
to: new Date(lastSegment.arrivalDateTime).toISOString(),
|
|
30762
|
-
productName: `${firstSegment.departureAirportName} - ${lastSegment.arrivalAirportName} (${firstSegment.marketingAirlineName})`,
|
|
30763
|
-
productCode: `${firstSegment.departureAirportCode} ${lastSegment.arrivalAirportCode}/${firstSegment.marketingAirlineCode}`,
|
|
30764
|
-
accommodationName: firstSegment.metaData?.farePriceClassName ?? line.accommodationName,
|
|
30765
|
-
accommodationCode: firstSegment.metaData?.fareCode ?? line.accommodationCode,
|
|
30766
|
-
flightInformation: {
|
|
30767
|
-
pnr: '',
|
|
30768
|
-
flightLines: mapFlightSegmentsToFlightLines(segments)
|
|
30769
|
-
},
|
|
30770
|
-
isChanged: true
|
|
30771
|
-
};
|
|
30772
|
-
};
|
|
30773
|
-
|
|
30774
31395
|
const getLocation = (result) => {
|
|
30775
31396
|
const place = result.locationName || result.regionName || result.oordName;
|
|
30776
31397
|
if (!place)
|
|
@@ -30785,13 +31406,12 @@ const toPlainText = (value) => {
|
|
|
30785
31406
|
.replace(/\s+/g, ' ')
|
|
30786
31407
|
.trim();
|
|
30787
31408
|
};
|
|
30788
|
-
const TrajectNodeCard = ({
|
|
30789
|
-
const { node } = item;
|
|
31409
|
+
const TrajectNodeCard = ({ node, result, isSelected, languageCode, translations, onSelect, onEditOptions, showNights, selectLabel, selectedLabel }) => {
|
|
30790
31410
|
const selectedPerRoom = (result.rooms ?? []).map((room) => (room.options ?? []).find((x) => x.isSelected) ?? (room.options ?? [])[0]);
|
|
30791
31411
|
const isMultiRoom = selectedPerRoom.length > 1;
|
|
30792
31412
|
const price = formatPrice$3(result.price, result.currencyCode, languageCode);
|
|
30793
31413
|
const nights = calculateNights(new Date(result.fromDate), new Date(result.toDate));
|
|
30794
|
-
const candidate = findCandidateForResult(node, result);
|
|
31414
|
+
const candidate = node ? findCandidateForResult(node, result) : null;
|
|
30795
31415
|
const image = candidate?.imageUrl ?? null;
|
|
30796
31416
|
const description = toPlainText(candidate?.description);
|
|
30797
31417
|
const title = candidate?.name ?? result.name;
|
|
@@ -30800,7 +31420,7 @@ const TrajectNodeCard = ({ item, result, isSelected, languageCode, translations,
|
|
|
30800
31420
|
React__default.createElement("span", { className: "search__result-card__price" }, price)));
|
|
30801
31421
|
const canEditOptions = isSelected && !!onEditOptions && (result.rooms ?? []).some((room) => (room.options ?? []).length > 1);
|
|
30802
31422
|
const selectButton = (React__default.createElement(React__default.Fragment, null,
|
|
30803
|
-
React__default.createElement("button", { type: "button", className: `cta ${isSelected ? 'cta--selected' : 'cta--select'}`, onClick: onSelect }, isSelected ? translations?.SHARED.SELECTED : translations?.SHARED.SELECT),
|
|
31423
|
+
React__default.createElement("button", { type: "button", className: `cta ${isSelected ? 'cta--selected' : 'cta--select'}`, onClick: onSelect }, isSelected ? selectedLabel ?? translations?.SHARED.SELECTED : selectLabel ?? translations?.SHARED.SELECT),
|
|
30804
31424
|
canEditOptions && (React__default.createElement("button", { type: "button", className: "cta cta--secondary", onClick: onEditOptions },
|
|
30805
31425
|
translations?.SRP.SELECT,
|
|
30806
31426
|
" ",
|
|
@@ -30880,7 +31500,118 @@ const TrajectResultsFlyIn = ({ isLoading }) => {
|
|
|
30880
31500
|
ordered.length,
|
|
30881
31501
|
"\u00A0",
|
|
30882
31502
|
translations.SRP.TOTAL_RESULTS_LABEL)),
|
|
30883
|
-
ordered.length === 0 ? (React__default.createElement("div", { className: "no-results" }, translations.SRP.NO_RESULTS)) : (React__default.createElement("div", { className: "search__results__cards
|
|
31503
|
+
ordered.length === 0 ? (React__default.createElement("div", { className: "no-results" }, translations.SRP.NO_RESULTS)) : (React__default.createElement("div", { className: "search__results__cards" }, ordered.map((result) => (React__default.createElement(TrajectNodeCard, { key: result.code, node: item.node, result: result, isSelected: result.code === availability?.selectedCode, languageCode: context.languageCode, translations: translations, onSelect: () => handleSelect(result.code), showNights: item.node.serviceType === ACCOMMODATION_SERVICE_TYPE })))))));
|
|
31504
|
+
};
|
|
31505
|
+
|
|
31506
|
+
/**
|
|
31507
|
+
* Every flight found for one traject leg, shown in the fly-in.
|
|
31508
|
+
*
|
|
31509
|
+
* A one-way search can return far more options than belong in the results column, so the column
|
|
31510
|
+
* keeps three and hands the rest to this list — the same shape the regular flight results use.
|
|
31511
|
+
* Each leg is searched on its own, so the single leg of a result always sits in `outward`.
|
|
31512
|
+
*/
|
|
31513
|
+
const TrajectFlightsFlyIn = ({ isLoading }) => {
|
|
31514
|
+
const context = useContext(SearchResultsConfigurationContext);
|
|
31515
|
+
const dispatch = useDispatch();
|
|
31516
|
+
const translations = getTranslations(context?.languageCode ?? 'en-GB');
|
|
31517
|
+
const { trajectFlightAvailability, trajectEditLineGuid, flightsLoading } = useSelector((state) => state.searchResults);
|
|
31518
|
+
const trajectEntry = context?.trajectEntry;
|
|
31519
|
+
const item = useMemo(() => {
|
|
31520
|
+
if (!trajectEntry || !trajectEditLineGuid)
|
|
31521
|
+
return null;
|
|
31522
|
+
return getTrajectNodesWithLines(trajectEntry).find((x) => x.line.guid === trajectEditLineGuid) ?? null;
|
|
31523
|
+
}, [trajectEntry, trajectEditLineGuid]);
|
|
31524
|
+
if (!context || !item)
|
|
31525
|
+
return null;
|
|
31526
|
+
const availability = trajectFlightAvailability[item.line.guid];
|
|
31527
|
+
const flights = availability?.results ?? [];
|
|
31528
|
+
const selectedGuid = availability?.selectedGuid;
|
|
31529
|
+
const selected = flights.find((x) => x.outwardGuid === selectedGuid) ?? null;
|
|
31530
|
+
const handleSelect = (outwardGuid) => {
|
|
31531
|
+
const flight = flights.find((x) => x.outwardGuid === outwardGuid);
|
|
31532
|
+
if (!flight)
|
|
31533
|
+
return;
|
|
31534
|
+
dispatch(setTrajectFlightSelection({ lineGuid: item.line.guid, guid: flight.outwardGuid }));
|
|
31535
|
+
dispatch(updateEditableEntryLine(applyFlightToLine(item.line, flight)));
|
|
31536
|
+
dispatch(setTrajectEditLineGuid(null));
|
|
31537
|
+
dispatch(setFlyInIsOpen(false));
|
|
31538
|
+
};
|
|
31539
|
+
if (isLoading || flightsLoading) {
|
|
31540
|
+
return React__default.createElement(React__default.Fragment, null, context.customSpinner ?? React__default.createElement(Spinner, { label: translations.SRP.LOADING_FLIGHTS }));
|
|
31541
|
+
}
|
|
31542
|
+
// The picked flight stays at the top; the rest follow by price.
|
|
31543
|
+
const ordered = [...flights].sort((a, b) => {
|
|
31544
|
+
if (a.outwardGuid === selectedGuid)
|
|
31545
|
+
return -1;
|
|
31546
|
+
if (b.outwardGuid === selectedGuid)
|
|
31547
|
+
return 1;
|
|
31548
|
+
return a.price - b.price;
|
|
31549
|
+
});
|
|
31550
|
+
return (React__default.createElement("div", { className: "flyin__content" },
|
|
31551
|
+
React__default.createElement("div", { className: "search__result-row" },
|
|
31552
|
+
React__default.createElement("span", { className: "search__result-row-text" },
|
|
31553
|
+
ordered.length,
|
|
31554
|
+
"\u00A0",
|
|
31555
|
+
translations.SRP.TOTAL_RESULTS_LABEL)),
|
|
31556
|
+
ordered.length === 0 ? (React__default.createElement("div", { className: "no-results" }, translations.SRP.NO_RESULTS)) : (React__default.createElement("div", { className: "search__results__cards search__results__cards--extended" }, ordered.map((result) => (React__default.createElement(IndependentFlightOption, { key: `flight-${result.outwardGuid}`, item: result.outward, guid: result.outwardGuid, selectedGuid: selectedGuid ?? undefined, isOutward: true, showSelectedState: true, price: result.price, currentSelectedPrice: selected?.price, onSelect: result.outwardGuid === selectedGuid ? undefined : () => handleSelect(result.outwardGuid) })))))));
|
|
31557
|
+
};
|
|
31558
|
+
|
|
31559
|
+
const TrajectDayActivitiesFlyIn = ({ isLoading }) => {
|
|
31560
|
+
const context = useContext(SearchResultsConfigurationContext);
|
|
31561
|
+
const dispatch = useDispatch();
|
|
31562
|
+
const translations = getTranslations(context?.languageCode ?? 'en-GB');
|
|
31563
|
+
const { trajectNodeAvailability, trajectDaySuggestions, trajectActivitiesDayOrder, editablePackagingEntry } = useSelector((state) => state.searchResults);
|
|
31564
|
+
const trajectEntry = context?.trajectEntry;
|
|
31565
|
+
const items = useMemo(() => {
|
|
31566
|
+
if (!trajectEntry || trajectActivitiesDayOrder === null)
|
|
31567
|
+
return [];
|
|
31568
|
+
return getTrajectNodesWithLines(trajectEntry).filter(({ node }) => node.dayOrder === trajectActivitiesDayOrder && node.trajectNodeType === REGULAR_TRAJECT_NODE_TYPE && node.serviceType === EXCURSION_SERVICE_TYPE);
|
|
31569
|
+
}, [trajectEntry, trajectActivitiesDayOrder]);
|
|
31570
|
+
if (!context || trajectActivitiesDayOrder === null)
|
|
31571
|
+
return null;
|
|
31572
|
+
const suggestions = trajectDaySuggestions[trajectActivitiesDayOrder];
|
|
31573
|
+
const cards = buildDayActivityCards(items, trajectNodeAvailability, suggestions?.results ?? []);
|
|
31574
|
+
const getAddedLines = (result) => (editablePackagingEntry?.lines ?? []).filter((line) => line.serviceType === EXCURSION_SERVICE_TYPE && line.productCode === result.code && isSameDay(new Date(line.from), new Date(result.fromDate)));
|
|
31575
|
+
/** Adding is not a decision that ends the list — the traveller may want several. */
|
|
31576
|
+
const handleToggle = (result) => {
|
|
31577
|
+
const added = getAddedLines(result);
|
|
31578
|
+
if (added.length) {
|
|
31579
|
+
dispatch(removeEditableEntryLines(added.map((line) => line.guid)));
|
|
31580
|
+
return;
|
|
31581
|
+
}
|
|
31582
|
+
if (excursionNeedsChoice(result)) {
|
|
31583
|
+
dispatch(setExcursionSearchParams(buildExcursionSearchParams(result)));
|
|
31584
|
+
dispatch(setSelectedExcursionSearchResult(result));
|
|
31585
|
+
dispatch(setFlyInType('excursion-details'));
|
|
31586
|
+
return;
|
|
31587
|
+
}
|
|
31588
|
+
const lines = buildTrajectExcursionLines(result);
|
|
31589
|
+
if (lines.length)
|
|
31590
|
+
dispatch(addEditableEntryLines(lines));
|
|
31591
|
+
};
|
|
31592
|
+
/** Swapping what the traject books is one decision, so the list closes behind it. */
|
|
31593
|
+
const handleSelect = ({ result, item }) => {
|
|
31594
|
+
if (!item)
|
|
31595
|
+
return;
|
|
31596
|
+
dispatch(setTrajectNodeSelection({ lineGuid: item.line.guid, code: result.code }));
|
|
31597
|
+
applyResultToLines(item.lines, result).forEach((line) => dispatch(updateEditableEntryLine(line)));
|
|
31598
|
+
dispatch(setTrajectActivitiesDayOrder(null));
|
|
31599
|
+
dispatch(setFlyInIsOpen(false));
|
|
31600
|
+
};
|
|
31601
|
+
if (isLoading || suggestions?.status === 'loading') {
|
|
31602
|
+
return React__default.createElement(React__default.Fragment, null, context.customSpinner ?? React__default.createElement(Spinner, { label: translations.SRP.LOADING_ACTIVITIES }));
|
|
31603
|
+
}
|
|
31604
|
+
return (React__default.createElement("div", { className: "flyin__content" },
|
|
31605
|
+
React__default.createElement("div", { className: "search__result-row" },
|
|
31606
|
+
React__default.createElement("span", { className: "search__result-row-text" },
|
|
31607
|
+
cards.length,
|
|
31608
|
+
"\u00A0",
|
|
31609
|
+
translations.SRP.TOTAL_RESULTS_LABEL)),
|
|
31610
|
+
cards.length === 0 ? (React__default.createElement("div", { className: "no-results" }, translations.SRP.NO_RESULTS)) : (React__default.createElement("div", { className: "search__results__cards" }, cards.map((card) => {
|
|
31611
|
+
const { result, item } = card;
|
|
31612
|
+
const isSelected = item ? trajectNodeAvailability[item.line.guid]?.selectedCode === result.code : getAddedLines(result).length > 0;
|
|
31613
|
+
return (React__default.createElement(TrajectNodeCard, { key: `activity-${result.code}`, node: item?.node, result: result, isSelected: isSelected, languageCode: context.languageCode, translations: translations, onSelect: () => (item ? handleSelect(card) : handleToggle(result)), selectLabel: item ? undefined : translations.SRP.ADD, selectedLabel: item ? undefined : translations.SRP.REMOVE }));
|
|
31614
|
+
})))));
|
|
30884
31615
|
};
|
|
30885
31616
|
|
|
30886
31617
|
const FlyIn = ({ srpType, isOpen, setIsOpen, className = '', onPanelRef, detailsLoading, flyInType, isPackageEditFlow, handleConfirm, sortByTypes, activeSearchSeed, toggleFilters, filtersOpen }) => {
|
|
@@ -30955,8 +31686,8 @@ const FlyIn = ({ srpType, isOpen, setIsOpen, className = '', onPanelRef, details
|
|
|
30955
31686
|
dispatch(setBookPackagingEntry(true));
|
|
30956
31687
|
}
|
|
30957
31688
|
};
|
|
30958
|
-
return (React__default.createElement("div", { className: `flyin ${isOpen ? 'flyin--active' : ''} ${className} ${isPackageEditFlow || flyInType === 'acco-results' ? 'flyin--large' : ''} ${flyInType === 'excursion-results' || flyInType === 'excursion-details' ? 'flyin--medium' : ''}
|
|
30959
|
-
${flyInType === 'flight-outward-results' || flyInType === 'flight-return-results' ? 'flyin--flight' : ''}` },
|
|
31689
|
+
return (React__default.createElement("div", { className: `flyin ${isOpen ? 'flyin--active' : ''} ${className} ${isPackageEditFlow || flyInType === 'acco-results' || flyInType === 'traject-day-activities' ? 'flyin--large' : ''} ${flyInType === 'excursion-results' || flyInType === 'excursion-details' ? 'flyin--medium' : ''}
|
|
31690
|
+
${flyInType === 'flight-outward-results' || flyInType === 'flight-return-results' || flyInType === 'traject-flight-results' ? 'flyin--flight' : ''}` },
|
|
30960
31691
|
React__default.createElement("div", { className: `flyin__panel ${isOpen ? 'flyin__panel--active' : ''}`, ref: panelRef },
|
|
30961
31692
|
React__default.createElement("div", { className: "flyin__content" },
|
|
30962
31693
|
React__default.createElement("div", { className: "flyin__content-title-row" },
|
|
@@ -30973,6 +31704,8 @@ const FlyIn = ({ srpType, isOpen, setIsOpen, className = '', onPanelRef, details
|
|
|
30973
31704
|
flyInType === 'flight-return-results' &&
|
|
30974
31705
|
`${translations.SRP.SELECT} ${translations.FLIGHTS_FORM.RETURN_FLIGHT}`,
|
|
30975
31706
|
srpType === build.PortalQsmType.AccommodationAndFlight && flyInType === 'flight-details' && 'Select your fare',
|
|
31707
|
+
flyInType === 'traject-flight-results' && `${translations.SRP.SELECT} ${translations.SRP.DEPARTURE}`,
|
|
31708
|
+
flyInType === 'traject-day-activities' && translations.SRP.ACTIVITIES,
|
|
30976
31709
|
flyInType === 'excursion-results' || (flyInType === 'excursion-details' && 'Select excursion')),
|
|
30977
31710
|
React__default.createElement("span", { className: "flyin__close", onClick: () => handleClose() },
|
|
30978
31711
|
React__default.createElement(Icon, { name: "ui-close", width: 30, height: 30, "aria-hidden": "true" }))),
|
|
@@ -30982,6 +31715,8 @@ const FlyIn = ({ srpType, isOpen, setIsOpen, className = '', onPanelRef, details
|
|
|
30982
31715
|
"Go Back")))),
|
|
30983
31716
|
srpType === build.PortalQsmType.Flight && React__default.createElement(FlightsFlyIn, { isOpen: isOpen, setIsOpen: setIsOpen }),
|
|
30984
31717
|
context?.trajectEntry && flyInType === 'acco-results' && React__default.createElement(TrajectResultsFlyIn, { isLoading: detailsLoading }),
|
|
31718
|
+
context?.trajectEntry && flyInType === 'traject-flight-results' && React__default.createElement(TrajectFlightsFlyIn, { isLoading: detailsLoading }),
|
|
31719
|
+
context?.trajectEntry && flyInType === 'traject-day-activities' && React__default.createElement(TrajectDayActivitiesFlyIn, { isLoading: detailsLoading }),
|
|
30985
31720
|
!context?.trajectEntry &&
|
|
30986
31721
|
(srpType === build.PortalQsmType.Accommodation || srpType === build.PortalQsmType.AccommodationAndFlight) &&
|
|
30987
31722
|
flyInType === 'acco-results' && (React__default.createElement("div", { className: "flyin__content flyin__content--columns" },
|
|
@@ -32812,6 +33547,27 @@ const DayByDayExcursions = () => {
|
|
|
32812
33547
|
})));
|
|
32813
33548
|
};
|
|
32814
33549
|
|
|
33550
|
+
/**
|
|
33551
|
+
* The entry to send to the backend, as opposed to the one the page works with.
|
|
33552
|
+
*
|
|
33553
|
+
* They differ only for a traject: the page keeps the line guids the traject was rendered with,
|
|
33554
|
+
* because that is what joins its nodes to its lines, while pricing and booking need the guids the
|
|
33555
|
+
* searches cached their lines under. Anything else passes through untouched, so this can be used
|
|
33556
|
+
* wherever the entry leaves the component without checking which flow is running.
|
|
33557
|
+
*/
|
|
33558
|
+
const useBookableEntry = () => {
|
|
33559
|
+
const context = useContext(SearchResultsConfigurationContext);
|
|
33560
|
+
const { editablePackagingEntry, trajectNodeAvailability, trajectFlightAvailability } = useSelector((state) => state.searchResults);
|
|
33561
|
+
const trajectEntry = context?.trajectEntry;
|
|
33562
|
+
return useMemo(() => {
|
|
33563
|
+
if (!editablePackagingEntry)
|
|
33564
|
+
return null;
|
|
33565
|
+
if (!trajectEntry)
|
|
33566
|
+
return editablePackagingEntry;
|
|
33567
|
+
return withCachedEntryLineGuids(editablePackagingEntry, trajectEntry, trajectNodeAvailability, trajectFlightAvailability);
|
|
33568
|
+
}, [editablePackagingEntry, trajectEntry, trajectNodeAvailability, trajectFlightAvailability]);
|
|
33569
|
+
};
|
|
33570
|
+
|
|
32815
33571
|
const renderEditablePackagingEntrySummaryOptions = (editablePackagingEntry, priceDetails, translations, currencyCode) => {
|
|
32816
33572
|
const priceDetailsByProduct = groupBy(priceDetails?.details ?? [], (detail) => `${detail.productCode}|${detail.accommodationCode}`);
|
|
32817
33573
|
return (editablePackagingEntry?.lines ?? []).map((line) => {
|
|
@@ -33196,6 +33952,8 @@ const BookPackagingEntry = ({ activeSearchSeed, isLoading, isConfirmationPage })
|
|
|
33196
33952
|
setSelectedPackagingAccoResult(selectedPackagingAccoResult);
|
|
33197
33953
|
}
|
|
33198
33954
|
}, [selectedPackagingAccoResultCode, packagingAccoResults]);
|
|
33955
|
+
// What the page holds versus what the backend can look up; they differ for a traject.
|
|
33956
|
+
const bookableEntry = useBookableEntry();
|
|
33199
33957
|
if (!context || !editablePackagingEntry || !priceDetails || !config)
|
|
33200
33958
|
return React__default.createElement(Spinner, { label: translations.SUMMARY.PROCESS_BOOKING });
|
|
33201
33959
|
const handleSummarySubmit = async (e) => {
|
|
@@ -33204,20 +33962,15 @@ const BookPackagingEntry = ({ activeSearchSeed, isLoading, isConfirmationPage })
|
|
|
33204
33962
|
if (typeof window !== 'undefined') {
|
|
33205
33963
|
window.scrollTo(0, 0);
|
|
33206
33964
|
}
|
|
33207
|
-
|
|
33208
|
-
|
|
33209
|
-
remarks
|
|
33210
|
-
};
|
|
33965
|
+
// Held apart from the entry itself so they can be put on both copies of it.
|
|
33966
|
+
let bookingFields = { remarks };
|
|
33211
33967
|
if (context.generatePaymentUrl && typeof window !== 'undefined') {
|
|
33212
33968
|
const redirectUrl = new URL(window.location.href);
|
|
33213
33969
|
redirectUrl.searchParams.set('bookingConfirmation', 'true');
|
|
33214
33970
|
redirectUrl.searchParams.set('link', '');
|
|
33215
|
-
|
|
33216
|
-
...updatedEditablePackagingEntry,
|
|
33217
|
-
redirectUrl: redirectUrl.toString(),
|
|
33218
|
-
returnPaymentUrl: true
|
|
33219
|
-
};
|
|
33971
|
+
bookingFields = { ...bookingFields, redirectUrl: redirectUrl.toString(), returnPaymentUrl: true };
|
|
33220
33972
|
}
|
|
33973
|
+
const updatedEditablePackagingEntry = { ...editablePackagingEntry, ...bookingFields };
|
|
33221
33974
|
dispatch(setEditablePackagingEntry(updatedEditablePackagingEntry));
|
|
33222
33975
|
try {
|
|
33223
33976
|
const request = {
|
|
@@ -33225,7 +33978,8 @@ const BookPackagingEntry = ({ activeSearchSeed, isLoading, isConfirmationPage })
|
|
|
33225
33978
|
officeId: context.tideConnection.officeId,
|
|
33226
33979
|
catalogueId: context.searchConfiguration.defaultCatalogueId ?? 0,
|
|
33227
33980
|
agentId: context.agentId,
|
|
33228
|
-
|
|
33981
|
+
// The page keeps the entry it works with; the backend gets the one it can look up.
|
|
33982
|
+
payload: { ...(bookableEntry ?? editablePackagingEntry), ...bookingFields }
|
|
33229
33983
|
};
|
|
33230
33984
|
const bookingResponse = await build.bookPackagingEntry(config, request);
|
|
33231
33985
|
dispatch(setBookingNumber(bookingResponse.number));
|
|
@@ -33257,6 +34011,41 @@ const BookPackagingEntry = ({ activeSearchSeed, isLoading, isConfirmationPage })
|
|
|
33257
34011
|
React__default.createElement(WLSidebar, { activeSearchSeed: activeSearchSeed, packagingAccoResult: selectedPackagingAccoResult }))));
|
|
33258
34012
|
};
|
|
33259
34013
|
|
|
34014
|
+
const useTrajectDayActivities = () => {
|
|
34015
|
+
const context = useContext(SearchResultsConfigurationContext);
|
|
34016
|
+
const dispatch = useDispatch();
|
|
34017
|
+
const { trajectDaySuggestions, trajectNodeAvailability } = useSelector((state) => state.searchResults);
|
|
34018
|
+
const trajectEntry = context?.trajectEntry;
|
|
34019
|
+
const showApiItems = !!context?.showApiItems;
|
|
34020
|
+
const loadDay = useCallback(async (day) => {
|
|
34021
|
+
if (!context || !trajectEntry)
|
|
34022
|
+
return;
|
|
34023
|
+
// Already asked for, or already answered: leave it be.
|
|
34024
|
+
if (trajectDaySuggestions[day.order])
|
|
34025
|
+
return;
|
|
34026
|
+
const nodesWithLines = getTrajectNodesWithLines(trajectEntry);
|
|
34027
|
+
const destination = getDayDestination(nodesWithLines, trajectNodeAvailability, day.order);
|
|
34028
|
+
if (!destination)
|
|
34029
|
+
return;
|
|
34030
|
+
dispatch(setTrajectDaySuggestionsLoading(day.order));
|
|
34031
|
+
try {
|
|
34032
|
+
const results = (await build.searchPackagingExcursions(buildTrajectClientConfig(context), buildTrajectDayExcursionRequest(day.date, destination, buildTrajectSearchContext(context, trajectEntry)))) ?? [];
|
|
34033
|
+
// With external items switched off a day only offers what Tide holds itself, and whatever
|
|
34034
|
+
// the traject already books that day has a card of its own further up the block.
|
|
34035
|
+
const configured = getConfiguredExcursionCodes(nodesWithLines, day.order);
|
|
34036
|
+
dispatch(setTrajectDaySuggestions({
|
|
34037
|
+
dayOrder: day.order,
|
|
34038
|
+
results: results.filter((x) => showApiItems || !isExternalResult(x)).filter((x) => !configured.has(x.code))
|
|
34039
|
+
}));
|
|
34040
|
+
}
|
|
34041
|
+
catch (error) {
|
|
34042
|
+
console.error('Failed to load traject day activities', day.order, error);
|
|
34043
|
+
dispatch(setTrajectDaySuggestions({ dayOrder: day.order, results: [] }));
|
|
34044
|
+
}
|
|
34045
|
+
}, [context, trajectEntry, showApiItems, trajectDaySuggestions, trajectNodeAvailability, dispatch]);
|
|
34046
|
+
return loadDay;
|
|
34047
|
+
};
|
|
34048
|
+
|
|
33260
34049
|
const getNodeIcon = (node) => {
|
|
33261
34050
|
if (isTrajectFlightNode(node))
|
|
33262
34051
|
return 'ui-flight';
|
|
@@ -33266,14 +34055,18 @@ const getNodeIcon = (node) => {
|
|
|
33266
34055
|
return 'ui-bed';
|
|
33267
34056
|
return 'ui-location';
|
|
33268
34057
|
};
|
|
34058
|
+
const isRenderableNode = (node) => node.trajectNodeType === REGULAR_TRAJECT_NODE_TYPE &&
|
|
34059
|
+
(node.serviceType === ACCOMMODATION_SERVICE_TYPE || node.serviceType === EXCURSION_SERVICE_TYPE || node.serviceType === FLIGHT_SERVICE_TYPE);
|
|
34060
|
+
const MAX_DAY_ACTIVITIES = 6;
|
|
33269
34061
|
const TrajectResults = ({ isLoading }) => {
|
|
33270
34062
|
const context = useContext(SearchResultsConfigurationContext);
|
|
33271
34063
|
const dispatch = useDispatch();
|
|
34064
|
+
const loadDay = useTrajectDayActivities();
|
|
33272
34065
|
const translations = getTranslations(context?.languageCode ?? 'en-GB');
|
|
33273
34066
|
const locale = getLocale(context?.languageCode ?? 'en-GB');
|
|
33274
|
-
const { trajectNodeAvailability, trajectFlightAvailability } = useSelector((state) => state.searchResults);
|
|
33275
|
-
const [expandedNodes, setExpandedNodes] = useState({});
|
|
34067
|
+
const { trajectNodeAvailability, trajectFlightAvailability, trajectDaySuggestions, editablePackagingEntry, trajectResolving } = useSelector((state) => state.searchResults);
|
|
33276
34068
|
const trajectEntry = context?.trajectEntry;
|
|
34069
|
+
const hideShowMoreResults = !!context?.hideShowMoreResults;
|
|
33277
34070
|
const nodesWithLines = useMemo(() => (trajectEntry ? getTrajectNodesWithLines(trajectEntry) : []), [trajectEntry]);
|
|
33278
34071
|
const lineByNodeId = useMemo(() => new Map(nodesWithLines.map((x) => [x.node.nodeId, x])), [nodesWithLines]);
|
|
33279
34072
|
const nodesByDay = useMemo(() => {
|
|
@@ -33287,9 +34080,22 @@ const TrajectResults = ({ isLoading }) => {
|
|
|
33287
34080
|
});
|
|
33288
34081
|
return map;
|
|
33289
34082
|
}, [trajectEntry]);
|
|
34083
|
+
const { stayByDay, stayStartByNodeId } = useMemo(() => {
|
|
34084
|
+
const byDay = new Map();
|
|
34085
|
+
const byNodeId = new Map();
|
|
34086
|
+
nodesWithLines
|
|
34087
|
+
.filter((x) => x.node.serviceType === ACCOMMODATION_SERVICE_TYPE)
|
|
34088
|
+
.forEach((item) => {
|
|
34089
|
+
const nights = Math.max(differenceInCalendarDays(new Date(item.line.to), new Date(item.line.from)), 1);
|
|
34090
|
+
byNodeId.set(item.node.nodeId, { item, night: 0, nights });
|
|
34091
|
+
for (let night = 0; night < nights; night++) {
|
|
34092
|
+
byDay.set(item.node.dayOrder + night, { item, night, nights });
|
|
34093
|
+
}
|
|
34094
|
+
});
|
|
34095
|
+
return { stayByDay: byDay, stayStartByNodeId: byNodeId };
|
|
34096
|
+
}, [nodesWithLines]);
|
|
33290
34097
|
if (!context || !trajectEntry)
|
|
33291
34098
|
return null;
|
|
33292
|
-
const toggleExpanded = (lineGuid) => setExpandedNodes((prev) => ({ ...prev, [lineGuid]: !prev[lineGuid] }));
|
|
33293
34099
|
const handleSelect = (item, code) => {
|
|
33294
34100
|
const availability = trajectNodeAvailability[item.line.guid];
|
|
33295
34101
|
const result = availability?.results.find((x) => x.code === code);
|
|
@@ -33298,6 +34104,12 @@ const TrajectResults = ({ isLoading }) => {
|
|
|
33298
34104
|
dispatch(setTrajectNodeSelection({ lineGuid: item.line.guid, code }));
|
|
33299
34105
|
applyResultToLines(item.lines, result).forEach((line) => dispatch(updateEditableEntryLine(line)));
|
|
33300
34106
|
};
|
|
34107
|
+
/** The remaining legs for a flight node open in the fly-in, as the regular flight results do. */
|
|
34108
|
+
const handleShowMoreFlights = (item) => {
|
|
34109
|
+
dispatch(setTrajectEditLineGuid(item.line.guid));
|
|
34110
|
+
dispatch(setFlyInType('traject-flight-results'));
|
|
34111
|
+
dispatch(setFlyInIsOpen(true));
|
|
34112
|
+
};
|
|
33301
34113
|
const handleShowMore = (item) => {
|
|
33302
34114
|
dispatch(setTrajectEditLineGuid(item.line.guid));
|
|
33303
34115
|
dispatch(setFlyInType('acco-results'));
|
|
@@ -33314,8 +34126,41 @@ const TrajectResults = ({ isLoading }) => {
|
|
|
33314
34126
|
dispatch(setFlyInType('acco-details'));
|
|
33315
34127
|
dispatch(setFlyInIsOpen(true));
|
|
33316
34128
|
};
|
|
33317
|
-
const
|
|
33318
|
-
|
|
34129
|
+
const getStayLocation = (item) => {
|
|
34130
|
+
const availability = trajectNodeAvailability[item.line.guid];
|
|
34131
|
+
const result = availability?.results.find((x) => x.code === availability?.selectedCode);
|
|
34132
|
+
return (result?.locationName ||
|
|
34133
|
+
result?.oordName ||
|
|
34134
|
+
result?.regionName ||
|
|
34135
|
+
result?.countryName ||
|
|
34136
|
+
item.line.location?.name ||
|
|
34137
|
+
item.line.oord?.name ||
|
|
34138
|
+
item.line.region?.name ||
|
|
34139
|
+
item.line.country?.name ||
|
|
34140
|
+
item.node.alternatives?.find((x) => x.isPreferred)?.city ||
|
|
34141
|
+
'');
|
|
34142
|
+
};
|
|
34143
|
+
const getStayName = (item) => {
|
|
34144
|
+
const availability = trajectNodeAvailability[item.line.guid];
|
|
34145
|
+
const result = availability?.results.find((x) => x.code === availability?.selectedCode);
|
|
34146
|
+
return result?.name || item.node.preferredAccommodationName || item.node.name || '';
|
|
34147
|
+
};
|
|
34148
|
+
const getStayTitle = (stay) => [getStayLocation(stay.item), getStayName(stay.item), `${stay.night + 1} ${translations.SRP.OF} ${stay.nights} ${translations.SRP.NIGHTS}`]
|
|
34149
|
+
.filter(Boolean)
|
|
34150
|
+
.join(' - ');
|
|
34151
|
+
const renderDayHeader = (day, stay) => {
|
|
34152
|
+
const date = new Date(day.date);
|
|
34153
|
+
const title = stay ? getStayTitle(stay) : day.name;
|
|
34154
|
+
return (React__default.createElement("div", { className: "search__results__label search__results__label--secondary" },
|
|
34155
|
+
React__default.createElement("div", { className: "search__results__label__date" },
|
|
34156
|
+
React__default.createElement("p", { className: "search__results__label__date-date" }, format$2(date, 'd', { locale })),
|
|
34157
|
+
React__default.createElement("p", null, format$2(date, 'MMM', { locale }))),
|
|
34158
|
+
React__default.createElement("div", { className: "search__results__label__text" },
|
|
34159
|
+
React__default.createElement(Icon, { name: stay ? 'ui-bed' : 'ui-location', height: 16 }),
|
|
34160
|
+
React__default.createElement("h3", null,
|
|
34161
|
+
React__default.createElement("strong", null, title)))));
|
|
34162
|
+
};
|
|
34163
|
+
const renderServiceNode = (item, showHeader = true, headerTitle) => {
|
|
33319
34164
|
const { node, line } = item;
|
|
33320
34165
|
const availability = trajectNodeAvailability[line.guid];
|
|
33321
34166
|
const results = availability?.results ?? [];
|
|
@@ -33325,13 +34170,15 @@ const TrajectResults = ({ isLoading }) => {
|
|
|
33325
34170
|
// No entry yet means the node's search has been queued but not dispatched — still loading.
|
|
33326
34171
|
const status = availability?.status ?? 'loading';
|
|
33327
34172
|
return (React__default.createElement(React__default.Fragment, { key: line.guid },
|
|
33328
|
-
React__default.createElement("div", { className: "search__results__label search__results__label--secondary" },
|
|
34173
|
+
showHeader && (React__default.createElement("div", { className: "search__results__label search__results__label--secondary" },
|
|
33329
34174
|
React__default.createElement("div", { className: "search__results__label__date" },
|
|
33330
34175
|
React__default.createElement("p", { className: "search__results__label__date-date" }, format$2(new Date(line.from), 'd', { locale })),
|
|
33331
34176
|
React__default.createElement("p", null, format$2(new Date(line.from), 'MMM', { locale }))),
|
|
33332
34177
|
React__default.createElement("div", { className: "search__results__label__text" },
|
|
33333
|
-
React__default.createElement(Icon, { name: getNodeIcon(node), height: 16 })
|
|
33334
|
-
|
|
34178
|
+
React__default.createElement(Icon, { name: getNodeIcon(node), height: 16 }),
|
|
34179
|
+
React__default.createElement("h3", null,
|
|
34180
|
+
React__default.createElement("strong", null, headerTitle || node.name))))),
|
|
34181
|
+
status === 'loading' && React__default.createElement(Spinner, { label: translations.SRP.LOADING_ACCOMMODATIONS }),
|
|
33335
34182
|
status === 'unavailable' && React__default.createElement("div", { className: "no-results" }, translations.SRP.NO_RESULTS),
|
|
33336
34183
|
status === 'resolved' && selected && (React__default.createElement(React__default.Fragment, null,
|
|
33337
34184
|
availability.preferredUnavailable && node.preferredAccommodationName && (React__default.createElement("div", { className: "search__results__cards__actions" },
|
|
@@ -33339,8 +34186,8 @@ const TrajectResults = ({ isLoading }) => {
|
|
|
33339
34186
|
node.preferredAccommodationName,
|
|
33340
34187
|
" \u2014 ",
|
|
33341
34188
|
translations.SRP.NO_RESULTS))),
|
|
33342
|
-
React__default.createElement("div", { className: "search__results__cards search__results__cards--
|
|
33343
|
-
others.length > 0 && (React__default.createElement("div", { className: "search__results__cards__actions" },
|
|
34189
|
+
React__default.createElement("div", { className: "search__results__cards search__results__cards--scroll" }, [selected, ...configured].map((result) => (React__default.createElement(TrajectNodeCard, { key: result.code, node: node, result: result, isSelected: result.code === selected.code, languageCode: context.languageCode, translations: translations, onSelect: () => handleSelect(item, result.code), onEditOptions: node.serviceType === ACCOMMODATION_SERVICE_TYPE ? () => handleEditOptions(item) : undefined, showNights: node.serviceType === ACCOMMODATION_SERVICE_TYPE })))),
|
|
34190
|
+
others.length > 0 && !hideShowMoreResults && (React__default.createElement("div", { className: "search__results__cards__actions" },
|
|
33344
34191
|
React__default.createElement("button", { type: "button", className: "cta cta--secondary", onClick: () => handleShowMore(item) },
|
|
33345
34192
|
translations.SRP.SHOW_MORE,
|
|
33346
34193
|
" (",
|
|
@@ -33358,8 +34205,7 @@ const TrajectResults = ({ isLoading }) => {
|
|
|
33358
34205
|
const flights = availability?.results ?? [];
|
|
33359
34206
|
const selected = flights.find((x) => x.outwardGuid === availability?.selectedGuid) ?? null;
|
|
33360
34207
|
const others = flights.filter((x) => x.outwardGuid !== availability?.selectedGuid);
|
|
33361
|
-
const
|
|
33362
|
-
const visible = isExpanded ? others : others.slice(0, 2);
|
|
34208
|
+
const visible = others.slice(0, 2);
|
|
33363
34209
|
return (React__default.createElement(React__default.Fragment, { key: line.guid },
|
|
33364
34210
|
React__default.createElement("div", { className: "search__results__label search__results__label--secondary" },
|
|
33365
34211
|
React__default.createElement("div", { className: "search__results__label__date" },
|
|
@@ -33368,8 +34214,6 @@ const TrajectResults = ({ isLoading }) => {
|
|
|
33368
34214
|
React__default.createElement("div", { className: "search__results__label__text" },
|
|
33369
34215
|
React__default.createElement(Icon, { name: "ui-flight", height: 16 }),
|
|
33370
34216
|
React__default.createElement("h3", null,
|
|
33371
|
-
translations.SRP.SELECT,
|
|
33372
|
-
' ',
|
|
33373
34217
|
React__default.createElement("strong", null,
|
|
33374
34218
|
node.departureAirportCode,
|
|
33375
34219
|
" - ",
|
|
@@ -33380,41 +34224,139 @@ const TrajectResults = ({ isLoading }) => {
|
|
|
33380
34224
|
React__default.createElement("div", { className: "search__results__cards search__results__cards--extended" },
|
|
33381
34225
|
React__default.createElement(IndependentFlightOption, { key: `flight-${selected.outwardGuid}`, item: selected.outward, guid: selected.outwardGuid, selectedGuid: selected.outwardGuid, isOutward: true, showSelectedState: true, price: selected.price }),
|
|
33382
34226
|
visible.map((result) => (React__default.createElement(IndependentFlightOption, { key: `flight-${result.outwardGuid}`, item: result.outward, guid: result.outwardGuid, isOutward: true, price: result.price, currentSelectedPrice: selected.price, onSelect: () => handleSelectFlight(item, result) })))),
|
|
33383
|
-
others.length > 2 && (React__default.createElement("div", { className: "search__results__cards__actions" },
|
|
33384
|
-
React__default.createElement("button", { type: "button", className: "cta cta--secondary", onClick: () =>
|
|
34227
|
+
others.length > 2 && !hideShowMoreResults && (React__default.createElement("div", { className: "search__results__cards__actions" },
|
|
34228
|
+
React__default.createElement("button", { type: "button", className: "cta cta--secondary", onClick: () => handleShowMoreFlights(item) },
|
|
34229
|
+
translations.SRP.SHOW_MORE,
|
|
34230
|
+
" (",
|
|
34231
|
+
others.length - 2,
|
|
34232
|
+
")")))))));
|
|
34233
|
+
};
|
|
34234
|
+
/** The lines a suggested activity put on the entry, which is also how we know it was added. */
|
|
34235
|
+
const getAddedExcursionLines = (result) => (editablePackagingEntry?.lines ?? []).filter((line) => line.serviceType === EXCURSION_SERVICE_TYPE && line.productCode === result.code && isSameDay(new Date(line.from), new Date(result.fromDate)));
|
|
34236
|
+
const handleToggleActivity = (result) => {
|
|
34237
|
+
const added = getAddedExcursionLines(result);
|
|
34238
|
+
if (added.length) {
|
|
34239
|
+
dispatch(removeEditableEntryLines(added.map((line) => line.guid)));
|
|
34240
|
+
return;
|
|
34241
|
+
}
|
|
34242
|
+
if (excursionNeedsChoice(result)) {
|
|
34243
|
+
dispatch(setExcursionSearchParams(buildExcursionSearchParams(result)));
|
|
34244
|
+
dispatch(setSelectedExcursionSearchResult(result));
|
|
34245
|
+
dispatch(setFlyInType('excursion-details'));
|
|
34246
|
+
dispatch(setFlyInIsOpen(true));
|
|
34247
|
+
return;
|
|
34248
|
+
}
|
|
34249
|
+
const lines = buildTrajectExcursionLines(result);
|
|
34250
|
+
if (lines.length)
|
|
34251
|
+
dispatch(addEditableEntryLines(lines));
|
|
34252
|
+
};
|
|
34253
|
+
const handleShowItinerary = () => {
|
|
34254
|
+
dispatch(setTrajectItineraryView(true));
|
|
34255
|
+
if (typeof window !== 'undefined')
|
|
34256
|
+
window.scrollTo(0, 0);
|
|
34257
|
+
};
|
|
34258
|
+
const handleShowMoreActivities = (dayOrder) => {
|
|
34259
|
+
dispatch(setTrajectActivitiesDayOrder(dayOrder));
|
|
34260
|
+
dispatch(setFlyInType('traject-day-activities'));
|
|
34261
|
+
dispatch(setFlyInIsOpen(true));
|
|
34262
|
+
};
|
|
34263
|
+
/** The excursion nodes the traject itself puts on a day. */
|
|
34264
|
+
const getDayExcursionItems = (dayOrder) => (nodesByDay.get(dayOrder) ?? [])
|
|
34265
|
+
.filter((node) => node.trajectNodeType === REGULAR_TRAJECT_NODE_TYPE && node.serviceType === EXCURSION_SERVICE_TYPE)
|
|
34266
|
+
.map((node) => lineByNodeId.get(node.nodeId))
|
|
34267
|
+
.filter((x) => !!x);
|
|
34268
|
+
/**
|
|
34269
|
+
* One block per day for everything there is to do that day.
|
|
34270
|
+
*
|
|
34271
|
+
* What the traject books sits first and stays selected; the rest of the day follows as things
|
|
34272
|
+
* the traveller may add. They share a row because they answer the same question — an excursion
|
|
34273
|
+
* on the traject and one found for the day are alternatives to each other, and a block apiece
|
|
34274
|
+
* would only hide that.
|
|
34275
|
+
*/
|
|
34276
|
+
const renderDayActivities = (day) => {
|
|
34277
|
+
const items = getDayExcursionItems(day.order);
|
|
34278
|
+
const suggestions = trajectDaySuggestions[day.order];
|
|
34279
|
+
const cards = buildDayActivityCards(items, trajectNodeAvailability, suggestions?.results ?? []);
|
|
34280
|
+
const nodesResolving = items.some((item) => (trajectNodeAvailability[item.line.guid]?.status ?? 'loading') === 'loading');
|
|
34281
|
+
const isSearching = suggestions?.status === 'loading';
|
|
34282
|
+
const canSearch = !!getDayDestination(nodesWithLines, trajectNodeAvailability, day.order);
|
|
34283
|
+
if (!items.length && !canSearch)
|
|
34284
|
+
return null;
|
|
34285
|
+
const visible = cards.slice(0, MAX_DAY_ACTIVITIES);
|
|
34286
|
+
const hidden = cards.length - visible.length;
|
|
34287
|
+
return (React__default.createElement(React__default.Fragment, { key: `activities-${day.order}` },
|
|
34288
|
+
React__default.createElement("div", { className: "search__results__label search__results__label--secondary" },
|
|
34289
|
+
React__default.createElement("div", { className: "search__results__label__date" },
|
|
34290
|
+
React__default.createElement("p", { className: "search__results__label__date-date" }, format$2(new Date(day.date), 'd', { locale })),
|
|
34291
|
+
React__default.createElement("p", null, format$2(new Date(day.date), 'MMM', { locale }))),
|
|
34292
|
+
React__default.createElement("div", { className: "search__results__label__text" },
|
|
34293
|
+
React__default.createElement(Icon, { name: "ui-excursion", height: 16 }),
|
|
34294
|
+
React__default.createElement("h3", null,
|
|
34295
|
+
React__default.createElement("strong", null, translations.SRP.ACTIVITIES)))),
|
|
34296
|
+
(nodesResolving || isSearching) && !cards.length && React__default.createElement(Spinner, { label: translations.SRP.LOADING_ACTIVITIES }),
|
|
34297
|
+
!!cards.length && React__default.createElement("div", { className: "search__results__cards search__results__cards--scroll" }, visible.map(renderActivityCard)),
|
|
34298
|
+
!nodesResolving && !isSearching && !cards.length && (!!items.length || !!suggestions) && (React__default.createElement("div", { className: "no-results" }, translations.SRP.NO_RESULTS)),
|
|
34299
|
+
React__default.createElement("div", { className: "search__results__cards__actions" },
|
|
34300
|
+
!suggestions && canSearch && !cards.length && !hideShowMoreResults && (React__default.createElement("button", { type: "button", className: "cta cta--secondary", onClick: () => loadDay(day) }, translations.SRP.SHOW_ACTIVITIES)),
|
|
34301
|
+
hidden > 0 && !hideShowMoreResults && (React__default.createElement("button", { type: "button", className: "cta cta--secondary", onClick: () => handleShowMoreActivities(day.order) },
|
|
34302
|
+
translations.SRP.SHOW_MORE,
|
|
34303
|
+
" (",
|
|
34304
|
+
hidden,
|
|
34305
|
+
")")))));
|
|
34306
|
+
};
|
|
34307
|
+
/**
|
|
34308
|
+
* An activity the traject books is swapped for another, the way a hotel is; one the traveller
|
|
34309
|
+
* found for themselves is added to the trip or taken back off it.
|
|
34310
|
+
*/
|
|
34311
|
+
const renderActivityCard = ({ result, item }) => {
|
|
34312
|
+
const isSelected = item ? trajectNodeAvailability[item.line.guid]?.selectedCode === result.code : getAddedExcursionLines(result).length > 0;
|
|
34313
|
+
return (React__default.createElement(TrajectNodeCard, { key: `activity-${result.code}`, node: item?.node, result: result, isSelected: isSelected, languageCode: context.languageCode, translations: translations, onSelect: () => (item ? handleSelect(item, result.code) : handleToggleActivity(result)), selectLabel: item ? undefined : translations.SRP.ADD, selectedLabel: item ? undefined : translations.SRP.REMOVE }));
|
|
33385
34314
|
};
|
|
33386
34315
|
if (isLoading)
|
|
33387
34316
|
return React__default.createElement(Spinner, { label: translations.SRP.LOADING_ITINERARY });
|
|
33388
|
-
return (React__default.createElement(React__default.Fragment, null,
|
|
33389
|
-
|
|
33390
|
-
|
|
33391
|
-
|
|
33392
|
-
|
|
33393
|
-
|
|
33394
|
-
|
|
33395
|
-
|
|
33396
|
-
|
|
33397
|
-
|
|
33398
|
-
|
|
33399
|
-
|
|
33400
|
-
|
|
33401
|
-
|
|
33402
|
-
|
|
33403
|
-
|
|
33404
|
-
|
|
33405
|
-
|
|
34317
|
+
return (React__default.createElement(React__default.Fragment, null,
|
|
34318
|
+
trajectEntry.traject.days.map((day) => {
|
|
34319
|
+
const stay = stayByDay.get(day.order);
|
|
34320
|
+
const dayNodes = (nodesByDay.get(day.order) ?? []).filter((node) => isRenderableNode(node) && lineByNodeId.has(node.nodeId));
|
|
34321
|
+
const stayLeadsDay = !!stay && (stay.night > 0 || dayNodes[0]?.nodeId === stay.item.node.nodeId);
|
|
34322
|
+
// Where the day's activities belong in the order: with the first excursion it carries.
|
|
34323
|
+
const firstExcursionNodeId = dayNodes.find((node) => node.serviceType === EXCURSION_SERVICE_TYPE)?.nodeId;
|
|
34324
|
+
const showDayHeader = stayLeadsDay || !!day.name?.trim();
|
|
34325
|
+
return (React__default.createElement(React__default.Fragment, { key: `day-${day.order}` },
|
|
34326
|
+
showDayHeader && renderDayHeader(day, stayLeadsDay ? stay : undefined),
|
|
34327
|
+
dayNodes.map((node) => {
|
|
34328
|
+
const item = lineByNodeId.get(node.nodeId);
|
|
34329
|
+
if (node.serviceType === ACCOMMODATION_SERVICE_TYPE) {
|
|
34330
|
+
const stayStart = stayStartByNodeId.get(node.nodeId);
|
|
34331
|
+
const titledByDayHeader = stayLeadsDay && stay.item.node.nodeId === node.nodeId;
|
|
34332
|
+
return renderServiceNode(item, !titledByDayHeader, stayStart && getStayTitle(stayStart));
|
|
34333
|
+
}
|
|
34334
|
+
// Every excursion the day holds is drawn once, in one block, where the first of
|
|
34335
|
+
// them falls in the order — so the activities still sit between the stay and the
|
|
34336
|
+
// flight that follow them.
|
|
34337
|
+
if (node.serviceType === EXCURSION_SERVICE_TYPE) {
|
|
34338
|
+
return node.nodeId === firstExcursionNodeId ? renderDayActivities(day) : null;
|
|
34339
|
+
}
|
|
34340
|
+
return renderFlightNode(item);
|
|
34341
|
+
}),
|
|
34342
|
+
firstExcursionNodeId === undefined && renderDayActivities(day)));
|
|
34343
|
+
}),
|
|
34344
|
+
React__default.createElement("div", { className: "search__results__cards__actions" },
|
|
34345
|
+
React__default.createElement("button", { type: "button", className: "cta", disabled: trajectResolving, onClick: handleShowItinerary }, translations.STEPS.NEXT))));
|
|
33406
34346
|
};
|
|
33407
34347
|
|
|
33408
34348
|
const useTrajectAvailability = () => {
|
|
33409
34349
|
const context = useContext(SearchResultsConfigurationContext);
|
|
33410
34350
|
const dispatch = useDispatch();
|
|
33411
34351
|
const trajectEntry = context?.trajectEntry;
|
|
33412
|
-
const
|
|
34352
|
+
const showApiItems = !!context?.showApiItems;
|
|
34353
|
+
const hideShowMoreResults = !!context?.hideShowMoreResults;
|
|
33413
34354
|
useEffect(() => {
|
|
33414
34355
|
if (!context || !trajectEntry)
|
|
33415
34356
|
return;
|
|
33416
34357
|
const controller = new AbortController();
|
|
33417
34358
|
dispatch(resetTrajectAvailability());
|
|
34359
|
+
dispatch(setTrajectResolving(true));
|
|
33418
34360
|
const config = {
|
|
33419
34361
|
host: context.tideConnection.host,
|
|
33420
34362
|
apiKey: context.tideConnection.apiKey
|
|
@@ -33441,36 +34383,51 @@ const useTrajectAvailability = () => {
|
|
|
33441
34383
|
const lineGuid = item.line.guid;
|
|
33442
34384
|
const search = item.node.serviceType === EXCURSION_SERVICE_TYPE ? build.searchPackagingExcursions : build.searchPackagingAccommodations;
|
|
33443
34385
|
dispatch(setTrajectNodeLoading(lineGuid));
|
|
34386
|
+
const destination = getNodeSearchDestination(nodesWithLines, item);
|
|
34387
|
+
const searchFor = async (productCode) => {
|
|
34388
|
+
try {
|
|
34389
|
+
const results = (await search(config, buildTrajectNodeRequest(item, searchContext, productCode, destination), controller.signal)) ?? [];
|
|
34390
|
+
return results.filter((x) => showApiItems || !isExternalResult(x));
|
|
34391
|
+
}
|
|
34392
|
+
catch (error) {
|
|
34393
|
+
if (!controller.signal.aborted)
|
|
34394
|
+
console.error('Traject product unavailable', item.node.name, productCode, error);
|
|
34395
|
+
return [];
|
|
34396
|
+
}
|
|
34397
|
+
};
|
|
33444
34398
|
try {
|
|
33445
|
-
|
|
33446
|
-
const
|
|
33447
|
-
|
|
33448
|
-
|
|
33449
|
-
|
|
33450
|
-
|
|
33451
|
-
|
|
33452
|
-
|
|
33453
|
-
|
|
33454
|
-
return
|
|
33455
|
-
|
|
34399
|
+
// With external items switched off the node only looks at products held inside Tide.
|
|
34400
|
+
const candidates = getNodeCandidates(item.node).filter((x) => showApiItems || !isExternalCandidate(x));
|
|
34401
|
+
const areaResults = hideShowMoreResults ? [] : await searchFor('');
|
|
34402
|
+
if (controller.signal.aborted)
|
|
34403
|
+
return [];
|
|
34404
|
+
const configuredResults = [];
|
|
34405
|
+
const found = await Promise.all(candidates.map(async (candidate) => {
|
|
34406
|
+
const known = areaResults.find((x) => matchesCode(x, candidate.code));
|
|
34407
|
+
if (known)
|
|
34408
|
+
return known;
|
|
34409
|
+
return (await searchFor(candidate.code)).find((x) => matchesCode(x, candidate.code)) ?? null;
|
|
33456
34410
|
}));
|
|
33457
|
-
|
|
33458
|
-
|
|
33459
|
-
|
|
34411
|
+
if (controller.signal.aborted)
|
|
34412
|
+
return [];
|
|
34413
|
+
// Two configured codes can land on the same product — a hotel and one of its room types.
|
|
34414
|
+
found.forEach((result) => {
|
|
34415
|
+
if (!result || configuredResults.some((x) => x.code === result.code))
|
|
34416
|
+
return;
|
|
34417
|
+
configuredResults.push(result);
|
|
34418
|
+
});
|
|
34419
|
+
const configuredCodes = new Set(configuredResults.map((x) => x.code));
|
|
34420
|
+
const destinationResults = areaResults.filter((x) => !configuredCodes.has(x.code));
|
|
33460
34421
|
const preferredCandidate = candidates.find((x) => x.isPreferred) ?? candidates[0];
|
|
33461
|
-
|
|
33462
|
-
|
|
33463
|
-
|
|
33464
|
-
: [];
|
|
33465
|
-
const results = [...configuredResults, ...destinationResults];
|
|
34422
|
+
const preferredUnavailable = configuredResults.length
|
|
34423
|
+
? !!preferredCandidate && !configuredResults.some((x) => matchesCode(x, preferredCandidate.code))
|
|
34424
|
+
: candidates.length > 0;
|
|
33466
34425
|
const picked = configuredResults[0] ?? pickCheapest(destinationResults);
|
|
33467
|
-
if (!configuredResults.length)
|
|
33468
|
-
preferredUnavailable = candidates.length > 0;
|
|
33469
34426
|
if (controller.signal.aborted)
|
|
33470
34427
|
return [];
|
|
33471
34428
|
dispatch(setTrajectNodeAvailability({
|
|
33472
34429
|
lineGuid,
|
|
33473
|
-
results,
|
|
34430
|
+
results: [...configuredResults, ...destinationResults],
|
|
33474
34431
|
selectedCode: picked?.code ?? null,
|
|
33475
34432
|
preferredUnavailable
|
|
33476
34433
|
}));
|
|
@@ -33514,11 +34471,11 @@ const useTrajectAvailability = () => {
|
|
|
33514
34471
|
}
|
|
33515
34472
|
};
|
|
33516
34473
|
(async () => {
|
|
33517
|
-
const
|
|
33518
|
-
const
|
|
33519
|
-
const unresolved = (await Promise.all([...searchable.map(resolveNode), ...flightNodes.map(resolveFlightNode)])).flat();
|
|
34474
|
+
const resolvable = nodesWithLines.filter((x) => isSearchableTrajectNode(x.node) || isTrajectFlightNode(x.node));
|
|
34475
|
+
const unresolved = (await Promise.all(resolvable.map((item) => (isTrajectFlightNode(item.node) ? resolveFlightNode(item) : resolveNode(item))))).flat();
|
|
33520
34476
|
if (controller.signal.aborted)
|
|
33521
34477
|
return;
|
|
34478
|
+
dispatch(setTrajectResolving(false));
|
|
33522
34479
|
// A node with no availability anywhere cannot be priced or booked, so its line comes off
|
|
33523
34480
|
// the entry. The card stays visible and reports the gap.
|
|
33524
34481
|
const emptyLineGuids = unresolved.filter(Boolean);
|
|
@@ -33526,7 +34483,7 @@ const useTrajectAvailability = () => {
|
|
|
33526
34483
|
dispatch(removeEditableEntryLines(emptyLineGuids));
|
|
33527
34484
|
})();
|
|
33528
34485
|
return () => controller.abort();
|
|
33529
|
-
}, [trajectEntry,
|
|
34486
|
+
}, [trajectEntry, showApiItems, hideShowMoreResults]);
|
|
33530
34487
|
};
|
|
33531
34488
|
|
|
33532
34489
|
const SearchResultsContainer = ({ onBookingStarted }) => {
|
|
@@ -33534,10 +34491,12 @@ const SearchResultsContainer = ({ onBookingStarted }) => {
|
|
|
33534
34491
|
const dispatch = useDispatch();
|
|
33535
34492
|
const context = useContext(SearchResultsConfigurationContext);
|
|
33536
34493
|
const translations = getTranslations(context?.languageCode ?? 'en-GB');
|
|
33537
|
-
const { results, filteredResults, packagingAccoResults, filteredPackagingAccoResults, isLoading, flightsLoading, initialFilters, filters, flightFilters, selectedSortType, selectedFlightSortType, selectedSearchResult, selectedPackagingAccoResultCode, flyInIsOpen, packagingAccoSearchDetails, editablePackagingEntry, transactionId, flyInType, packagingFlightResults, confirmedExcursionsByDay, bookPackagingEntry, trajectEditLineGuid } = useSelector((state) => state.searchResults);
|
|
34494
|
+
const { results, filteredResults, packagingAccoResults, filteredPackagingAccoResults, isLoading, flightsLoading, initialFilters, filters, flightFilters, selectedSortType, selectedFlightSortType, selectedSearchResult, selectedPackagingAccoResultCode, flyInIsOpen, packagingAccoSearchDetails, editablePackagingEntry, transactionId, flyInType, packagingFlightResults, confirmedExcursionsByDay, bookPackagingEntry, trajectEditLineGuid, trajectResolving, trajectItineraryView } = useSelector((state) => state.searchResults);
|
|
33538
34495
|
const isMobile = useMediaQuery('(max-width: 1200px)');
|
|
33539
34496
|
// Resolves live availability per traject node; no-op unless a traject entry is configured.
|
|
33540
34497
|
useTrajectAvailability();
|
|
34498
|
+
// The entry as the backend can price it: a traject swaps in the guids its searches cached.
|
|
34499
|
+
const bookableEntry = useBookableEntry();
|
|
33541
34500
|
const [initialFiltersSet, setInitialFiltersSet] = useState(false);
|
|
33542
34501
|
const [initialFlightFiltersSet, setInitialFlightFiltersSet] = useState(false);
|
|
33543
34502
|
const [filtersOpen, setFiltersOpen] = useState(false);
|
|
@@ -34304,6 +35263,10 @@ const SearchResultsContainer = ({ onBookingStarted }) => {
|
|
|
34304
35263
|
const fetchPriceDetails = async () => {
|
|
34305
35264
|
if (!context || !editablePackagingEntry || isEmpty$1(editablePackagingEntry.lines))
|
|
34306
35265
|
return;
|
|
35266
|
+
// A traject resolves one node at a time. Pricing each step would show a total that climbs
|
|
35267
|
+
// as the trip fills in, so the whole trip is priced once, when the last node is in.
|
|
35268
|
+
if (context.trajectEntry && trajectResolving)
|
|
35269
|
+
return;
|
|
34307
35270
|
setPricesAreLoading(true);
|
|
34308
35271
|
try {
|
|
34309
35272
|
const config = {
|
|
@@ -34315,7 +35278,7 @@ const SearchResultsContainer = ({ onBookingStarted }) => {
|
|
|
34315
35278
|
officeId: context.tideConnection.officeId,
|
|
34316
35279
|
catalogueId: context.searchConfiguration.defaultCatalogueId ?? first(context?.tideConnection?.catalogueIds) ?? 1,
|
|
34317
35280
|
agentId: context.agentId,
|
|
34318
|
-
payload: editablePackagingEntry
|
|
35281
|
+
payload: bookableEntry ?? editablePackagingEntry
|
|
34319
35282
|
};
|
|
34320
35283
|
const priceDetails = await build.getPriceDetails(config, request);
|
|
34321
35284
|
dispatch(setPriceDetails(priceDetails));
|
|
@@ -34329,8 +35292,9 @@ const SearchResultsContainer = ({ onBookingStarted }) => {
|
|
|
34329
35292
|
const fetchItinerary = async () => {
|
|
34330
35293
|
if (!context || !context.packagingEntry || !editablePackagingEntry || isEmpty$1(editablePackagingEntry.lines))
|
|
34331
35294
|
return;
|
|
34332
|
-
//
|
|
34333
|
-
|
|
35295
|
+
// While a traject is being composed its own day-by-day column is the itinerary; the generated
|
|
35296
|
+
// one is what the traveller reads the finished trip back from, so it waits until then.
|
|
35297
|
+
if (context.trajectEntry && (trajectResolving || !trajectItineraryView))
|
|
34334
35298
|
return;
|
|
34335
35299
|
setItineraryIsLoading(true);
|
|
34336
35300
|
try {
|
|
@@ -34343,7 +35307,7 @@ const SearchResultsContainer = ({ onBookingStarted }) => {
|
|
|
34343
35307
|
officeId: context.tideConnection.officeId,
|
|
34344
35308
|
catalogueId: context.searchConfiguration.defaultCatalogueId ?? first(context?.tideConnection?.catalogueIds) ?? 1,
|
|
34345
35309
|
agentId: context.agentId,
|
|
34346
|
-
payload: editablePackagingEntry
|
|
35310
|
+
payload: bookableEntry ?? editablePackagingEntry
|
|
34347
35311
|
};
|
|
34348
35312
|
const itinerary = await build.getItinerary(config, request);
|
|
34349
35313
|
dispatch(setItinerary(itinerary));
|
|
@@ -34356,7 +35320,7 @@ const SearchResultsContainer = ({ onBookingStarted }) => {
|
|
|
34356
35320
|
};
|
|
34357
35321
|
fetchPriceDetails();
|
|
34358
35322
|
fetchItinerary();
|
|
34359
|
-
}, [editablePackagingEntry]);
|
|
35323
|
+
}, [editablePackagingEntry, bookableEntry, trajectResolving, trajectItineraryView]);
|
|
34360
35324
|
// Flight selection
|
|
34361
35325
|
// const [selectedOutwardKey, setSelectedOutwardKey] = useState<string | null>(null);
|
|
34362
35326
|
// const [selectedReturnKey, setSelectedReturnKey] = useState<string | null>(null);
|
|
@@ -34705,6 +35669,11 @@ const SearchResultsContainer = ({ onBookingStarted }) => {
|
|
|
34705
35669
|
address: {}
|
|
34706
35670
|
};
|
|
34707
35671
|
};
|
|
35672
|
+
const handleComposeTraject = () => {
|
|
35673
|
+
dispatch(setTrajectItineraryView(false));
|
|
35674
|
+
if (typeof window !== 'undefined')
|
|
35675
|
+
window.scrollTo(0, 0);
|
|
35676
|
+
};
|
|
34708
35677
|
const handleShowMoreFlights = (flyInType) => {
|
|
34709
35678
|
dispatch(setFlyInType(flyInType));
|
|
34710
35679
|
dispatch(setFlyInIsOpen(true));
|
|
@@ -34720,7 +35689,7 @@ const SearchResultsContainer = ({ onBookingStarted }) => {
|
|
|
34720
35689
|
context.searchConfiguration.qsmType != build.PortalQsmType.AccommodationAndFlight && context.showFilters && (React__default.createElement(Filters, { initialFilters: initialFilters, filters: filters, isOpen: filtersOpen, handleSetIsOpen: () => setFiltersOpen(!filtersOpen),
|
|
34721
35690
|
// handleApplyFilters={() => setSearchTrigger((prev) => prev + 1)}
|
|
34722
35691
|
isLoading: isLoading, setFilters: (filters) => dispatch(setFilters(filters)), resetFilters: (filters) => dispatch(resetFilters(filters)) })),
|
|
34723
|
-
context.searchConfiguration.qsmType === build.PortalQsmType.AccommodationAndFlight && (React__default.createElement(Itinerary, { isOpen: itineraryOpen, handleSetIsOpen: () => setItineraryOpen(!itineraryOpen), isLoading: isLoading || pricesAreLoading || flightsLoading || itineraryIsLoading, onEditAccommodation: handleEditAccommodation })),
|
|
35692
|
+
context.searchConfiguration.qsmType === build.PortalQsmType.AccommodationAndFlight && (React__default.createElement(Itinerary, { isOpen: itineraryOpen, handleSetIsOpen: () => setItineraryOpen(!itineraryOpen), isLoading: isLoading || pricesAreLoading || flightsLoading || itineraryIsLoading || trajectResolving, onEditAccommodation: handleEditAccommodation })),
|
|
34724
35693
|
React__default.createElement("div", { className: "search__results" },
|
|
34725
35694
|
isMobile && (React__default.createElement("div", { className: "search__result-row" },
|
|
34726
35695
|
React__default.createElement("div", { className: "search__results__actions" },
|
|
@@ -34794,7 +35763,11 @@ const SearchResultsContainer = ({ onBookingStarted }) => {
|
|
|
34794
35763
|
React__default.createElement("div", { className: "search__results__cards search__results__cards--extended" },
|
|
34795
35764
|
selectedReturnKey && selectedReturn && (React__default.createElement(IndependentFlightOption, { key: `flight-${selectedReturnKey}`, item: selectedReturn.return, guid: selectedReturn.outwardGuid, selectedGuid: selectedReturn.outwardGuid, isOutward: false, showSelectedState: true, price: selectedReturn.price })),
|
|
34796
35765
|
uniqueReturnFlights.map((result) => (React__default.createElement(IndependentFlightOption, { key: `flight-${result.outwardGuid}`, item: result.return, onSelect: () => dispatch(setSelectedReturnKey(getFlightKey(result.return.segments))), guid: result.outwardGuid, isOutward: false, currentSelectedPrice: selectedReturn?.price, price: result.price })))))),
|
|
34797
|
-
context.searchConfiguration.qsmType === build.PortalQsmType.AccommodationAndFlight && context.trajectEntry && (React__default.createElement(TrajectResults, { isLoading: isLoading })),
|
|
35766
|
+
context.searchConfiguration.qsmType === build.PortalQsmType.AccommodationAndFlight && context.trajectEntry && !trajectItineraryView && (React__default.createElement(TrajectResults, { isLoading: isLoading })),
|
|
35767
|
+
context.searchConfiguration.qsmType === build.PortalQsmType.AccommodationAndFlight && context.trajectEntry && trajectItineraryView && (React__default.createElement(React__default.Fragment, null,
|
|
35768
|
+
React__default.createElement("div", { className: "search__results__cards__actions" },
|
|
35769
|
+
React__default.createElement("button", { type: "button", className: "cta cta--secondary", onClick: handleComposeTraject }, translations.STEPS.PREVIOUS)),
|
|
35770
|
+
React__default.createElement(FullItinerary, { isLoading: itineraryIsLoading }))),
|
|
34798
35771
|
context.searchConfiguration.qsmType === build.PortalQsmType.AccommodationAndFlight && context.packagingEntry && !context.trajectEntry && (React__default.createElement(FullItinerary, { isLoading: itineraryIsLoading })))),
|
|
34799
35772
|
React__default.createElement(FlyIn, { srpType: context.searchConfiguration.qsmType, isOpen: flyInIsOpen, setIsOpen: handleFlyInToggle, handleConfirm: () => handleConfirmHotelSwap(), onPanelRef: (el) => (panelRef.current = el), detailsLoading: detailsIsLoading, flyInType: flyInType, isPackageEditFlow: !!context.packagingEntry, sortByTypes: sortByTypes, activeSearchSeed: activeSearchSeed, toggleFilters: () => setFiltersOpen(!filtersOpen), filtersOpen: filtersOpen })))))))));
|
|
34800
35773
|
};
|
|
@@ -37703,20 +38676,20 @@ function ItineraryMapView({ destinations: propDestinations, adults = 2, dateFrom
|
|
|
37703
38676
|
const isStartEnd = dest.isStart || dest.isEnd;
|
|
37704
38677
|
let iconHtml;
|
|
37705
38678
|
if (isStartEnd) {
|
|
37706
|
-
iconHtml = `
|
|
37707
|
-
<div class="map-view__marker map-view__marker--plane">
|
|
37708
|
-
<svg viewBox="0 0 576 512" fill="currentColor" width="16" height="16" xmlns="http://www.w3.org/2000/svg">
|
|
37709
|
-
<path d="M482.3 192c34.2 0 93.7 29 93.7 64c0 36-59.5 64-93.7 64l-116.6 0L265.2 495.9c-5.7 10.1-16.3 16.1-27.8 16.1l-56.2 0c-10.6 0-18.3-10.3-15.8-20.6l49-196.3L112 288 68.8 377.6c-3 6.1-9.2 10.4-16 10.4l-2.8 0c-9.2 0-16-8.8-13.3-17.6L80 256L36.7 141.6C34 132.8 40.8 124 50 124l2.8 0c6.8 0 13 4.3 16 10.4L112 224l102.9 0-49-196.3C163.5 17.3 171.2 7 181.8 7l56.2 0c11.5 0 22.1 6 27.8 16.1L365.7 192l116.6 0z"/>
|
|
37710
|
-
</svg>
|
|
38679
|
+
iconHtml = `
|
|
38680
|
+
<div class="map-view__marker map-view__marker--plane">
|
|
38681
|
+
<svg viewBox="0 0 576 512" fill="currentColor" width="16" height="16" xmlns="http://www.w3.org/2000/svg">
|
|
38682
|
+
<path d="M482.3 192c34.2 0 93.7 29 93.7 64c0 36-59.5 64-93.7 64l-116.6 0L265.2 495.9c-5.7 10.1-16.3 16.1-27.8 16.1l-56.2 0c-10.6 0-18.3-10.3-15.8-20.6l49-196.3L112 288 68.8 377.6c-3 6.1-9.2 10.4-16 10.4l-2.8 0c-9.2 0-16-8.8-13.3-17.6L80 256L36.7 141.6C34 132.8 40.8 124 50 124l2.8 0c6.8 0 13 4.3 16 10.4L112 224l102.9 0-49-196.3C163.5 17.3 171.2 7 181.8 7l56.2 0c11.5 0 22.1 6 27.8 16.1L365.7 192l116.6 0z"/>
|
|
38683
|
+
</svg>
|
|
37711
38684
|
</div>`;
|
|
37712
38685
|
}
|
|
37713
38686
|
else {
|
|
37714
38687
|
stopIndex++;
|
|
37715
38688
|
const imgStyle = dest.imageUrl ? `style="background-image:url('${dest.imageUrl}')"` : '';
|
|
37716
|
-
iconHtml = `
|
|
37717
|
-
<div class="map-view__marker map-view__marker--stop" ${imgStyle}>
|
|
37718
|
-
${!dest.imageUrl ? `<span class="map-view__marker-number">${stopIndex}</span>` : ''}
|
|
37719
|
-
<span class="map-view__marker-badge">${stopIndex}</span>
|
|
38689
|
+
iconHtml = `
|
|
38690
|
+
<div class="map-view__marker map-view__marker--stop" ${imgStyle}>
|
|
38691
|
+
${!dest.imageUrl ? `<span class="map-view__marker-number">${stopIndex}</span>` : ''}
|
|
38692
|
+
<span class="map-view__marker-badge">${stopIndex}</span>
|
|
37720
38693
|
</div>`;
|
|
37721
38694
|
}
|
|
37722
38695
|
const icon = L.divIcon({
|