@qite/tide-components 1.0.14-preview.5 → 1.0.14-preview.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/build-cjs/index.js +725 -523
- package/build/build-cjs/src/search-results/components/itinerary/index.d.ts +0 -1
- package/build/build-cjs/src/search-results/components/itinerary/itinerary-utils.d.ts +0 -1
- package/build/build-cjs/src/search-results/store/search-results-slice.d.ts +11 -2
- package/build/build-cjs/src/search-results/utils/booked-entry-utils.d.ts +1 -0
- package/build/build-cjs/src/search-results/utils/traject-utils.d.ts +11 -2
- package/build/build-esm/index.js +725 -523
- package/build/build-esm/src/search-results/components/itinerary/index.d.ts +0 -1
- package/build/build-esm/src/search-results/components/itinerary/itinerary-utils.d.ts +0 -1
- package/build/build-esm/src/search-results/store/search-results-slice.d.ts +11 -2
- package/build/build-esm/src/search-results/utils/booked-entry-utils.d.ts +1 -0
- package/build/build-esm/src/search-results/utils/traject-utils.d.ts +11 -2
- package/package.json +2 -2
package/build/build-cjs/index.js
CHANGED
|
@@ -490,7 +490,10 @@ var SRP$f = {
|
|
|
490
490
|
LOADING_ACCOMMODATIONS: "جارٍ تحميل الإقامات...",
|
|
491
491
|
LOADING_ITINERARY: "جارٍ تحميل خط سير الرحلة...",
|
|
492
492
|
LOADING_OPTIONS: "جارٍ تحميل الخيارات...",
|
|
493
|
-
BOOK_AND_PAY: "احجز وادفع"
|
|
493
|
+
BOOK_AND_PAY: "احجز وادفع",
|
|
494
|
+
PRICE_DETAILS_FAILED: "تعذر حساب السعر.",
|
|
495
|
+
TRY_AGAIN: "حاول مرة أخرى",
|
|
496
|
+
BACK_TO_SERVICES: "العودة إلى الخدمات"
|
|
494
497
|
};
|
|
495
498
|
var ITINERARY$f = {
|
|
496
499
|
DAY: "اليوم",
|
|
@@ -997,7 +1000,10 @@ var SRP$e = {
|
|
|
997
1000
|
LOADING_ACCOMMODATIONS: "Indlæser indkvarteringer...",
|
|
998
1001
|
LOADING_ITINERARY: "Indlæser rejseplan...",
|
|
999
1002
|
LOADING_OPTIONS: "Indlæser muligheder...",
|
|
1000
|
-
BOOK_AND_PAY: "Book og betal"
|
|
1003
|
+
BOOK_AND_PAY: "Book og betal",
|
|
1004
|
+
PRICE_DETAILS_FAILED: "Prisen kunne ikke beregnes.",
|
|
1005
|
+
TRY_AGAIN: "Prøv igen",
|
|
1006
|
+
BACK_TO_SERVICES: "Tilbage til ydelser"
|
|
1001
1007
|
};
|
|
1002
1008
|
var ITINERARY$e = {
|
|
1003
1009
|
DAY: "Dag",
|
|
@@ -1504,7 +1510,10 @@ var SRP$d = {
|
|
|
1504
1510
|
LOADING_ACCOMMODATIONS: "Lade Unterkünfte...",
|
|
1505
1511
|
LOADING_ITINERARY: "Lade Reiseverlauf...",
|
|
1506
1512
|
LOADING_OPTIONS: "Lade Optionen...",
|
|
1507
|
-
BOOK_AND_PAY: "Buchen und bezahlen"
|
|
1513
|
+
BOOK_AND_PAY: "Buchen und bezahlen",
|
|
1514
|
+
PRICE_DETAILS_FAILED: "Der Preis konnte nicht berechnet werden.",
|
|
1515
|
+
TRY_AGAIN: "Erneut versuchen",
|
|
1516
|
+
BACK_TO_SERVICES: "Zurück zu den Leistungen"
|
|
1508
1517
|
};
|
|
1509
1518
|
var ITINERARY$d = {
|
|
1510
1519
|
DAY: "Tag",
|
|
@@ -2015,7 +2024,10 @@ var SRP$c = {
|
|
|
2015
2024
|
LOADING_ACCOMMODATIONS: "Loading accommodations...",
|
|
2016
2025
|
LOADING_ITINERARY: "Loading itinerary...",
|
|
2017
2026
|
LOADING_OPTIONS: "Loading options...",
|
|
2018
|
-
BOOK_AND_PAY: "Book and pay"
|
|
2027
|
+
BOOK_AND_PAY: "Book and pay",
|
|
2028
|
+
PRICE_DETAILS_FAILED: "The price could not be calculated.",
|
|
2029
|
+
TRY_AGAIN: "Try again",
|
|
2030
|
+
BACK_TO_SERVICES: "Back to services"
|
|
2019
2031
|
};
|
|
2020
2032
|
var ITINERARY$c = {
|
|
2021
2033
|
DAY: "Day",
|
|
@@ -2522,7 +2534,10 @@ var SRP$b = {
|
|
|
2522
2534
|
LOADING_ACCOMMODATIONS: "Cargando alojamientos...",
|
|
2523
2535
|
LOADING_ITINERARY: "Cargando itinerario...",
|
|
2524
2536
|
LOADING_OPTIONS: "Cargando opciones...",
|
|
2525
|
-
BOOK_AND_PAY: "Reservar y pagar"
|
|
2537
|
+
BOOK_AND_PAY: "Reservar y pagar",
|
|
2538
|
+
PRICE_DETAILS_FAILED: "No se ha podido calcular el precio.",
|
|
2539
|
+
TRY_AGAIN: "Volver a intentarlo",
|
|
2540
|
+
BACK_TO_SERVICES: "Volver a los servicios"
|
|
2526
2541
|
};
|
|
2527
2542
|
var ITINERARY$b = {
|
|
2528
2543
|
DAY: "Día",
|
|
@@ -3033,7 +3048,10 @@ var SRP$a = {
|
|
|
3033
3048
|
LOADING_ACCOMMODATIONS: "Chargement des hébergements...",
|
|
3034
3049
|
LOADING_ITINERARY: "Chargement de l’itinéraire...",
|
|
3035
3050
|
LOADING_OPTIONS: "Chargement des options...",
|
|
3036
|
-
BOOK_AND_PAY: "Réserver et payer"
|
|
3051
|
+
BOOK_AND_PAY: "Réserver et payer",
|
|
3052
|
+
PRICE_DETAILS_FAILED: "Le prix n’a pas pu être calculé.",
|
|
3053
|
+
TRY_AGAIN: "Réessayer",
|
|
3054
|
+
BACK_TO_SERVICES: "Retour aux services"
|
|
3037
3055
|
};
|
|
3038
3056
|
var ITINERARY$a = {
|
|
3039
3057
|
DAY: "Jour",
|
|
@@ -3540,7 +3558,10 @@ var SRP$9 = {
|
|
|
3540
3558
|
LOADING_ACCOMMODATIONS: "Chargement des hébergements...",
|
|
3541
3559
|
LOADING_ITINERARY: "Chargement de l’itinéraire...",
|
|
3542
3560
|
LOADING_OPTIONS: "Chargement des options...",
|
|
3543
|
-
BOOK_AND_PAY: "Réserver et payer"
|
|
3561
|
+
BOOK_AND_PAY: "Réserver et payer",
|
|
3562
|
+
PRICE_DETAILS_FAILED: "Le prix n’a pas pu être calculé.",
|
|
3563
|
+
TRY_AGAIN: "Réessayer",
|
|
3564
|
+
BACK_TO_SERVICES: "Retour aux services"
|
|
3544
3565
|
};
|
|
3545
3566
|
var ITINERARY$9 = {
|
|
3546
3567
|
DAY: "Jour",
|
|
@@ -4047,7 +4068,10 @@ var SRP$8 = {
|
|
|
4047
4068
|
LOADING_ACCOMMODATIONS: "Hleðst inn í gistingar...",
|
|
4048
4069
|
LOADING_ITINERARY: "Hleðst inn í ferðaráætlun...",
|
|
4049
4070
|
LOADING_OPTIONS: "Hleðst inn í valkosti...",
|
|
4050
|
-
BOOK_AND_PAY: "Panta og greiða"
|
|
4071
|
+
BOOK_AND_PAY: "Panta og greiða",
|
|
4072
|
+
PRICE_DETAILS_FAILED: "Ekki tókst að reikna verðið.",
|
|
4073
|
+
TRY_AGAIN: "Reyna aftur",
|
|
4074
|
+
BACK_TO_SERVICES: "Aftur í þjónustu"
|
|
4051
4075
|
};
|
|
4052
4076
|
var ITINERARY$8 = {
|
|
4053
4077
|
DAY: "Dagur",
|
|
@@ -4554,7 +4578,10 @@ var SRP$7 = {
|
|
|
4554
4578
|
LOADING_ACCOMMODATIONS: "Caricamento degli alloggi...",
|
|
4555
4579
|
LOADING_ITINERARY: "Caricamento dell'itinerario...",
|
|
4556
4580
|
LOADING_OPTIONS: "Caricamento delle opzioni...",
|
|
4557
|
-
BOOK_AND_PAY: "Prenota e paga"
|
|
4581
|
+
BOOK_AND_PAY: "Prenota e paga",
|
|
4582
|
+
PRICE_DETAILS_FAILED: "Non è stato possibile calcolare il prezzo.",
|
|
4583
|
+
TRY_AGAIN: "Riprova",
|
|
4584
|
+
BACK_TO_SERVICES: "Torna ai servizi"
|
|
4558
4585
|
};
|
|
4559
4586
|
var ITINERARY$7 = {
|
|
4560
4587
|
DAY: "Giorno",
|
|
@@ -5065,7 +5092,10 @@ var SRP$6 = {
|
|
|
5065
5092
|
LOADING_ACCOMMODATIONS: "Accommodaties laden...",
|
|
5066
5093
|
LOADING_ITINERARY: "Reisplan laden...",
|
|
5067
5094
|
LOADING_OPTIONS: "Opties laden...",
|
|
5068
|
-
BOOK_AND_PAY: "Boek en betaal"
|
|
5095
|
+
BOOK_AND_PAY: "Boek en betaal",
|
|
5096
|
+
PRICE_DETAILS_FAILED: "De prijs kon niet berekend worden.",
|
|
5097
|
+
TRY_AGAIN: "Opnieuw proberen",
|
|
5098
|
+
BACK_TO_SERVICES: "Terug naar diensten"
|
|
5069
5099
|
};
|
|
5070
5100
|
var ITINERARY$6 = {
|
|
5071
5101
|
DAY: "Dag",
|
|
@@ -5572,7 +5602,10 @@ var SRP$5 = {
|
|
|
5572
5602
|
LOADING_ACCOMMODATIONS: "Accommodaties laden...",
|
|
5573
5603
|
LOADING_ITINERARY: "Reisplan laden...",
|
|
5574
5604
|
LOADING_OPTIONS: "Opties laden...",
|
|
5575
|
-
BOOK_AND_PAY: "Boeken en betalen"
|
|
5605
|
+
BOOK_AND_PAY: "Boeken en betalen",
|
|
5606
|
+
PRICE_DETAILS_FAILED: "De prijs kon niet worden berekend.",
|
|
5607
|
+
TRY_AGAIN: "Opnieuw proberen",
|
|
5608
|
+
BACK_TO_SERVICES: "Terug naar diensten"
|
|
5576
5609
|
};
|
|
5577
5610
|
var ITINERARY$5 = {
|
|
5578
5611
|
DAY: "Dag",
|
|
@@ -6079,7 +6112,10 @@ var SRP$4 = {
|
|
|
6079
6112
|
LOADING_ACCOMMODATIONS: "Laster overnattinger...",
|
|
6080
6113
|
LOADING_ITINERARY: "Laster reiserute...",
|
|
6081
6114
|
LOADING_OPTIONS: "Laster alternativer...",
|
|
6082
|
-
BOOK_AND_PAY: "Bestill og betal"
|
|
6115
|
+
BOOK_AND_PAY: "Bestill og betal",
|
|
6116
|
+
PRICE_DETAILS_FAILED: "Prisen kunne ikke beregnes.",
|
|
6117
|
+
TRY_AGAIN: "Prøv igjen",
|
|
6118
|
+
BACK_TO_SERVICES: "Tilbake til tjenester"
|
|
6083
6119
|
};
|
|
6084
6120
|
var ITINERARY$4 = {
|
|
6085
6121
|
DAY: "Dag",
|
|
@@ -6586,7 +6622,10 @@ var SRP$3 = {
|
|
|
6586
6622
|
LOADING_ACCOMMODATIONS: "Ładowanie zakwaterowań...",
|
|
6587
6623
|
LOADING_ITINERARY: "Ładowanie planu podróży...",
|
|
6588
6624
|
LOADING_OPTIONS: "Ładowanie opcji...",
|
|
6589
|
-
BOOK_AND_PAY: "Zarezerwuj i zapłać"
|
|
6625
|
+
BOOK_AND_PAY: "Zarezerwuj i zapłać",
|
|
6626
|
+
PRICE_DETAILS_FAILED: "Nie udało się obliczyć ceny.",
|
|
6627
|
+
TRY_AGAIN: "Spróbuj ponownie",
|
|
6628
|
+
BACK_TO_SERVICES: "Wróć do usług"
|
|
6590
6629
|
};
|
|
6591
6630
|
var ITINERARY$3 = {
|
|
6592
6631
|
DAY: "Dzień",
|
|
@@ -7093,7 +7132,10 @@ var SRP$2 = {
|
|
|
7093
7132
|
LOADING_ACCOMMODATIONS: "A carregar alojamentos...",
|
|
7094
7133
|
LOADING_ITINERARY: "A carregar itinerário...",
|
|
7095
7134
|
LOADING_OPTIONS: "A carregar opções...",
|
|
7096
|
-
BOOK_AND_PAY: "Reservar e pagar"
|
|
7135
|
+
BOOK_AND_PAY: "Reservar e pagar",
|
|
7136
|
+
PRICE_DETAILS_FAILED: "Não foi possível calcular o preço.",
|
|
7137
|
+
TRY_AGAIN: "Tentar novamente",
|
|
7138
|
+
BACK_TO_SERVICES: "Voltar aos serviços"
|
|
7097
7139
|
};
|
|
7098
7140
|
var ITINERARY$2 = {
|
|
7099
7141
|
DAY: "Dia",
|
|
@@ -7600,7 +7642,10 @@ var SRP$1 = {
|
|
|
7600
7642
|
LOADING_ACCOMMODATIONS: "Laddar boenden...",
|
|
7601
7643
|
LOADING_ITINERARY: "Laddar resplan...",
|
|
7602
7644
|
LOADING_OPTIONS: "Laddar alternativ...",
|
|
7603
|
-
BOOK_AND_PAY: "Boka och betala"
|
|
7645
|
+
BOOK_AND_PAY: "Boka och betala",
|
|
7646
|
+
PRICE_DETAILS_FAILED: "Priset kunde inte beräknas.",
|
|
7647
|
+
TRY_AGAIN: "Försök igen",
|
|
7648
|
+
BACK_TO_SERVICES: "Tillbaka till tjänster"
|
|
7604
7649
|
};
|
|
7605
7650
|
var ITINERARY$1 = {
|
|
7606
7651
|
DAY: "Dag",
|
|
@@ -8107,7 +8152,10 @@ var SRP = {
|
|
|
8107
8152
|
LOADING_ACCOMMODATIONS: "宿泊施設を読み込み中...",
|
|
8108
8153
|
LOADING_ITINERARY: "旅程を読み込み中...",
|
|
8109
8154
|
LOADING_OPTIONS: "オプションを読み込み中...",
|
|
8110
|
-
BOOK_AND_PAY: "予約して支払う"
|
|
8155
|
+
BOOK_AND_PAY: "予約して支払う",
|
|
8156
|
+
PRICE_DETAILS_FAILED: "料金を計算できませんでした。",
|
|
8157
|
+
TRY_AGAIN: "再試行",
|
|
8158
|
+
BACK_TO_SERVICES: "サービスに戻る"
|
|
8111
8159
|
};
|
|
8112
8160
|
var ITINERARY = {
|
|
8113
8161
|
DAY: "日",
|
|
@@ -29235,6 +29283,8 @@ const initialState$1 = {
|
|
|
29235
29283
|
selectedFlightDetails: null,
|
|
29236
29284
|
bookingPackageDetails: null,
|
|
29237
29285
|
priceDetails: null,
|
|
29286
|
+
priceDetailsFailed: false,
|
|
29287
|
+
priceDetailsRequest: 0,
|
|
29238
29288
|
isLoading: false,
|
|
29239
29289
|
flightsLoading: false,
|
|
29240
29290
|
selectedSortType: null,
|
|
@@ -29395,6 +29445,12 @@ const searchResultsSlice = toolkit.createSlice({
|
|
|
29395
29445
|
setPriceDetails(state, action) {
|
|
29396
29446
|
state.priceDetails = action.payload;
|
|
29397
29447
|
},
|
|
29448
|
+
setPriceDetailsFailed(state, action) {
|
|
29449
|
+
state.priceDetailsFailed = action.payload;
|
|
29450
|
+
},
|
|
29451
|
+
retryPriceDetails(state) {
|
|
29452
|
+
state.priceDetailsRequest += 1;
|
|
29453
|
+
},
|
|
29398
29454
|
setItinerary(state, action) {
|
|
29399
29455
|
state.itinerary = action.payload;
|
|
29400
29456
|
},
|
|
@@ -29554,11 +29610,12 @@ const searchResultsSlice = toolkit.createSlice({
|
|
|
29554
29610
|
flight.selectedGuid = null;
|
|
29555
29611
|
},
|
|
29556
29612
|
setTrajectTransferLoading(state, action) {
|
|
29557
|
-
|
|
29613
|
+
const { nodeId, key } = action.payload;
|
|
29614
|
+
state.trajectTransferAvailability[nodeId] = { nodeId, key, status: 'loading', result: null };
|
|
29558
29615
|
},
|
|
29559
29616
|
setTrajectTransferAvailability(state, action) {
|
|
29560
|
-
const { nodeId, result } = action.payload;
|
|
29561
|
-
state.trajectTransferAvailability[nodeId] = { nodeId, status: result ? 'resolved' : 'unavailable', result };
|
|
29617
|
+
const { nodeId, key, result } = action.payload;
|
|
29618
|
+
state.trajectTransferAvailability[nodeId] = { nodeId, key, status: result ? 'resolved' : 'unavailable', result };
|
|
29562
29619
|
},
|
|
29563
29620
|
setTrajectDaySuggestionsLoading(state, action) {
|
|
29564
29621
|
const dayOrder = action.payload;
|
|
@@ -29605,7 +29662,7 @@ const searchResultsSlice = toolkit.createSlice({
|
|
|
29605
29662
|
}
|
|
29606
29663
|
}
|
|
29607
29664
|
});
|
|
29608
|
-
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, clearTrajectNodeSelection, setTrajectResolving, setTrajectItineraryView, setTrajectFlightLoading, setTrajectFlightAvailability, setTrajectFlightSelection, clearTrajectFlightSelection, setTrajectTransferLoading, setTrajectTransferAvailability, setTrajectEditLineGuid, setTrajectActivitiesDayOrder, setTrajectDaySuggestionsLoading, setTrajectDaySuggestions, addEditableEntryLines, upsertEditableEntryLines, removeTrajectNode, updateTrajectNodeResult, resetTrajectAvailability, updateEditableEntryLine, removeEditableEntryLines } = searchResultsSlice.actions;
|
|
29665
|
+
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, setPriceDetailsFailed, retryPriceDetails, setItinerary, setSelectedOutwardKey, setSelectedReturnKey, resetFlightSelection, setExcursionSearchParams, setSelectedExcursionSearchResult, confirmExcursionForDay, removeConfirmedExcursionForDay, clearConfirmedExcursionsForDay, setBookPackagingEntry, setCurrentStep, setBookingNumber, setTrajectNodeLoading, setTrajectNodeAvailability, setTrajectNodeSelection, clearTrajectNodeSelection, setTrajectResolving, setTrajectItineraryView, setTrajectFlightLoading, setTrajectFlightAvailability, setTrajectFlightSelection, clearTrajectFlightSelection, setTrajectTransferLoading, setTrajectTransferAvailability, setTrajectEditLineGuid, setTrajectActivitiesDayOrder, setTrajectDaySuggestionsLoading, setTrajectDaySuggestions, addEditableEntryLines, upsertEditableEntryLines, removeTrajectNode, updateTrajectNodeResult, resetTrajectAvailability, updateEditableEntryLine, removeEditableEntryLines } = searchResultsSlice.actions;
|
|
29609
29666
|
var searchResultsReducer = searchResultsSlice.reducer;
|
|
29610
29667
|
|
|
29611
29668
|
const ItemPicker = ({ items, selection, selectedSortByType, label, placeholder, classModifier, onPick, valueFormatter }) => {
|
|
@@ -29770,211 +29827,9 @@ const getPackagingRequestRoomsFromBookingRooms = (rooms) => {
|
|
|
29770
29827
|
});
|
|
29771
29828
|
};
|
|
29772
29829
|
|
|
29773
|
-
// Administrative service lines (fees, insurances, reductions, ...) live on the entry but say
|
|
29774
|
-
// nothing about the journey itself, so the itinerary never draws them.
|
|
29775
|
-
const HIDDEN_ITINERARY_SERVICE_TYPES = new Set([
|
|
29776
|
-
build.ServiceType.insurance,
|
|
29777
|
-
build.ServiceType.extraService,
|
|
29778
|
-
build.ServiceType.serviceFee,
|
|
29779
|
-
build.ServiceType.gift,
|
|
29780
|
-
build.ServiceType.reduction,
|
|
29781
|
-
build.ServiceType.fee,
|
|
29782
|
-
build.ServiceType.visum,
|
|
29783
|
-
build.ServiceType.voucher,
|
|
29784
|
-
build.ServiceType.specialServices
|
|
29785
|
-
]);
|
|
29786
|
-
// An inactive member of a line group is an alternative the traveller did not pick — not travelled.
|
|
29787
|
-
const isMeaningfulItineraryLine = (line) => {
|
|
29788
|
-
return isActiveEntryLine(line) && !HIDDEN_ITINERARY_SERVICE_TYPES.has(line.serviceType ?? -1);
|
|
29789
|
-
};
|
|
29790
|
-
const getFlightLines = (flight) => {
|
|
29791
|
-
return flight?.flightInformation?.flightLines ?? [];
|
|
29792
|
-
};
|
|
29793
|
-
const getDepartureTime = (flight) => {
|
|
29794
|
-
const firstLine = lodash.first(getFlightLines(flight));
|
|
29795
|
-
if (!firstLine?.departureTime)
|
|
29796
|
-
return '';
|
|
29797
|
-
return firstLine.departureTime.slice(0, 5);
|
|
29798
|
-
};
|
|
29799
|
-
const getArrivalTime = (flight) => {
|
|
29800
|
-
const lastLine = lodash.last(getFlightLines(flight));
|
|
29801
|
-
if (!lastLine?.arrivalTime)
|
|
29802
|
-
return '';
|
|
29803
|
-
return lastLine.arrivalTime.slice(0, 5);
|
|
29804
|
-
};
|
|
29805
|
-
const getDuration = (flight) => {
|
|
29806
|
-
const lines = getFlightLines(flight);
|
|
29807
|
-
if (!lines.length)
|
|
29808
|
-
return '';
|
|
29809
|
-
const totalTicks = lines.reduce((sum, line) => sum + (line.durationInTicks ?? 0), 0) || 0;
|
|
29810
|
-
if (!totalTicks)
|
|
29811
|
-
return '';
|
|
29812
|
-
const seconds = totalTicks / 10_000_000;
|
|
29813
|
-
const hours = Math.floor(seconds / 3600);
|
|
29814
|
-
const minutes = Math.floor((seconds % 3600) / 60);
|
|
29815
|
-
return `${hours}h ${minutes.toString().padStart(2, '0')}m`;
|
|
29816
|
-
};
|
|
29817
|
-
const numberOfNights = (segment) => {
|
|
29818
|
-
return dateFns.differenceInCalendarDays(new Date(segment.to), new Date(segment.from));
|
|
29819
|
-
};
|
|
29820
|
-
const CHARTER_SERVICE_TYPE = 10;
|
|
29821
|
-
const CRUISE_SERVICE_TYPE = 11;
|
|
29822
|
-
const FERRY_SERVICE_TYPE = 12;
|
|
29823
|
-
const TRANSFER_SERVICE_TYPE$1 = 13;
|
|
29824
|
-
const BUS_SERVICE_TYPE = 14;
|
|
29825
|
-
const CAR_SERVICE_TYPE = 17;
|
|
29826
|
-
const CAMPER_SERVICE_TYPE = 18;
|
|
29827
|
-
const TRAIN_SERVICE_TYPE = 20;
|
|
29828
|
-
const RENTAL_SERVICE_TYPE = 22;
|
|
29829
|
-
const RESTAURANT_SERVICE_TYPE = 23;
|
|
29830
|
-
const EVENT_SERVICE_TYPE = 24;
|
|
29831
|
-
const TRAINING_SERVICE_TYPE = 25;
|
|
29832
|
-
/** Icon per service type, so every node on the itinerary reads at a glance. */
|
|
29833
|
-
const getServiceTypeIcon = (serviceType) => {
|
|
29834
|
-
switch (serviceType) {
|
|
29835
|
-
case ACCOMMODATION_SERVICE_TYPE:
|
|
29836
|
-
return 'ui-hotel';
|
|
29837
|
-
case EXCURSION_SERVICE_TYPE:
|
|
29838
|
-
return 'ui-excursion';
|
|
29839
|
-
case FLIGHT_SERVICE_TYPE:
|
|
29840
|
-
case CHARTER_SERVICE_TYPE:
|
|
29841
|
-
return 'ui-plane';
|
|
29842
|
-
case RESTAURANT_SERVICE_TYPE:
|
|
29843
|
-
return 'ui-utensils';
|
|
29844
|
-
case CRUISE_SERVICE_TYPE:
|
|
29845
|
-
case FERRY_SERVICE_TYPE:
|
|
29846
|
-
return 'ui-ship';
|
|
29847
|
-
case CAR_SERVICE_TYPE:
|
|
29848
|
-
case CAMPER_SERVICE_TYPE:
|
|
29849
|
-
case RENTAL_SERVICE_TYPE:
|
|
29850
|
-
return 'ui-car';
|
|
29851
|
-
case BUS_SERVICE_TYPE:
|
|
29852
|
-
return 'ui-bus';
|
|
29853
|
-
case TRAIN_SERVICE_TYPE:
|
|
29854
|
-
return 'ui-train';
|
|
29855
|
-
case TRANSFER_SERVICE_TYPE$1:
|
|
29856
|
-
return 'ui-transfer';
|
|
29857
|
-
case EVENT_SERVICE_TYPE:
|
|
29858
|
-
return 'ui-ticket';
|
|
29859
|
-
case TRAINING_SERVICE_TYPE:
|
|
29860
|
-
return 'ui-group';
|
|
29861
|
-
default:
|
|
29862
|
-
return 'ui-location';
|
|
29863
|
-
}
|
|
29864
|
-
};
|
|
29865
|
-
const getServiceTypeBadgeModifier = (serviceType) => {
|
|
29866
|
-
switch (serviceType) {
|
|
29867
|
-
case FLIGHT_SERVICE_TYPE:
|
|
29868
|
-
case CHARTER_SERVICE_TYPE:
|
|
29869
|
-
case CRUISE_SERVICE_TYPE:
|
|
29870
|
-
case FERRY_SERVICE_TYPE:
|
|
29871
|
-
return 'flight';
|
|
29872
|
-
case ACCOMMODATION_SERVICE_TYPE:
|
|
29873
|
-
return 'hotel';
|
|
29874
|
-
case EXCURSION_SERVICE_TYPE:
|
|
29875
|
-
case EVENT_SERVICE_TYPE:
|
|
29876
|
-
return 'excursion';
|
|
29877
|
-
case RESTAURANT_SERVICE_TYPE:
|
|
29878
|
-
return 'restaurant';
|
|
29879
|
-
default:
|
|
29880
|
-
return '';
|
|
29881
|
-
}
|
|
29882
|
-
};
|
|
29883
|
-
// DurationType, as the SDK numbers it.
|
|
29884
|
-
const DURATION_HOURS = 0;
|
|
29885
|
-
const DURATION_DAYS = 1;
|
|
29886
|
-
const DURATION_NIGHTS = 2;
|
|
29887
|
-
const DURATION_MINUTES = 3;
|
|
29888
|
-
/**
|
|
29889
|
-
* How long a node takes, in the same shape the flight legs are written in — an hour count only
|
|
29890
|
-
* once there is one, so a short drive reads as plain minutes.
|
|
29891
|
-
*/
|
|
29892
|
-
const getTrajectNodeDuration = (node) => {
|
|
29893
|
-
const minutes = toMinutes(node);
|
|
29894
|
-
if (!minutes)
|
|
29895
|
-
return '';
|
|
29896
|
-
const hours = Math.floor(minutes / 60);
|
|
29897
|
-
const rest = minutes % 60;
|
|
29898
|
-
return hours ? `${hours}h ${rest.toString().padStart(2, '0')}m` : `${rest} min`;
|
|
29899
|
-
};
|
|
29900
|
-
const toMinutes = (node) => {
|
|
29901
|
-
const duration = node.duration ?? 0;
|
|
29902
|
-
if (duration <= 0)
|
|
29903
|
-
return 0;
|
|
29904
|
-
switch (node.durationType) {
|
|
29905
|
-
case DURATION_MINUTES:
|
|
29906
|
-
return duration;
|
|
29907
|
-
case DURATION_HOURS:
|
|
29908
|
-
return duration * 60;
|
|
29909
|
-
case DURATION_DAYS:
|
|
29910
|
-
case DURATION_NIGHTS:
|
|
29911
|
-
return duration * 24 * 60;
|
|
29912
|
-
default:
|
|
29913
|
-
return 0;
|
|
29914
|
-
}
|
|
29915
|
-
};
|
|
29916
|
-
// RouteTransportType, as the SDK numbers it. It says how a leg is travelled, which is not the
|
|
29917
|
-
// same question as what was sold: cycling and walking have no service type of their own.
|
|
29918
|
-
const TRANSPORT_PRIVATE = 1;
|
|
29919
|
-
const TRANSPORT_PUBLIC = 2;
|
|
29920
|
-
const TRANSPORT_RENTAL_CAR = 3;
|
|
29921
|
-
const TRANSPORT_BUS = 4;
|
|
29922
|
-
const TRANSPORT_CAMPER = 5;
|
|
29923
|
-
const TRANSPORT_CAR = 6;
|
|
29924
|
-
const TRANSPORT_CHARTER = 7;
|
|
29925
|
-
const TRANSPORT_CRUISE = 8;
|
|
29926
|
-
const TRANSPORT_FERRY = 9;
|
|
29927
|
-
const TRANSPORT_FLIGHT = 10;
|
|
29928
|
-
const TRANSPORT_TAXI = 11;
|
|
29929
|
-
const TRANSPORT_TRAIN = 12;
|
|
29930
|
-
const TRANSPORT_TRANSFER = 13;
|
|
29931
|
-
const TRANSPORT_WALKING = 15;
|
|
29932
|
-
const TRANSPORT_CYCLING = 16;
|
|
29933
|
-
/** Icon per way of travelling, where the route records one. */
|
|
29934
|
-
const getTransportTypeIcon = (transportType) => {
|
|
29935
|
-
switch (transportType) {
|
|
29936
|
-
case TRANSPORT_WALKING:
|
|
29937
|
-
return 'ui-walking';
|
|
29938
|
-
case TRANSPORT_CYCLING:
|
|
29939
|
-
return 'ui-cycling';
|
|
29940
|
-
case TRANSPORT_PRIVATE:
|
|
29941
|
-
case TRANSPORT_RENTAL_CAR:
|
|
29942
|
-
case TRANSPORT_CAMPER:
|
|
29943
|
-
case TRANSPORT_CAR:
|
|
29944
|
-
return 'ui-car';
|
|
29945
|
-
case TRANSPORT_TAXI:
|
|
29946
|
-
case TRANSPORT_TRANSFER:
|
|
29947
|
-
return 'ui-transfer';
|
|
29948
|
-
case TRANSPORT_PUBLIC:
|
|
29949
|
-
case TRANSPORT_BUS:
|
|
29950
|
-
return 'ui-bus';
|
|
29951
|
-
case TRANSPORT_TRAIN:
|
|
29952
|
-
return 'ui-train';
|
|
29953
|
-
case TRANSPORT_CRUISE:
|
|
29954
|
-
case TRANSPORT_FERRY:
|
|
29955
|
-
return 'ui-ship';
|
|
29956
|
-
case TRANSPORT_FLIGHT:
|
|
29957
|
-
case TRANSPORT_CHARTER:
|
|
29958
|
-
return 'ui-plane';
|
|
29959
|
-
default:
|
|
29960
|
-
return null;
|
|
29961
|
-
}
|
|
29962
|
-
};
|
|
29963
|
-
const POI_TRAJECT_NODE_TYPE = 2;
|
|
29964
|
-
/**
|
|
29965
|
-
* A point of interest is a place on the map rather than a service, so it gets a marker. Anything
|
|
29966
|
-
* else goes by how it is travelled where the route says so — a leg on foot or by bike is sold as
|
|
29967
|
-
* nothing, and its service type falls back to a transfer — and by what it is where it does not.
|
|
29968
|
-
*/
|
|
29969
|
-
const getTrajectNodeIcon = (node) => {
|
|
29970
|
-
if (node.trajectNodeType === POI_TRAJECT_NODE_TYPE)
|
|
29971
|
-
return 'ui-location';
|
|
29972
|
-
return getTransportTypeIcon(node.transportType) ?? getServiceTypeIcon(node.serviceType);
|
|
29973
|
-
};
|
|
29974
|
-
const getTrajectNodeBadgeModifier = (node) => node.trajectNodeType === POI_TRAJECT_NODE_TYPE ? 'poi' : getServiceTypeBadgeModifier(node.serviceType);
|
|
29975
|
-
|
|
29976
29830
|
const REGULAR_TRAJECT_NODE_TYPE = 0;
|
|
29977
29831
|
const CONNECTING_TRAJECT_NODE_TYPE = 1;
|
|
29832
|
+
const POI_TRAJECT_NODE_TYPE = 2;
|
|
29978
29833
|
const isSearchableTrajectNode = (node) => node.serviceType === ACCOMMODATION_SERVICE_TYPE || node.serviceType === EXCURSION_SERVICE_TYPE;
|
|
29979
29834
|
const isTrajectFlightNode = (node) => node.serviceType === FLIGHT_SERVICE_TYPE;
|
|
29980
29835
|
const getTrajectNodesWithLines = (trajectEntry) => {
|
|
@@ -29989,7 +29844,7 @@ const getTrajectNodesWithLines = (trajectEntry) => {
|
|
|
29989
29844
|
.filter((x) => !!x.line);
|
|
29990
29845
|
};
|
|
29991
29846
|
// Qite.Offer.Enums.ServiceType.Transfer: a traject's connecting node is booked as one.
|
|
29992
|
-
const TRANSFER_SERVICE_TYPE = 13;
|
|
29847
|
+
const TRANSFER_SERVICE_TYPE$1 = 13;
|
|
29993
29848
|
// Qite.Offer.Enums.RequirementType. A Mandatory (0) stop, and one RequiredInGroup (3) — one of the node's
|
|
29994
29849
|
// alternatives has to be booked — always keep a selection; only these two may be taken off the trip.
|
|
29995
29850
|
const OPTIONAL_REQUIREMENT_TYPE = 1;
|
|
@@ -30017,7 +29872,53 @@ const getDroppedTrajectNodeIds = (trajectEntry, entry) => {
|
|
|
30017
29872
|
return dropped;
|
|
30018
29873
|
};
|
|
30019
29874
|
const isTrajectTransferNode = (node) => node.trajectNodeType === CONNECTING_TRAJECT_NODE_TYPE && !!node.pickUp && !!node.dropOff;
|
|
30020
|
-
const
|
|
29875
|
+
const getStopLine = (node, lines) => {
|
|
29876
|
+
const guids = node.lineGuids?.length ? node.lineGuids : [node.lineGuid];
|
|
29877
|
+
const own = lines.find((line) => guids.includes(line.guid));
|
|
29878
|
+
const groupKey = own ? getLineGroupKey(own) : '';
|
|
29879
|
+
return (groupKey ? getGroupLines(lines, groupKey)[0]?.[0] : undefined) ?? own;
|
|
29880
|
+
};
|
|
29881
|
+
const getMovedStopCoordinate = (node, originalLines, lines) => {
|
|
29882
|
+
if (node.trajectNodeType !== REGULAR_TRAJECT_NODE_TYPE || !isSearchableTrajectNode(node))
|
|
29883
|
+
return null;
|
|
29884
|
+
const current = getStopLine(node, lines);
|
|
29885
|
+
if (!current || current.latitude == null || current.longitude == null)
|
|
29886
|
+
return null;
|
|
29887
|
+
if (current.productCode === getStopLine(node, originalLines)?.productCode)
|
|
29888
|
+
return null;
|
|
29889
|
+
return { name: current.productName, latitude: current.latitude, longitude: current.longitude };
|
|
29890
|
+
};
|
|
29891
|
+
const getNightStay = (nodes, lines, night) => nodes.find((node) => {
|
|
29892
|
+
if (node.trajectNodeType !== REGULAR_TRAJECT_NODE_TYPE || node.serviceType !== ACCOMMODATION_SERVICE_TYPE)
|
|
29893
|
+
return false;
|
|
29894
|
+
const line = getStopLine(node, lines);
|
|
29895
|
+
const nights = Math.max(dateFns.differenceInCalendarDays(new Date(line?.to ?? node.to), new Date(line?.from ?? node.from)), 1);
|
|
29896
|
+
return node.dayOrder <= night && night < node.dayOrder + nights;
|
|
29897
|
+
});
|
|
29898
|
+
const getTrajectTransferEndpoints = (trajectEntry, lines, connector) => {
|
|
29899
|
+
if (!isTrajectTransferNode(connector))
|
|
29900
|
+
return null;
|
|
29901
|
+
const routeNodes = trajectEntry.entry.routeNodes ?? [];
|
|
29902
|
+
const onRoute = routeNodes.find((node) => node.nodeId === connector.nodeId);
|
|
29903
|
+
const nodes = onRoute ? routeNodes : trajectEntry.nodes ?? [];
|
|
29904
|
+
const at = onRoute ?? connector;
|
|
29905
|
+
const originalLines = trajectEntry.entry.lines ?? [];
|
|
29906
|
+
const day = nodes.filter((node) => node.dayOrder === at.dayOrder).sort((a, b) => a.order - b.order);
|
|
29907
|
+
const index = day.indexOf(at);
|
|
29908
|
+
const isStop = (node) => node.trajectNodeType !== CONNECTING_TRAJECT_NODE_TYPE;
|
|
29909
|
+
const previous = day.slice(0, index).filter(isStop).pop() ?? getNightStay(nodes, lines, at.dayOrder - 1);
|
|
29910
|
+
const next = day.slice(index + 1).find(isStop) ?? getNightStay(nodes, lines, at.dayOrder);
|
|
29911
|
+
const pickUp = (previous && getMovedStopCoordinate(previous, originalLines, lines)) ?? connector.pickUp;
|
|
29912
|
+
const dropOff = (next && getMovedStopCoordinate(next, originalLines, lines)) ?? connector.dropOff;
|
|
29913
|
+
const place = (coordinate) => `${coordinate.latitude.toFixed(5)},${coordinate.longitude.toFixed(5)}`;
|
|
29914
|
+
return {
|
|
29915
|
+
pickUp,
|
|
29916
|
+
dropOff,
|
|
29917
|
+
key: `${place(pickUp)}|${place(dropOff)}|${toDateOnlyString(connector.from)}`,
|
|
29918
|
+
asDrawn: pickUp === connector.pickUp && dropOff === connector.dropOff
|
|
29919
|
+
};
|
|
29920
|
+
};
|
|
29921
|
+
const buildTrajectTransferRequest = (node, endpoints, context) => ({
|
|
30021
29922
|
transactionId: context.transactionId,
|
|
30022
29923
|
officeId: context.officeId,
|
|
30023
29924
|
agentId: context.agentId ?? null,
|
|
@@ -30025,10 +29926,11 @@ const buildTrajectTransferRequest = (node, context) => ({
|
|
|
30025
29926
|
searchConfigurationId: context.searchConfigurationId,
|
|
30026
29927
|
language: context.language,
|
|
30027
29928
|
date: node.from,
|
|
30028
|
-
pickUp:
|
|
30029
|
-
dropOff:
|
|
29929
|
+
pickUp: endpoints.pickUp,
|
|
29930
|
+
dropOff: endpoints.dropOff,
|
|
30030
29931
|
travellers: context.rooms.flatMap((room) => room.travellers)
|
|
30031
29932
|
});
|
|
29933
|
+
const getTrajectTransferLines = (node, lines) => lines.filter((line) => line.trajectNodeId === node.nodeId || (!!node.lineGuids?.length && node.lineGuids.includes(line.guid)));
|
|
30032
29934
|
const buildTrajectTransferLine = (node, result) => {
|
|
30033
29935
|
const option = result.rooms?.[0]?.options?.find((x) => x.isSelected) ?? result.rooms?.[0]?.options?.[0];
|
|
30034
29936
|
if (!option?.guid)
|
|
@@ -30042,7 +29944,7 @@ const buildTrajectTransferLine = (node, result) => {
|
|
|
30042
29944
|
from: result.fromDate ?? node.from,
|
|
30043
29945
|
to: result.toDate ?? node.to,
|
|
30044
29946
|
moment: node.moment ?? '',
|
|
30045
|
-
serviceType: TRANSFER_SERVICE_TYPE,
|
|
29947
|
+
serviceType: TRANSFER_SERVICE_TYPE$1,
|
|
30046
29948
|
productName: result.name,
|
|
30047
29949
|
productCode: result.code,
|
|
30048
29950
|
accommodationName: option.accommodationName,
|
|
@@ -30417,7 +30319,15 @@ const withCachedEntryLineGuids = (entry, trajectEntry, nodeAvailability, flightA
|
|
|
30417
30319
|
const bookedMode = !!trajectEntry.entry.magicLink;
|
|
30418
30320
|
const editableByGuid = new Map((entry.lines ?? []).map((line) => [line.guid, line]));
|
|
30419
30321
|
const keepsOwnGuid = (guid) => bookedMode && !editableByGuid.get(guid)?.isChanged;
|
|
30322
|
+
// A swapped line tells which stop of the dossier's route it replaces, so saving links the route to it.
|
|
30323
|
+
const routeNodeIds = new Map();
|
|
30324
|
+
if (bookedMode)
|
|
30325
|
+
(trajectEntry.entry.routeNodes ?? []).forEach((node) => (node.lineGuids ?? []).forEach((guid) => routeNodeIds.set(guid, node.nodeId)));
|
|
30326
|
+
const trajectNodeIds = new Map();
|
|
30420
30327
|
getTrajectNodesWithLines(trajectEntry).forEach((item) => {
|
|
30328
|
+
const routeNodeId = item.lines.map((line) => routeNodeIds.get(line.guid)).find((id) => id !== undefined);
|
|
30329
|
+
if (routeNodeId !== undefined)
|
|
30330
|
+
item.lines.filter((line) => !keepsOwnGuid(line.guid)).forEach((line) => trajectNodeIds.set(line.guid, routeNodeId));
|
|
30421
30331
|
if (isTrajectFlightNode(item.node)) {
|
|
30422
30332
|
if (keepsOwnGuid(item.line.guid))
|
|
30423
30333
|
return;
|
|
@@ -30440,14 +30350,15 @@ const withCachedEntryLineGuids = (entry, trajectEntry, nodeAvailability, flightA
|
|
|
30440
30350
|
cachedGuids.set(line.guid, guid);
|
|
30441
30351
|
});
|
|
30442
30352
|
});
|
|
30443
|
-
if (!cachedGuids.size)
|
|
30353
|
+
if (!cachedGuids.size && !trajectNodeIds.size)
|
|
30444
30354
|
return entry;
|
|
30445
30355
|
return {
|
|
30446
30356
|
...entry,
|
|
30447
30357
|
lines: (entry.lines ?? []).map((line) => ({
|
|
30448
30358
|
...line,
|
|
30449
30359
|
guid: cachedGuids.get(line.guid) ?? line.guid,
|
|
30450
|
-
parentGuid: (line.parentGuid && cachedGuids.get(line.parentGuid)) ?? line.parentGuid
|
|
30360
|
+
parentGuid: (line.parentGuid && cachedGuids.get(line.parentGuid)) ?? line.parentGuid,
|
|
30361
|
+
trajectNodeId: trajectNodeIds.get(line.guid) ?? line.trajectNodeId
|
|
30451
30362
|
}))
|
|
30452
30363
|
};
|
|
30453
30364
|
};
|
|
@@ -30481,214 +30392,214 @@ const applyFlightToLine = (line, flight) => {
|
|
|
30481
30392
|
const isExternalResult = (result) => result.vendorId != null || result.vendorConfigurationId != null;
|
|
30482
30393
|
const isExternalCandidate = (candidate) => candidate.externalVendorId != null || candidate.vendor != null;
|
|
30483
30394
|
|
|
30484
|
-
|
|
30485
|
-
|
|
30486
|
-
|
|
30487
|
-
|
|
30395
|
+
// Administrative service lines (fees, insurances, reductions, ...) live on the entry but say
|
|
30396
|
+
// nothing about the journey itself, so the itinerary never draws them.
|
|
30397
|
+
const HIDDEN_ITINERARY_SERVICE_TYPES = new Set([
|
|
30398
|
+
build.ServiceType.insurance,
|
|
30399
|
+
build.ServiceType.extraService,
|
|
30400
|
+
build.ServiceType.serviceFee,
|
|
30401
|
+
build.ServiceType.gift,
|
|
30402
|
+
build.ServiceType.reduction,
|
|
30403
|
+
build.ServiceType.fee,
|
|
30404
|
+
build.ServiceType.visum,
|
|
30405
|
+
build.ServiceType.voucher,
|
|
30406
|
+
build.ServiceType.specialServices
|
|
30407
|
+
]);
|
|
30408
|
+
// An inactive member of a line group is an alternative the traveller did not pick — not travelled.
|
|
30409
|
+
const isMeaningfulItineraryLine = (line) => {
|
|
30410
|
+
return isActiveEntryLine(line) && !HIDDEN_ITINERARY_SERVICE_TYPES.has(line.serviceType ?? -1);
|
|
30488
30411
|
};
|
|
30489
|
-
const
|
|
30490
|
-
|
|
30491
|
-
|
|
30492
|
-
|
|
30493
|
-
|
|
30494
|
-
|
|
30495
|
-
|
|
30496
|
-
|
|
30497
|
-
|
|
30498
|
-
|
|
30499
|
-
|
|
30500
|
-
|
|
30501
|
-
|
|
30502
|
-
|
|
30503
|
-
|
|
30504
|
-
|
|
30505
|
-
|
|
30506
|
-
|
|
30507
|
-
|
|
30508
|
-
|
|
30509
|
-
|
|
30510
|
-
|
|
30511
|
-
|
|
30512
|
-
|
|
30513
|
-
|
|
30514
|
-
|
|
30515
|
-
|
|
30516
|
-
|
|
30517
|
-
|
|
30518
|
-
|
|
30519
|
-
|
|
30520
|
-
|
|
30521
|
-
|
|
30522
|
-
|
|
30523
|
-
|
|
30524
|
-
|
|
30525
|
-
|
|
30526
|
-
|
|
30527
|
-
|
|
30528
|
-
|
|
30529
|
-
|
|
30530
|
-
|
|
30531
|
-
|
|
30532
|
-
|
|
30533
|
-
|
|
30534
|
-
|
|
30535
|
-
|
|
30536
|
-
|
|
30537
|
-
|
|
30538
|
-
|
|
30539
|
-
|
|
30540
|
-
|
|
30541
|
-
|
|
30542
|
-
|
|
30543
|
-
|
|
30544
|
-
|
|
30545
|
-
|
|
30546
|
-
|
|
30547
|
-
|
|
30548
|
-
|
|
30549
|
-
|
|
30550
|
-
|
|
30551
|
-
|
|
30552
|
-
|
|
30553
|
-
|
|
30554
|
-
|
|
30555
|
-
|
|
30556
|
-
|
|
30557
|
-
|
|
30558
|
-
|
|
30559
|
-
|
|
30560
|
-
|
|
30561
|
-
|
|
30562
|
-
|
|
30563
|
-
|
|
30564
|
-
|
|
30565
|
-
|
|
30566
|
-
|
|
30567
|
-
|
|
30568
|
-
|
|
30569
|
-
|
|
30570
|
-
|
|
30571
|
-
|
|
30572
|
-
|
|
30573
|
-
|
|
30574
|
-
|
|
30575
|
-
|
|
30576
|
-
|
|
30577
|
-
|
|
30578
|
-
|
|
30579
|
-
|
|
30580
|
-
|
|
30581
|
-
|
|
30582
|
-
|
|
30583
|
-
|
|
30584
|
-
|
|
30585
|
-
|
|
30586
|
-
|
|
30587
|
-
|
|
30588
|
-
|
|
30589
|
-
|
|
30590
|
-
|
|
30591
|
-
|
|
30412
|
+
const getFlightLines = (flight) => {
|
|
30413
|
+
return flight?.flightInformation?.flightLines ?? [];
|
|
30414
|
+
};
|
|
30415
|
+
const getDepartureTime = (flight) => {
|
|
30416
|
+
const firstLine = lodash.first(getFlightLines(flight));
|
|
30417
|
+
if (!firstLine?.departureTime)
|
|
30418
|
+
return '';
|
|
30419
|
+
return firstLine.departureTime.slice(0, 5);
|
|
30420
|
+
};
|
|
30421
|
+
const getArrivalTime = (flight) => {
|
|
30422
|
+
const lastLine = lodash.last(getFlightLines(flight));
|
|
30423
|
+
if (!lastLine?.arrivalTime)
|
|
30424
|
+
return '';
|
|
30425
|
+
return lastLine.arrivalTime.slice(0, 5);
|
|
30426
|
+
};
|
|
30427
|
+
const getDuration = (flight) => {
|
|
30428
|
+
const lines = getFlightLines(flight);
|
|
30429
|
+
if (!lines.length)
|
|
30430
|
+
return '';
|
|
30431
|
+
const totalTicks = lines.reduce((sum, line) => sum + (line.durationInTicks ?? 0), 0) || 0;
|
|
30432
|
+
if (!totalTicks)
|
|
30433
|
+
return '';
|
|
30434
|
+
const seconds = totalTicks / 10_000_000;
|
|
30435
|
+
const hours = Math.floor(seconds / 3600);
|
|
30436
|
+
const minutes = Math.floor((seconds % 3600) / 60);
|
|
30437
|
+
return `${hours}h ${minutes.toString().padStart(2, '0')}m`;
|
|
30438
|
+
};
|
|
30439
|
+
const numberOfNights = (segment) => {
|
|
30440
|
+
return dateFns.differenceInCalendarDays(new Date(segment.to), new Date(segment.from));
|
|
30441
|
+
};
|
|
30442
|
+
const CHARTER_SERVICE_TYPE = 10;
|
|
30443
|
+
const CRUISE_SERVICE_TYPE = 11;
|
|
30444
|
+
const FERRY_SERVICE_TYPE = 12;
|
|
30445
|
+
const TRANSFER_SERVICE_TYPE = 13;
|
|
30446
|
+
const BUS_SERVICE_TYPE = 14;
|
|
30447
|
+
const CAR_SERVICE_TYPE = 17;
|
|
30448
|
+
const CAMPER_SERVICE_TYPE = 18;
|
|
30449
|
+
const TRAIN_SERVICE_TYPE = 20;
|
|
30450
|
+
const RENTAL_SERVICE_TYPE = 22;
|
|
30451
|
+
const RESTAURANT_SERVICE_TYPE = 23;
|
|
30452
|
+
const EVENT_SERVICE_TYPE = 24;
|
|
30453
|
+
const TRAINING_SERVICE_TYPE = 25;
|
|
30454
|
+
/** Icon per service type, so every node on the itinerary reads at a glance. */
|
|
30455
|
+
const getServiceTypeIcon = (serviceType) => {
|
|
30456
|
+
switch (serviceType) {
|
|
30457
|
+
case ACCOMMODATION_SERVICE_TYPE:
|
|
30458
|
+
return 'ui-hotel';
|
|
30459
|
+
case EXCURSION_SERVICE_TYPE:
|
|
30460
|
+
return 'ui-excursion';
|
|
30461
|
+
case FLIGHT_SERVICE_TYPE:
|
|
30462
|
+
case CHARTER_SERVICE_TYPE:
|
|
30463
|
+
return 'ui-plane';
|
|
30464
|
+
case RESTAURANT_SERVICE_TYPE:
|
|
30465
|
+
return 'ui-utensils';
|
|
30466
|
+
case CRUISE_SERVICE_TYPE:
|
|
30467
|
+
case FERRY_SERVICE_TYPE:
|
|
30468
|
+
return 'ui-ship';
|
|
30469
|
+
case CAR_SERVICE_TYPE:
|
|
30470
|
+
case CAMPER_SERVICE_TYPE:
|
|
30471
|
+
case RENTAL_SERVICE_TYPE:
|
|
30472
|
+
return 'ui-car';
|
|
30473
|
+
case BUS_SERVICE_TYPE:
|
|
30474
|
+
return 'ui-bus';
|
|
30475
|
+
case TRAIN_SERVICE_TYPE:
|
|
30476
|
+
return 'ui-train';
|
|
30477
|
+
case TRANSFER_SERVICE_TYPE:
|
|
30478
|
+
return 'ui-transfer';
|
|
30479
|
+
case EVENT_SERVICE_TYPE:
|
|
30480
|
+
return 'ui-ticket';
|
|
30481
|
+
case TRAINING_SERVICE_TYPE:
|
|
30482
|
+
return 'ui-group';
|
|
30483
|
+
default:
|
|
30484
|
+
return 'ui-location';
|
|
30485
|
+
}
|
|
30486
|
+
};
|
|
30487
|
+
const getServiceTypeBadgeModifier = (serviceType) => {
|
|
30488
|
+
switch (serviceType) {
|
|
30489
|
+
case FLIGHT_SERVICE_TYPE:
|
|
30490
|
+
case CHARTER_SERVICE_TYPE:
|
|
30491
|
+
case CRUISE_SERVICE_TYPE:
|
|
30492
|
+
case FERRY_SERVICE_TYPE:
|
|
30493
|
+
return 'flight';
|
|
30494
|
+
case ACCOMMODATION_SERVICE_TYPE:
|
|
30495
|
+
return 'hotel';
|
|
30496
|
+
case EXCURSION_SERVICE_TYPE:
|
|
30497
|
+
case EVENT_SERVICE_TYPE:
|
|
30498
|
+
return 'excursion';
|
|
30499
|
+
case RESTAURANT_SERVICE_TYPE:
|
|
30500
|
+
return 'restaurant';
|
|
30501
|
+
default:
|
|
30502
|
+
return '';
|
|
30503
|
+
}
|
|
30504
|
+
};
|
|
30505
|
+
// DurationType, as the SDK numbers it.
|
|
30506
|
+
const DURATION_HOURS = 0;
|
|
30507
|
+
const DURATION_DAYS = 1;
|
|
30508
|
+
const DURATION_NIGHTS = 2;
|
|
30509
|
+
const DURATION_MINUTES = 3;
|
|
30510
|
+
/**
|
|
30511
|
+
* How long a node takes, in the same shape the flight legs are written in — an hour count only
|
|
30512
|
+
* once there is one, so a short drive reads as plain minutes.
|
|
30513
|
+
*/
|
|
30514
|
+
const getTrajectNodeDuration = (node) => {
|
|
30515
|
+
const minutes = toMinutes(node);
|
|
30516
|
+
if (!minutes)
|
|
30517
|
+
return '';
|
|
30518
|
+
const hours = Math.floor(minutes / 60);
|
|
30519
|
+
const rest = minutes % 60;
|
|
30520
|
+
return hours ? `${hours}h ${rest.toString().padStart(2, '0')}m` : `${rest} min`;
|
|
30521
|
+
};
|
|
30522
|
+
const toMinutes = (node) => {
|
|
30523
|
+
const duration = node.duration ?? 0;
|
|
30524
|
+
if (duration <= 0)
|
|
30525
|
+
return 0;
|
|
30526
|
+
switch (node.durationType) {
|
|
30527
|
+
case DURATION_MINUTES:
|
|
30528
|
+
return duration;
|
|
30529
|
+
case DURATION_HOURS:
|
|
30530
|
+
return duration * 60;
|
|
30531
|
+
case DURATION_DAYS:
|
|
30532
|
+
case DURATION_NIGHTS:
|
|
30533
|
+
return duration * 24 * 60;
|
|
30534
|
+
default:
|
|
30535
|
+
return 0;
|
|
30536
|
+
}
|
|
30537
|
+
};
|
|
30538
|
+
// RouteTransportType, as the SDK numbers it. It says how a leg is travelled, which is not the
|
|
30539
|
+
// same question as what was sold: cycling and walking have no service type of their own.
|
|
30540
|
+
const TRANSPORT_PRIVATE = 1;
|
|
30541
|
+
const TRANSPORT_PUBLIC = 2;
|
|
30542
|
+
const TRANSPORT_RENTAL_CAR = 3;
|
|
30543
|
+
const TRANSPORT_BUS = 4;
|
|
30544
|
+
const TRANSPORT_CAMPER = 5;
|
|
30545
|
+
const TRANSPORT_CAR = 6;
|
|
30546
|
+
const TRANSPORT_CHARTER = 7;
|
|
30547
|
+
const TRANSPORT_CRUISE = 8;
|
|
30548
|
+
const TRANSPORT_FERRY = 9;
|
|
30549
|
+
const TRANSPORT_FLIGHT = 10;
|
|
30550
|
+
const TRANSPORT_TAXI = 11;
|
|
30551
|
+
const TRANSPORT_TRAIN = 12;
|
|
30552
|
+
const TRANSPORT_TRANSFER = 13;
|
|
30553
|
+
const TRANSPORT_WALKING = 15;
|
|
30554
|
+
const TRANSPORT_CYCLING = 16;
|
|
30555
|
+
/** Icon per way of travelling, where the route records one. */
|
|
30556
|
+
const getTransportTypeIcon = (transportType) => {
|
|
30557
|
+
switch (transportType) {
|
|
30558
|
+
case TRANSPORT_WALKING:
|
|
30559
|
+
return 'ui-walking';
|
|
30560
|
+
case TRANSPORT_CYCLING:
|
|
30561
|
+
return 'ui-cycling';
|
|
30562
|
+
case TRANSPORT_PRIVATE:
|
|
30563
|
+
case TRANSPORT_RENTAL_CAR:
|
|
30564
|
+
case TRANSPORT_CAMPER:
|
|
30565
|
+
case TRANSPORT_CAR:
|
|
30566
|
+
return 'ui-car';
|
|
30567
|
+
case TRANSPORT_TAXI:
|
|
30568
|
+
case TRANSPORT_TRANSFER:
|
|
30569
|
+
return 'ui-transfer';
|
|
30570
|
+
case TRANSPORT_PUBLIC:
|
|
30571
|
+
case TRANSPORT_BUS:
|
|
30572
|
+
return 'ui-bus';
|
|
30573
|
+
case TRANSPORT_TRAIN:
|
|
30574
|
+
return 'ui-train';
|
|
30575
|
+
case TRANSPORT_CRUISE:
|
|
30576
|
+
case TRANSPORT_FERRY:
|
|
30577
|
+
return 'ui-ship';
|
|
30578
|
+
case TRANSPORT_FLIGHT:
|
|
30579
|
+
case TRANSPORT_CHARTER:
|
|
30580
|
+
return 'ui-plane';
|
|
30581
|
+
default:
|
|
30592
30582
|
return null;
|
|
30593
|
-
|
|
30594
|
-
React__default["default"].createElement("div", { className: "search__filter__itinerary__transport-timeline" },
|
|
30595
|
-
React__default["default"].createElement("div", { className: "search__filter__itinerary__transport-timeline-line" })),
|
|
30596
|
-
React__default["default"].createElement("div", { className: "search__filter__itinerary__transport-item" },
|
|
30597
|
-
renderDate(item.node.from),
|
|
30598
|
-
React__default["default"].createElement("div", { className: badgeClass('transport', getServiceTypeBadgeModifier(ACCOMMODATION_SERVICE_TYPE)) },
|
|
30599
|
-
React__default["default"].createElement(Icon, { name: "ui-bed", height: 15 })),
|
|
30600
|
-
React__default["default"].createElement("div", { className: "search__filter__itinerary__transport-details" },
|
|
30601
|
-
React__default["default"].createElement("h6", null, stay.name),
|
|
30602
|
-
React__default["default"].createElement("p", { className: "search__filter__itinerary__transport-details-time" },
|
|
30603
|
-
stay.night + 1,
|
|
30604
|
-
" ",
|
|
30605
|
-
translations.SRP.OF,
|
|
30606
|
-
" ",
|
|
30607
|
-
stay.nights,
|
|
30608
|
-
" ",
|
|
30609
|
-
translations.SRP.NIGHTS)))));
|
|
30610
|
-
};
|
|
30611
|
-
const renderFlightNode = ({ node, lines }) => {
|
|
30612
|
-
const line = lines[0];
|
|
30613
|
-
return (React__default["default"].createElement("div", { className: "search__filter__itinerary__transport", key: `flight-${node.nodeId}` },
|
|
30614
|
-
React__default["default"].createElement("div", { className: "search__filter__itinerary__transport-timeline" },
|
|
30615
|
-
React__default["default"].createElement("div", { className: "search__filter__itinerary__transport-timeline-line" })),
|
|
30616
|
-
React__default["default"].createElement("div", { className: "search__filter__itinerary__transport-item" },
|
|
30617
|
-
renderDate(line?.from ?? node.from),
|
|
30618
|
-
React__default["default"].createElement("div", { className: badgeClass('transport', getServiceTypeBadgeModifier(FLIGHT_SERVICE_TYPE)) },
|
|
30619
|
-
React__default["default"].createElement(Icon, { name: "ui-plane", height: 15 })),
|
|
30620
|
-
React__default["default"].createElement("div", { className: "search__filter__itinerary__transport-details" },
|
|
30621
|
-
React__default["default"].createElement("h6", null, line?.productName || node.name),
|
|
30622
|
-
!!getDepartureTime(line) && (React__default["default"].createElement("p", { className: "search__filter__itinerary__transport-details-plane" },
|
|
30623
|
-
React__default["default"].createElement("span", null,
|
|
30624
|
-
React__default["default"].createElement(Icon, { name: "ui-plane-depart", height: 14 }),
|
|
30625
|
-
" ",
|
|
30626
|
-
React__default["default"].createElement("strong", null, getDepartureTime(line))),
|
|
30627
|
-
' ',
|
|
30628
|
-
"-",
|
|
30629
|
-
' ',
|
|
30630
|
-
React__default["default"].createElement("span", null,
|
|
30631
|
-
React__default["default"].createElement(Icon, { name: "ui-plane-arrive", height: 14 }),
|
|
30632
|
-
" ",
|
|
30633
|
-
React__default["default"].createElement("strong", null, getArrivalTime(line))))),
|
|
30634
|
-
!!getDuration(line) && (React__default["default"].createElement("p", { className: "search__filter__itinerary__transport-details-time" },
|
|
30635
|
-
React__default["default"].createElement("span", null,
|
|
30636
|
-
React__default["default"].createElement(Icon, { name: "ui-clock", height: 20 }),
|
|
30637
|
-
" ",
|
|
30638
|
-
getDuration(line))))))));
|
|
30639
|
-
};
|
|
30640
|
-
const renderServiceNode = ({ node, lines }) => {
|
|
30641
|
-
const line = lines[0];
|
|
30642
|
-
const isStay = node.serviceType === ACCOMMODATION_SERVICE_TYPE;
|
|
30643
|
-
const nights = isStay ? nightsOf(node, line) : 0;
|
|
30644
|
-
return (React__default["default"].createElement("div", { className: "search__filter__itinerary__segments", key: `node-${node.nodeId}` },
|
|
30645
|
-
React__default["default"].createElement("div", { className: "search__filter__itinerary__segments__wrapper" },
|
|
30646
|
-
React__default["default"].createElement("div", { className: "search__filter__itinerary__segments-timeline" },
|
|
30647
|
-
React__default["default"].createElement("div", { className: "search__filter__itinerary__segments-timeline-line" })),
|
|
30648
|
-
React__default["default"].createElement("div", { className: "search__filter__itinerary__segment" },
|
|
30649
|
-
React__default["default"].createElement("div", { className: "search__filter__itinerary__segment-item" },
|
|
30650
|
-
React__default["default"].createElement("div", { className: `search__filter__itinerary__segment-date ${isStay && nights > 0 ? 'search__filter__itinerary__segment-nights' : ''}` }, isStay && nights > 0 ? (React__default["default"].createElement(React__default["default"].Fragment, null,
|
|
30651
|
-
React__default["default"].createElement("p", { className: "search__filter__itinerary__segment-date-date" },
|
|
30652
|
-
React__default["default"].createElement("strong", null, nights)),
|
|
30653
|
-
React__default["default"].createElement(Icon, { name: "ui-moon", width: 16, height: 16 }))) : (React__default["default"].createElement(React__default["default"].Fragment, null,
|
|
30654
|
-
React__default["default"].createElement("p", { className: "search__filter__itinerary__segment-date-date" },
|
|
30655
|
-
React__default["default"].createElement("strong", null, dateFns.format(new Date(line?.from ?? node.from), 'd'))),
|
|
30656
|
-
React__default["default"].createElement("p", null, dateFns.format(new Date(line?.from ?? node.from), 'MMM'))))),
|
|
30657
|
-
React__default["default"].createElement("div", { className: badgeClass('segment', getTrajectNodeBadgeModifier(node)) },
|
|
30658
|
-
React__default["default"].createElement(Icon, { name: getTrajectNodeIcon(node), width: 15, height: 15 })),
|
|
30659
|
-
React__default["default"].createElement("div", { className: "search__filter__itinerary__segment-details" },
|
|
30660
|
-
React__default["default"].createElement("h6", null, line?.productName || node.preferredAccommodationName || node.name),
|
|
30661
|
-
isStay &&
|
|
30662
|
-
lines.map((roomLine, index) => (React__default["default"].createElement(React__default["default"].Fragment, { key: roomLine.guid },
|
|
30663
|
-
lines.length > 1 && (React__default["default"].createElement("strong", null,
|
|
30664
|
-
translations.SHARED.ROOM,
|
|
30665
|
-
"\u00A0",
|
|
30666
|
-
index + 1)),
|
|
30667
|
-
roomLine.accommodationName && (React__default["default"].createElement("div", { className: "search__filter__itinerary__segment-details__room" },
|
|
30668
|
-
React__default["default"].createElement(Icon, { name: "ui-bed", width: 12, height: 12 }),
|
|
30669
|
-
roomLine.accommodationName)),
|
|
30670
|
-
roomLine.regimeName && (React__default["default"].createElement("div", { className: "search__filter__itinerary__segment-details__room" },
|
|
30671
|
-
React__default["default"].createElement(Icon, { name: "ui-utensils", width: 12, height: 12 }),
|
|
30672
|
-
roomLine.regimeName))))),
|
|
30673
|
-
!isStay && React__default["default"].createElement("p", { className: "search__filter__itinerary__segment-details-text" }, dateFns.format(new Date(line?.from ?? node.from), 'EEE. d MMM yyyy'))))))));
|
|
30674
|
-
};
|
|
30675
|
-
return (React__default["default"].createElement(React__default["default"].Fragment, null, timeline.map((item, index) => {
|
|
30676
|
-
if (item.node.trajectNodeType === CONNECTING_TRAJECT_NODE_TYPE) {
|
|
30677
|
-
return (React__default["default"].createElement(React__default["default"].Fragment, { key: `connect-${item.node.nodeId}` },
|
|
30678
|
-
renderConnectingNode(item),
|
|
30679
|
-
renderOvernight(item, index)));
|
|
30680
|
-
}
|
|
30681
|
-
if (item.node.trajectNodeType === REGULAR_TRAJECT_NODE_TYPE && item.node.serviceType === FLIGHT_SERVICE_TYPE)
|
|
30682
|
-
return renderFlightNode(item);
|
|
30683
|
-
return renderServiceNode(item);
|
|
30684
|
-
})));
|
|
30583
|
+
}
|
|
30685
30584
|
};
|
|
30585
|
+
/**
|
|
30586
|
+
* A point of interest is a place on the map rather than a service, so it gets a marker. Anything
|
|
30587
|
+
* else goes by how it is travelled where the route says so — a leg on foot or by bike is sold as
|
|
30588
|
+
* nothing, and its service type falls back to a transfer — and by what it is where it does not.
|
|
30589
|
+
*/
|
|
30590
|
+
const getTrajectNodeIcon = (node) => {
|
|
30591
|
+
if (node.trajectNodeType === POI_TRAJECT_NODE_TYPE)
|
|
30592
|
+
return 'ui-location';
|
|
30593
|
+
return getTransportTypeIcon(node.transportType) ?? getServiceTypeIcon(node.serviceType);
|
|
30594
|
+
};
|
|
30595
|
+
const getTrajectNodeBadgeModifier = (node) => node.trajectNodeType === POI_TRAJECT_NODE_TYPE ? 'poi' : getServiceTypeBadgeModifier(node.serviceType);
|
|
30686
30596
|
|
|
30687
30597
|
/**
|
|
30688
30598
|
* A booked entry opened through its magic link knows nothing about the traject it may once have
|
|
30689
30599
|
* been composed from — the dossier only keeps the lines. To give it the same day-by-day services
|
|
30690
30600
|
* screen a traject gets, the traject structure is rebuilt from those lines: every booked service
|
|
30691
30601
|
* becomes a node whose one "alternative" is the booked product itself, and the days span the trip.
|
|
30602
|
+
* The points of interest on the dossier's own route (entry.routeNodes) are added between them.
|
|
30692
30603
|
*
|
|
30693
30604
|
* The synthesized response keeps the entry untouched (magic link included), which is also how the
|
|
30694
30605
|
* rest of the component tells this mode apart from a real traject: see isBookedEntryTraject.
|
|
@@ -30765,21 +30676,24 @@ const buildBookedTrajectEntry = (entry) => {
|
|
|
30765
30676
|
const tos = lines.map((line) => new Date(line.to).getTime());
|
|
30766
30677
|
const start = froms.length ? new Date(Math.min(...froms)) : new Date();
|
|
30767
30678
|
const end = tos.length ? new Date(Math.max(...tos)) : start;
|
|
30679
|
+
// Local midnights, written the way the backend writes a traject's days. A UTC date (toDateOnlyString)
|
|
30680
|
+
// names another day away from Greenwich, and every line looked up by its day would slide onto it.
|
|
30768
30681
|
const dayCount = Math.max(dateFns.differenceInCalendarDays(end, start), 0) + 1;
|
|
30769
30682
|
const days = Array.from({ length: dayCount }, (_, order) => ({
|
|
30770
30683
|
order,
|
|
30771
30684
|
name: '',
|
|
30772
|
-
date:
|
|
30685
|
+
date: dateFns.format(dateFns.addDays(start, order), "yyyy-MM-dd'T00:00:00'")
|
|
30773
30686
|
}));
|
|
30774
|
-
const dayOrderOf = (
|
|
30687
|
+
const dayOrderOf = (item) => Math.min(Math.max(dateFns.differenceInCalendarDays(new Date(item.from), start), 0), dayCount - 1);
|
|
30775
30688
|
const flights = lodash.sortBy(lines.filter((line) => line.serviceType === FLIGHT_SERVICE_TYPE && isActiveEntryLine(line)), (line) => new Date(line.from).getTime(), (line) => line.order);
|
|
30776
30689
|
const outward = lodash.first(flights);
|
|
30777
30690
|
const homeward = flights.length > 1 ? lodash.last(flights) : undefined;
|
|
30778
|
-
|
|
30779
|
-
|
|
30780
|
-
|
|
30781
|
-
|
|
30782
|
-
|
|
30691
|
+
// The dossier keeps its lines in route order, so a node's order is its line's — except that the
|
|
30692
|
+
// outward flight opens its day and the homeward one closes it, whatever else that day holds.
|
|
30693
|
+
const homewardOrder = Math.max(0, ...(entry.lines ?? []).map((line) => line.order)) + 1;
|
|
30694
|
+
const orderOf = (line) => (line === outward ? -1 : line === homeward ? homewardOrder : line.order);
|
|
30695
|
+
const orderedGroups = groupNodeLines(lines).sort((a, b) => dayOrderOf(a.lines[0]) - dayOrderOf(b.lines[0]) || orderOf(a.lines[0]) - orderOf(b.lines[0]));
|
|
30696
|
+
const idBase = Math.max(0, ...(entry.routeNodes ?? []).map((routeNode) => routeNode.nodeId));
|
|
30783
30697
|
const nodes = orderedGroups.map(({ lines: groupLines, alternatives }, index) => {
|
|
30784
30698
|
const line = groupLines[0];
|
|
30785
30699
|
const flightLines = line.flightInformation?.flightLines ?? [];
|
|
@@ -30789,9 +30703,9 @@ const buildBookedTrajectEntry = (entry) => {
|
|
|
30789
30703
|
return {
|
|
30790
30704
|
lineGuid: line.guid,
|
|
30791
30705
|
lineGuids: groupLines.map((x) => x.guid),
|
|
30792
|
-
nodeId: index + 1,
|
|
30706
|
+
nodeId: idBase + index + 1,
|
|
30793
30707
|
dayOrder: dayOrderOf(line),
|
|
30794
|
-
order:
|
|
30708
|
+
order: orderOf(line),
|
|
30795
30709
|
name: (alternatives.length && getLineGroupKey(line)) || line.productName,
|
|
30796
30710
|
serviceType: line.serviceType,
|
|
30797
30711
|
trajectNodeType: REGULAR_TRAJECT_NODE_TYPE,
|
|
@@ -30809,6 +30723,23 @@ const buildBookedTrajectEntry = (entry) => {
|
|
|
30809
30723
|
to: line.to
|
|
30810
30724
|
};
|
|
30811
30725
|
});
|
|
30726
|
+
const linesByGuid = new Map((entry.lines ?? []).map((line) => [line.guid, line]));
|
|
30727
|
+
let anchorOrder = -2;
|
|
30728
|
+
let step = 0;
|
|
30729
|
+
(entry.routeNodes ?? []).forEach((routeNode) => {
|
|
30730
|
+
const anchor = linesByGuid.get(routeNode.lineGuid);
|
|
30731
|
+
if (anchor) {
|
|
30732
|
+
anchorOrder = orderOf(anchor);
|
|
30733
|
+
step = 0;
|
|
30734
|
+
if (routeNode.trajectNodeType === CONNECTING_TRAJECT_NODE_TYPE)
|
|
30735
|
+
nodes.push({ ...routeNode, dayOrder: dayOrderOf(routeNode), order: anchorOrder });
|
|
30736
|
+
return;
|
|
30737
|
+
}
|
|
30738
|
+
if (routeNode.trajectNodeType !== POI_TRAJECT_NODE_TYPE)
|
|
30739
|
+
return;
|
|
30740
|
+
step++;
|
|
30741
|
+
nodes.push({ ...routeNode, nodeId: idBase + nodes.length + 1, dayOrder: dayOrderOf(routeNode), order: anchorOrder + step / 1000 });
|
|
30742
|
+
});
|
|
30812
30743
|
return {
|
|
30813
30744
|
entry,
|
|
30814
30745
|
traject: {
|
|
@@ -30930,6 +30861,222 @@ const buildBookedFlightResult = (line) => {
|
|
|
30930
30861
|
};
|
|
30931
30862
|
};
|
|
30932
30863
|
|
|
30864
|
+
const badgeClass = (kind, modifier) => {
|
|
30865
|
+
const base = `search__filter__itinerary__${kind}-badge`;
|
|
30866
|
+
const fallback = kind === 'segment' ? `${base}--secondary` : `${base}--default`;
|
|
30867
|
+
return `${base} ${modifier ? `${base}--${modifier}` : fallback}`;
|
|
30868
|
+
};
|
|
30869
|
+
const nightsOf = (node, line) => dateFns.differenceInCalendarDays(new Date(line?.to ?? node.to), new Date(line?.from ?? node.from));
|
|
30870
|
+
const TrajectItinerary = ({ trajectEntry, packagingEntry, translations }) => {
|
|
30871
|
+
const timeline = React.useMemo(() => {
|
|
30872
|
+
const linesByGuid = new Map((packagingEntry.lines ?? []).map((line) => [line.guid, line]));
|
|
30873
|
+
const dropped = getDroppedTrajectNodeIds(trajectEntry, packagingEntry);
|
|
30874
|
+
const nodes = [...(trajectEntry.nodes ?? [])]
|
|
30875
|
+
.filter((node) => !dropped.has(node.nodeId))
|
|
30876
|
+
.sort((a, b) => a.dayOrder - b.dayOrder || a.order - b.order)
|
|
30877
|
+
.map((node) => {
|
|
30878
|
+
if (node.trajectNodeType === CONNECTING_TRAJECT_NODE_TYPE) {
|
|
30879
|
+
return { node, lines: getTrajectTransferLines(node, packagingEntry.lines ?? []) };
|
|
30880
|
+
}
|
|
30881
|
+
const guids = node.lineGuids?.length ? node.lineGuids : [node.lineGuid];
|
|
30882
|
+
const lines = guids.map((guid) => linesByGuid.get(guid)).filter((x) => !!x);
|
|
30883
|
+
const groupKey = lines.length ? getLineGroupKey(lines[0]) : '';
|
|
30884
|
+
const active = groupKey ? getGroupLines(packagingEntry.lines ?? [], groupKey)[0] : undefined;
|
|
30885
|
+
return { node, lines: active ?? lines };
|
|
30886
|
+
});
|
|
30887
|
+
const claimed = new Set(nodes.flatMap((item) => item.lines.map((line) => line.guid)));
|
|
30888
|
+
const extras = (packagingEntry.lines ?? []).filter((line) => !claimed.has(line.guid)).filter(isMeaningfulItineraryLine);
|
|
30889
|
+
if (!extras.length)
|
|
30890
|
+
return nodes;
|
|
30891
|
+
const dayOrderOf = (date) => trajectEntry.traject.days.find((day) => dateFns.isSameDay(new Date(day.date), new Date(date)))?.order ??
|
|
30892
|
+
Math.max(dateFns.differenceInCalendarDays(new Date(date), new Date(trajectEntry.traject.startDate)), 0);
|
|
30893
|
+
// One product bought once, however many option lines it took (an adult and a child ticket).
|
|
30894
|
+
const grouped = new Map();
|
|
30895
|
+
extras.forEach((line) => {
|
|
30896
|
+
const key = `${line.productCode}|${line.from}`;
|
|
30897
|
+
grouped.set(key, [...(grouped.get(key) ?? []), line]);
|
|
30898
|
+
});
|
|
30899
|
+
const byDay = new Map();
|
|
30900
|
+
nodes.forEach((item) => byDay.set(item.node.dayOrder, [...(byDay.get(item.node.dayOrder) ?? []), item]));
|
|
30901
|
+
const flightNodes = nodes.filter(({ node }) => node.trajectNodeType === REGULAR_TRAJECT_NODE_TYPE && node.serviceType === FLIGHT_SERVICE_TYPE);
|
|
30902
|
+
const homeward = flightNodes.length > 1 ? flightNodes[flightNodes.length - 1] : undefined;
|
|
30903
|
+
// A booked entry's nodes are ordered as the dossier orders its lines (see buildBookedTrajectEntry).
|
|
30904
|
+
const bookedOrders = isBookedEntryTraject(trajectEntry) ? new Map((trajectEntry.entry.lines ?? []).map((line) => [line.guid, line.order])) : undefined;
|
|
30905
|
+
[...grouped.values()].forEach((lines, index) => {
|
|
30906
|
+
const line = lines[0];
|
|
30907
|
+
const dayOrder = dayOrderOf(line.from);
|
|
30908
|
+
const bookedOrder = bookedOrders?.get(line.guid);
|
|
30909
|
+
const item = {
|
|
30910
|
+
lines,
|
|
30911
|
+
node: {
|
|
30912
|
+
nodeId: -(index + 1),
|
|
30913
|
+
lineGuid: line.guid,
|
|
30914
|
+
lineGuids: lines.map((x) => x.guid),
|
|
30915
|
+
dayOrder,
|
|
30916
|
+
order: bookedOrder ?? Number.MAX_SAFE_INTEGER,
|
|
30917
|
+
name: line.productName,
|
|
30918
|
+
serviceType: line.serviceType,
|
|
30919
|
+
trajectNodeType: REGULAR_TRAJECT_NODE_TYPE,
|
|
30920
|
+
requirementType: 0,
|
|
30921
|
+
contentSource: 0,
|
|
30922
|
+
duration: 0,
|
|
30923
|
+
durationType: 0,
|
|
30924
|
+
alternatives: [],
|
|
30925
|
+
from: line.from,
|
|
30926
|
+
to: line.to
|
|
30927
|
+
}
|
|
30928
|
+
};
|
|
30929
|
+
const dayItems = [...(byDay.get(dayOrder) ?? [])];
|
|
30930
|
+
// A booked line goes where the dossier's order puts it; one the traveller added closes its
|
|
30931
|
+
// day, but before the hops that leave it and the flight home.
|
|
30932
|
+
let at = dayItems.length;
|
|
30933
|
+
if (bookedOrder !== undefined) {
|
|
30934
|
+
at = dayItems.findIndex((x) => x.node.order > bookedOrder);
|
|
30935
|
+
if (at < 0)
|
|
30936
|
+
at = dayItems.length;
|
|
30937
|
+
}
|
|
30938
|
+
else {
|
|
30939
|
+
while (at > 0 && (dayItems[at - 1].node.trajectNodeType === CONNECTING_TRAJECT_NODE_TYPE || dayItems[at - 1] === homeward))
|
|
30940
|
+
at--;
|
|
30941
|
+
}
|
|
30942
|
+
dayItems.splice(at, 0, item);
|
|
30943
|
+
byDay.set(dayOrder, dayItems);
|
|
30944
|
+
});
|
|
30945
|
+
return [...byDay.keys()].sort((a, b) => a - b).flatMap((dayOrder) => byDay.get(dayOrder));
|
|
30946
|
+
}, [trajectEntry, packagingEntry]);
|
|
30947
|
+
const stayByDay = React.useMemo(() => {
|
|
30948
|
+
const map = new Map();
|
|
30949
|
+
timeline
|
|
30950
|
+
.filter(({ node }) => node.trajectNodeType === REGULAR_TRAJECT_NODE_TYPE && node.serviceType === ACCOMMODATION_SERVICE_TYPE)
|
|
30951
|
+
.forEach(({ node, lines }) => {
|
|
30952
|
+
const line = lines[0];
|
|
30953
|
+
const nights = Math.max(nightsOf(node, line), 1);
|
|
30954
|
+
const name = line?.productName || node.preferredAccommodationName || node.name || '';
|
|
30955
|
+
for (let night = 0; night < nights; night++)
|
|
30956
|
+
map.set(node.dayOrder + night, { name, night, nights });
|
|
30957
|
+
});
|
|
30958
|
+
return map;
|
|
30959
|
+
}, [timeline]);
|
|
30960
|
+
const renderDate = (value) => (React__default["default"].createElement("div", { className: "search__filter__itinerary__transport-date" },
|
|
30961
|
+
React__default["default"].createElement("p", { className: "search__filter__itinerary__transport-date-date" },
|
|
30962
|
+
React__default["default"].createElement("strong", null, dateFns.format(new Date(value), 'd'))),
|
|
30963
|
+
React__default["default"].createElement("p", null, dateFns.format(new Date(value), 'MMM'))));
|
|
30964
|
+
const renderConnectingNode = ({ node, lines }) => {
|
|
30965
|
+
const distance = node.distanceInKm ? `${Math.round(node.distanceInKm)} km` : '';
|
|
30966
|
+
// How long the hop takes and how far it is, the way the traject editor puts them.
|
|
30967
|
+
const journey = [getTrajectNodeDuration(node), distance].filter(Boolean).join(' · ');
|
|
30968
|
+
const title = lines[0]?.productName || node.name;
|
|
30969
|
+
return (React__default["default"].createElement("div", { className: "search__filter__itinerary__transport", key: `connect-${node.nodeId}` },
|
|
30970
|
+
React__default["default"].createElement("div", { className: "search__filter__itinerary__transport-timeline" },
|
|
30971
|
+
React__default["default"].createElement("div", { className: "search__filter__itinerary__transport-timeline-line" })),
|
|
30972
|
+
React__default["default"].createElement("div", { className: "search__filter__itinerary__transport-item" },
|
|
30973
|
+
React__default["default"].createElement("div", { className: "search__filter__itinerary__transport-date" }),
|
|
30974
|
+
React__default["default"].createElement("div", { className: badgeClass('transport', getTrajectNodeBadgeModifier(node)) },
|
|
30975
|
+
React__default["default"].createElement(Icon, { name: getTrajectNodeIcon(node), height: 15 })),
|
|
30976
|
+
React__default["default"].createElement("div", { className: "search__filter__itinerary__transport-details" },
|
|
30977
|
+
React__default["default"].createElement("h6", null, title || journey),
|
|
30978
|
+
!!title && !!journey && React__default["default"].createElement("p", { className: "search__filter__itinerary__transport-details-time" }, journey)))));
|
|
30979
|
+
};
|
|
30980
|
+
const renderOvernight = (item, index) => {
|
|
30981
|
+
const { dayOrder } = item.node;
|
|
30982
|
+
const closesTheDay = timeline[index + 1]?.node.dayOrder !== dayOrder;
|
|
30983
|
+
const stay = stayByDay.get(dayOrder);
|
|
30984
|
+
if (!closesTheDay || !stay)
|
|
30985
|
+
return null;
|
|
30986
|
+
return (React__default["default"].createElement("div", { className: "search__filter__itinerary__transport", key: `overnight-${item.node.nodeId}` },
|
|
30987
|
+
React__default["default"].createElement("div", { className: "search__filter__itinerary__transport-timeline" },
|
|
30988
|
+
React__default["default"].createElement("div", { className: "search__filter__itinerary__transport-timeline-line" })),
|
|
30989
|
+
React__default["default"].createElement("div", { className: "search__filter__itinerary__transport-item" },
|
|
30990
|
+
renderDate(item.node.from),
|
|
30991
|
+
React__default["default"].createElement("div", { className: badgeClass('transport', getServiceTypeBadgeModifier(ACCOMMODATION_SERVICE_TYPE)) },
|
|
30992
|
+
React__default["default"].createElement(Icon, { name: "ui-bed", height: 15 })),
|
|
30993
|
+
React__default["default"].createElement("div", { className: "search__filter__itinerary__transport-details" },
|
|
30994
|
+
React__default["default"].createElement("h6", null, stay.name),
|
|
30995
|
+
React__default["default"].createElement("p", { className: "search__filter__itinerary__transport-details-time" },
|
|
30996
|
+
stay.night + 1,
|
|
30997
|
+
" ",
|
|
30998
|
+
translations.SRP.OF,
|
|
30999
|
+
" ",
|
|
31000
|
+
stay.nights,
|
|
31001
|
+
" ",
|
|
31002
|
+
translations.SRP.NIGHTS)))));
|
|
31003
|
+
};
|
|
31004
|
+
const renderFlightNode = ({ node, lines }) => {
|
|
31005
|
+
const line = lines[0];
|
|
31006
|
+
return (React__default["default"].createElement("div", { className: "search__filter__itinerary__transport", key: `flight-${node.nodeId}` },
|
|
31007
|
+
React__default["default"].createElement("div", { className: "search__filter__itinerary__transport-timeline" },
|
|
31008
|
+
React__default["default"].createElement("div", { className: "search__filter__itinerary__transport-timeline-line" })),
|
|
31009
|
+
React__default["default"].createElement("div", { className: "search__filter__itinerary__transport-item" },
|
|
31010
|
+
renderDate(line?.from ?? node.from),
|
|
31011
|
+
React__default["default"].createElement("div", { className: badgeClass('transport', getServiceTypeBadgeModifier(FLIGHT_SERVICE_TYPE)) },
|
|
31012
|
+
React__default["default"].createElement(Icon, { name: "ui-plane", height: 15 })),
|
|
31013
|
+
React__default["default"].createElement("div", { className: "search__filter__itinerary__transport-details" },
|
|
31014
|
+
React__default["default"].createElement("h6", null, line?.productName || node.name),
|
|
31015
|
+
!!getDepartureTime(line) && (React__default["default"].createElement("p", { className: "search__filter__itinerary__transport-details-plane" },
|
|
31016
|
+
React__default["default"].createElement("span", null,
|
|
31017
|
+
React__default["default"].createElement(Icon, { name: "ui-plane-depart", height: 14 }),
|
|
31018
|
+
" ",
|
|
31019
|
+
React__default["default"].createElement("strong", null, getDepartureTime(line))),
|
|
31020
|
+
' ',
|
|
31021
|
+
"-",
|
|
31022
|
+
' ',
|
|
31023
|
+
React__default["default"].createElement("span", null,
|
|
31024
|
+
React__default["default"].createElement(Icon, { name: "ui-plane-arrive", height: 14 }),
|
|
31025
|
+
" ",
|
|
31026
|
+
React__default["default"].createElement("strong", null, getArrivalTime(line))))),
|
|
31027
|
+
!!getDuration(line) && (React__default["default"].createElement("p", { className: "search__filter__itinerary__transport-details-time" },
|
|
31028
|
+
React__default["default"].createElement("span", null,
|
|
31029
|
+
React__default["default"].createElement(Icon, { name: "ui-clock", height: 20 }),
|
|
31030
|
+
" ",
|
|
31031
|
+
getDuration(line))))))));
|
|
31032
|
+
};
|
|
31033
|
+
const renderServiceNode = ({ node, lines }) => {
|
|
31034
|
+
const line = lines[0];
|
|
31035
|
+
const isStay = node.serviceType === ACCOMMODATION_SERVICE_TYPE;
|
|
31036
|
+
const nights = isStay ? nightsOf(node, line) : 0;
|
|
31037
|
+
return (React__default["default"].createElement("div", { className: "search__filter__itinerary__segments", key: `node-${node.nodeId}` },
|
|
31038
|
+
React__default["default"].createElement("div", { className: "search__filter__itinerary__segments__wrapper" },
|
|
31039
|
+
React__default["default"].createElement("div", { className: "search__filter__itinerary__segments-timeline" },
|
|
31040
|
+
React__default["default"].createElement("div", { className: "search__filter__itinerary__segments-timeline-line" })),
|
|
31041
|
+
React__default["default"].createElement("div", { className: "search__filter__itinerary__segment" },
|
|
31042
|
+
React__default["default"].createElement("div", { className: "search__filter__itinerary__segment-item" },
|
|
31043
|
+
React__default["default"].createElement("div", { className: `search__filter__itinerary__segment-date ${isStay && nights > 0 ? 'search__filter__itinerary__segment-nights' : ''}` }, isStay && nights > 0 ? (React__default["default"].createElement(React__default["default"].Fragment, null,
|
|
31044
|
+
React__default["default"].createElement("p", { className: "search__filter__itinerary__segment-date-date" },
|
|
31045
|
+
React__default["default"].createElement("strong", null, nights)),
|
|
31046
|
+
React__default["default"].createElement(Icon, { name: "ui-moon", width: 16, height: 16 }))) : (React__default["default"].createElement(React__default["default"].Fragment, null,
|
|
31047
|
+
React__default["default"].createElement("p", { className: "search__filter__itinerary__segment-date-date" },
|
|
31048
|
+
React__default["default"].createElement("strong", null, dateFns.format(new Date(line?.from ?? node.from), 'd'))),
|
|
31049
|
+
React__default["default"].createElement("p", null, dateFns.format(new Date(line?.from ?? node.from), 'MMM'))))),
|
|
31050
|
+
React__default["default"].createElement("div", { className: badgeClass('segment', getTrajectNodeBadgeModifier(node)) },
|
|
31051
|
+
React__default["default"].createElement(Icon, { name: getTrajectNodeIcon(node), width: 15, height: 15 })),
|
|
31052
|
+
React__default["default"].createElement("div", { className: "search__filter__itinerary__segment-details" },
|
|
31053
|
+
React__default["default"].createElement("h6", null, line?.productName || node.preferredAccommodationName || node.name),
|
|
31054
|
+
isStay &&
|
|
31055
|
+
lines.map((roomLine, index) => (React__default["default"].createElement(React__default["default"].Fragment, { key: roomLine.guid },
|
|
31056
|
+
lines.length > 1 && (React__default["default"].createElement("strong", null,
|
|
31057
|
+
translations.SHARED.ROOM,
|
|
31058
|
+
"\u00A0",
|
|
31059
|
+
index + 1)),
|
|
31060
|
+
roomLine.accommodationName && (React__default["default"].createElement("div", { className: "search__filter__itinerary__segment-details__room" },
|
|
31061
|
+
React__default["default"].createElement(Icon, { name: "ui-bed", width: 12, height: 12 }),
|
|
31062
|
+
roomLine.accommodationName)),
|
|
31063
|
+
roomLine.regimeName && (React__default["default"].createElement("div", { className: "search__filter__itinerary__segment-details__room" },
|
|
31064
|
+
React__default["default"].createElement(Icon, { name: "ui-utensils", width: 12, height: 12 }),
|
|
31065
|
+
roomLine.regimeName))))),
|
|
31066
|
+
!isStay && React__default["default"].createElement("p", { className: "search__filter__itinerary__segment-details-text" }, dateFns.format(new Date(line?.from ?? node.from), 'EEE. d MMM yyyy'))))))));
|
|
31067
|
+
};
|
|
31068
|
+
return (React__default["default"].createElement(React__default["default"].Fragment, null, timeline.map((item, index) => {
|
|
31069
|
+
if (item.node.trajectNodeType === CONNECTING_TRAJECT_NODE_TYPE) {
|
|
31070
|
+
return (React__default["default"].createElement(React__default["default"].Fragment, { key: `connect-${item.node.nodeId}` },
|
|
31071
|
+
renderConnectingNode(item),
|
|
31072
|
+
renderOvernight(item, index)));
|
|
31073
|
+
}
|
|
31074
|
+
if (item.node.trajectNodeType === REGULAR_TRAJECT_NODE_TYPE && item.node.serviceType === FLIGHT_SERVICE_TYPE)
|
|
31075
|
+
return renderFlightNode(item);
|
|
31076
|
+
return renderServiceNode(item);
|
|
31077
|
+
})));
|
|
31078
|
+
};
|
|
31079
|
+
|
|
30933
31080
|
const getSegmentIcon = (segment) => {
|
|
30934
31081
|
switch (segment.serviceType) {
|
|
30935
31082
|
case 3:
|
|
@@ -30969,10 +31116,10 @@ const SERVICE_TYPE_PRIORITY = {
|
|
|
30969
31116
|
const getServiceTypePriority = (serviceType) => {
|
|
30970
31117
|
return SERVICE_TYPE_PRIORITY[serviceType ?? -1] ?? 2;
|
|
30971
31118
|
};
|
|
30972
|
-
const Itinerary = ({ isOpen, handleSetIsOpen, isLoading, onEditAccommodation
|
|
31119
|
+
const Itinerary = ({ isOpen, handleSetIsOpen, isLoading, onEditAccommodation }) => {
|
|
30973
31120
|
const context = React.useContext(SearchResultsConfigurationContext);
|
|
30974
31121
|
const translations = getTranslations(context?.languageCode ?? 'en-GB');
|
|
30975
|
-
const { editablePackagingEntry, priceDetails, trajectItineraryView } = reactRedux.useSelector((state) => state.searchResults);
|
|
31122
|
+
const { editablePackagingEntry, priceDetails, priceDetailsFailed, trajectItineraryView } = reactRedux.useSelector((state) => state.searchResults);
|
|
30976
31123
|
const dispatch = reactRedux.useDispatch();
|
|
30977
31124
|
const packagingEntry = editablePackagingEntry ?? context?.packagingEntry;
|
|
30978
31125
|
const sortedLines = React.useMemo(() => {
|
|
@@ -31038,7 +31185,9 @@ const Itinerary = ({ isOpen, handleSetIsOpen, isLoading, onEditAccommodation, on
|
|
|
31038
31185
|
packagingEntry.dossierNumber)),
|
|
31039
31186
|
React__default["default"].createElement("h4", { className: "search__filter__image__title" }, (location || '') + (location && country ? ' - ' : '') + (country || ''))))),
|
|
31040
31187
|
React__default["default"].createElement("div", { className: "search__filter__prices" }, isLoading ? (React__default["default"].createElement(Spinner, { label: translations.PRODUCT.LOADING_PRICE })) : (React__default["default"].createElement(React__default["default"].Fragment, null,
|
|
31041
|
-
React__default["default"].createElement("div", { className: "search__filter__prices__wrapper" },
|
|
31188
|
+
priceDetailsFailed ? (React__default["default"].createElement("div", { className: "search__filter__prices__wrapper" },
|
|
31189
|
+
React__default["default"].createElement("p", { className: "info-message--error" }, translations.SRP.PRICE_DETAILS_FAILED),
|
|
31190
|
+
React__default["default"].createElement("button", { type: "button", className: "cta cta--secondary", onClick: () => dispatch(retryPriceDetails()) }, translations.SRP.TRY_AGAIN))) : (React__default["default"].createElement("div", { className: "search__filter__prices__wrapper" },
|
|
31042
31191
|
React__default["default"].createElement("h3", { className: "search__filter__prices--amount" }, formatPrice$3(pricePerPerson, 'EUR')),
|
|
31043
31192
|
React__default["default"].createElement("p", null, translations.SRP.PACKAGE_PRICE_PER_PERSON),
|
|
31044
31193
|
React__default["default"].createElement("p", null,
|
|
@@ -31047,8 +31196,8 @@ const Itinerary = ({ isOpen, handleSetIsOpen, isLoading, onEditAccommodation, on
|
|
|
31047
31196
|
formatPrice$3(totalPrice, 'EUR'),
|
|
31048
31197
|
" ",
|
|
31049
31198
|
translations.SRP.TOTAL,
|
|
31050
|
-
")"))),
|
|
31051
|
-
isBookedEntryTraject(context?.trajectEntry) ? (React__default["default"].createElement("button", { className: "cta", onClick:
|
|
31199
|
+
")")))),
|
|
31200
|
+
isBookedEntryTraject(context?.trajectEntry) ? (React__default["default"].createElement("button", { className: "cta", onClick: handleConfirm, disabled: priceDetailsFailed || !hasBookedEntryChanges(context?.packagingEntry, packagingEntry) }, translations.SRP.BOOK_AND_PAY)) : isBooked ? (React__default["default"].createElement("span", { className: "cta cta--disabled search__filter__prices--booked", "aria-disabled": "true" }, translations.SHARED.BOOKED)) : (React__default["default"].createElement("button", { className: "cta", onClick: handleConfirm, disabled: priceDetailsFailed }, translations.SRP.BOOK_AND_PAY)),
|
|
31052
31201
|
!!context?.trajectEntry && (React__default["default"].createElement("button", { type: "button", className: "cta cta--secondary", onClick: () => handleTrajectView(!trajectItineraryView) }, trajectItineraryView ? translations.SRP.SHOW_SERVICES : translations.SRP.VIEW_FULL_ITINERARY))))),
|
|
31053
31202
|
React__default["default"].createElement("div", { className: "search__filter__itinerary" },
|
|
31054
31203
|
React__default["default"].createElement("p", null, translations.SRP.DAY_BY_DAY),
|
|
@@ -36347,10 +36496,12 @@ const travellersSettings = {
|
|
|
36347
36496
|
const BookPackagingEntry = ({ activeSearchSeed, isLoading, isConfirmationPage }) => {
|
|
36348
36497
|
const context = React.useContext(SearchResultsConfigurationContext);
|
|
36349
36498
|
const dispatch = reactRedux.useDispatch();
|
|
36350
|
-
const { editablePackagingEntry, priceDetails, currentStep, bookingNumber, selectedPackagingAccoResultCode, packagingAccoResults } = reactRedux.useSelector((state) => state.searchResults);
|
|
36499
|
+
const { editablePackagingEntry, priceDetails, priceDetailsFailed, currentStep, bookingNumber, selectedPackagingAccoResultCode, packagingAccoResults } = reactRedux.useSelector((state) => state.searchResults);
|
|
36500
|
+
// A booked entry opened through its magic link is saved onto its own dossier instead of booked again.
|
|
36501
|
+
const bookedMode = isBookedEntryTraject(context?.trajectEntry);
|
|
36351
36502
|
const [countries, setCountries] = React.useState([]);
|
|
36352
36503
|
const [userValidated, setUserValidated] = React.useState(true);
|
|
36353
|
-
const [remarks, setRemarks] = React.useState('');
|
|
36504
|
+
const [remarks, setRemarks] = React.useState(() => editablePackagingEntry?.remarks ?? '');
|
|
36354
36505
|
const [isSubmitting, setIsSubmitting] = React.useState(false);
|
|
36355
36506
|
const [selectedPackagingAccoResult, setSelectedPackagingAccoResult] = React.useState(null);
|
|
36356
36507
|
const translations = React.useMemo(() => getTranslations(context?.languageCode ?? 'en-GB'), [context?.languageCode]);
|
|
@@ -36410,7 +36561,27 @@ const BookPackagingEntry = ({ activeSearchSeed, isLoading, isConfirmationPage })
|
|
|
36410
36561
|
}, [selectedPackagingAccoResultCode, packagingAccoResults]);
|
|
36411
36562
|
// What the page holds versus what the backend can look up; they differ for a traject.
|
|
36412
36563
|
const bookableEntry = useBookableEntry();
|
|
36413
|
-
|
|
36564
|
+
// What was already typed stays on the entry, so the form comes back filled in when the traveller returns.
|
|
36565
|
+
const handleBackToServices = () => {
|
|
36566
|
+
if (currentStep === 0 && editablePackagingEntry) {
|
|
36567
|
+
dispatch(setEditablePackagingEntry(applyTravelersFormValuesToEditablePackagingEntry(editablePackagingEntry, formik$1.values)));
|
|
36568
|
+
}
|
|
36569
|
+
dispatch(setCurrentStep(0));
|
|
36570
|
+
dispatch(setBookPackagingEntry(false));
|
|
36571
|
+
if (typeof window !== 'undefined')
|
|
36572
|
+
window.scrollTo(0, 0);
|
|
36573
|
+
};
|
|
36574
|
+
// Nothing can be confirmed without a price for the entry as it is now; the confirmation itself needs none.
|
|
36575
|
+
if (context && priceDetailsFailed && currentStep < 2) {
|
|
36576
|
+
return (React__default["default"].createElement("div", { className: "booking" },
|
|
36577
|
+
React__default["default"].createElement("div", { className: "booking__content" },
|
|
36578
|
+
React__default["default"].createElement("div", { className: "booking__panel" },
|
|
36579
|
+
React__default["default"].createElement("p", { className: "info-message--error" }, translations.SRP.PRICE_DETAILS_FAILED),
|
|
36580
|
+
React__default["default"].createElement("div", { className: "booking__navigator" },
|
|
36581
|
+
React__default["default"].createElement("button", { type: "button", className: "cta cta--secondary", onClick: handleBackToServices }, translations.SRP.BACK_TO_SERVICES),
|
|
36582
|
+
React__default["default"].createElement("button", { type: "button", className: "cta", onClick: () => dispatch(retryPriceDetails()) }, translations.SRP.TRY_AGAIN))))));
|
|
36583
|
+
}
|
|
36584
|
+
if (!context || !editablePackagingEntry || !config || (!priceDetails && currentStep < 2))
|
|
36414
36585
|
return React__default["default"].createElement(Spinner, { label: translations.SUMMARY.PROCESS_BOOKING });
|
|
36415
36586
|
const handleSummarySubmit = async (e) => {
|
|
36416
36587
|
e.preventDefault();
|
|
@@ -36420,7 +36591,7 @@ const BookPackagingEntry = ({ activeSearchSeed, isLoading, isConfirmationPage })
|
|
|
36420
36591
|
}
|
|
36421
36592
|
// Held apart from the entry itself so they can be put on both copies of it.
|
|
36422
36593
|
let bookingFields = { remarks };
|
|
36423
|
-
if (context.generatePaymentUrl && typeof window !== 'undefined') {
|
|
36594
|
+
if (!bookedMode && context.generatePaymentUrl && typeof window !== 'undefined') {
|
|
36424
36595
|
const redirectUrl = new URL(window.location.href);
|
|
36425
36596
|
redirectUrl.searchParams.set('bookingConfirmation', 'true');
|
|
36426
36597
|
redirectUrl.searchParams.set('link', '');
|
|
@@ -36437,6 +36608,13 @@ const BookPackagingEntry = ({ activeSearchSeed, isLoading, isConfirmationPage })
|
|
|
36437
36608
|
// The page keeps the entry it works with; the backend gets the one it can look up.
|
|
36438
36609
|
payload: { ...(bookableEntry ?? editablePackagingEntry), ...bookingFields }
|
|
36439
36610
|
};
|
|
36611
|
+
if (bookedMode) {
|
|
36612
|
+
const saved = await build.savePackagingEntry(config, request);
|
|
36613
|
+
dispatch(setBookingNumber(saved.dossierNumber ?? editablePackagingEntry.dossierNumber ?? ''));
|
|
36614
|
+
dispatch(setCurrentStep(2));
|
|
36615
|
+
setIsSubmitting(false);
|
|
36616
|
+
return;
|
|
36617
|
+
}
|
|
36440
36618
|
const bookingResponse = await build.bookPackagingEntry(config, request);
|
|
36441
36619
|
dispatch(setBookingNumber(bookingResponse.number));
|
|
36442
36620
|
if (bookingResponse.paymentUrl) {
|
|
@@ -36459,8 +36637,8 @@ const BookPackagingEntry = ({ activeSearchSeed, isLoading, isConfirmationPage })
|
|
|
36459
36637
|
".\u00A0",
|
|
36460
36638
|
stepLabels[step])) },
|
|
36461
36639
|
isConfirmationPage && isLoading && React__default["default"].createElement(Spinner, { label: translations.SUMMARY.PROCESS_BOOKING }),
|
|
36462
|
-
currentStep === 0 && (React__default["default"].createElement(SharedTravelersForm, { formik: formik$1, translations: translations, travellersSettings: travellersSettings, countries: countries, travelersFirstStep: false, isUnavailable: false, useCompactForm: false, showAgentSelection: false, paxFields: paxFields })),
|
|
36463
|
-
currentStep === 1 && (React__default["default"].createElement(SharedSummary, { translations: translations, travelerFormValues: formik$1.values, isSubmitting: isSubmitting, skipPayment: !context.generatePaymentUrl, userValidated: userValidated, remarks: remarks, enableVoucher: false, allowOption: false, isOffer: false, onUserValidatedChange: setUserValidated, onRemarksChange: setRemarks, onSubmit: handleSummarySubmit, renderOptions: () => renderEditablePackagingEntrySummaryOptions(editablePackagingEntry, priceDetails, translations, selectedPackagingAccoResult?.currencyCode), renderPreviousButton: () => (React__default["default"].createElement("button", { type: "button", title: translations.STEPS.PREVIOUS, onClick: () => dispatch(setCurrentStep(0)), className: "cta cta--secondary" }, translations.STEPS.PREVIOUS)) })),
|
|
36640
|
+
currentStep === 0 && (React__default["default"].createElement(SharedTravelersForm, { formik: formik$1, translations: translations, travellersSettings: travellersSettings, countries: countries, travelersFirstStep: false, isUnavailable: false, useCompactForm: false, showAgentSelection: false, paxFields: paxFields, renderPreviousButton: () => (React__default["default"].createElement("button", { type: "button", title: translations.SRP.BACK_TO_SERVICES, onClick: handleBackToServices, className: "cta cta--secondary" }, translations.SRP.BACK_TO_SERVICES)) })),
|
|
36641
|
+
currentStep === 1 && priceDetails && (React__default["default"].createElement(SharedSummary, { translations: translations, travelerFormValues: formik$1.values, isSubmitting: isSubmitting, skipPayment: bookedMode || !context.generatePaymentUrl, userValidated: userValidated, remarks: remarks, enableVoucher: false, allowOption: false, isOffer: false, onUserValidatedChange: setUserValidated, onRemarksChange: setRemarks, onSubmit: handleSummarySubmit, renderOptions: () => renderEditablePackagingEntrySummaryOptions(editablePackagingEntry, priceDetails, translations, selectedPackagingAccoResult?.currencyCode), renderPreviousButton: () => (React__default["default"].createElement("button", { type: "button", title: translations.STEPS.PREVIOUS, onClick: () => dispatch(setCurrentStep(0)), className: "cta cta--secondary" }, translations.STEPS.PREVIOUS)) })),
|
|
36464
36642
|
currentStep === 2 && (React__default["default"].createElement(SharedConfirmation, { bookingNumber: bookingNumber ?? editablePackagingEntry?.dossierNumber ?? '', isOption: false, isOffer: false, translations: translations.CONFIRMATION })),
|
|
36465
36643
|
currentStep === 3 && React__default["default"].createElement("div", null))),
|
|
36466
36644
|
React__default["default"].createElement("div", { className: "backdrop", id: "backdrop" }),
|
|
@@ -36881,6 +37059,16 @@ const TrajectResults = ({ isLoading }) => {
|
|
|
36881
37059
|
})));
|
|
36882
37060
|
};
|
|
36883
37061
|
|
|
37062
|
+
const getSearchContext = (context, trajectEntry) => ({
|
|
37063
|
+
transactionId: trajectEntry.entry.transactionId,
|
|
37064
|
+
officeId: context.tideConnection.officeId ?? 1,
|
|
37065
|
+
catalogueId: context.searchConfiguration.defaultCatalogueId ?? lodash.first(context.tideConnection.catalogueIds) ?? 1,
|
|
37066
|
+
searchConfigurationId: context.searchConfiguration.id,
|
|
37067
|
+
portalId: context.portalId ?? null,
|
|
37068
|
+
agentId: context.agentId ?? null,
|
|
37069
|
+
language: context.languageCode ?? 'en-GB',
|
|
37070
|
+
rooms: getPackagingRoomsFromEntry(trajectEntry.entry)
|
|
37071
|
+
});
|
|
36884
37072
|
const useTrajectAvailability = () => {
|
|
36885
37073
|
const context = React.useContext(SearchResultsConfigurationContext);
|
|
36886
37074
|
const dispatch = reactRedux.useDispatch();
|
|
@@ -36899,16 +37087,7 @@ const useTrajectAvailability = () => {
|
|
|
36899
37087
|
host: context.tideConnection.host,
|
|
36900
37088
|
apiKey: context.tideConnection.apiKey
|
|
36901
37089
|
};
|
|
36902
|
-
const searchContext =
|
|
36903
|
-
transactionId: trajectEntry.entry.transactionId,
|
|
36904
|
-
officeId: context.tideConnection.officeId ?? 1,
|
|
36905
|
-
catalogueId: context.searchConfiguration.defaultCatalogueId ?? lodash.first(context.tideConnection.catalogueIds) ?? 1,
|
|
36906
|
-
searchConfigurationId: context.searchConfiguration.id,
|
|
36907
|
-
portalId: context.portalId ?? null,
|
|
36908
|
-
agentId: context.agentId ?? null,
|
|
36909
|
-
language: context.languageCode ?? 'en-GB',
|
|
36910
|
-
rooms: getPackagingRoomsFromEntry(trajectEntry.entry)
|
|
36911
|
-
};
|
|
37090
|
+
const searchContext = getSearchContext(context, trajectEntry);
|
|
36912
37091
|
// Flight searches take passengers rather than rooms; the API buckets by age.
|
|
36913
37092
|
// The ids must be the entry's own pax ids: the flight lines this search caches carry them as
|
|
36914
37093
|
// EntryLinePax.PaxId, and booking maps those onto the entry's travelers (PackagingService.SyncPaxIds).
|
|
@@ -37048,30 +37227,9 @@ const useTrajectAvailability = () => {
|
|
|
37048
37227
|
return item.lines.map((x) => x.guid);
|
|
37049
37228
|
}
|
|
37050
37229
|
};
|
|
37051
|
-
const resolveTransferNode = async (node) => {
|
|
37052
|
-
dispatch(setTrajectTransferLoading(node.nodeId));
|
|
37053
|
-
try {
|
|
37054
|
-
const results = (await build.searchPackagingTransfers(config, buildTrajectTransferRequest(node, searchContext), controller.signal)) ?? [];
|
|
37055
|
-
if (controller.signal.aborted)
|
|
37056
|
-
return [];
|
|
37057
|
-
const result = results[0] ?? null;
|
|
37058
|
-
dispatch(setTrajectTransferAvailability({ nodeId: node.nodeId, result: result && buildTrajectTransferLine(node, result) ? result : null }));
|
|
37059
|
-
}
|
|
37060
|
-
catch (error) {
|
|
37061
|
-
if (controller.signal.aborted)
|
|
37062
|
-
return [];
|
|
37063
|
-
console.error('Failed to resolve traject transfer', node.name, error);
|
|
37064
|
-
dispatch(setTrajectTransferAvailability({ nodeId: node.nodeId, result: null }));
|
|
37065
|
-
}
|
|
37066
|
-
return [];
|
|
37067
|
-
};
|
|
37068
37230
|
(async () => {
|
|
37069
37231
|
const resolvable = nodesWithLines.filter((x) => isSearchableTrajectNode(x.node) || isTrajectFlightNode(x.node));
|
|
37070
|
-
const
|
|
37071
|
-
const unresolved = (await Promise.all([
|
|
37072
|
-
...resolvable.map((item) => (isTrajectFlightNode(item.node) ? resolveFlightNode(item) : resolveNode(item))),
|
|
37073
|
-
...transfers.map(resolveTransferNode)
|
|
37074
|
-
])).flat();
|
|
37232
|
+
const unresolved = (await Promise.all(resolvable.map((item) => (isTrajectFlightNode(item.node) ? resolveFlightNode(item) : resolveNode(item))))).flat();
|
|
37075
37233
|
if (controller.signal.aborted)
|
|
37076
37234
|
return;
|
|
37077
37235
|
dispatch(setTrajectResolving(false));
|
|
@@ -37083,22 +37241,87 @@ const useTrajectAvailability = () => {
|
|
|
37083
37241
|
})();
|
|
37084
37242
|
return () => controller.abort();
|
|
37085
37243
|
}, [trajectEntry, showApiItems, hideShowMoreResults]);
|
|
37244
|
+
const transferSearches = React.useRef(new Map());
|
|
37245
|
+
React.useEffect(() => {
|
|
37246
|
+
const searches = transferSearches.current;
|
|
37247
|
+
return () => {
|
|
37248
|
+
searches.forEach((search) => search.controller.abort());
|
|
37249
|
+
searches.clear();
|
|
37250
|
+
};
|
|
37251
|
+
}, [trajectEntry]);
|
|
37252
|
+
React.useEffect(() => {
|
|
37253
|
+
if (!context || !trajectEntry || !editablePackagingEntry)
|
|
37254
|
+
return;
|
|
37255
|
+
const bookedMode = isBookedEntryTraject(trajectEntry);
|
|
37256
|
+
const searches = transferSearches.current;
|
|
37257
|
+
const dropped = getDroppedTrajectNodeIds(trajectEntry, editablePackagingEntry);
|
|
37258
|
+
const lines = editablePackagingEntry.lines ?? [];
|
|
37259
|
+
const config = { host: context.tideConnection.host, apiKey: context.tideConnection.apiKey };
|
|
37260
|
+
const searchContext = getSearchContext(context, trajectEntry);
|
|
37261
|
+
const cancel = (nodeId) => {
|
|
37262
|
+
const search = searches.get(nodeId);
|
|
37263
|
+
if (!search)
|
|
37264
|
+
return;
|
|
37265
|
+
search.controller.abort();
|
|
37266
|
+
searches.delete(nodeId);
|
|
37267
|
+
dispatch(setTrajectTransferAvailability({ nodeId, key: search.key, result: null }));
|
|
37268
|
+
};
|
|
37269
|
+
(trajectEntry.nodes ?? []).filter(isTrajectTransferNode).forEach((node) => {
|
|
37270
|
+
const endpoints = dropped.has(node.nodeId) ? null : getTrajectTransferEndpoints(trajectEntry, lines, node);
|
|
37271
|
+
if (!endpoints || (bookedMode && endpoints.asDrawn))
|
|
37272
|
+
return cancel(node.nodeId);
|
|
37273
|
+
if (searches.get(node.nodeId)?.key === endpoints.key || trajectTransferAvailability[node.nodeId]?.key === endpoints.key)
|
|
37274
|
+
return;
|
|
37275
|
+
cancel(node.nodeId);
|
|
37276
|
+
const controller = new AbortController();
|
|
37277
|
+
const { key } = endpoints;
|
|
37278
|
+
searches.set(node.nodeId, { key, controller });
|
|
37279
|
+
dispatch(setTrajectTransferLoading({ nodeId: node.nodeId, key }));
|
|
37280
|
+
build.searchPackagingTransfers(config, buildTrajectTransferRequest(node, endpoints, searchContext), controller.signal)
|
|
37281
|
+
.then((results) => {
|
|
37282
|
+
if (controller.signal.aborted)
|
|
37283
|
+
return;
|
|
37284
|
+
const result = results?.[0] ?? null;
|
|
37285
|
+
dispatch(setTrajectTransferAvailability({ nodeId: node.nodeId, key, result: result && buildTrajectTransferLine(node, result) ? result : null }));
|
|
37286
|
+
})
|
|
37287
|
+
.catch((error) => {
|
|
37288
|
+
if (controller.signal.aborted)
|
|
37289
|
+
return;
|
|
37290
|
+
console.error('Failed to resolve traject transfer', node.name, error);
|
|
37291
|
+
dispatch(setTrajectTransferAvailability({ nodeId: node.nodeId, key, result: null }));
|
|
37292
|
+
})
|
|
37293
|
+
.finally(() => {
|
|
37294
|
+
if (searches.get(node.nodeId)?.controller === controller)
|
|
37295
|
+
searches.delete(node.nodeId);
|
|
37296
|
+
});
|
|
37297
|
+
});
|
|
37298
|
+
}, [trajectEntry, editablePackagingEntry, trajectTransferAvailability]);
|
|
37086
37299
|
React.useEffect(() => {
|
|
37087
|
-
if (!trajectEntry || !editablePackagingEntry
|
|
37300
|
+
if (!trajectEntry || !editablePackagingEntry)
|
|
37088
37301
|
return;
|
|
37302
|
+
const bookedMode = isBookedEntryTraject(trajectEntry);
|
|
37089
37303
|
const dropped = getDroppedTrajectNodeIds(trajectEntry, editablePackagingEntry);
|
|
37090
|
-
const
|
|
37304
|
+
const lines = editablePackagingEntry.lines ?? [];
|
|
37305
|
+
const guids = new Set(lines.map((line) => line.guid));
|
|
37091
37306
|
const toAdd = [];
|
|
37092
37307
|
const toRemove = [];
|
|
37093
37308
|
(trajectEntry.nodes ?? []).filter(isTrajectTransferNode).forEach((node) => {
|
|
37094
|
-
const
|
|
37095
|
-
|
|
37096
|
-
if (
|
|
37097
|
-
|
|
37098
|
-
|
|
37099
|
-
|
|
37100
|
-
|
|
37101
|
-
|
|
37309
|
+
const endpoints = dropped.has(node.nodeId) ? null : getTrajectTransferEndpoints(trajectEntry, lines, node);
|
|
37310
|
+
let wanted = [];
|
|
37311
|
+
if (endpoints && bookedMode && endpoints.asDrawn) {
|
|
37312
|
+
wanted = getTrajectTransferLines(node, trajectEntry.entry.lines ?? []);
|
|
37313
|
+
}
|
|
37314
|
+
else if (endpoints) {
|
|
37315
|
+
const availability = trajectTransferAvailability[node.nodeId];
|
|
37316
|
+
const line = availability?.key === endpoints.key && availability.result ? buildTrajectTransferLine(node, availability.result) : null;
|
|
37317
|
+
if (line)
|
|
37318
|
+
wanted = [line];
|
|
37319
|
+
}
|
|
37320
|
+
const wantedGuids = new Set(wanted.map((line) => line.guid));
|
|
37321
|
+
getTrajectTransferLines(node, lines)
|
|
37322
|
+
.filter((line) => !wantedGuids.has(line.guid))
|
|
37323
|
+
.forEach((line) => toRemove.push(line.guid));
|
|
37324
|
+
wanted.filter((line) => !guids.has(line.guid)).forEach((line) => toAdd.push(line));
|
|
37102
37325
|
});
|
|
37103
37326
|
if (toRemove.length)
|
|
37104
37327
|
dispatch(removeEditableEntryLines(toRemove));
|
|
@@ -37112,7 +37335,8 @@ const SearchResultsContainer = ({ onBookingStarted }) => {
|
|
|
37112
37335
|
const dispatch = reactRedux.useDispatch();
|
|
37113
37336
|
const context = React.useContext(SearchResultsConfigurationContext);
|
|
37114
37337
|
const translations = getTranslations(context?.languageCode ?? 'en-GB');
|
|
37115
|
-
const { results, filteredResults, packagingAccoResults, filteredPackagingAccoResults, isLoading, flightsLoading, initialFilters, filters, flightFilters, selectedSortType, selectedFlightSortType, selectedSearchResult, selectedPackagingAccoResultCode, flyInIsOpen, packagingAccoSearchDetails, editablePackagingEntry, transactionId, flyInType, packagingFlightResults, confirmedExcursionsByDay, bookPackagingEntry, trajectEditLineGuid, trajectResolving, trajectItineraryView } = reactRedux.useSelector((state) => state.searchResults);
|
|
37338
|
+
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, priceDetailsRequest, trajectTransferAvailability } = reactRedux.useSelector((state) => state.searchResults);
|
|
37339
|
+
const trajectBusy = trajectResolving || Object.values(trajectTransferAvailability).some((transfer) => transfer.status === 'loading');
|
|
37116
37340
|
const isMobile = useMediaQuery('(max-width: 1200px)');
|
|
37117
37341
|
// Resolves live availability per traject node; no-op unless a traject entry is configured.
|
|
37118
37342
|
useTrajectAvailability();
|
|
@@ -37124,7 +37348,6 @@ const SearchResultsContainer = ({ onBookingStarted }) => {
|
|
|
37124
37348
|
const [detailsIsLoading, setDetailsIsLoading] = React.useState(false);
|
|
37125
37349
|
const [pricesAreLoading, setPricesAreLoading] = React.useState(false);
|
|
37126
37350
|
const [itineraryIsLoading, setItineraryIsLoading] = React.useState(false);
|
|
37127
|
-
const [entrySaving, setEntrySaving] = React.useState(false);
|
|
37128
37351
|
const [itineraryOpen, setItineraryOpen] = React.useState(false);
|
|
37129
37352
|
const [isBookingConfirmation, setIsBookingConfirmation] = React.useState(false);
|
|
37130
37353
|
const [selectedAccommodationSeed, setSelectedAccommodationSeed] = React.useState(null);
|
|
@@ -37882,40 +38105,16 @@ const SearchResultsContainer = ({ onBookingStarted }) => {
|
|
|
37882
38105
|
React.useEffect(() => {
|
|
37883
38106
|
setInitialFiltersSet(false);
|
|
37884
38107
|
}, [activeSearchSeed]);
|
|
37885
|
-
const handleSaveEntry = async () => {
|
|
37886
|
-
if (!context || !bookableEntry || entrySaving)
|
|
37887
|
-
return;
|
|
37888
|
-
setEntrySaving(true);
|
|
37889
|
-
try {
|
|
37890
|
-
const config = {
|
|
37891
|
-
host: context.tideConnection.host,
|
|
37892
|
-
apiKey: context.tideConnection.apiKey
|
|
37893
|
-
};
|
|
37894
|
-
const request = {
|
|
37895
|
-
language: context.languageCode ?? 'en-GB',
|
|
37896
|
-
officeId: context.tideConnection.officeId,
|
|
37897
|
-
catalogueId: context.searchConfiguration.defaultCatalogueId ?? lodash.first(context?.tideConnection?.catalogueIds) ?? 1,
|
|
37898
|
-
agentId: context.agentId,
|
|
37899
|
-
payload: bookableEntry
|
|
37900
|
-
};
|
|
37901
|
-
await build.savePackagingEntry(config, request);
|
|
37902
|
-
if (typeof window !== 'undefined')
|
|
37903
|
-
window.location.reload();
|
|
37904
|
-
}
|
|
37905
|
-
catch (err) {
|
|
37906
|
-
console.error('Failed to save the entry', err);
|
|
37907
|
-
setEntrySaving(false);
|
|
37908
|
-
}
|
|
37909
|
-
};
|
|
37910
38108
|
React.useEffect(() => {
|
|
37911
38109
|
const fetchPriceDetails = async () => {
|
|
37912
38110
|
if (!context || !editablePackagingEntry || lodash.isEmpty(editablePackagingEntry.lines))
|
|
37913
38111
|
return;
|
|
37914
38112
|
// A traject resolves one node at a time. Pricing each step would show a total that climbs
|
|
37915
38113
|
// as the trip fills in, so the whole trip is priced once, when the last node is in.
|
|
37916
|
-
if (context.trajectEntry &&
|
|
38114
|
+
if (context.trajectEntry && trajectBusy)
|
|
37917
38115
|
return;
|
|
37918
38116
|
setPricesAreLoading(true);
|
|
38117
|
+
dispatch(setPriceDetailsFailed(false));
|
|
37919
38118
|
try {
|
|
37920
38119
|
const config = {
|
|
37921
38120
|
host: context.tideConnection.host,
|
|
@@ -37934,18 +38133,21 @@ const SearchResultsContainer = ({ onBookingStarted }) => {
|
|
|
37934
38133
|
}
|
|
37935
38134
|
catch (err) {
|
|
37936
38135
|
console.error('Error fetching price details', err);
|
|
38136
|
+
// A price from before the change would be wrong for the entry as it is now.
|
|
38137
|
+
dispatch(setPriceDetails(null));
|
|
38138
|
+
dispatch(setPriceDetailsFailed(true));
|
|
37937
38139
|
setPricesAreLoading(false);
|
|
37938
38140
|
}
|
|
37939
38141
|
};
|
|
37940
38142
|
fetchPriceDetails();
|
|
37941
|
-
}, [editablePackagingEntry, bookableEntry,
|
|
38143
|
+
}, [editablePackagingEntry, bookableEntry, trajectBusy, priceDetailsRequest]);
|
|
37942
38144
|
React.useEffect(() => {
|
|
37943
38145
|
const fetchItinerary = async () => {
|
|
37944
38146
|
if (!context || !context.packagingEntry || !editablePackagingEntry || lodash.isEmpty(editablePackagingEntry.lines))
|
|
37945
38147
|
return;
|
|
37946
38148
|
// While a traject is being composed its own day-by-day column is the itinerary; the generated
|
|
37947
38149
|
// one is what the traveller reads the finished trip back from, so it waits until then.
|
|
37948
|
-
if (context.trajectEntry && (
|
|
38150
|
+
if (context.trajectEntry && (trajectBusy || !trajectItineraryView))
|
|
37949
38151
|
return;
|
|
37950
38152
|
setItineraryIsLoading(true);
|
|
37951
38153
|
try {
|
|
@@ -37970,7 +38172,7 @@ const SearchResultsContainer = ({ onBookingStarted }) => {
|
|
|
37970
38172
|
}
|
|
37971
38173
|
};
|
|
37972
38174
|
fetchItinerary();
|
|
37973
|
-
}, [editablePackagingEntry, bookableEntry,
|
|
38175
|
+
}, [editablePackagingEntry, bookableEntry, trajectBusy, trajectItineraryView]);
|
|
37974
38176
|
// Flight selection
|
|
37975
38177
|
// const [selectedOutwardKey, setSelectedOutwardKey] = useState<string | null>(null);
|
|
37976
38178
|
// const [selectedReturnKey, setSelectedReturnKey] = useState<string | null>(null);
|
|
@@ -38348,7 +38550,7 @@ const SearchResultsContainer = ({ onBookingStarted }) => {
|
|
|
38348
38550
|
context.searchConfiguration.qsmType != build.PortalQsmType.AccommodationAndFlight && context.showFilters && (React__default["default"].createElement(Filters, { initialFilters: initialFilters, filters: filters, isOpen: filtersOpen, handleSetIsOpen: () => setFiltersOpen(!filtersOpen),
|
|
38349
38551
|
// handleApplyFilters={() => setSearchTrigger((prev) => prev + 1)}
|
|
38350
38552
|
isLoading: isLoading, setFilters: (filters) => dispatch(setFilters(filters)), resetFilters: (filters) => dispatch(resetFilters(filters)) })),
|
|
38351
|
-
context.searchConfiguration.qsmType === build.PortalQsmType.AccommodationAndFlight && (React__default["default"].createElement(Itinerary, { isOpen: itineraryOpen, handleSetIsOpen: () => setItineraryOpen(!itineraryOpen), isLoading: isLoading || pricesAreLoading || flightsLoading || itineraryIsLoading ||
|
|
38553
|
+
context.searchConfiguration.qsmType === build.PortalQsmType.AccommodationAndFlight && (React__default["default"].createElement(Itinerary, { isOpen: itineraryOpen, handleSetIsOpen: () => setItineraryOpen(!itineraryOpen), isLoading: isLoading || pricesAreLoading || flightsLoading || itineraryIsLoading || trajectBusy, onEditAccommodation: handleEditAccommodation })),
|
|
38352
38554
|
React__default["default"].createElement("div", { className: "search__results" },
|
|
38353
38555
|
isMobile && (React__default["default"].createElement("div", { className: "search__result-row" },
|
|
38354
38556
|
React__default["default"].createElement("div", { className: "search__results__actions" },
|