@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.
Files changed (25) hide show
  1. package/build/build-cjs/index.js +1368 -395
  2. package/build/build-cjs/src/search-results/components/itinerary/itinerary-utils.d.ts +12 -0
  3. package/build/build-cjs/src/search-results/components/itinerary/traject-itinerary.d.ts +9 -0
  4. package/build/build-cjs/src/search-results/components/traject/traject-day-activities-flyin.d.ts +6 -0
  5. package/build/build-cjs/src/search-results/components/traject/traject-flights-flyin.d.ts +13 -0
  6. package/build/build-cjs/src/search-results/components/traject/traject-node-card.d.ts +6 -3
  7. package/build/build-cjs/src/search-results/hooks/use-bookable-entry.d.ts +11 -0
  8. package/build/build-cjs/src/search-results/hooks/use-traject-day-activities.d.ts +6 -0
  9. package/build/build-cjs/src/search-results/store/search-results-slice.d.ts +14 -2
  10. package/build/build-cjs/src/search-results/types.d.ts +12 -2
  11. package/build/build-cjs/src/search-results/utils/traject-utils.d.ts +76 -2
  12. package/build/build-esm/index.js +1368 -395
  13. package/build/build-esm/src/search-results/components/itinerary/itinerary-utils.d.ts +12 -0
  14. package/build/build-esm/src/search-results/components/itinerary/traject-itinerary.d.ts +9 -0
  15. package/build/build-esm/src/search-results/components/traject/traject-day-activities-flyin.d.ts +6 -0
  16. package/build/build-esm/src/search-results/components/traject/traject-flights-flyin.d.ts +13 -0
  17. package/build/build-esm/src/search-results/components/traject/traject-node-card.d.ts +6 -3
  18. package/build/build-esm/src/search-results/hooks/use-bookable-entry.d.ts +11 -0
  19. package/build/build-esm/src/search-results/hooks/use-traject-day-activities.d.ts +6 -0
  20. package/build/build-esm/src/search-results/store/search-results-slice.d.ts +14 -2
  21. package/build/build-esm/src/search-results/types.d.ts +12 -2
  22. package/build/build-esm/src/search-results/utils/traject-utils.d.ts +76 -2
  23. package/package.json +2 -2
  24. package/styles/booking-search-results-variables.scss +6 -0
  25. package/styles/components/_search.scss +94 -5
