@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-esm/index.js
CHANGED
|
@@ -461,7 +461,10 @@ var SRP$f = {
|
|
|
461
461
|
LOADING_ACCOMMODATIONS: "جارٍ تحميل الإقامات...",
|
|
462
462
|
LOADING_ITINERARY: "جارٍ تحميل خط سير الرحلة...",
|
|
463
463
|
LOADING_OPTIONS: "جارٍ تحميل الخيارات...",
|
|
464
|
-
BOOK_AND_PAY: "احجز وادفع"
|
|
464
|
+
BOOK_AND_PAY: "احجز وادفع",
|
|
465
|
+
PRICE_DETAILS_FAILED: "تعذر حساب السعر.",
|
|
466
|
+
TRY_AGAIN: "حاول مرة أخرى",
|
|
467
|
+
BACK_TO_SERVICES: "العودة إلى الخدمات"
|
|
465
468
|
};
|
|
466
469
|
var ITINERARY$f = {
|
|
467
470
|
DAY: "اليوم",
|
|
@@ -968,7 +971,10 @@ var SRP$e = {
|
|
|
968
971
|
LOADING_ACCOMMODATIONS: "Indlæser indkvarteringer...",
|
|
969
972
|
LOADING_ITINERARY: "Indlæser rejseplan...",
|
|
970
973
|
LOADING_OPTIONS: "Indlæser muligheder...",
|
|
971
|
-
BOOK_AND_PAY: "Book og betal"
|
|
974
|
+
BOOK_AND_PAY: "Book og betal",
|
|
975
|
+
PRICE_DETAILS_FAILED: "Prisen kunne ikke beregnes.",
|
|
976
|
+
TRY_AGAIN: "Prøv igen",
|
|
977
|
+
BACK_TO_SERVICES: "Tilbage til ydelser"
|
|
972
978
|
};
|
|
973
979
|
var ITINERARY$e = {
|
|
974
980
|
DAY: "Dag",
|
|
@@ -1475,7 +1481,10 @@ var SRP$d = {
|
|
|
1475
1481
|
LOADING_ACCOMMODATIONS: "Lade Unterkünfte...",
|
|
1476
1482
|
LOADING_ITINERARY: "Lade Reiseverlauf...",
|
|
1477
1483
|
LOADING_OPTIONS: "Lade Optionen...",
|
|
1478
|
-
BOOK_AND_PAY: "Buchen und bezahlen"
|
|
1484
|
+
BOOK_AND_PAY: "Buchen und bezahlen",
|
|
1485
|
+
PRICE_DETAILS_FAILED: "Der Preis konnte nicht berechnet werden.",
|
|
1486
|
+
TRY_AGAIN: "Erneut versuchen",
|
|
1487
|
+
BACK_TO_SERVICES: "Zurück zu den Leistungen"
|
|
1479
1488
|
};
|
|
1480
1489
|
var ITINERARY$d = {
|
|
1481
1490
|
DAY: "Tag",
|
|
@@ -1986,7 +1995,10 @@ var SRP$c = {
|
|
|
1986
1995
|
LOADING_ACCOMMODATIONS: "Loading accommodations...",
|
|
1987
1996
|
LOADING_ITINERARY: "Loading itinerary...",
|
|
1988
1997
|
LOADING_OPTIONS: "Loading options...",
|
|
1989
|
-
BOOK_AND_PAY: "Book and pay"
|
|
1998
|
+
BOOK_AND_PAY: "Book and pay",
|
|
1999
|
+
PRICE_DETAILS_FAILED: "The price could not be calculated.",
|
|
2000
|
+
TRY_AGAIN: "Try again",
|
|
2001
|
+
BACK_TO_SERVICES: "Back to services"
|
|
1990
2002
|
};
|
|
1991
2003
|
var ITINERARY$c = {
|
|
1992
2004
|
DAY: "Day",
|
|
@@ -2493,7 +2505,10 @@ var SRP$b = {
|
|
|
2493
2505
|
LOADING_ACCOMMODATIONS: "Cargando alojamientos...",
|
|
2494
2506
|
LOADING_ITINERARY: "Cargando itinerario...",
|
|
2495
2507
|
LOADING_OPTIONS: "Cargando opciones...",
|
|
2496
|
-
BOOK_AND_PAY: "Reservar y pagar"
|
|
2508
|
+
BOOK_AND_PAY: "Reservar y pagar",
|
|
2509
|
+
PRICE_DETAILS_FAILED: "No se ha podido calcular el precio.",
|
|
2510
|
+
TRY_AGAIN: "Volver a intentarlo",
|
|
2511
|
+
BACK_TO_SERVICES: "Volver a los servicios"
|
|
2497
2512
|
};
|
|
2498
2513
|
var ITINERARY$b = {
|
|
2499
2514
|
DAY: "Día",
|
|
@@ -3004,7 +3019,10 @@ var SRP$a = {
|
|
|
3004
3019
|
LOADING_ACCOMMODATIONS: "Chargement des hébergements...",
|
|
3005
3020
|
LOADING_ITINERARY: "Chargement de l’itinéraire...",
|
|
3006
3021
|
LOADING_OPTIONS: "Chargement des options...",
|
|
3007
|
-
BOOK_AND_PAY: "Réserver et payer"
|
|
3022
|
+
BOOK_AND_PAY: "Réserver et payer",
|
|
3023
|
+
PRICE_DETAILS_FAILED: "Le prix n’a pas pu être calculé.",
|
|
3024
|
+
TRY_AGAIN: "Réessayer",
|
|
3025
|
+
BACK_TO_SERVICES: "Retour aux services"
|
|
3008
3026
|
};
|
|
3009
3027
|
var ITINERARY$a = {
|
|
3010
3028
|
DAY: "Jour",
|
|
@@ -3511,7 +3529,10 @@ var SRP$9 = {
|
|
|
3511
3529
|
LOADING_ACCOMMODATIONS: "Chargement des hébergements...",
|
|
3512
3530
|
LOADING_ITINERARY: "Chargement de l’itinéraire...",
|
|
3513
3531
|
LOADING_OPTIONS: "Chargement des options...",
|
|
3514
|
-
BOOK_AND_PAY: "Réserver et payer"
|
|
3532
|
+
BOOK_AND_PAY: "Réserver et payer",
|
|
3533
|
+
PRICE_DETAILS_FAILED: "Le prix n’a pas pu être calculé.",
|
|
3534
|
+
TRY_AGAIN: "Réessayer",
|
|
3535
|
+
BACK_TO_SERVICES: "Retour aux services"
|
|
3515
3536
|
};
|
|
3516
3537
|
var ITINERARY$9 = {
|
|
3517
3538
|
DAY: "Jour",
|
|
@@ -4018,7 +4039,10 @@ var SRP$8 = {
|
|
|
4018
4039
|
LOADING_ACCOMMODATIONS: "Hleðst inn í gistingar...",
|
|
4019
4040
|
LOADING_ITINERARY: "Hleðst inn í ferðaráætlun...",
|
|
4020
4041
|
LOADING_OPTIONS: "Hleðst inn í valkosti...",
|
|
4021
|
-
BOOK_AND_PAY: "Panta og greiða"
|
|
4042
|
+
BOOK_AND_PAY: "Panta og greiða",
|
|
4043
|
+
PRICE_DETAILS_FAILED: "Ekki tókst að reikna verðið.",
|
|
4044
|
+
TRY_AGAIN: "Reyna aftur",
|
|
4045
|
+
BACK_TO_SERVICES: "Aftur í þjónustu"
|
|
4022
4046
|
};
|
|
4023
4047
|
var ITINERARY$8 = {
|
|
4024
4048
|
DAY: "Dagur",
|
|
@@ -4525,7 +4549,10 @@ var SRP$7 = {
|
|
|
4525
4549
|
LOADING_ACCOMMODATIONS: "Caricamento degli alloggi...",
|
|
4526
4550
|
LOADING_ITINERARY: "Caricamento dell'itinerario...",
|
|
4527
4551
|
LOADING_OPTIONS: "Caricamento delle opzioni...",
|
|
4528
|
-
BOOK_AND_PAY: "Prenota e paga"
|
|
4552
|
+
BOOK_AND_PAY: "Prenota e paga",
|
|
4553
|
+
PRICE_DETAILS_FAILED: "Non è stato possibile calcolare il prezzo.",
|
|
4554
|
+
TRY_AGAIN: "Riprova",
|
|
4555
|
+
BACK_TO_SERVICES: "Torna ai servizi"
|
|
4529
4556
|
};
|
|
4530
4557
|
var ITINERARY$7 = {
|
|
4531
4558
|
DAY: "Giorno",
|
|
@@ -5036,7 +5063,10 @@ var SRP$6 = {
|
|
|
5036
5063
|
LOADING_ACCOMMODATIONS: "Accommodaties laden...",
|
|
5037
5064
|
LOADING_ITINERARY: "Reisplan laden...",
|
|
5038
5065
|
LOADING_OPTIONS: "Opties laden...",
|
|
5039
|
-
BOOK_AND_PAY: "Boek en betaal"
|
|
5066
|
+
BOOK_AND_PAY: "Boek en betaal",
|
|
5067
|
+
PRICE_DETAILS_FAILED: "De prijs kon niet berekend worden.",
|
|
5068
|
+
TRY_AGAIN: "Opnieuw proberen",
|
|
5069
|
+
BACK_TO_SERVICES: "Terug naar diensten"
|
|
5040
5070
|
};
|
|
5041
5071
|
var ITINERARY$6 = {
|
|
5042
5072
|
DAY: "Dag",
|
|
@@ -5543,7 +5573,10 @@ var SRP$5 = {
|
|
|
5543
5573
|
LOADING_ACCOMMODATIONS: "Accommodaties laden...",
|
|
5544
5574
|
LOADING_ITINERARY: "Reisplan laden...",
|
|
5545
5575
|
LOADING_OPTIONS: "Opties laden...",
|
|
5546
|
-
BOOK_AND_PAY: "Boeken en betalen"
|
|
5576
|
+
BOOK_AND_PAY: "Boeken en betalen",
|
|
5577
|
+
PRICE_DETAILS_FAILED: "De prijs kon niet worden berekend.",
|
|
5578
|
+
TRY_AGAIN: "Opnieuw proberen",
|
|
5579
|
+
BACK_TO_SERVICES: "Terug naar diensten"
|
|
5547
5580
|
};
|
|
5548
5581
|
var ITINERARY$5 = {
|
|
5549
5582
|
DAY: "Dag",
|
|
@@ -6050,7 +6083,10 @@ var SRP$4 = {
|
|
|
6050
6083
|
LOADING_ACCOMMODATIONS: "Laster overnattinger...",
|
|
6051
6084
|
LOADING_ITINERARY: "Laster reiserute...",
|
|
6052
6085
|
LOADING_OPTIONS: "Laster alternativer...",
|
|
6053
|
-
BOOK_AND_PAY: "Bestill og betal"
|
|
6086
|
+
BOOK_AND_PAY: "Bestill og betal",
|
|
6087
|
+
PRICE_DETAILS_FAILED: "Prisen kunne ikke beregnes.",
|
|
6088
|
+
TRY_AGAIN: "Prøv igjen",
|
|
6089
|
+
BACK_TO_SERVICES: "Tilbake til tjenester"
|
|
6054
6090
|
};
|
|
6055
6091
|
var ITINERARY$4 = {
|
|
6056
6092
|
DAY: "Dag",
|
|
@@ -6557,7 +6593,10 @@ var SRP$3 = {
|
|
|
6557
6593
|
LOADING_ACCOMMODATIONS: "Ładowanie zakwaterowań...",
|
|
6558
6594
|
LOADING_ITINERARY: "Ładowanie planu podróży...",
|
|
6559
6595
|
LOADING_OPTIONS: "Ładowanie opcji...",
|
|
6560
|
-
BOOK_AND_PAY: "Zarezerwuj i zapłać"
|
|
6596
|
+
BOOK_AND_PAY: "Zarezerwuj i zapłać",
|
|
6597
|
+
PRICE_DETAILS_FAILED: "Nie udało się obliczyć ceny.",
|
|
6598
|
+
TRY_AGAIN: "Spróbuj ponownie",
|
|
6599
|
+
BACK_TO_SERVICES: "Wróć do usług"
|
|
6561
6600
|
};
|
|
6562
6601
|
var ITINERARY$3 = {
|
|
6563
6602
|
DAY: "Dzień",
|
|
@@ -7064,7 +7103,10 @@ var SRP$2 = {
|
|
|
7064
7103
|
LOADING_ACCOMMODATIONS: "A carregar alojamentos...",
|
|
7065
7104
|
LOADING_ITINERARY: "A carregar itinerário...",
|
|
7066
7105
|
LOADING_OPTIONS: "A carregar opções...",
|
|
7067
|
-
BOOK_AND_PAY: "Reservar e pagar"
|
|
7106
|
+
BOOK_AND_PAY: "Reservar e pagar",
|
|
7107
|
+
PRICE_DETAILS_FAILED: "Não foi possível calcular o preço.",
|
|
7108
|
+
TRY_AGAIN: "Tentar novamente",
|
|
7109
|
+
BACK_TO_SERVICES: "Voltar aos serviços"
|
|
7068
7110
|
};
|
|
7069
7111
|
var ITINERARY$2 = {
|
|
7070
7112
|
DAY: "Dia",
|
|
@@ -7571,7 +7613,10 @@ var SRP$1 = {
|
|
|
7571
7613
|
LOADING_ACCOMMODATIONS: "Laddar boenden...",
|
|
7572
7614
|
LOADING_ITINERARY: "Laddar resplan...",
|
|
7573
7615
|
LOADING_OPTIONS: "Laddar alternativ...",
|
|
7574
|
-
BOOK_AND_PAY: "Boka och betala"
|
|
7616
|
+
BOOK_AND_PAY: "Boka och betala",
|
|
7617
|
+
PRICE_DETAILS_FAILED: "Priset kunde inte beräknas.",
|
|
7618
|
+
TRY_AGAIN: "Försök igen",
|
|
7619
|
+
BACK_TO_SERVICES: "Tillbaka till tjänster"
|
|
7575
7620
|
};
|
|
7576
7621
|
var ITINERARY$1 = {
|
|
7577
7622
|
DAY: "Dag",
|
|
@@ -8078,7 +8123,10 @@ var SRP = {
|
|
|
8078
8123
|
LOADING_ACCOMMODATIONS: "宿泊施設を読み込み中...",
|
|
8079
8124
|
LOADING_ITINERARY: "旅程を読み込み中...",
|
|
8080
8125
|
LOADING_OPTIONS: "オプションを読み込み中...",
|
|
8081
|
-
BOOK_AND_PAY: "予約して支払う"
|
|
8126
|
+
BOOK_AND_PAY: "予約して支払う",
|
|
8127
|
+
PRICE_DETAILS_FAILED: "料金を計算できませんでした。",
|
|
8128
|
+
TRY_AGAIN: "再試行",
|
|
8129
|
+
BACK_TO_SERVICES: "サービスに戻る"
|
|
8082
8130
|
};
|
|
8083
8131
|
var ITINERARY = {
|
|
8084
8132
|
DAY: "日",
|
|
@@ -29206,6 +29254,8 @@ const initialState$1 = {
|
|
|
29206
29254
|
selectedFlightDetails: null,
|
|
29207
29255
|
bookingPackageDetails: null,
|
|
29208
29256
|
priceDetails: null,
|
|
29257
|
+
priceDetailsFailed: false,
|
|
29258
|
+
priceDetailsRequest: 0,
|
|
29209
29259
|
isLoading: false,
|
|
29210
29260
|
flightsLoading: false,
|
|
29211
29261
|
selectedSortType: null,
|
|
@@ -29366,6 +29416,12 @@ const searchResultsSlice = createSlice({
|
|
|
29366
29416
|
setPriceDetails(state, action) {
|
|
29367
29417
|
state.priceDetails = action.payload;
|
|
29368
29418
|
},
|
|
29419
|
+
setPriceDetailsFailed(state, action) {
|
|
29420
|
+
state.priceDetailsFailed = action.payload;
|
|
29421
|
+
},
|
|
29422
|
+
retryPriceDetails(state) {
|
|
29423
|
+
state.priceDetailsRequest += 1;
|
|
29424
|
+
},
|
|
29369
29425
|
setItinerary(state, action) {
|
|
29370
29426
|
state.itinerary = action.payload;
|
|
29371
29427
|
},
|
|
@@ -29525,11 +29581,12 @@ const searchResultsSlice = createSlice({
|
|
|
29525
29581
|
flight.selectedGuid = null;
|
|
29526
29582
|
},
|
|
29527
29583
|
setTrajectTransferLoading(state, action) {
|
|
29528
|
-
|
|
29584
|
+
const { nodeId, key } = action.payload;
|
|
29585
|
+
state.trajectTransferAvailability[nodeId] = { nodeId, key, status: 'loading', result: null };
|
|
29529
29586
|
},
|
|
29530
29587
|
setTrajectTransferAvailability(state, action) {
|
|
29531
|
-
const { nodeId, result } = action.payload;
|
|
29532
|
-
state.trajectTransferAvailability[nodeId] = { nodeId, status: result ? 'resolved' : 'unavailable', result };
|
|
29588
|
+
const { nodeId, key, result } = action.payload;
|
|
29589
|
+
state.trajectTransferAvailability[nodeId] = { nodeId, key, status: result ? 'resolved' : 'unavailable', result };
|
|
29533
29590
|
},
|
|
29534
29591
|
setTrajectDaySuggestionsLoading(state, action) {
|
|
29535
29592
|
const dayOrder = action.payload;
|
|
@@ -29576,7 +29633,7 @@ const searchResultsSlice = createSlice({
|
|
|
29576
29633
|
}
|
|
29577
29634
|
}
|
|
29578
29635
|
});
|
|
29579
|
-
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;
|
|
29636
|
+
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;
|
|
29580
29637
|
var searchResultsReducer = searchResultsSlice.reducer;
|
|
29581
29638
|
|
|
29582
29639
|
const ItemPicker = ({ items, selection, selectedSortByType, label, placeholder, classModifier, onPick, valueFormatter }) => {
|
|
@@ -29741,211 +29798,9 @@ const getPackagingRequestRoomsFromBookingRooms = (rooms) => {
|
|
|
29741
29798
|
});
|
|
29742
29799
|
};
|
|
29743
29800
|
|
|
29744
|
-
// Administrative service lines (fees, insurances, reductions, ...) live on the entry but say
|
|
29745
|
-
// nothing about the journey itself, so the itinerary never draws them.
|
|
29746
|
-
const HIDDEN_ITINERARY_SERVICE_TYPES = new Set([
|
|
29747
|
-
build.ServiceType.insurance,
|
|
29748
|
-
build.ServiceType.extraService,
|
|
29749
|
-
build.ServiceType.serviceFee,
|
|
29750
|
-
build.ServiceType.gift,
|
|
29751
|
-
build.ServiceType.reduction,
|
|
29752
|
-
build.ServiceType.fee,
|
|
29753
|
-
build.ServiceType.visum,
|
|
29754
|
-
build.ServiceType.voucher,
|
|
29755
|
-
build.ServiceType.specialServices
|
|
29756
|
-
]);
|
|
29757
|
-
// An inactive member of a line group is an alternative the traveller did not pick — not travelled.
|
|
29758
|
-
const isMeaningfulItineraryLine = (line) => {
|
|
29759
|
-
return isActiveEntryLine(line) && !HIDDEN_ITINERARY_SERVICE_TYPES.has(line.serviceType ?? -1);
|
|
29760
|
-
};
|
|
29761
|
-
const getFlightLines = (flight) => {
|
|
29762
|
-
return flight?.flightInformation?.flightLines ?? [];
|
|
29763
|
-
};
|
|
29764
|
-
const getDepartureTime = (flight) => {
|
|
29765
|
-
const firstLine = first(getFlightLines(flight));
|
|
29766
|
-
if (!firstLine?.departureTime)
|
|
29767
|
-
return '';
|
|
29768
|
-
return firstLine.departureTime.slice(0, 5);
|
|
29769
|
-
};
|
|
29770
|
-
const getArrivalTime = (flight) => {
|
|
29771
|
-
const lastLine = last(getFlightLines(flight));
|
|
29772
|
-
if (!lastLine?.arrivalTime)
|
|
29773
|
-
return '';
|
|
29774
|
-
return lastLine.arrivalTime.slice(0, 5);
|
|
29775
|
-
};
|
|
29776
|
-
const getDuration = (flight) => {
|
|
29777
|
-
const lines = getFlightLines(flight);
|
|
29778
|
-
if (!lines.length)
|
|
29779
|
-
return '';
|
|
29780
|
-
const totalTicks = lines.reduce((sum, line) => sum + (line.durationInTicks ?? 0), 0) || 0;
|
|
29781
|
-
if (!totalTicks)
|
|
29782
|
-
return '';
|
|
29783
|
-
const seconds = totalTicks / 10_000_000;
|
|
29784
|
-
const hours = Math.floor(seconds / 3600);
|
|
29785
|
-
const minutes = Math.floor((seconds % 3600) / 60);
|
|
29786
|
-
return `${hours}h ${minutes.toString().padStart(2, '0')}m`;
|
|
29787
|
-
};
|
|
29788
|
-
const numberOfNights = (segment) => {
|
|
29789
|
-
return differenceInCalendarDays(new Date(segment.to), new Date(segment.from));
|
|
29790
|
-
};
|
|
29791
|
-
const CHARTER_SERVICE_TYPE = 10;
|
|
29792
|
-
const CRUISE_SERVICE_TYPE = 11;
|
|
29793
|
-
const FERRY_SERVICE_TYPE = 12;
|
|
29794
|
-
const TRANSFER_SERVICE_TYPE$1 = 13;
|
|
29795
|
-
const BUS_SERVICE_TYPE = 14;
|
|
29796
|
-
const CAR_SERVICE_TYPE = 17;
|
|
29797
|
-
const CAMPER_SERVICE_TYPE = 18;
|
|
29798
|
-
const TRAIN_SERVICE_TYPE = 20;
|
|
29799
|
-
const RENTAL_SERVICE_TYPE = 22;
|
|
29800
|
-
const RESTAURANT_SERVICE_TYPE = 23;
|
|
29801
|
-
const EVENT_SERVICE_TYPE = 24;
|
|
29802
|
-
const TRAINING_SERVICE_TYPE = 25;
|
|
29803
|
-
/** Icon per service type, so every node on the itinerary reads at a glance. */
|
|
29804
|
-
const getServiceTypeIcon = (serviceType) => {
|
|
29805
|
-
switch (serviceType) {
|
|
29806
|
-
case ACCOMMODATION_SERVICE_TYPE:
|
|
29807
|
-
return 'ui-hotel';
|
|
29808
|
-
case EXCURSION_SERVICE_TYPE:
|
|
29809
|
-
return 'ui-excursion';
|
|
29810
|
-
case FLIGHT_SERVICE_TYPE:
|
|
29811
|
-
case CHARTER_SERVICE_TYPE:
|
|
29812
|
-
return 'ui-plane';
|
|
29813
|
-
case RESTAURANT_SERVICE_TYPE:
|
|
29814
|
-
return 'ui-utensils';
|
|
29815
|
-
case CRUISE_SERVICE_TYPE:
|
|
29816
|
-
case FERRY_SERVICE_TYPE:
|
|
29817
|
-
return 'ui-ship';
|
|
29818
|
-
case CAR_SERVICE_TYPE:
|
|
29819
|
-
case CAMPER_SERVICE_TYPE:
|
|
29820
|
-
case RENTAL_SERVICE_TYPE:
|
|
29821
|
-
return 'ui-car';
|
|
29822
|
-
case BUS_SERVICE_TYPE:
|
|
29823
|
-
return 'ui-bus';
|
|
29824
|
-
case TRAIN_SERVICE_TYPE:
|
|
29825
|
-
return 'ui-train';
|
|
29826
|
-
case TRANSFER_SERVICE_TYPE$1:
|
|
29827
|
-
return 'ui-transfer';
|
|
29828
|
-
case EVENT_SERVICE_TYPE:
|
|
29829
|
-
return 'ui-ticket';
|
|
29830
|
-
case TRAINING_SERVICE_TYPE:
|
|
29831
|
-
return 'ui-group';
|
|
29832
|
-
default:
|
|
29833
|
-
return 'ui-location';
|
|
29834
|
-
}
|
|
29835
|
-
};
|
|
29836
|
-
const getServiceTypeBadgeModifier = (serviceType) => {
|
|
29837
|
-
switch (serviceType) {
|
|
29838
|
-
case FLIGHT_SERVICE_TYPE:
|
|
29839
|
-
case CHARTER_SERVICE_TYPE:
|
|
29840
|
-
case CRUISE_SERVICE_TYPE:
|
|
29841
|
-
case FERRY_SERVICE_TYPE:
|
|
29842
|
-
return 'flight';
|
|
29843
|
-
case ACCOMMODATION_SERVICE_TYPE:
|
|
29844
|
-
return 'hotel';
|
|
29845
|
-
case EXCURSION_SERVICE_TYPE:
|
|
29846
|
-
case EVENT_SERVICE_TYPE:
|
|
29847
|
-
return 'excursion';
|
|
29848
|
-
case RESTAURANT_SERVICE_TYPE:
|
|
29849
|
-
return 'restaurant';
|
|
29850
|
-
default:
|
|
29851
|
-
return '';
|
|
29852
|
-
}
|
|
29853
|
-
};
|
|
29854
|
-
// DurationType, as the SDK numbers it.
|
|
29855
|
-
const DURATION_HOURS = 0;
|
|
29856
|
-
const DURATION_DAYS = 1;
|
|
29857
|
-
const DURATION_NIGHTS = 2;
|
|
29858
|
-
const DURATION_MINUTES = 3;
|
|
29859
|
-
/**
|
|
29860
|
-
* How long a node takes, in the same shape the flight legs are written in — an hour count only
|
|
29861
|
-
* once there is one, so a short drive reads as plain minutes.
|
|
29862
|
-
*/
|
|
29863
|
-
const getTrajectNodeDuration = (node) => {
|
|
29864
|
-
const minutes = toMinutes(node);
|
|
29865
|
-
if (!minutes)
|
|
29866
|
-
return '';
|
|
29867
|
-
const hours = Math.floor(minutes / 60);
|
|
29868
|
-
const rest = minutes % 60;
|
|
29869
|
-
return hours ? `${hours}h ${rest.toString().padStart(2, '0')}m` : `${rest} min`;
|
|
29870
|
-
};
|
|
29871
|
-
const toMinutes = (node) => {
|
|
29872
|
-
const duration = node.duration ?? 0;
|
|
29873
|
-
if (duration <= 0)
|
|
29874
|
-
return 0;
|
|
29875
|
-
switch (node.durationType) {
|
|
29876
|
-
case DURATION_MINUTES:
|
|
29877
|
-
return duration;
|
|
29878
|
-
case DURATION_HOURS:
|
|
29879
|
-
return duration * 60;
|
|
29880
|
-
case DURATION_DAYS:
|
|
29881
|
-
case DURATION_NIGHTS:
|
|
29882
|
-
return duration * 24 * 60;
|
|
29883
|
-
default:
|
|
29884
|
-
return 0;
|
|
29885
|
-
}
|
|
29886
|
-
};
|
|
29887
|
-
// RouteTransportType, as the SDK numbers it. It says how a leg is travelled, which is not the
|
|
29888
|
-
// same question as what was sold: cycling and walking have no service type of their own.
|
|
29889
|
-
const TRANSPORT_PRIVATE = 1;
|
|
29890
|
-
const TRANSPORT_PUBLIC = 2;
|
|
29891
|
-
const TRANSPORT_RENTAL_CAR = 3;
|
|
29892
|
-
const TRANSPORT_BUS = 4;
|
|
29893
|
-
const TRANSPORT_CAMPER = 5;
|
|
29894
|
-
const TRANSPORT_CAR = 6;
|
|
29895
|
-
const TRANSPORT_CHARTER = 7;
|
|
29896
|
-
const TRANSPORT_CRUISE = 8;
|
|
29897
|
-
const TRANSPORT_FERRY = 9;
|
|
29898
|
-
const TRANSPORT_FLIGHT = 10;
|
|
29899
|
-
const TRANSPORT_TAXI = 11;
|
|
29900
|
-
const TRANSPORT_TRAIN = 12;
|
|
29901
|
-
const TRANSPORT_TRANSFER = 13;
|
|
29902
|
-
const TRANSPORT_WALKING = 15;
|
|
29903
|
-
const TRANSPORT_CYCLING = 16;
|
|
29904
|
-
/** Icon per way of travelling, where the route records one. */
|
|
29905
|
-
const getTransportTypeIcon = (transportType) => {
|
|
29906
|
-
switch (transportType) {
|
|
29907
|
-
case TRANSPORT_WALKING:
|
|
29908
|
-
return 'ui-walking';
|
|
29909
|
-
case TRANSPORT_CYCLING:
|
|
29910
|
-
return 'ui-cycling';
|
|
29911
|
-
case TRANSPORT_PRIVATE:
|
|
29912
|
-
case TRANSPORT_RENTAL_CAR:
|
|
29913
|
-
case TRANSPORT_CAMPER:
|
|
29914
|
-
case TRANSPORT_CAR:
|
|
29915
|
-
return 'ui-car';
|
|
29916
|
-
case TRANSPORT_TAXI:
|
|
29917
|
-
case TRANSPORT_TRANSFER:
|
|
29918
|
-
return 'ui-transfer';
|
|
29919
|
-
case TRANSPORT_PUBLIC:
|
|
29920
|
-
case TRANSPORT_BUS:
|
|
29921
|
-
return 'ui-bus';
|
|
29922
|
-
case TRANSPORT_TRAIN:
|
|
29923
|
-
return 'ui-train';
|
|
29924
|
-
case TRANSPORT_CRUISE:
|
|
29925
|
-
case TRANSPORT_FERRY:
|
|
29926
|
-
return 'ui-ship';
|
|
29927
|
-
case TRANSPORT_FLIGHT:
|
|
29928
|
-
case TRANSPORT_CHARTER:
|
|
29929
|
-
return 'ui-plane';
|
|
29930
|
-
default:
|
|
29931
|
-
return null;
|
|
29932
|
-
}
|
|
29933
|
-
};
|
|
29934
|
-
const POI_TRAJECT_NODE_TYPE = 2;
|
|
29935
|
-
/**
|
|
29936
|
-
* A point of interest is a place on the map rather than a service, so it gets a marker. Anything
|
|
29937
|
-
* else goes by how it is travelled where the route says so — a leg on foot or by bike is sold as
|
|
29938
|
-
* nothing, and its service type falls back to a transfer — and by what it is where it does not.
|
|
29939
|
-
*/
|
|
29940
|
-
const getTrajectNodeIcon = (node) => {
|
|
29941
|
-
if (node.trajectNodeType === POI_TRAJECT_NODE_TYPE)
|
|
29942
|
-
return 'ui-location';
|
|
29943
|
-
return getTransportTypeIcon(node.transportType) ?? getServiceTypeIcon(node.serviceType);
|
|
29944
|
-
};
|
|
29945
|
-
const getTrajectNodeBadgeModifier = (node) => node.trajectNodeType === POI_TRAJECT_NODE_TYPE ? 'poi' : getServiceTypeBadgeModifier(node.serviceType);
|
|
29946
|
-
|
|
29947
29801
|
const REGULAR_TRAJECT_NODE_TYPE = 0;
|
|
29948
29802
|
const CONNECTING_TRAJECT_NODE_TYPE = 1;
|
|
29803
|
+
const POI_TRAJECT_NODE_TYPE = 2;
|
|
29949
29804
|
const isSearchableTrajectNode = (node) => node.serviceType === ACCOMMODATION_SERVICE_TYPE || node.serviceType === EXCURSION_SERVICE_TYPE;
|
|
29950
29805
|
const isTrajectFlightNode = (node) => node.serviceType === FLIGHT_SERVICE_TYPE;
|
|
29951
29806
|
const getTrajectNodesWithLines = (trajectEntry) => {
|
|
@@ -29960,7 +29815,7 @@ const getTrajectNodesWithLines = (trajectEntry) => {
|
|
|
29960
29815
|
.filter((x) => !!x.line);
|
|
29961
29816
|
};
|
|
29962
29817
|
// Qite.Offer.Enums.ServiceType.Transfer: a traject's connecting node is booked as one.
|
|
29963
|
-
const TRANSFER_SERVICE_TYPE = 13;
|
|
29818
|
+
const TRANSFER_SERVICE_TYPE$1 = 13;
|
|
29964
29819
|
// Qite.Offer.Enums.RequirementType. A Mandatory (0) stop, and one RequiredInGroup (3) — one of the node's
|
|
29965
29820
|
// alternatives has to be booked — always keep a selection; only these two may be taken off the trip.
|
|
29966
29821
|
const OPTIONAL_REQUIREMENT_TYPE = 1;
|
|
@@ -29988,7 +29843,53 @@ const getDroppedTrajectNodeIds = (trajectEntry, entry) => {
|
|
|
29988
29843
|
return dropped;
|
|
29989
29844
|
};
|
|
29990
29845
|
const isTrajectTransferNode = (node) => node.trajectNodeType === CONNECTING_TRAJECT_NODE_TYPE && !!node.pickUp && !!node.dropOff;
|
|
29991
|
-
const
|
|
29846
|
+
const getStopLine = (node, lines) => {
|
|
29847
|
+
const guids = node.lineGuids?.length ? node.lineGuids : [node.lineGuid];
|
|
29848
|
+
const own = lines.find((line) => guids.includes(line.guid));
|
|
29849
|
+
const groupKey = own ? getLineGroupKey(own) : '';
|
|
29850
|
+
return (groupKey ? getGroupLines(lines, groupKey)[0]?.[0] : undefined) ?? own;
|
|
29851
|
+
};
|
|
29852
|
+
const getMovedStopCoordinate = (node, originalLines, lines) => {
|
|
29853
|
+
if (node.trajectNodeType !== REGULAR_TRAJECT_NODE_TYPE || !isSearchableTrajectNode(node))
|
|
29854
|
+
return null;
|
|
29855
|
+
const current = getStopLine(node, lines);
|
|
29856
|
+
if (!current || current.latitude == null || current.longitude == null)
|
|
29857
|
+
return null;
|
|
29858
|
+
if (current.productCode === getStopLine(node, originalLines)?.productCode)
|
|
29859
|
+
return null;
|
|
29860
|
+
return { name: current.productName, latitude: current.latitude, longitude: current.longitude };
|
|
29861
|
+
};
|
|
29862
|
+
const getNightStay = (nodes, lines, night) => nodes.find((node) => {
|
|
29863
|
+
if (node.trajectNodeType !== REGULAR_TRAJECT_NODE_TYPE || node.serviceType !== ACCOMMODATION_SERVICE_TYPE)
|
|
29864
|
+
return false;
|
|
29865
|
+
const line = getStopLine(node, lines);
|
|
29866
|
+
const nights = Math.max(differenceInCalendarDays(new Date(line?.to ?? node.to), new Date(line?.from ?? node.from)), 1);
|
|
29867
|
+
return node.dayOrder <= night && night < node.dayOrder + nights;
|
|
29868
|
+
});
|
|
29869
|
+
const getTrajectTransferEndpoints = (trajectEntry, lines, connector) => {
|
|
29870
|
+
if (!isTrajectTransferNode(connector))
|
|
29871
|
+
return null;
|
|
29872
|
+
const routeNodes = trajectEntry.entry.routeNodes ?? [];
|
|
29873
|
+
const onRoute = routeNodes.find((node) => node.nodeId === connector.nodeId);
|
|
29874
|
+
const nodes = onRoute ? routeNodes : trajectEntry.nodes ?? [];
|
|
29875
|
+
const at = onRoute ?? connector;
|
|
29876
|
+
const originalLines = trajectEntry.entry.lines ?? [];
|
|
29877
|
+
const day = nodes.filter((node) => node.dayOrder === at.dayOrder).sort((a, b) => a.order - b.order);
|
|
29878
|
+
const index = day.indexOf(at);
|
|
29879
|
+
const isStop = (node) => node.trajectNodeType !== CONNECTING_TRAJECT_NODE_TYPE;
|
|
29880
|
+
const previous = day.slice(0, index).filter(isStop).pop() ?? getNightStay(nodes, lines, at.dayOrder - 1);
|
|
29881
|
+
const next = day.slice(index + 1).find(isStop) ?? getNightStay(nodes, lines, at.dayOrder);
|
|
29882
|
+
const pickUp = (previous && getMovedStopCoordinate(previous, originalLines, lines)) ?? connector.pickUp;
|
|
29883
|
+
const dropOff = (next && getMovedStopCoordinate(next, originalLines, lines)) ?? connector.dropOff;
|
|
29884
|
+
const place = (coordinate) => `${coordinate.latitude.toFixed(5)},${coordinate.longitude.toFixed(5)}`;
|
|
29885
|
+
return {
|
|
29886
|
+
pickUp,
|
|
29887
|
+
dropOff,
|
|
29888
|
+
key: `${place(pickUp)}|${place(dropOff)}|${toDateOnlyString(connector.from)}`,
|
|
29889
|
+
asDrawn: pickUp === connector.pickUp && dropOff === connector.dropOff
|
|
29890
|
+
};
|
|
29891
|
+
};
|
|
29892
|
+
const buildTrajectTransferRequest = (node, endpoints, context) => ({
|
|
29992
29893
|
transactionId: context.transactionId,
|
|
29993
29894
|
officeId: context.officeId,
|
|
29994
29895
|
agentId: context.agentId ?? null,
|
|
@@ -29996,10 +29897,11 @@ const buildTrajectTransferRequest = (node, context) => ({
|
|
|
29996
29897
|
searchConfigurationId: context.searchConfigurationId,
|
|
29997
29898
|
language: context.language,
|
|
29998
29899
|
date: node.from,
|
|
29999
|
-
pickUp:
|
|
30000
|
-
dropOff:
|
|
29900
|
+
pickUp: endpoints.pickUp,
|
|
29901
|
+
dropOff: endpoints.dropOff,
|
|
30001
29902
|
travellers: context.rooms.flatMap((room) => room.travellers)
|
|
30002
29903
|
});
|
|
29904
|
+
const getTrajectTransferLines = (node, lines) => lines.filter((line) => line.trajectNodeId === node.nodeId || (!!node.lineGuids?.length && node.lineGuids.includes(line.guid)));
|
|
30003
29905
|
const buildTrajectTransferLine = (node, result) => {
|
|
30004
29906
|
const option = result.rooms?.[0]?.options?.find((x) => x.isSelected) ?? result.rooms?.[0]?.options?.[0];
|
|
30005
29907
|
if (!option?.guid)
|
|
@@ -30013,7 +29915,7 @@ const buildTrajectTransferLine = (node, result) => {
|
|
|
30013
29915
|
from: result.fromDate ?? node.from,
|
|
30014
29916
|
to: result.toDate ?? node.to,
|
|
30015
29917
|
moment: node.moment ?? '',
|
|
30016
|
-
serviceType: TRANSFER_SERVICE_TYPE,
|
|
29918
|
+
serviceType: TRANSFER_SERVICE_TYPE$1,
|
|
30017
29919
|
productName: result.name,
|
|
30018
29920
|
productCode: result.code,
|
|
30019
29921
|
accommodationName: option.accommodationName,
|
|
@@ -30388,7 +30290,15 @@ const withCachedEntryLineGuids = (entry, trajectEntry, nodeAvailability, flightA
|
|
|
30388
30290
|
const bookedMode = !!trajectEntry.entry.magicLink;
|
|
30389
30291
|
const editableByGuid = new Map((entry.lines ?? []).map((line) => [line.guid, line]));
|
|
30390
30292
|
const keepsOwnGuid = (guid) => bookedMode && !editableByGuid.get(guid)?.isChanged;
|
|
30293
|
+
// A swapped line tells which stop of the dossier's route it replaces, so saving links the route to it.
|
|
30294
|
+
const routeNodeIds = new Map();
|
|
30295
|
+
if (bookedMode)
|
|
30296
|
+
(trajectEntry.entry.routeNodes ?? []).forEach((node) => (node.lineGuids ?? []).forEach((guid) => routeNodeIds.set(guid, node.nodeId)));
|
|
30297
|
+
const trajectNodeIds = new Map();
|
|
30391
30298
|
getTrajectNodesWithLines(trajectEntry).forEach((item) => {
|
|
30299
|
+
const routeNodeId = item.lines.map((line) => routeNodeIds.get(line.guid)).find((id) => id !== undefined);
|
|
30300
|
+
if (routeNodeId !== undefined)
|
|
30301
|
+
item.lines.filter((line) => !keepsOwnGuid(line.guid)).forEach((line) => trajectNodeIds.set(line.guid, routeNodeId));
|
|
30392
30302
|
if (isTrajectFlightNode(item.node)) {
|
|
30393
30303
|
if (keepsOwnGuid(item.line.guid))
|
|
30394
30304
|
return;
|
|
@@ -30411,14 +30321,15 @@ const withCachedEntryLineGuids = (entry, trajectEntry, nodeAvailability, flightA
|
|
|
30411
30321
|
cachedGuids.set(line.guid, guid);
|
|
30412
30322
|
});
|
|
30413
30323
|
});
|
|
30414
|
-
if (!cachedGuids.size)
|
|
30324
|
+
if (!cachedGuids.size && !trajectNodeIds.size)
|
|
30415
30325
|
return entry;
|
|
30416
30326
|
return {
|
|
30417
30327
|
...entry,
|
|
30418
30328
|
lines: (entry.lines ?? []).map((line) => ({
|
|
30419
30329
|
...line,
|
|
30420
30330
|
guid: cachedGuids.get(line.guid) ?? line.guid,
|
|
30421
|
-
parentGuid: (line.parentGuid && cachedGuids.get(line.parentGuid)) ?? line.parentGuid
|
|
30331
|
+
parentGuid: (line.parentGuid && cachedGuids.get(line.parentGuid)) ?? line.parentGuid,
|
|
30332
|
+
trajectNodeId: trajectNodeIds.get(line.guid) ?? line.trajectNodeId
|
|
30422
30333
|
}))
|
|
30423
30334
|
};
|
|
30424
30335
|
};
|
|
@@ -30452,214 +30363,214 @@ const applyFlightToLine = (line, flight) => {
|
|
|
30452
30363
|
const isExternalResult = (result) => result.vendorId != null || result.vendorConfigurationId != null;
|
|
30453
30364
|
const isExternalCandidate = (candidate) => candidate.externalVendorId != null || candidate.vendor != null;
|
|
30454
30365
|
|
|
30455
|
-
|
|
30456
|
-
|
|
30457
|
-
|
|
30458
|
-
|
|
30366
|
+
// Administrative service lines (fees, insurances, reductions, ...) live on the entry but say
|
|
30367
|
+
// nothing about the journey itself, so the itinerary never draws them.
|
|
30368
|
+
const HIDDEN_ITINERARY_SERVICE_TYPES = new Set([
|
|
30369
|
+
build.ServiceType.insurance,
|
|
30370
|
+
build.ServiceType.extraService,
|
|
30371
|
+
build.ServiceType.serviceFee,
|
|
30372
|
+
build.ServiceType.gift,
|
|
30373
|
+
build.ServiceType.reduction,
|
|
30374
|
+
build.ServiceType.fee,
|
|
30375
|
+
build.ServiceType.visum,
|
|
30376
|
+
build.ServiceType.voucher,
|
|
30377
|
+
build.ServiceType.specialServices
|
|
30378
|
+
]);
|
|
30379
|
+
// An inactive member of a line group is an alternative the traveller did not pick — not travelled.
|
|
30380
|
+
const isMeaningfulItineraryLine = (line) => {
|
|
30381
|
+
return isActiveEntryLine(line) && !HIDDEN_ITINERARY_SERVICE_TYPES.has(line.serviceType ?? -1);
|
|
30459
30382
|
};
|
|
30460
|
-
const
|
|
30461
|
-
|
|
30462
|
-
|
|
30463
|
-
|
|
30464
|
-
|
|
30465
|
-
|
|
30466
|
-
|
|
30467
|
-
|
|
30468
|
-
|
|
30469
|
-
|
|
30470
|
-
|
|
30471
|
-
|
|
30472
|
-
|
|
30473
|
-
|
|
30474
|
-
|
|
30475
|
-
|
|
30476
|
-
|
|
30477
|
-
|
|
30478
|
-
|
|
30479
|
-
|
|
30480
|
-
|
|
30481
|
-
|
|
30482
|
-
|
|
30483
|
-
|
|
30484
|
-
|
|
30485
|
-
|
|
30486
|
-
|
|
30487
|
-
|
|
30488
|
-
|
|
30489
|
-
|
|
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
|
-
|
|
30383
|
+
const getFlightLines = (flight) => {
|
|
30384
|
+
return flight?.flightInformation?.flightLines ?? [];
|
|
30385
|
+
};
|
|
30386
|
+
const getDepartureTime = (flight) => {
|
|
30387
|
+
const firstLine = first(getFlightLines(flight));
|
|
30388
|
+
if (!firstLine?.departureTime)
|
|
30389
|
+
return '';
|
|
30390
|
+
return firstLine.departureTime.slice(0, 5);
|
|
30391
|
+
};
|
|
30392
|
+
const getArrivalTime = (flight) => {
|
|
30393
|
+
const lastLine = last(getFlightLines(flight));
|
|
30394
|
+
if (!lastLine?.arrivalTime)
|
|
30395
|
+
return '';
|
|
30396
|
+
return lastLine.arrivalTime.slice(0, 5);
|
|
30397
|
+
};
|
|
30398
|
+
const getDuration = (flight) => {
|
|
30399
|
+
const lines = getFlightLines(flight);
|
|
30400
|
+
if (!lines.length)
|
|
30401
|
+
return '';
|
|
30402
|
+
const totalTicks = lines.reduce((sum, line) => sum + (line.durationInTicks ?? 0), 0) || 0;
|
|
30403
|
+
if (!totalTicks)
|
|
30404
|
+
return '';
|
|
30405
|
+
const seconds = totalTicks / 10_000_000;
|
|
30406
|
+
const hours = Math.floor(seconds / 3600);
|
|
30407
|
+
const minutes = Math.floor((seconds % 3600) / 60);
|
|
30408
|
+
return `${hours}h ${minutes.toString().padStart(2, '0')}m`;
|
|
30409
|
+
};
|
|
30410
|
+
const numberOfNights = (segment) => {
|
|
30411
|
+
return differenceInCalendarDays(new Date(segment.to), new Date(segment.from));
|
|
30412
|
+
};
|
|
30413
|
+
const CHARTER_SERVICE_TYPE = 10;
|
|
30414
|
+
const CRUISE_SERVICE_TYPE = 11;
|
|
30415
|
+
const FERRY_SERVICE_TYPE = 12;
|
|
30416
|
+
const TRANSFER_SERVICE_TYPE = 13;
|
|
30417
|
+
const BUS_SERVICE_TYPE = 14;
|
|
30418
|
+
const CAR_SERVICE_TYPE = 17;
|
|
30419
|
+
const CAMPER_SERVICE_TYPE = 18;
|
|
30420
|
+
const TRAIN_SERVICE_TYPE = 20;
|
|
30421
|
+
const RENTAL_SERVICE_TYPE = 22;
|
|
30422
|
+
const RESTAURANT_SERVICE_TYPE = 23;
|
|
30423
|
+
const EVENT_SERVICE_TYPE = 24;
|
|
30424
|
+
const TRAINING_SERVICE_TYPE = 25;
|
|
30425
|
+
/** Icon per service type, so every node on the itinerary reads at a glance. */
|
|
30426
|
+
const getServiceTypeIcon = (serviceType) => {
|
|
30427
|
+
switch (serviceType) {
|
|
30428
|
+
case ACCOMMODATION_SERVICE_TYPE:
|
|
30429
|
+
return 'ui-hotel';
|
|
30430
|
+
case EXCURSION_SERVICE_TYPE:
|
|
30431
|
+
return 'ui-excursion';
|
|
30432
|
+
case FLIGHT_SERVICE_TYPE:
|
|
30433
|
+
case CHARTER_SERVICE_TYPE:
|
|
30434
|
+
return 'ui-plane';
|
|
30435
|
+
case RESTAURANT_SERVICE_TYPE:
|
|
30436
|
+
return 'ui-utensils';
|
|
30437
|
+
case CRUISE_SERVICE_TYPE:
|
|
30438
|
+
case FERRY_SERVICE_TYPE:
|
|
30439
|
+
return 'ui-ship';
|
|
30440
|
+
case CAR_SERVICE_TYPE:
|
|
30441
|
+
case CAMPER_SERVICE_TYPE:
|
|
30442
|
+
case RENTAL_SERVICE_TYPE:
|
|
30443
|
+
return 'ui-car';
|
|
30444
|
+
case BUS_SERVICE_TYPE:
|
|
30445
|
+
return 'ui-bus';
|
|
30446
|
+
case TRAIN_SERVICE_TYPE:
|
|
30447
|
+
return 'ui-train';
|
|
30448
|
+
case TRANSFER_SERVICE_TYPE:
|
|
30449
|
+
return 'ui-transfer';
|
|
30450
|
+
case EVENT_SERVICE_TYPE:
|
|
30451
|
+
return 'ui-ticket';
|
|
30452
|
+
case TRAINING_SERVICE_TYPE:
|
|
30453
|
+
return 'ui-group';
|
|
30454
|
+
default:
|
|
30455
|
+
return 'ui-location';
|
|
30456
|
+
}
|
|
30457
|
+
};
|
|
30458
|
+
const getServiceTypeBadgeModifier = (serviceType) => {
|
|
30459
|
+
switch (serviceType) {
|
|
30460
|
+
case FLIGHT_SERVICE_TYPE:
|
|
30461
|
+
case CHARTER_SERVICE_TYPE:
|
|
30462
|
+
case CRUISE_SERVICE_TYPE:
|
|
30463
|
+
case FERRY_SERVICE_TYPE:
|
|
30464
|
+
return 'flight';
|
|
30465
|
+
case ACCOMMODATION_SERVICE_TYPE:
|
|
30466
|
+
return 'hotel';
|
|
30467
|
+
case EXCURSION_SERVICE_TYPE:
|
|
30468
|
+
case EVENT_SERVICE_TYPE:
|
|
30469
|
+
return 'excursion';
|
|
30470
|
+
case RESTAURANT_SERVICE_TYPE:
|
|
30471
|
+
return 'restaurant';
|
|
30472
|
+
default:
|
|
30473
|
+
return '';
|
|
30474
|
+
}
|
|
30475
|
+
};
|
|
30476
|
+
// DurationType, as the SDK numbers it.
|
|
30477
|
+
const DURATION_HOURS = 0;
|
|
30478
|
+
const DURATION_DAYS = 1;
|
|
30479
|
+
const DURATION_NIGHTS = 2;
|
|
30480
|
+
const DURATION_MINUTES = 3;
|
|
30481
|
+
/**
|
|
30482
|
+
* How long a node takes, in the same shape the flight legs are written in — an hour count only
|
|
30483
|
+
* once there is one, so a short drive reads as plain minutes.
|
|
30484
|
+
*/
|
|
30485
|
+
const getTrajectNodeDuration = (node) => {
|
|
30486
|
+
const minutes = toMinutes(node);
|
|
30487
|
+
if (!minutes)
|
|
30488
|
+
return '';
|
|
30489
|
+
const hours = Math.floor(minutes / 60);
|
|
30490
|
+
const rest = minutes % 60;
|
|
30491
|
+
return hours ? `${hours}h ${rest.toString().padStart(2, '0')}m` : `${rest} min`;
|
|
30492
|
+
};
|
|
30493
|
+
const toMinutes = (node) => {
|
|
30494
|
+
const duration = node.duration ?? 0;
|
|
30495
|
+
if (duration <= 0)
|
|
30496
|
+
return 0;
|
|
30497
|
+
switch (node.durationType) {
|
|
30498
|
+
case DURATION_MINUTES:
|
|
30499
|
+
return duration;
|
|
30500
|
+
case DURATION_HOURS:
|
|
30501
|
+
return duration * 60;
|
|
30502
|
+
case DURATION_DAYS:
|
|
30503
|
+
case DURATION_NIGHTS:
|
|
30504
|
+
return duration * 24 * 60;
|
|
30505
|
+
default:
|
|
30506
|
+
return 0;
|
|
30507
|
+
}
|
|
30508
|
+
};
|
|
30509
|
+
// RouteTransportType, as the SDK numbers it. It says how a leg is travelled, which is not the
|
|
30510
|
+
// same question as what was sold: cycling and walking have no service type of their own.
|
|
30511
|
+
const TRANSPORT_PRIVATE = 1;
|
|
30512
|
+
const TRANSPORT_PUBLIC = 2;
|
|
30513
|
+
const TRANSPORT_RENTAL_CAR = 3;
|
|
30514
|
+
const TRANSPORT_BUS = 4;
|
|
30515
|
+
const TRANSPORT_CAMPER = 5;
|
|
30516
|
+
const TRANSPORT_CAR = 6;
|
|
30517
|
+
const TRANSPORT_CHARTER = 7;
|
|
30518
|
+
const TRANSPORT_CRUISE = 8;
|
|
30519
|
+
const TRANSPORT_FERRY = 9;
|
|
30520
|
+
const TRANSPORT_FLIGHT = 10;
|
|
30521
|
+
const TRANSPORT_TAXI = 11;
|
|
30522
|
+
const TRANSPORT_TRAIN = 12;
|
|
30523
|
+
const TRANSPORT_TRANSFER = 13;
|
|
30524
|
+
const TRANSPORT_WALKING = 15;
|
|
30525
|
+
const TRANSPORT_CYCLING = 16;
|
|
30526
|
+
/** Icon per way of travelling, where the route records one. */
|
|
30527
|
+
const getTransportTypeIcon = (transportType) => {
|
|
30528
|
+
switch (transportType) {
|
|
30529
|
+
case TRANSPORT_WALKING:
|
|
30530
|
+
return 'ui-walking';
|
|
30531
|
+
case TRANSPORT_CYCLING:
|
|
30532
|
+
return 'ui-cycling';
|
|
30533
|
+
case TRANSPORT_PRIVATE:
|
|
30534
|
+
case TRANSPORT_RENTAL_CAR:
|
|
30535
|
+
case TRANSPORT_CAMPER:
|
|
30536
|
+
case TRANSPORT_CAR:
|
|
30537
|
+
return 'ui-car';
|
|
30538
|
+
case TRANSPORT_TAXI:
|
|
30539
|
+
case TRANSPORT_TRANSFER:
|
|
30540
|
+
return 'ui-transfer';
|
|
30541
|
+
case TRANSPORT_PUBLIC:
|
|
30542
|
+
case TRANSPORT_BUS:
|
|
30543
|
+
return 'ui-bus';
|
|
30544
|
+
case TRANSPORT_TRAIN:
|
|
30545
|
+
return 'ui-train';
|
|
30546
|
+
case TRANSPORT_CRUISE:
|
|
30547
|
+
case TRANSPORT_FERRY:
|
|
30548
|
+
return 'ui-ship';
|
|
30549
|
+
case TRANSPORT_FLIGHT:
|
|
30550
|
+
case TRANSPORT_CHARTER:
|
|
30551
|
+
return 'ui-plane';
|
|
30552
|
+
default:
|
|
30563
30553
|
return null;
|
|
30564
|
-
|
|
30565
|
-
React__default.createElement("div", { className: "search__filter__itinerary__transport-timeline" },
|
|
30566
|
-
React__default.createElement("div", { className: "search__filter__itinerary__transport-timeline-line" })),
|
|
30567
|
-
React__default.createElement("div", { className: "search__filter__itinerary__transport-item" },
|
|
30568
|
-
renderDate(item.node.from),
|
|
30569
|
-
React__default.createElement("div", { className: badgeClass('transport', getServiceTypeBadgeModifier(ACCOMMODATION_SERVICE_TYPE)) },
|
|
30570
|
-
React__default.createElement(Icon, { name: "ui-bed", height: 15 })),
|
|
30571
|
-
React__default.createElement("div", { className: "search__filter__itinerary__transport-details" },
|
|
30572
|
-
React__default.createElement("h6", null, stay.name),
|
|
30573
|
-
React__default.createElement("p", { className: "search__filter__itinerary__transport-details-time" },
|
|
30574
|
-
stay.night + 1,
|
|
30575
|
-
" ",
|
|
30576
|
-
translations.SRP.OF,
|
|
30577
|
-
" ",
|
|
30578
|
-
stay.nights,
|
|
30579
|
-
" ",
|
|
30580
|
-
translations.SRP.NIGHTS)))));
|
|
30581
|
-
};
|
|
30582
|
-
const renderFlightNode = ({ node, lines }) => {
|
|
30583
|
-
const line = lines[0];
|
|
30584
|
-
return (React__default.createElement("div", { className: "search__filter__itinerary__transport", key: `flight-${node.nodeId}` },
|
|
30585
|
-
React__default.createElement("div", { className: "search__filter__itinerary__transport-timeline" },
|
|
30586
|
-
React__default.createElement("div", { className: "search__filter__itinerary__transport-timeline-line" })),
|
|
30587
|
-
React__default.createElement("div", { className: "search__filter__itinerary__transport-item" },
|
|
30588
|
-
renderDate(line?.from ?? node.from),
|
|
30589
|
-
React__default.createElement("div", { className: badgeClass('transport', getServiceTypeBadgeModifier(FLIGHT_SERVICE_TYPE)) },
|
|
30590
|
-
React__default.createElement(Icon, { name: "ui-plane", height: 15 })),
|
|
30591
|
-
React__default.createElement("div", { className: "search__filter__itinerary__transport-details" },
|
|
30592
|
-
React__default.createElement("h6", null, line?.productName || node.name),
|
|
30593
|
-
!!getDepartureTime(line) && (React__default.createElement("p", { className: "search__filter__itinerary__transport-details-plane" },
|
|
30594
|
-
React__default.createElement("span", null,
|
|
30595
|
-
React__default.createElement(Icon, { name: "ui-plane-depart", height: 14 }),
|
|
30596
|
-
" ",
|
|
30597
|
-
React__default.createElement("strong", null, getDepartureTime(line))),
|
|
30598
|
-
' ',
|
|
30599
|
-
"-",
|
|
30600
|
-
' ',
|
|
30601
|
-
React__default.createElement("span", null,
|
|
30602
|
-
React__default.createElement(Icon, { name: "ui-plane-arrive", height: 14 }),
|
|
30603
|
-
" ",
|
|
30604
|
-
React__default.createElement("strong", null, getArrivalTime(line))))),
|
|
30605
|
-
!!getDuration(line) && (React__default.createElement("p", { className: "search__filter__itinerary__transport-details-time" },
|
|
30606
|
-
React__default.createElement("span", null,
|
|
30607
|
-
React__default.createElement(Icon, { name: "ui-clock", height: 20 }),
|
|
30608
|
-
" ",
|
|
30609
|
-
getDuration(line))))))));
|
|
30610
|
-
};
|
|
30611
|
-
const renderServiceNode = ({ node, lines }) => {
|
|
30612
|
-
const line = lines[0];
|
|
30613
|
-
const isStay = node.serviceType === ACCOMMODATION_SERVICE_TYPE;
|
|
30614
|
-
const nights = isStay ? nightsOf(node, line) : 0;
|
|
30615
|
-
return (React__default.createElement("div", { className: "search__filter__itinerary__segments", key: `node-${node.nodeId}` },
|
|
30616
|
-
React__default.createElement("div", { className: "search__filter__itinerary__segments__wrapper" },
|
|
30617
|
-
React__default.createElement("div", { className: "search__filter__itinerary__segments-timeline" },
|
|
30618
|
-
React__default.createElement("div", { className: "search__filter__itinerary__segments-timeline-line" })),
|
|
30619
|
-
React__default.createElement("div", { className: "search__filter__itinerary__segment" },
|
|
30620
|
-
React__default.createElement("div", { className: "search__filter__itinerary__segment-item" },
|
|
30621
|
-
React__default.createElement("div", { className: `search__filter__itinerary__segment-date ${isStay && nights > 0 ? 'search__filter__itinerary__segment-nights' : ''}` }, isStay && nights > 0 ? (React__default.createElement(React__default.Fragment, null,
|
|
30622
|
-
React__default.createElement("p", { className: "search__filter__itinerary__segment-date-date" },
|
|
30623
|
-
React__default.createElement("strong", null, nights)),
|
|
30624
|
-
React__default.createElement(Icon, { name: "ui-moon", width: 16, height: 16 }))) : (React__default.createElement(React__default.Fragment, null,
|
|
30625
|
-
React__default.createElement("p", { className: "search__filter__itinerary__segment-date-date" },
|
|
30626
|
-
React__default.createElement("strong", null, format$2(new Date(line?.from ?? node.from), 'd'))),
|
|
30627
|
-
React__default.createElement("p", null, format$2(new Date(line?.from ?? node.from), 'MMM'))))),
|
|
30628
|
-
React__default.createElement("div", { className: badgeClass('segment', getTrajectNodeBadgeModifier(node)) },
|
|
30629
|
-
React__default.createElement(Icon, { name: getTrajectNodeIcon(node), width: 15, height: 15 })),
|
|
30630
|
-
React__default.createElement("div", { className: "search__filter__itinerary__segment-details" },
|
|
30631
|
-
React__default.createElement("h6", null, line?.productName || node.preferredAccommodationName || node.name),
|
|
30632
|
-
isStay &&
|
|
30633
|
-
lines.map((roomLine, index) => (React__default.createElement(React__default.Fragment, { key: roomLine.guid },
|
|
30634
|
-
lines.length > 1 && (React__default.createElement("strong", null,
|
|
30635
|
-
translations.SHARED.ROOM,
|
|
30636
|
-
"\u00A0",
|
|
30637
|
-
index + 1)),
|
|
30638
|
-
roomLine.accommodationName && (React__default.createElement("div", { className: "search__filter__itinerary__segment-details__room" },
|
|
30639
|
-
React__default.createElement(Icon, { name: "ui-bed", width: 12, height: 12 }),
|
|
30640
|
-
roomLine.accommodationName)),
|
|
30641
|
-
roomLine.regimeName && (React__default.createElement("div", { className: "search__filter__itinerary__segment-details__room" },
|
|
30642
|
-
React__default.createElement(Icon, { name: "ui-utensils", width: 12, height: 12 }),
|
|
30643
|
-
roomLine.regimeName))))),
|
|
30644
|
-
!isStay && React__default.createElement("p", { className: "search__filter__itinerary__segment-details-text" }, format$2(new Date(line?.from ?? node.from), 'EEE. d MMM yyyy'))))))));
|
|
30645
|
-
};
|
|
30646
|
-
return (React__default.createElement(React__default.Fragment, null, timeline.map((item, index) => {
|
|
30647
|
-
if (item.node.trajectNodeType === CONNECTING_TRAJECT_NODE_TYPE) {
|
|
30648
|
-
return (React__default.createElement(React__default.Fragment, { key: `connect-${item.node.nodeId}` },
|
|
30649
|
-
renderConnectingNode(item),
|
|
30650
|
-
renderOvernight(item, index)));
|
|
30651
|
-
}
|
|
30652
|
-
if (item.node.trajectNodeType === REGULAR_TRAJECT_NODE_TYPE && item.node.serviceType === FLIGHT_SERVICE_TYPE)
|
|
30653
|
-
return renderFlightNode(item);
|
|
30654
|
-
return renderServiceNode(item);
|
|
30655
|
-
})));
|
|
30554
|
+
}
|
|
30656
30555
|
};
|
|
30556
|
+
/**
|
|
30557
|
+
* A point of interest is a place on the map rather than a service, so it gets a marker. Anything
|
|
30558
|
+
* else goes by how it is travelled where the route says so — a leg on foot or by bike is sold as
|
|
30559
|
+
* nothing, and its service type falls back to a transfer — and by what it is where it does not.
|
|
30560
|
+
*/
|
|
30561
|
+
const getTrajectNodeIcon = (node) => {
|
|
30562
|
+
if (node.trajectNodeType === POI_TRAJECT_NODE_TYPE)
|
|
30563
|
+
return 'ui-location';
|
|
30564
|
+
return getTransportTypeIcon(node.transportType) ?? getServiceTypeIcon(node.serviceType);
|
|
30565
|
+
};
|
|
30566
|
+
const getTrajectNodeBadgeModifier = (node) => node.trajectNodeType === POI_TRAJECT_NODE_TYPE ? 'poi' : getServiceTypeBadgeModifier(node.serviceType);
|
|
30657
30567
|
|
|
30658
30568
|
/**
|
|
30659
30569
|
* A booked entry opened through its magic link knows nothing about the traject it may once have
|
|
30660
30570
|
* been composed from — the dossier only keeps the lines. To give it the same day-by-day services
|
|
30661
30571
|
* screen a traject gets, the traject structure is rebuilt from those lines: every booked service
|
|
30662
30572
|
* becomes a node whose one "alternative" is the booked product itself, and the days span the trip.
|
|
30573
|
+
* The points of interest on the dossier's own route (entry.routeNodes) are added between them.
|
|
30663
30574
|
*
|
|
30664
30575
|
* The synthesized response keeps the entry untouched (magic link included), which is also how the
|
|
30665
30576
|
* rest of the component tells this mode apart from a real traject: see isBookedEntryTraject.
|
|
@@ -30736,21 +30647,24 @@ const buildBookedTrajectEntry = (entry) => {
|
|
|
30736
30647
|
const tos = lines.map((line) => new Date(line.to).getTime());
|
|
30737
30648
|
const start = froms.length ? new Date(Math.min(...froms)) : new Date();
|
|
30738
30649
|
const end = tos.length ? new Date(Math.max(...tos)) : start;
|
|
30650
|
+
// Local midnights, written the way the backend writes a traject's days. A UTC date (toDateOnlyString)
|
|
30651
|
+
// names another day away from Greenwich, and every line looked up by its day would slide onto it.
|
|
30739
30652
|
const dayCount = Math.max(differenceInCalendarDays(end, start), 0) + 1;
|
|
30740
30653
|
const days = Array.from({ length: dayCount }, (_, order) => ({
|
|
30741
30654
|
order,
|
|
30742
30655
|
name: '',
|
|
30743
|
-
date:
|
|
30656
|
+
date: format$2(addDays(start, order), "yyyy-MM-dd'T00:00:00'")
|
|
30744
30657
|
}));
|
|
30745
|
-
const dayOrderOf = (
|
|
30658
|
+
const dayOrderOf = (item) => Math.min(Math.max(differenceInCalendarDays(new Date(item.from), start), 0), dayCount - 1);
|
|
30746
30659
|
const flights = sortBy(lines.filter((line) => line.serviceType === FLIGHT_SERVICE_TYPE && isActiveEntryLine(line)), (line) => new Date(line.from).getTime(), (line) => line.order);
|
|
30747
30660
|
const outward = first(flights);
|
|
30748
30661
|
const homeward = flights.length > 1 ? last(flights) : undefined;
|
|
30749
|
-
|
|
30750
|
-
|
|
30751
|
-
|
|
30752
|
-
|
|
30753
|
-
|
|
30662
|
+
// The dossier keeps its lines in route order, so a node's order is its line's — except that the
|
|
30663
|
+
// outward flight opens its day and the homeward one closes it, whatever else that day holds.
|
|
30664
|
+
const homewardOrder = Math.max(0, ...(entry.lines ?? []).map((line) => line.order)) + 1;
|
|
30665
|
+
const orderOf = (line) => (line === outward ? -1 : line === homeward ? homewardOrder : line.order);
|
|
30666
|
+
const orderedGroups = groupNodeLines(lines).sort((a, b) => dayOrderOf(a.lines[0]) - dayOrderOf(b.lines[0]) || orderOf(a.lines[0]) - orderOf(b.lines[0]));
|
|
30667
|
+
const idBase = Math.max(0, ...(entry.routeNodes ?? []).map((routeNode) => routeNode.nodeId));
|
|
30754
30668
|
const nodes = orderedGroups.map(({ lines: groupLines, alternatives }, index) => {
|
|
30755
30669
|
const line = groupLines[0];
|
|
30756
30670
|
const flightLines = line.flightInformation?.flightLines ?? [];
|
|
@@ -30760,9 +30674,9 @@ const buildBookedTrajectEntry = (entry) => {
|
|
|
30760
30674
|
return {
|
|
30761
30675
|
lineGuid: line.guid,
|
|
30762
30676
|
lineGuids: groupLines.map((x) => x.guid),
|
|
30763
|
-
nodeId: index + 1,
|
|
30677
|
+
nodeId: idBase + index + 1,
|
|
30764
30678
|
dayOrder: dayOrderOf(line),
|
|
30765
|
-
order:
|
|
30679
|
+
order: orderOf(line),
|
|
30766
30680
|
name: (alternatives.length && getLineGroupKey(line)) || line.productName,
|
|
30767
30681
|
serviceType: line.serviceType,
|
|
30768
30682
|
trajectNodeType: REGULAR_TRAJECT_NODE_TYPE,
|
|
@@ -30780,6 +30694,23 @@ const buildBookedTrajectEntry = (entry) => {
|
|
|
30780
30694
|
to: line.to
|
|
30781
30695
|
};
|
|
30782
30696
|
});
|
|
30697
|
+
const linesByGuid = new Map((entry.lines ?? []).map((line) => [line.guid, line]));
|
|
30698
|
+
let anchorOrder = -2;
|
|
30699
|
+
let step = 0;
|
|
30700
|
+
(entry.routeNodes ?? []).forEach((routeNode) => {
|
|
30701
|
+
const anchor = linesByGuid.get(routeNode.lineGuid);
|
|
30702
|
+
if (anchor) {
|
|
30703
|
+
anchorOrder = orderOf(anchor);
|
|
30704
|
+
step = 0;
|
|
30705
|
+
if (routeNode.trajectNodeType === CONNECTING_TRAJECT_NODE_TYPE)
|
|
30706
|
+
nodes.push({ ...routeNode, dayOrder: dayOrderOf(routeNode), order: anchorOrder });
|
|
30707
|
+
return;
|
|
30708
|
+
}
|
|
30709
|
+
if (routeNode.trajectNodeType !== POI_TRAJECT_NODE_TYPE)
|
|
30710
|
+
return;
|
|
30711
|
+
step++;
|
|
30712
|
+
nodes.push({ ...routeNode, nodeId: idBase + nodes.length + 1, dayOrder: dayOrderOf(routeNode), order: anchorOrder + step / 1000 });
|
|
30713
|
+
});
|
|
30783
30714
|
return {
|
|
30784
30715
|
entry,
|
|
30785
30716
|
traject: {
|
|
@@ -30901,6 +30832,222 @@ const buildBookedFlightResult = (line) => {
|
|
|
30901
30832
|
};
|
|
30902
30833
|
};
|
|
30903
30834
|
|
|
30835
|
+
const badgeClass = (kind, modifier) => {
|
|
30836
|
+
const base = `search__filter__itinerary__${kind}-badge`;
|
|
30837
|
+
const fallback = kind === 'segment' ? `${base}--secondary` : `${base}--default`;
|
|
30838
|
+
return `${base} ${modifier ? `${base}--${modifier}` : fallback}`;
|
|
30839
|
+
};
|
|
30840
|
+
const nightsOf = (node, line) => differenceInCalendarDays(new Date(line?.to ?? node.to), new Date(line?.from ?? node.from));
|
|
30841
|
+
const TrajectItinerary = ({ trajectEntry, packagingEntry, translations }) => {
|
|
30842
|
+
const timeline = useMemo(() => {
|
|
30843
|
+
const linesByGuid = new Map((packagingEntry.lines ?? []).map((line) => [line.guid, line]));
|
|
30844
|
+
const dropped = getDroppedTrajectNodeIds(trajectEntry, packagingEntry);
|
|
30845
|
+
const nodes = [...(trajectEntry.nodes ?? [])]
|
|
30846
|
+
.filter((node) => !dropped.has(node.nodeId))
|
|
30847
|
+
.sort((a, b) => a.dayOrder - b.dayOrder || a.order - b.order)
|
|
30848
|
+
.map((node) => {
|
|
30849
|
+
if (node.trajectNodeType === CONNECTING_TRAJECT_NODE_TYPE) {
|
|
30850
|
+
return { node, lines: getTrajectTransferLines(node, packagingEntry.lines ?? []) };
|
|
30851
|
+
}
|
|
30852
|
+
const guids = node.lineGuids?.length ? node.lineGuids : [node.lineGuid];
|
|
30853
|
+
const lines = guids.map((guid) => linesByGuid.get(guid)).filter((x) => !!x);
|
|
30854
|
+
const groupKey = lines.length ? getLineGroupKey(lines[0]) : '';
|
|
30855
|
+
const active = groupKey ? getGroupLines(packagingEntry.lines ?? [], groupKey)[0] : undefined;
|
|
30856
|
+
return { node, lines: active ?? lines };
|
|
30857
|
+
});
|
|
30858
|
+
const claimed = new Set(nodes.flatMap((item) => item.lines.map((line) => line.guid)));
|
|
30859
|
+
const extras = (packagingEntry.lines ?? []).filter((line) => !claimed.has(line.guid)).filter(isMeaningfulItineraryLine);
|
|
30860
|
+
if (!extras.length)
|
|
30861
|
+
return nodes;
|
|
30862
|
+
const dayOrderOf = (date) => trajectEntry.traject.days.find((day) => isSameDay(new Date(day.date), new Date(date)))?.order ??
|
|
30863
|
+
Math.max(differenceInCalendarDays(new Date(date), new Date(trajectEntry.traject.startDate)), 0);
|
|
30864
|
+
// One product bought once, however many option lines it took (an adult and a child ticket).
|
|
30865
|
+
const grouped = new Map();
|
|
30866
|
+
extras.forEach((line) => {
|
|
30867
|
+
const key = `${line.productCode}|${line.from}`;
|
|
30868
|
+
grouped.set(key, [...(grouped.get(key) ?? []), line]);
|
|
30869
|
+
});
|
|
30870
|
+
const byDay = new Map();
|
|
30871
|
+
nodes.forEach((item) => byDay.set(item.node.dayOrder, [...(byDay.get(item.node.dayOrder) ?? []), item]));
|
|
30872
|
+
const flightNodes = nodes.filter(({ node }) => node.trajectNodeType === REGULAR_TRAJECT_NODE_TYPE && node.serviceType === FLIGHT_SERVICE_TYPE);
|
|
30873
|
+
const homeward = flightNodes.length > 1 ? flightNodes[flightNodes.length - 1] : undefined;
|
|
30874
|
+
// A booked entry's nodes are ordered as the dossier orders its lines (see buildBookedTrajectEntry).
|
|
30875
|
+
const bookedOrders = isBookedEntryTraject(trajectEntry) ? new Map((trajectEntry.entry.lines ?? []).map((line) => [line.guid, line.order])) : undefined;
|
|
30876
|
+
[...grouped.values()].forEach((lines, index) => {
|
|
30877
|
+
const line = lines[0];
|
|
30878
|
+
const dayOrder = dayOrderOf(line.from);
|
|
30879
|
+
const bookedOrder = bookedOrders?.get(line.guid);
|
|
30880
|
+
const item = {
|
|
30881
|
+
lines,
|
|
30882
|
+
node: {
|
|
30883
|
+
nodeId: -(index + 1),
|
|
30884
|
+
lineGuid: line.guid,
|
|
30885
|
+
lineGuids: lines.map((x) => x.guid),
|
|
30886
|
+
dayOrder,
|
|
30887
|
+
order: bookedOrder ?? Number.MAX_SAFE_INTEGER,
|
|
30888
|
+
name: line.productName,
|
|
30889
|
+
serviceType: line.serviceType,
|
|
30890
|
+
trajectNodeType: REGULAR_TRAJECT_NODE_TYPE,
|
|
30891
|
+
requirementType: 0,
|
|
30892
|
+
contentSource: 0,
|
|
30893
|
+
duration: 0,
|
|
30894
|
+
durationType: 0,
|
|
30895
|
+
alternatives: [],
|
|
30896
|
+
from: line.from,
|
|
30897
|
+
to: line.to
|
|
30898
|
+
}
|
|
30899
|
+
};
|
|
30900
|
+
const dayItems = [...(byDay.get(dayOrder) ?? [])];
|
|
30901
|
+
// A booked line goes where the dossier's order puts it; one the traveller added closes its
|
|
30902
|
+
// day, but before the hops that leave it and the flight home.
|
|
30903
|
+
let at = dayItems.length;
|
|
30904
|
+
if (bookedOrder !== undefined) {
|
|
30905
|
+
at = dayItems.findIndex((x) => x.node.order > bookedOrder);
|
|
30906
|
+
if (at < 0)
|
|
30907
|
+
at = dayItems.length;
|
|
30908
|
+
}
|
|
30909
|
+
else {
|
|
30910
|
+
while (at > 0 && (dayItems[at - 1].node.trajectNodeType === CONNECTING_TRAJECT_NODE_TYPE || dayItems[at - 1] === homeward))
|
|
30911
|
+
at--;
|
|
30912
|
+
}
|
|
30913
|
+
dayItems.splice(at, 0, item);
|
|
30914
|
+
byDay.set(dayOrder, dayItems);
|
|
30915
|
+
});
|
|
30916
|
+
return [...byDay.keys()].sort((a, b) => a - b).flatMap((dayOrder) => byDay.get(dayOrder));
|
|
30917
|
+
}, [trajectEntry, packagingEntry]);
|
|
30918
|
+
const stayByDay = useMemo(() => {
|
|
30919
|
+
const map = new Map();
|
|
30920
|
+
timeline
|
|
30921
|
+
.filter(({ node }) => node.trajectNodeType === REGULAR_TRAJECT_NODE_TYPE && node.serviceType === ACCOMMODATION_SERVICE_TYPE)
|
|
30922
|
+
.forEach(({ node, lines }) => {
|
|
30923
|
+
const line = lines[0];
|
|
30924
|
+
const nights = Math.max(nightsOf(node, line), 1);
|
|
30925
|
+
const name = line?.productName || node.preferredAccommodationName || node.name || '';
|
|
30926
|
+
for (let night = 0; night < nights; night++)
|
|
30927
|
+
map.set(node.dayOrder + night, { name, night, nights });
|
|
30928
|
+
});
|
|
30929
|
+
return map;
|
|
30930
|
+
}, [timeline]);
|
|
30931
|
+
const renderDate = (value) => (React__default.createElement("div", { className: "search__filter__itinerary__transport-date" },
|
|
30932
|
+
React__default.createElement("p", { className: "search__filter__itinerary__transport-date-date" },
|
|
30933
|
+
React__default.createElement("strong", null, format$2(new Date(value), 'd'))),
|
|
30934
|
+
React__default.createElement("p", null, format$2(new Date(value), 'MMM'))));
|
|
30935
|
+
const renderConnectingNode = ({ node, lines }) => {
|
|
30936
|
+
const distance = node.distanceInKm ? `${Math.round(node.distanceInKm)} km` : '';
|
|
30937
|
+
// How long the hop takes and how far it is, the way the traject editor puts them.
|
|
30938
|
+
const journey = [getTrajectNodeDuration(node), distance].filter(Boolean).join(' · ');
|
|
30939
|
+
const title = lines[0]?.productName || node.name;
|
|
30940
|
+
return (React__default.createElement("div", { className: "search__filter__itinerary__transport", key: `connect-${node.nodeId}` },
|
|
30941
|
+
React__default.createElement("div", { className: "search__filter__itinerary__transport-timeline" },
|
|
30942
|
+
React__default.createElement("div", { className: "search__filter__itinerary__transport-timeline-line" })),
|
|
30943
|
+
React__default.createElement("div", { className: "search__filter__itinerary__transport-item" },
|
|
30944
|
+
React__default.createElement("div", { className: "search__filter__itinerary__transport-date" }),
|
|
30945
|
+
React__default.createElement("div", { className: badgeClass('transport', getTrajectNodeBadgeModifier(node)) },
|
|
30946
|
+
React__default.createElement(Icon, { name: getTrajectNodeIcon(node), height: 15 })),
|
|
30947
|
+
React__default.createElement("div", { className: "search__filter__itinerary__transport-details" },
|
|
30948
|
+
React__default.createElement("h6", null, title || journey),
|
|
30949
|
+
!!title && !!journey && React__default.createElement("p", { className: "search__filter__itinerary__transport-details-time" }, journey)))));
|
|
30950
|
+
};
|
|
30951
|
+
const renderOvernight = (item, index) => {
|
|
30952
|
+
const { dayOrder } = item.node;
|
|
30953
|
+
const closesTheDay = timeline[index + 1]?.node.dayOrder !== dayOrder;
|
|
30954
|
+
const stay = stayByDay.get(dayOrder);
|
|
30955
|
+
if (!closesTheDay || !stay)
|
|
30956
|
+
return null;
|
|
30957
|
+
return (React__default.createElement("div", { className: "search__filter__itinerary__transport", key: `overnight-${item.node.nodeId}` },
|
|
30958
|
+
React__default.createElement("div", { className: "search__filter__itinerary__transport-timeline" },
|
|
30959
|
+
React__default.createElement("div", { className: "search__filter__itinerary__transport-timeline-line" })),
|
|
30960
|
+
React__default.createElement("div", { className: "search__filter__itinerary__transport-item" },
|
|
30961
|
+
renderDate(item.node.from),
|
|
30962
|
+
React__default.createElement("div", { className: badgeClass('transport', getServiceTypeBadgeModifier(ACCOMMODATION_SERVICE_TYPE)) },
|
|
30963
|
+
React__default.createElement(Icon, { name: "ui-bed", height: 15 })),
|
|
30964
|
+
React__default.createElement("div", { className: "search__filter__itinerary__transport-details" },
|
|
30965
|
+
React__default.createElement("h6", null, stay.name),
|
|
30966
|
+
React__default.createElement("p", { className: "search__filter__itinerary__transport-details-time" },
|
|
30967
|
+
stay.night + 1,
|
|
30968
|
+
" ",
|
|
30969
|
+
translations.SRP.OF,
|
|
30970
|
+
" ",
|
|
30971
|
+
stay.nights,
|
|
30972
|
+
" ",
|
|
30973
|
+
translations.SRP.NIGHTS)))));
|
|
30974
|
+
};
|
|
30975
|
+
const renderFlightNode = ({ node, lines }) => {
|
|
30976
|
+
const line = lines[0];
|
|
30977
|
+
return (React__default.createElement("div", { className: "search__filter__itinerary__transport", key: `flight-${node.nodeId}` },
|
|
30978
|
+
React__default.createElement("div", { className: "search__filter__itinerary__transport-timeline" },
|
|
30979
|
+
React__default.createElement("div", { className: "search__filter__itinerary__transport-timeline-line" })),
|
|
30980
|
+
React__default.createElement("div", { className: "search__filter__itinerary__transport-item" },
|
|
30981
|
+
renderDate(line?.from ?? node.from),
|
|
30982
|
+
React__default.createElement("div", { className: badgeClass('transport', getServiceTypeBadgeModifier(FLIGHT_SERVICE_TYPE)) },
|
|
30983
|
+
React__default.createElement(Icon, { name: "ui-plane", height: 15 })),
|
|
30984
|
+
React__default.createElement("div", { className: "search__filter__itinerary__transport-details" },
|
|
30985
|
+
React__default.createElement("h6", null, line?.productName || node.name),
|
|
30986
|
+
!!getDepartureTime(line) && (React__default.createElement("p", { className: "search__filter__itinerary__transport-details-plane" },
|
|
30987
|
+
React__default.createElement("span", null,
|
|
30988
|
+
React__default.createElement(Icon, { name: "ui-plane-depart", height: 14 }),
|
|
30989
|
+
" ",
|
|
30990
|
+
React__default.createElement("strong", null, getDepartureTime(line))),
|
|
30991
|
+
' ',
|
|
30992
|
+
"-",
|
|
30993
|
+
' ',
|
|
30994
|
+
React__default.createElement("span", null,
|
|
30995
|
+
React__default.createElement(Icon, { name: "ui-plane-arrive", height: 14 }),
|
|
30996
|
+
" ",
|
|
30997
|
+
React__default.createElement("strong", null, getArrivalTime(line))))),
|
|
30998
|
+
!!getDuration(line) && (React__default.createElement("p", { className: "search__filter__itinerary__transport-details-time" },
|
|
30999
|
+
React__default.createElement("span", null,
|
|
31000
|
+
React__default.createElement(Icon, { name: "ui-clock", height: 20 }),
|
|
31001
|
+
" ",
|
|
31002
|
+
getDuration(line))))))));
|
|
31003
|
+
};
|
|
31004
|
+
const renderServiceNode = ({ node, lines }) => {
|
|
31005
|
+
const line = lines[0];
|
|
31006
|
+
const isStay = node.serviceType === ACCOMMODATION_SERVICE_TYPE;
|
|
31007
|
+
const nights = isStay ? nightsOf(node, line) : 0;
|
|
31008
|
+
return (React__default.createElement("div", { className: "search__filter__itinerary__segments", key: `node-${node.nodeId}` },
|
|
31009
|
+
React__default.createElement("div", { className: "search__filter__itinerary__segments__wrapper" },
|
|
31010
|
+
React__default.createElement("div", { className: "search__filter__itinerary__segments-timeline" },
|
|
31011
|
+
React__default.createElement("div", { className: "search__filter__itinerary__segments-timeline-line" })),
|
|
31012
|
+
React__default.createElement("div", { className: "search__filter__itinerary__segment" },
|
|
31013
|
+
React__default.createElement("div", { className: "search__filter__itinerary__segment-item" },
|
|
31014
|
+
React__default.createElement("div", { className: `search__filter__itinerary__segment-date ${isStay && nights > 0 ? 'search__filter__itinerary__segment-nights' : ''}` }, isStay && nights > 0 ? (React__default.createElement(React__default.Fragment, null,
|
|
31015
|
+
React__default.createElement("p", { className: "search__filter__itinerary__segment-date-date" },
|
|
31016
|
+
React__default.createElement("strong", null, nights)),
|
|
31017
|
+
React__default.createElement(Icon, { name: "ui-moon", width: 16, height: 16 }))) : (React__default.createElement(React__default.Fragment, null,
|
|
31018
|
+
React__default.createElement("p", { className: "search__filter__itinerary__segment-date-date" },
|
|
31019
|
+
React__default.createElement("strong", null, format$2(new Date(line?.from ?? node.from), 'd'))),
|
|
31020
|
+
React__default.createElement("p", null, format$2(new Date(line?.from ?? node.from), 'MMM'))))),
|
|
31021
|
+
React__default.createElement("div", { className: badgeClass('segment', getTrajectNodeBadgeModifier(node)) },
|
|
31022
|
+
React__default.createElement(Icon, { name: getTrajectNodeIcon(node), width: 15, height: 15 })),
|
|
31023
|
+
React__default.createElement("div", { className: "search__filter__itinerary__segment-details" },
|
|
31024
|
+
React__default.createElement("h6", null, line?.productName || node.preferredAccommodationName || node.name),
|
|
31025
|
+
isStay &&
|
|
31026
|
+
lines.map((roomLine, index) => (React__default.createElement(React__default.Fragment, { key: roomLine.guid },
|
|
31027
|
+
lines.length > 1 && (React__default.createElement("strong", null,
|
|
31028
|
+
translations.SHARED.ROOM,
|
|
31029
|
+
"\u00A0",
|
|
31030
|
+
index + 1)),
|
|
31031
|
+
roomLine.accommodationName && (React__default.createElement("div", { className: "search__filter__itinerary__segment-details__room" },
|
|
31032
|
+
React__default.createElement(Icon, { name: "ui-bed", width: 12, height: 12 }),
|
|
31033
|
+
roomLine.accommodationName)),
|
|
31034
|
+
roomLine.regimeName && (React__default.createElement("div", { className: "search__filter__itinerary__segment-details__room" },
|
|
31035
|
+
React__default.createElement(Icon, { name: "ui-utensils", width: 12, height: 12 }),
|
|
31036
|
+
roomLine.regimeName))))),
|
|
31037
|
+
!isStay && React__default.createElement("p", { className: "search__filter__itinerary__segment-details-text" }, format$2(new Date(line?.from ?? node.from), 'EEE. d MMM yyyy'))))))));
|
|
31038
|
+
};
|
|
31039
|
+
return (React__default.createElement(React__default.Fragment, null, timeline.map((item, index) => {
|
|
31040
|
+
if (item.node.trajectNodeType === CONNECTING_TRAJECT_NODE_TYPE) {
|
|
31041
|
+
return (React__default.createElement(React__default.Fragment, { key: `connect-${item.node.nodeId}` },
|
|
31042
|
+
renderConnectingNode(item),
|
|
31043
|
+
renderOvernight(item, index)));
|
|
31044
|
+
}
|
|
31045
|
+
if (item.node.trajectNodeType === REGULAR_TRAJECT_NODE_TYPE && item.node.serviceType === FLIGHT_SERVICE_TYPE)
|
|
31046
|
+
return renderFlightNode(item);
|
|
31047
|
+
return renderServiceNode(item);
|
|
31048
|
+
})));
|
|
31049
|
+
};
|
|
31050
|
+
|
|
30904
31051
|
const getSegmentIcon = (segment) => {
|
|
30905
31052
|
switch (segment.serviceType) {
|
|
30906
31053
|
case 3:
|
|
@@ -30940,10 +31087,10 @@ const SERVICE_TYPE_PRIORITY = {
|
|
|
30940
31087
|
const getServiceTypePriority = (serviceType) => {
|
|
30941
31088
|
return SERVICE_TYPE_PRIORITY[serviceType ?? -1] ?? 2;
|
|
30942
31089
|
};
|
|
30943
|
-
const Itinerary = ({ isOpen, handleSetIsOpen, isLoading, onEditAccommodation
|
|
31090
|
+
const Itinerary = ({ isOpen, handleSetIsOpen, isLoading, onEditAccommodation }) => {
|
|
30944
31091
|
const context = useContext(SearchResultsConfigurationContext);
|
|
30945
31092
|
const translations = getTranslations(context?.languageCode ?? 'en-GB');
|
|
30946
|
-
const { editablePackagingEntry, priceDetails, trajectItineraryView } = useSelector((state) => state.searchResults);
|
|
31093
|
+
const { editablePackagingEntry, priceDetails, priceDetailsFailed, trajectItineraryView } = useSelector((state) => state.searchResults);
|
|
30947
31094
|
const dispatch = useDispatch();
|
|
30948
31095
|
const packagingEntry = editablePackagingEntry ?? context?.packagingEntry;
|
|
30949
31096
|
const sortedLines = useMemo(() => {
|
|
@@ -31009,7 +31156,9 @@ const Itinerary = ({ isOpen, handleSetIsOpen, isLoading, onEditAccommodation, on
|
|
|
31009
31156
|
packagingEntry.dossierNumber)),
|
|
31010
31157
|
React__default.createElement("h4", { className: "search__filter__image__title" }, (location || '') + (location && country ? ' - ' : '') + (country || ''))))),
|
|
31011
31158
|
React__default.createElement("div", { className: "search__filter__prices" }, isLoading ? (React__default.createElement(Spinner, { label: translations.PRODUCT.LOADING_PRICE })) : (React__default.createElement(React__default.Fragment, null,
|
|
31012
|
-
React__default.createElement("div", { className: "search__filter__prices__wrapper" },
|
|
31159
|
+
priceDetailsFailed ? (React__default.createElement("div", { className: "search__filter__prices__wrapper" },
|
|
31160
|
+
React__default.createElement("p", { className: "info-message--error" }, translations.SRP.PRICE_DETAILS_FAILED),
|
|
31161
|
+
React__default.createElement("button", { type: "button", className: "cta cta--secondary", onClick: () => dispatch(retryPriceDetails()) }, translations.SRP.TRY_AGAIN))) : (React__default.createElement("div", { className: "search__filter__prices__wrapper" },
|
|
31013
31162
|
React__default.createElement("h3", { className: "search__filter__prices--amount" }, formatPrice$3(pricePerPerson, 'EUR')),
|
|
31014
31163
|
React__default.createElement("p", null, translations.SRP.PACKAGE_PRICE_PER_PERSON),
|
|
31015
31164
|
React__default.createElement("p", null,
|
|
@@ -31018,8 +31167,8 @@ const Itinerary = ({ isOpen, handleSetIsOpen, isLoading, onEditAccommodation, on
|
|
|
31018
31167
|
formatPrice$3(totalPrice, 'EUR'),
|
|
31019
31168
|
" ",
|
|
31020
31169
|
translations.SRP.TOTAL,
|
|
31021
|
-
")"))),
|
|
31022
|
-
isBookedEntryTraject(context?.trajectEntry) ? (React__default.createElement("button", { className: "cta", onClick:
|
|
31170
|
+
")")))),
|
|
31171
|
+
isBookedEntryTraject(context?.trajectEntry) ? (React__default.createElement("button", { className: "cta", onClick: handleConfirm, disabled: priceDetailsFailed || !hasBookedEntryChanges(context?.packagingEntry, packagingEntry) }, translations.SRP.BOOK_AND_PAY)) : isBooked ? (React__default.createElement("span", { className: "cta cta--disabled search__filter__prices--booked", "aria-disabled": "true" }, translations.SHARED.BOOKED)) : (React__default.createElement("button", { className: "cta", onClick: handleConfirm, disabled: priceDetailsFailed }, translations.SRP.BOOK_AND_PAY)),
|
|
31023
31172
|
!!context?.trajectEntry && (React__default.createElement("button", { type: "button", className: "cta cta--secondary", onClick: () => handleTrajectView(!trajectItineraryView) }, trajectItineraryView ? translations.SRP.SHOW_SERVICES : translations.SRP.VIEW_FULL_ITINERARY))))),
|
|
31024
31173
|
React__default.createElement("div", { className: "search__filter__itinerary" },
|
|
31025
31174
|
React__default.createElement("p", null, translations.SRP.DAY_BY_DAY),
|
|
@@ -36318,10 +36467,12 @@ const travellersSettings = {
|
|
|
36318
36467
|
const BookPackagingEntry = ({ activeSearchSeed, isLoading, isConfirmationPage }) => {
|
|
36319
36468
|
const context = useContext(SearchResultsConfigurationContext);
|
|
36320
36469
|
const dispatch = useDispatch();
|
|
36321
|
-
const { editablePackagingEntry, priceDetails, currentStep, bookingNumber, selectedPackagingAccoResultCode, packagingAccoResults } = useSelector((state) => state.searchResults);
|
|
36470
|
+
const { editablePackagingEntry, priceDetails, priceDetailsFailed, currentStep, bookingNumber, selectedPackagingAccoResultCode, packagingAccoResults } = useSelector((state) => state.searchResults);
|
|
36471
|
+
// A booked entry opened through its magic link is saved onto its own dossier instead of booked again.
|
|
36472
|
+
const bookedMode = isBookedEntryTraject(context?.trajectEntry);
|
|
36322
36473
|
const [countries, setCountries] = useState([]);
|
|
36323
36474
|
const [userValidated, setUserValidated] = useState(true);
|
|
36324
|
-
const [remarks, setRemarks] = useState('');
|
|
36475
|
+
const [remarks, setRemarks] = useState(() => editablePackagingEntry?.remarks ?? '');
|
|
36325
36476
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
36326
36477
|
const [selectedPackagingAccoResult, setSelectedPackagingAccoResult] = useState(null);
|
|
36327
36478
|
const translations = useMemo(() => getTranslations(context?.languageCode ?? 'en-GB'), [context?.languageCode]);
|
|
@@ -36381,7 +36532,27 @@ const BookPackagingEntry = ({ activeSearchSeed, isLoading, isConfirmationPage })
|
|
|
36381
36532
|
}, [selectedPackagingAccoResultCode, packagingAccoResults]);
|
|
36382
36533
|
// What the page holds versus what the backend can look up; they differ for a traject.
|
|
36383
36534
|
const bookableEntry = useBookableEntry();
|
|
36384
|
-
|
|
36535
|
+
// What was already typed stays on the entry, so the form comes back filled in when the traveller returns.
|
|
36536
|
+
const handleBackToServices = () => {
|
|
36537
|
+
if (currentStep === 0 && editablePackagingEntry) {
|
|
36538
|
+
dispatch(setEditablePackagingEntry(applyTravelersFormValuesToEditablePackagingEntry(editablePackagingEntry, formik.values)));
|
|
36539
|
+
}
|
|
36540
|
+
dispatch(setCurrentStep(0));
|
|
36541
|
+
dispatch(setBookPackagingEntry(false));
|
|
36542
|
+
if (typeof window !== 'undefined')
|
|
36543
|
+
window.scrollTo(0, 0);
|
|
36544
|
+
};
|
|
36545
|
+
// Nothing can be confirmed without a price for the entry as it is now; the confirmation itself needs none.
|
|
36546
|
+
if (context && priceDetailsFailed && currentStep < 2) {
|
|
36547
|
+
return (React__default.createElement("div", { className: "booking" },
|
|
36548
|
+
React__default.createElement("div", { className: "booking__content" },
|
|
36549
|
+
React__default.createElement("div", { className: "booking__panel" },
|
|
36550
|
+
React__default.createElement("p", { className: "info-message--error" }, translations.SRP.PRICE_DETAILS_FAILED),
|
|
36551
|
+
React__default.createElement("div", { className: "booking__navigator" },
|
|
36552
|
+
React__default.createElement("button", { type: "button", className: "cta cta--secondary", onClick: handleBackToServices }, translations.SRP.BACK_TO_SERVICES),
|
|
36553
|
+
React__default.createElement("button", { type: "button", className: "cta", onClick: () => dispatch(retryPriceDetails()) }, translations.SRP.TRY_AGAIN))))));
|
|
36554
|
+
}
|
|
36555
|
+
if (!context || !editablePackagingEntry || !config || (!priceDetails && currentStep < 2))
|
|
36385
36556
|
return React__default.createElement(Spinner, { label: translations.SUMMARY.PROCESS_BOOKING });
|
|
36386
36557
|
const handleSummarySubmit = async (e) => {
|
|
36387
36558
|
e.preventDefault();
|
|
@@ -36391,7 +36562,7 @@ const BookPackagingEntry = ({ activeSearchSeed, isLoading, isConfirmationPage })
|
|
|
36391
36562
|
}
|
|
36392
36563
|
// Held apart from the entry itself so they can be put on both copies of it.
|
|
36393
36564
|
let bookingFields = { remarks };
|
|
36394
|
-
if (context.generatePaymentUrl && typeof window !== 'undefined') {
|
|
36565
|
+
if (!bookedMode && context.generatePaymentUrl && typeof window !== 'undefined') {
|
|
36395
36566
|
const redirectUrl = new URL(window.location.href);
|
|
36396
36567
|
redirectUrl.searchParams.set('bookingConfirmation', 'true');
|
|
36397
36568
|
redirectUrl.searchParams.set('link', '');
|
|
@@ -36408,6 +36579,13 @@ const BookPackagingEntry = ({ activeSearchSeed, isLoading, isConfirmationPage })
|
|
|
36408
36579
|
// The page keeps the entry it works with; the backend gets the one it can look up.
|
|
36409
36580
|
payload: { ...(bookableEntry ?? editablePackagingEntry), ...bookingFields }
|
|
36410
36581
|
};
|
|
36582
|
+
if (bookedMode) {
|
|
36583
|
+
const saved = await build.savePackagingEntry(config, request);
|
|
36584
|
+
dispatch(setBookingNumber(saved.dossierNumber ?? editablePackagingEntry.dossierNumber ?? ''));
|
|
36585
|
+
dispatch(setCurrentStep(2));
|
|
36586
|
+
setIsSubmitting(false);
|
|
36587
|
+
return;
|
|
36588
|
+
}
|
|
36411
36589
|
const bookingResponse = await build.bookPackagingEntry(config, request);
|
|
36412
36590
|
dispatch(setBookingNumber(bookingResponse.number));
|
|
36413
36591
|
if (bookingResponse.paymentUrl) {
|
|
@@ -36430,8 +36608,8 @@ const BookPackagingEntry = ({ activeSearchSeed, isLoading, isConfirmationPage })
|
|
|
36430
36608
|
".\u00A0",
|
|
36431
36609
|
stepLabels[step])) },
|
|
36432
36610
|
isConfirmationPage && isLoading && React__default.createElement(Spinner, { label: translations.SUMMARY.PROCESS_BOOKING }),
|
|
36433
|
-
currentStep === 0 && (React__default.createElement(SharedTravelersForm, { formik: formik, translations: translations, travellersSettings: travellersSettings, countries: countries, travelersFirstStep: false, isUnavailable: false, useCompactForm: false, showAgentSelection: false, paxFields: paxFields })),
|
|
36434
|
-
currentStep === 1 && (React__default.createElement(SharedSummary, { translations: translations, travelerFormValues: formik.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.createElement("button", { type: "button", title: translations.STEPS.PREVIOUS, onClick: () => dispatch(setCurrentStep(0)), className: "cta cta--secondary" }, translations.STEPS.PREVIOUS)) })),
|
|
36611
|
+
currentStep === 0 && (React__default.createElement(SharedTravelersForm, { formik: formik, translations: translations, travellersSettings: travellersSettings, countries: countries, travelersFirstStep: false, isUnavailable: false, useCompactForm: false, showAgentSelection: false, paxFields: paxFields, renderPreviousButton: () => (React__default.createElement("button", { type: "button", title: translations.SRP.BACK_TO_SERVICES, onClick: handleBackToServices, className: "cta cta--secondary" }, translations.SRP.BACK_TO_SERVICES)) })),
|
|
36612
|
+
currentStep === 1 && priceDetails && (React__default.createElement(SharedSummary, { translations: translations, travelerFormValues: formik.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.createElement("button", { type: "button", title: translations.STEPS.PREVIOUS, onClick: () => dispatch(setCurrentStep(0)), className: "cta cta--secondary" }, translations.STEPS.PREVIOUS)) })),
|
|
36435
36613
|
currentStep === 2 && (React__default.createElement(SharedConfirmation, { bookingNumber: bookingNumber ?? editablePackagingEntry?.dossierNumber ?? '', isOption: false, isOffer: false, translations: translations.CONFIRMATION })),
|
|
36436
36614
|
currentStep === 3 && React__default.createElement("div", null))),
|
|
36437
36615
|
React__default.createElement("div", { className: "backdrop", id: "backdrop" }),
|
|
@@ -36852,6 +37030,16 @@ const TrajectResults = ({ isLoading }) => {
|
|
|
36852
37030
|
})));
|
|
36853
37031
|
};
|
|
36854
37032
|
|
|
37033
|
+
const getSearchContext = (context, trajectEntry) => ({
|
|
37034
|
+
transactionId: trajectEntry.entry.transactionId,
|
|
37035
|
+
officeId: context.tideConnection.officeId ?? 1,
|
|
37036
|
+
catalogueId: context.searchConfiguration.defaultCatalogueId ?? first(context.tideConnection.catalogueIds) ?? 1,
|
|
37037
|
+
searchConfigurationId: context.searchConfiguration.id,
|
|
37038
|
+
portalId: context.portalId ?? null,
|
|
37039
|
+
agentId: context.agentId ?? null,
|
|
37040
|
+
language: context.languageCode ?? 'en-GB',
|
|
37041
|
+
rooms: getPackagingRoomsFromEntry(trajectEntry.entry)
|
|
37042
|
+
});
|
|
36855
37043
|
const useTrajectAvailability = () => {
|
|
36856
37044
|
const context = useContext(SearchResultsConfigurationContext);
|
|
36857
37045
|
const dispatch = useDispatch();
|
|
@@ -36870,16 +37058,7 @@ const useTrajectAvailability = () => {
|
|
|
36870
37058
|
host: context.tideConnection.host,
|
|
36871
37059
|
apiKey: context.tideConnection.apiKey
|
|
36872
37060
|
};
|
|
36873
|
-
const searchContext =
|
|
36874
|
-
transactionId: trajectEntry.entry.transactionId,
|
|
36875
|
-
officeId: context.tideConnection.officeId ?? 1,
|
|
36876
|
-
catalogueId: context.searchConfiguration.defaultCatalogueId ?? first(context.tideConnection.catalogueIds) ?? 1,
|
|
36877
|
-
searchConfigurationId: context.searchConfiguration.id,
|
|
36878
|
-
portalId: context.portalId ?? null,
|
|
36879
|
-
agentId: context.agentId ?? null,
|
|
36880
|
-
language: context.languageCode ?? 'en-GB',
|
|
36881
|
-
rooms: getPackagingRoomsFromEntry(trajectEntry.entry)
|
|
36882
|
-
};
|
|
37061
|
+
const searchContext = getSearchContext(context, trajectEntry);
|
|
36883
37062
|
// Flight searches take passengers rather than rooms; the API buckets by age.
|
|
36884
37063
|
// The ids must be the entry's own pax ids: the flight lines this search caches carry them as
|
|
36885
37064
|
// EntryLinePax.PaxId, and booking maps those onto the entry's travelers (PackagingService.SyncPaxIds).
|
|
@@ -37019,30 +37198,9 @@ const useTrajectAvailability = () => {
|
|
|
37019
37198
|
return item.lines.map((x) => x.guid);
|
|
37020
37199
|
}
|
|
37021
37200
|
};
|
|
37022
|
-
const resolveTransferNode = async (node) => {
|
|
37023
|
-
dispatch(setTrajectTransferLoading(node.nodeId));
|
|
37024
|
-
try {
|
|
37025
|
-
const results = (await build.searchPackagingTransfers(config, buildTrajectTransferRequest(node, searchContext), controller.signal)) ?? [];
|
|
37026
|
-
if (controller.signal.aborted)
|
|
37027
|
-
return [];
|
|
37028
|
-
const result = results[0] ?? null;
|
|
37029
|
-
dispatch(setTrajectTransferAvailability({ nodeId: node.nodeId, result: result && buildTrajectTransferLine(node, result) ? result : null }));
|
|
37030
|
-
}
|
|
37031
|
-
catch (error) {
|
|
37032
|
-
if (controller.signal.aborted)
|
|
37033
|
-
return [];
|
|
37034
|
-
console.error('Failed to resolve traject transfer', node.name, error);
|
|
37035
|
-
dispatch(setTrajectTransferAvailability({ nodeId: node.nodeId, result: null }));
|
|
37036
|
-
}
|
|
37037
|
-
return [];
|
|
37038
|
-
};
|
|
37039
37201
|
(async () => {
|
|
37040
37202
|
const resolvable = nodesWithLines.filter((x) => isSearchableTrajectNode(x.node) || isTrajectFlightNode(x.node));
|
|
37041
|
-
const
|
|
37042
|
-
const unresolved = (await Promise.all([
|
|
37043
|
-
...resolvable.map((item) => (isTrajectFlightNode(item.node) ? resolveFlightNode(item) : resolveNode(item))),
|
|
37044
|
-
...transfers.map(resolveTransferNode)
|
|
37045
|
-
])).flat();
|
|
37203
|
+
const unresolved = (await Promise.all(resolvable.map((item) => (isTrajectFlightNode(item.node) ? resolveFlightNode(item) : resolveNode(item))))).flat();
|
|
37046
37204
|
if (controller.signal.aborted)
|
|
37047
37205
|
return;
|
|
37048
37206
|
dispatch(setTrajectResolving(false));
|
|
@@ -37054,22 +37212,87 @@ const useTrajectAvailability = () => {
|
|
|
37054
37212
|
})();
|
|
37055
37213
|
return () => controller.abort();
|
|
37056
37214
|
}, [trajectEntry, showApiItems, hideShowMoreResults]);
|
|
37215
|
+
const transferSearches = useRef(new Map());
|
|
37216
|
+
useEffect(() => {
|
|
37217
|
+
const searches = transferSearches.current;
|
|
37218
|
+
return () => {
|
|
37219
|
+
searches.forEach((search) => search.controller.abort());
|
|
37220
|
+
searches.clear();
|
|
37221
|
+
};
|
|
37222
|
+
}, [trajectEntry]);
|
|
37223
|
+
useEffect(() => {
|
|
37224
|
+
if (!context || !trajectEntry || !editablePackagingEntry)
|
|
37225
|
+
return;
|
|
37226
|
+
const bookedMode = isBookedEntryTraject(trajectEntry);
|
|
37227
|
+
const searches = transferSearches.current;
|
|
37228
|
+
const dropped = getDroppedTrajectNodeIds(trajectEntry, editablePackagingEntry);
|
|
37229
|
+
const lines = editablePackagingEntry.lines ?? [];
|
|
37230
|
+
const config = { host: context.tideConnection.host, apiKey: context.tideConnection.apiKey };
|
|
37231
|
+
const searchContext = getSearchContext(context, trajectEntry);
|
|
37232
|
+
const cancel = (nodeId) => {
|
|
37233
|
+
const search = searches.get(nodeId);
|
|
37234
|
+
if (!search)
|
|
37235
|
+
return;
|
|
37236
|
+
search.controller.abort();
|
|
37237
|
+
searches.delete(nodeId);
|
|
37238
|
+
dispatch(setTrajectTransferAvailability({ nodeId, key: search.key, result: null }));
|
|
37239
|
+
};
|
|
37240
|
+
(trajectEntry.nodes ?? []).filter(isTrajectTransferNode).forEach((node) => {
|
|
37241
|
+
const endpoints = dropped.has(node.nodeId) ? null : getTrajectTransferEndpoints(trajectEntry, lines, node);
|
|
37242
|
+
if (!endpoints || (bookedMode && endpoints.asDrawn))
|
|
37243
|
+
return cancel(node.nodeId);
|
|
37244
|
+
if (searches.get(node.nodeId)?.key === endpoints.key || trajectTransferAvailability[node.nodeId]?.key === endpoints.key)
|
|
37245
|
+
return;
|
|
37246
|
+
cancel(node.nodeId);
|
|
37247
|
+
const controller = new AbortController();
|
|
37248
|
+
const { key } = endpoints;
|
|
37249
|
+
searches.set(node.nodeId, { key, controller });
|
|
37250
|
+
dispatch(setTrajectTransferLoading({ nodeId: node.nodeId, key }));
|
|
37251
|
+
build.searchPackagingTransfers(config, buildTrajectTransferRequest(node, endpoints, searchContext), controller.signal)
|
|
37252
|
+
.then((results) => {
|
|
37253
|
+
if (controller.signal.aborted)
|
|
37254
|
+
return;
|
|
37255
|
+
const result = results?.[0] ?? null;
|
|
37256
|
+
dispatch(setTrajectTransferAvailability({ nodeId: node.nodeId, key, result: result && buildTrajectTransferLine(node, result) ? result : null }));
|
|
37257
|
+
})
|
|
37258
|
+
.catch((error) => {
|
|
37259
|
+
if (controller.signal.aborted)
|
|
37260
|
+
return;
|
|
37261
|
+
console.error('Failed to resolve traject transfer', node.name, error);
|
|
37262
|
+
dispatch(setTrajectTransferAvailability({ nodeId: node.nodeId, key, result: null }));
|
|
37263
|
+
})
|
|
37264
|
+
.finally(() => {
|
|
37265
|
+
if (searches.get(node.nodeId)?.controller === controller)
|
|
37266
|
+
searches.delete(node.nodeId);
|
|
37267
|
+
});
|
|
37268
|
+
});
|
|
37269
|
+
}, [trajectEntry, editablePackagingEntry, trajectTransferAvailability]);
|
|
37057
37270
|
useEffect(() => {
|
|
37058
|
-
if (!trajectEntry || !editablePackagingEntry
|
|
37271
|
+
if (!trajectEntry || !editablePackagingEntry)
|
|
37059
37272
|
return;
|
|
37273
|
+
const bookedMode = isBookedEntryTraject(trajectEntry);
|
|
37060
37274
|
const dropped = getDroppedTrajectNodeIds(trajectEntry, editablePackagingEntry);
|
|
37061
|
-
const
|
|
37275
|
+
const lines = editablePackagingEntry.lines ?? [];
|
|
37276
|
+
const guids = new Set(lines.map((line) => line.guid));
|
|
37062
37277
|
const toAdd = [];
|
|
37063
37278
|
const toRemove = [];
|
|
37064
37279
|
(trajectEntry.nodes ?? []).filter(isTrajectTransferNode).forEach((node) => {
|
|
37065
|
-
const
|
|
37066
|
-
|
|
37067
|
-
if (
|
|
37068
|
-
|
|
37069
|
-
|
|
37070
|
-
|
|
37071
|
-
|
|
37072
|
-
|
|
37280
|
+
const endpoints = dropped.has(node.nodeId) ? null : getTrajectTransferEndpoints(trajectEntry, lines, node);
|
|
37281
|
+
let wanted = [];
|
|
37282
|
+
if (endpoints && bookedMode && endpoints.asDrawn) {
|
|
37283
|
+
wanted = getTrajectTransferLines(node, trajectEntry.entry.lines ?? []);
|
|
37284
|
+
}
|
|
37285
|
+
else if (endpoints) {
|
|
37286
|
+
const availability = trajectTransferAvailability[node.nodeId];
|
|
37287
|
+
const line = availability?.key === endpoints.key && availability.result ? buildTrajectTransferLine(node, availability.result) : null;
|
|
37288
|
+
if (line)
|
|
37289
|
+
wanted = [line];
|
|
37290
|
+
}
|
|
37291
|
+
const wantedGuids = new Set(wanted.map((line) => line.guid));
|
|
37292
|
+
getTrajectTransferLines(node, lines)
|
|
37293
|
+
.filter((line) => !wantedGuids.has(line.guid))
|
|
37294
|
+
.forEach((line) => toRemove.push(line.guid));
|
|
37295
|
+
wanted.filter((line) => !guids.has(line.guid)).forEach((line) => toAdd.push(line));
|
|
37073
37296
|
});
|
|
37074
37297
|
if (toRemove.length)
|
|
37075
37298
|
dispatch(removeEditableEntryLines(toRemove));
|
|
@@ -37083,7 +37306,8 @@ const SearchResultsContainer = ({ onBookingStarted }) => {
|
|
|
37083
37306
|
const dispatch = useDispatch();
|
|
37084
37307
|
const context = useContext(SearchResultsConfigurationContext);
|
|
37085
37308
|
const translations = getTranslations(context?.languageCode ?? 'en-GB');
|
|
37086
|
-
const { results, filteredResults, packagingAccoResults, filteredPackagingAccoResults, isLoading, flightsLoading, initialFilters, filters, flightFilters, selectedSortType, selectedFlightSortType, selectedSearchResult, selectedPackagingAccoResultCode, flyInIsOpen, packagingAccoSearchDetails, editablePackagingEntry, transactionId, flyInType, packagingFlightResults, confirmedExcursionsByDay, bookPackagingEntry, trajectEditLineGuid, trajectResolving, trajectItineraryView } = useSelector((state) => state.searchResults);
|
|
37309
|
+
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 } = useSelector((state) => state.searchResults);
|
|
37310
|
+
const trajectBusy = trajectResolving || Object.values(trajectTransferAvailability).some((transfer) => transfer.status === 'loading');
|
|
37087
37311
|
const isMobile = useMediaQuery('(max-width: 1200px)');
|
|
37088
37312
|
// Resolves live availability per traject node; no-op unless a traject entry is configured.
|
|
37089
37313
|
useTrajectAvailability();
|
|
@@ -37095,7 +37319,6 @@ const SearchResultsContainer = ({ onBookingStarted }) => {
|
|
|
37095
37319
|
const [detailsIsLoading, setDetailsIsLoading] = useState(false);
|
|
37096
37320
|
const [pricesAreLoading, setPricesAreLoading] = useState(false);
|
|
37097
37321
|
const [itineraryIsLoading, setItineraryIsLoading] = useState(false);
|
|
37098
|
-
const [entrySaving, setEntrySaving] = useState(false);
|
|
37099
37322
|
const [itineraryOpen, setItineraryOpen] = useState(false);
|
|
37100
37323
|
const [isBookingConfirmation, setIsBookingConfirmation] = useState(false);
|
|
37101
37324
|
const [selectedAccommodationSeed, setSelectedAccommodationSeed] = useState(null);
|
|
@@ -37853,40 +38076,16 @@ const SearchResultsContainer = ({ onBookingStarted }) => {
|
|
|
37853
38076
|
useEffect(() => {
|
|
37854
38077
|
setInitialFiltersSet(false);
|
|
37855
38078
|
}, [activeSearchSeed]);
|
|
37856
|
-
const handleSaveEntry = async () => {
|
|
37857
|
-
if (!context || !bookableEntry || entrySaving)
|
|
37858
|
-
return;
|
|
37859
|
-
setEntrySaving(true);
|
|
37860
|
-
try {
|
|
37861
|
-
const config = {
|
|
37862
|
-
host: context.tideConnection.host,
|
|
37863
|
-
apiKey: context.tideConnection.apiKey
|
|
37864
|
-
};
|
|
37865
|
-
const request = {
|
|
37866
|
-
language: context.languageCode ?? 'en-GB',
|
|
37867
|
-
officeId: context.tideConnection.officeId,
|
|
37868
|
-
catalogueId: context.searchConfiguration.defaultCatalogueId ?? first(context?.tideConnection?.catalogueIds) ?? 1,
|
|
37869
|
-
agentId: context.agentId,
|
|
37870
|
-
payload: bookableEntry
|
|
37871
|
-
};
|
|
37872
|
-
await build.savePackagingEntry(config, request);
|
|
37873
|
-
if (typeof window !== 'undefined')
|
|
37874
|
-
window.location.reload();
|
|
37875
|
-
}
|
|
37876
|
-
catch (err) {
|
|
37877
|
-
console.error('Failed to save the entry', err);
|
|
37878
|
-
setEntrySaving(false);
|
|
37879
|
-
}
|
|
37880
|
-
};
|
|
37881
38079
|
useEffect(() => {
|
|
37882
38080
|
const fetchPriceDetails = async () => {
|
|
37883
38081
|
if (!context || !editablePackagingEntry || isEmpty$1(editablePackagingEntry.lines))
|
|
37884
38082
|
return;
|
|
37885
38083
|
// A traject resolves one node at a time. Pricing each step would show a total that climbs
|
|
37886
38084
|
// as the trip fills in, so the whole trip is priced once, when the last node is in.
|
|
37887
|
-
if (context.trajectEntry &&
|
|
38085
|
+
if (context.trajectEntry && trajectBusy)
|
|
37888
38086
|
return;
|
|
37889
38087
|
setPricesAreLoading(true);
|
|
38088
|
+
dispatch(setPriceDetailsFailed(false));
|
|
37890
38089
|
try {
|
|
37891
38090
|
const config = {
|
|
37892
38091
|
host: context.tideConnection.host,
|
|
@@ -37905,18 +38104,21 @@ const SearchResultsContainer = ({ onBookingStarted }) => {
|
|
|
37905
38104
|
}
|
|
37906
38105
|
catch (err) {
|
|
37907
38106
|
console.error('Error fetching price details', err);
|
|
38107
|
+
// A price from before the change would be wrong for the entry as it is now.
|
|
38108
|
+
dispatch(setPriceDetails(null));
|
|
38109
|
+
dispatch(setPriceDetailsFailed(true));
|
|
37908
38110
|
setPricesAreLoading(false);
|
|
37909
38111
|
}
|
|
37910
38112
|
};
|
|
37911
38113
|
fetchPriceDetails();
|
|
37912
|
-
}, [editablePackagingEntry, bookableEntry,
|
|
38114
|
+
}, [editablePackagingEntry, bookableEntry, trajectBusy, priceDetailsRequest]);
|
|
37913
38115
|
useEffect(() => {
|
|
37914
38116
|
const fetchItinerary = async () => {
|
|
37915
38117
|
if (!context || !context.packagingEntry || !editablePackagingEntry || isEmpty$1(editablePackagingEntry.lines))
|
|
37916
38118
|
return;
|
|
37917
38119
|
// While a traject is being composed its own day-by-day column is the itinerary; the generated
|
|
37918
38120
|
// one is what the traveller reads the finished trip back from, so it waits until then.
|
|
37919
|
-
if (context.trajectEntry && (
|
|
38121
|
+
if (context.trajectEntry && (trajectBusy || !trajectItineraryView))
|
|
37920
38122
|
return;
|
|
37921
38123
|
setItineraryIsLoading(true);
|
|
37922
38124
|
try {
|
|
@@ -37941,7 +38143,7 @@ const SearchResultsContainer = ({ onBookingStarted }) => {
|
|
|
37941
38143
|
}
|
|
37942
38144
|
};
|
|
37943
38145
|
fetchItinerary();
|
|
37944
|
-
}, [editablePackagingEntry, bookableEntry,
|
|
38146
|
+
}, [editablePackagingEntry, bookableEntry, trajectBusy, trajectItineraryView]);
|
|
37945
38147
|
// Flight selection
|
|
37946
38148
|
// const [selectedOutwardKey, setSelectedOutwardKey] = useState<string | null>(null);
|
|
37947
38149
|
// const [selectedReturnKey, setSelectedReturnKey] = useState<string | null>(null);
|
|
@@ -38319,7 +38521,7 @@ const SearchResultsContainer = ({ onBookingStarted }) => {
|
|
|
38319
38521
|
context.searchConfiguration.qsmType != build.PortalQsmType.AccommodationAndFlight && context.showFilters && (React__default.createElement(Filters, { initialFilters: initialFilters, filters: filters, isOpen: filtersOpen, handleSetIsOpen: () => setFiltersOpen(!filtersOpen),
|
|
38320
38522
|
// handleApplyFilters={() => setSearchTrigger((prev) => prev + 1)}
|
|
38321
38523
|
isLoading: isLoading, setFilters: (filters) => dispatch(setFilters(filters)), resetFilters: (filters) => dispatch(resetFilters(filters)) })),
|
|
38322
|
-
context.searchConfiguration.qsmType === build.PortalQsmType.AccommodationAndFlight && (React__default.createElement(Itinerary, { isOpen: itineraryOpen, handleSetIsOpen: () => setItineraryOpen(!itineraryOpen), isLoading: isLoading || pricesAreLoading || flightsLoading || itineraryIsLoading ||
|
|
38524
|
+
context.searchConfiguration.qsmType === build.PortalQsmType.AccommodationAndFlight && (React__default.createElement(Itinerary, { isOpen: itineraryOpen, handleSetIsOpen: () => setItineraryOpen(!itineraryOpen), isLoading: isLoading || pricesAreLoading || flightsLoading || itineraryIsLoading || trajectBusy, onEditAccommodation: handleEditAccommodation })),
|
|
38323
38525
|
React__default.createElement("div", { className: "search__results" },
|
|
38324
38526
|
isMobile && (React__default.createElement("div", { className: "search__result-row" },
|
|
38325
38527
|
React__default.createElement("div", { className: "search__results__actions" },
|