@@ -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 640 640", fill: fill ?? 'currentColor' },
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
- trajectEditLineGuid: null
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
- outboundFlight && (React__default.createElement("div", { className: "search__filter__itinerary__transport" },
27928
- React__default.createElement("div", { className: "search__filter__itinerary__transport-timeline" },
27929
- React__default.createElement("div", { className: "search__filter__itinerary__transport-timeline-line" })),
27930
- React__default.createElement("div", { className: "search__filter__itinerary__transport-item" },
27931
- React__default.createElement("div", { className: "search__filter__itinerary__transport-date" },
27932
- React__default.createElement("p", { className: "search__filter__itinerary__transport-date-date" },
27933
- React__default.createElement("strong", null, format$2(new Date(outboundFlight.from), 'd'))),
27934
- React__default.createElement("p", null, format$2(new Date(outboundFlight.from), 'MMM'))),
27935
- React__default.createElement("div", { className: "search__filter__itinerary__transport-badge" },
27936
- React__default.createElement(Icon, { name: "ui-plane", height: 15 })),
27937
- React__default.createElement("div", { className: "search__filter__itinerary__transport-details" },
27938
- React__default.createElement("h6", null, outboundFlight.productName),
27939
- React__default.createElement("p", { className: "search__filter__itinerary__transport-details-plane" },
27940
- React__default.createElement("span", null,
27941
- React__default.createElement(Icon, { name: "ui-plane-depart", height: 14 }),
27942
- " ",
27943
- React__default.createElement("strong", null, getDepartureTime(outboundFlight))),
27944
- ' ',
27945
- "-",
27946
- ' ',
27947
- React__default.createElement("span", null,
27948
- React__default.createElement(Icon, { name: "ui-plane-arrive", height: 14 }),
27949
- " ",
27950
- React__default.createElement("strong", null, getArrivalTime(outboundFlight)))),
27951
- React__default.createElement("p", { className: "search__filter__itinerary__transport-details-time" },
27952
- React__default.createElement("span", null,
27953
- React__default.createElement(Icon, { name: "ui-clock", height: 20 }),
27954
- " ",
27955
- getDuration(outboundFlight))))))),
27956
- !isEmpty$1(groupedOtherSegments) &&
27957
- groupedOtherSegments.map((group) => {
27958
- const firstSegment = first(group.segments);
27959
- if (!firstSegment)
27960
- return null;
27961
- return (React__default.createElement("div", { className: "search__filter__itinerary__segments" },
27962
- React__default.createElement("div", { className: "search__filter__itinerary__segments__wrapper", key: group.key },
27963
- React__default.createElement("div", { className: "search__filter__itinerary__segments-timeline" },
27964
- React__default.createElement("div", { className: "search__filter__itinerary__segments-timeline-line" })),
27965
- React__default.createElement("div", { className: "search__filter__itinerary__segment" },
27966
- React__default.createElement("div", { className: "search__filter__itinerary__segment-item search__filter__itinerary__segment-item--start" },
27967
- React__default.createElement("div", { className: "search__filter__itinerary__transport-date" },
27968
- React__default.createElement("p", { className: "search__filter__itinerary__transport-date-date" },
27969
- React__default.createElement("strong", null, format$2(new Date(firstSegment.from), 'd'))),
27970
- React__default.createElement("p", null, format$2(new Date(firstSegment.from), 'MMM'))),
27971
- React__default.createElement("div", { className: "search__filter__itinerary__segment-badge" },
27972
- React__default.createElement(Icon, { name: "ui-location", width: 15, height: 15 })),
27973
- React__default.createElement("div", { className: "search__filter__itinerary__segment-details" },
27974
- React__default.createElement("h6", null,
27975
- firstSegment.location?.name || firstSegment.oord?.name || firstSegment.region?.name,
27976
- (firstSegment.location?.name || firstSegment.oord?.name || firstSegment.region?.name) && firstSegment.country?.name ? ', ' : '',
27977
- firstSegment.country?.name),
27978
- React__default.createElement("p", { className: "search__filter__itinerary__segment-details-text" },
27979
- format$2(new Date(firstSegment.from), 'EEE. d MMM yyyy'),
27980
- "> ",
27981
- format$2(new Date(firstSegment.to), 'EEE. d MMM yyyy'))))),
27982
- React__default.createElement("div", { className: "search__filter__itinerary__segment" },
27983
- React__default.createElement("div", { className: `search__filter__itinerary__segment-item ${canEdit(firstSegment) ? 'search__filter__itinerary__segment-item--editable' : ''}`, onClick: () => {
27984
- if (canEdit(firstSegment) && onEditAccommodation) {
27985
- onEditAccommodation(group.segments);
27986
- }
27987
- } },
27988
- 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,
27989
- React__default.createElement("p", { className: "search__filter__itinerary__segment-date-date" },
27990
- React__default.createElement("strong", null, numberOfNights(firstSegment))),
27991
- React__default.createElement(Icon, { name: "ui-moon", width: 16, height: 16 })))),
27992
- getSegmentIcon(firstSegment),
27993
- React__default.createElement("div", { className: "search__filter__itinerary__segment-details" },
27994
- React__default.createElement("h6", null, getSegmentTitle(firstSegment)),
27995
- firstSegment.serviceType === ACCOMMODATION_SERVICE_TYPE &&
27996
- group.segments.map((segment, index) => (React__default.createElement(React__default.Fragment, { key: segment.guid },
27997
- React__default.createElement("strong", null,
27998
- translations.SHARED.ROOM,
27999
- "\u00A0",
28000
- index + 1),
28001
- segment.productName && segment.accommodationName && (React__default.createElement("div", { className: "search__filter__itinerary__segment-details__room" },
28002
- React__default.createElement(Icon, { name: "ui-bed", width: 12, height: 12 }),
28003
- segment.accommodationName)),
28004
- segment.regimeName && (React__default.createElement("div", { className: "search__filter__itinerary__segment-details__room" },
28005
- React__default.createElement(Icon, { name: "ui-utensils", width: 12, height: 12 }),
28006
- segment.regimeName)))))))))));
28007
- }),
28008
- returnFlight && returnFlight !== outboundFlight && (React__default.createElement("div", { className: "search__filter__itinerary__transport" },
28009
- React__default.createElement("div", { className: "search__filter__itinerary__transport-timeline" },
28010
- React__default.createElement("div", { className: "search__filter__itinerary__transport-timeline-line" })),
28011
- React__default.createElement("div", { className: "search__filter__itinerary__transport-item" },
28012
- React__default.createElement("div", { className: "search__filter__itinerary__transport-date" },
28013
- React__default.createElement("p", { className: "search__filter__itinerary__transport-date-date" },
28014
- React__default.createElement("strong", null, format$2(new Date(returnFlight.from), 'd'))),
28015
- React__default.createElement("p", null, format$2(new Date(returnFlight.from), 'MMM'))),
28016
- React__default.createElement("div", { className: "search__filter__itinerary__transport-badge" },
28017
- React__default.createElement(Icon, { name: "ui-plane", height: 15 })),
28018
- React__default.createElement("div", { className: "search__filter__itinerary__transport-details" },
28019
- React__default.createElement("h6", null, returnFlight.productName),
28020
- React__default.createElement("p", { className: "search__filter__itinerary__transport-details-plane" },
28021
- React__default.createElement("span", null,
28022
- React__default.createElement(Icon, { name: "ui-plane-depart", height: 14 }),
28023
- " ",
28024
- React__default.createElement("strong", null, getDepartureTime(returnFlight))),
28025
- ' ',
28026
- "-",
28027
- ' ',
28028
- React__default.createElement("span", null,
28029
- React__default.createElement(Icon, { name: "ui-plane-arrive", height: 14 }),
28030
- " ",
28031
- React__default.createElement("strong", null, getArrivalTime(returnFlight)))),
28032
- React__default.createElement("p", { className: "search__filter__itinerary__transport-details-time" },
28033
- React__default.createElement("span", null,
28034
- React__default.createElement(Icon, { name: "ui-clock", height: 20 }),
28035
- " ",
28036
- getDuration(returnFlight))))))),
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, travellerCount) => {
31197
+ const optionAppliesToAllTravellers = (option, travellerIds) => {
30408
31198
  const paxIds = getOptionPaxIds(option);
30409
- const expected = Array.from({ length: travellerCount }, (_, i) => i);
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 travellerCount = editablePackagingEntry.pax.length;
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, travellerCount));
30441
- }, [allOptions, travellerCount]);
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, travellerCount));
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, travellerCount]);
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, travellerCount) && option.isSelected);
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, travellerCount) &&
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, travellerCount);
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, travellerCount) && option.accommodationCode === accommodationCode);
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 = ({ item, result, isSelected, languageCode, translations, onSelect, onEditOptions, showNights }) => {
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 search__results__cards--compact" }, ordered.map((result) => (React__default.createElement(TrajectNodeCard, { key: result.code, item: item, result: result, isSelected: result.code === availability?.selectedCode, languageCode: context.languageCode, translations: translations, onSelect: () => handleSelect(result.code), showNights: item.node.serviceType === ACCOMMODATION_SERVICE_TYPE })))))));
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
- let updatedEditablePackagingEntry = {
33208
- ...editablePackagingEntry,
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
- updatedEditablePackagingEntry = {
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
- payload: updatedEditablePackagingEntry
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 renderServiceNode = (item) => {
33318
- console.log('renderServiceNode', item);
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
- status === 'loading' && (React__default.createElement(Spinner, { label: node.serviceType === EXCURSION_SERVICE_TYPE ? translations.SRP.LOADING_EXCURSIONS : translations.SRP.LOADING_ACCOMMODATIONS })),
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--compact" }, [selected, ...configured].map((result) => (React__default.createElement(TrajectNodeCard, { key: result.code, item: item, 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 })))),
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 isExpanded = !!expandedNodes[line.guid];
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: () => toggleExpanded(line.guid) }, isExpanded ? translations.SRP.SHOW_LESS : `${translations.SRP.SHOW_MORE} (${others.length - 2})`)))))));
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, trajectEntry.traject.days.map((day) => {
33389
- const dayNodes = nodesByDay.get(day.order) ?? [];
33390
- if (!dayNodes.length)
33391
- return null;
33392
- return (React__default.createElement(React__default.Fragment, { key: `day-${day.order}` }, dayNodes.map((node) => {
33393
- if (node.serviceType !== ACCOMMODATION_SERVICE_TYPE && node.serviceType !== EXCURSION_SERVICE_TYPE && node.serviceType !== FLIGHT_SERVICE_TYPE)
33394
- return null;
33395
- // if (node.trajectNodeType === CONNECTING_TRAJECT_NODE_TYPE) return null;
33396
- // if (node.trajectNodeType !== REGULAR_TRAJECT_NODE_TYPE) return renderContextNode(node);
33397
- if (node.trajectNodeType !== REGULAR_TRAJECT_NODE_TYPE)
33398
- return null;
33399
- const item = lineByNodeId.get(node.nodeId);
33400
- // if (!item) return renderContextNode(node);
33401
- if (!item)
33402
- return null;
33403
- return isTrajectFlightNode(node) ? renderFlightNode(item) : renderServiceNode(item);
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 showNonPreferredItems = !!context?.showNonPreferredItems;
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
- const candidates = getNodeCandidates(item.node);
33446
- const attempts = await Promise.all(candidates.map(async (candidate) => {
33447
- try {
33448
- const attempt = await search(config, buildTrajectNodeRequest(item, searchContext, candidate.code), controller.signal);
33449
- return (attempt ?? []).find((result) => matchesCode(result, candidate.code)) ?? null;
33450
- }
33451
- catch (error) {
33452
- if (!controller.signal.aborted)
33453
- console.error('Traject hotel unavailable', candidate.code, error);
33454
- return null;
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
- const configuredResults = attempts
33458
- .filter((x) => !!x)
33459
- .filter((x, i, all) => all.findIndex((y) => y.code === x.code) === i);
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
- let preferredUnavailable = !!preferredCandidate && !configuredResults.some((x) => matchesCode(x, preferredCandidate.code));
33462
- const destinationResults = showNonPreferredItems
33463
- ? ((await search(config, buildTrajectNodeRequest(item, searchContext, ''), controller.signal)) ?? []).filter((x) => !configuredResults.some((configured) => configured.code === x.code))
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 searchable = nodesWithLines.filter((x) => isSearchableTrajectNode(x.node));
33518
- const flightNodes = nodesWithLines.filter((x) => isTrajectFlightNode(x.node));
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, showNonPreferredItems]);
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
- // The traject view renders its own day-by-day column, so the generated itinerary is unused.
34333
- if (context.trajectEntry)
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({