@qite/tide-booking-component 1.4.101 → 1.4.103
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 +998 -451
- package/build/build-cjs/src/index.d.ts +3 -1
- package/build/build-cjs/src/search-results/components/flight/flight-selection/independent-flight-option.d.ts +3 -0
- package/build/build-cjs/src/search-results/store/search-results-slice.d.ts +8 -1
- package/build/build-cjs/src/search-results/types.d.ts +3 -1
- package/build/build-cjs/src/search-results/utils/flight-utils.d.ts +1 -0
- package/build/build-cjs/src/shared/utils/localization-util.d.ts +2 -0
- package/build/build-esm/index.js +998 -445
- package/build/build-esm/src/index.d.ts +3 -1
- package/build/build-esm/src/search-results/components/flight/flight-selection/independent-flight-option.d.ts +3 -0
- package/build/build-esm/src/search-results/store/search-results-slice.d.ts +8 -1
- package/build/build-esm/src/search-results/types.d.ts +3 -1
- package/build/build-esm/src/search-results/utils/flight-utils.d.ts +1 -0
- package/build/build-esm/src/shared/utils/localization-util.d.ts +2 -0
- package/package.json +2 -2
- package/src/index.ts +3 -1
- package/src/search-results/components/flight/flight-selection/independent-flight-option.tsx +30 -9
- package/src/search-results/components/flight/flight-selection/independent-flight-selection.tsx +2 -6
- package/src/search-results/components/hotel/hotel-accommodation-results.tsx +11 -12
- package/src/search-results/components/icon.tsx +1 -1
- package/src/search-results/components/itinerary/index.tsx +0 -2
- package/src/search-results/components/search-results-container/search-results-container.tsx +695 -83
- package/src/search-results/store/search-results-slice.ts +20 -1
- package/src/search-results/types.ts +3 -1
- package/src/search-results/utils/flight-utils.ts +5 -0
- package/src/shared/components/flyin/accommodation-flyin.tsx +4 -2
- package/src/shared/translations/ar-SA.json +2 -0
- package/src/shared/translations/da-DK.json +2 -0
- package/src/shared/translations/de-DE.json +2 -0
- package/src/shared/translations/en-GB.json +2 -0
- package/src/shared/translations/es-ES.json +2 -0
- package/src/shared/translations/fr-BE.json +2 -0
- package/src/shared/translations/fr-FR.json +2 -0
- package/src/shared/translations/is-IS.json +2 -0
- package/src/shared/translations/it-IT.json +2 -0
- package/src/shared/translations/ja-JP.json +2 -0
- package/src/shared/translations/nl-BE.json +2 -0
- package/src/shared/translations/nl-NL.json +2 -0
- package/src/shared/translations/no-NO.json +2 -0
- package/src/shared/translations/pl-PL.json +2 -0
- package/src/shared/translations/pt-PT.json +2 -0
- package/src/shared/translations/sv-SE.json +2 -0
- package/src/shared/utils/localization-util.ts +5 -2
- package/styles/components/_flight-option.scss +14 -1
- package/styles/components/_search.scss +5 -0
|
@@ -18,7 +18,11 @@ import {
|
|
|
18
18
|
setTransactionId,
|
|
19
19
|
setAccommodationFlyInStep,
|
|
20
20
|
setPriceDetails,
|
|
21
|
-
setItinerary
|
|
21
|
+
setItinerary,
|
|
22
|
+
setFlightsLoading,
|
|
23
|
+
setPackagingFlightResults,
|
|
24
|
+
setSelectedPackagingFlight,
|
|
25
|
+
setSelectedPackagingAccoResult
|
|
22
26
|
} from '../../store/search-results-slice';
|
|
23
27
|
import { AccommodationFlyInStep, Filter, SearchSeed, SortByType } from '../../types';
|
|
24
28
|
import useMediaQuery from '../../../shared/utils/use-media-query-util';
|
|
@@ -41,10 +45,16 @@ import {
|
|
|
41
45
|
startTransaction,
|
|
42
46
|
PackagingEntryLine,
|
|
43
47
|
getPriceDetails,
|
|
44
|
-
getItinerary
|
|
48
|
+
getItinerary,
|
|
49
|
+
FlightSearchRequest,
|
|
50
|
+
searchPackagingFlights,
|
|
51
|
+
PackagingFlightResponse,
|
|
52
|
+
PackagingAccommodationResponse,
|
|
53
|
+
FlightSearchResponseFlightSegment,
|
|
54
|
+
PackagingEntryLineFlightLine
|
|
45
55
|
} from '@qite/tide-client';
|
|
46
56
|
import { getDateFromParams, getNumberFromParams, getRoomsFromParams, getStringFromParams } from '../../../shared/utils/query-string-util';
|
|
47
|
-
import { first, last, range } from 'lodash';
|
|
57
|
+
import { concat, first, isEmpty, last, range } from 'lodash';
|
|
48
58
|
import { Room } from '../../../booking-wizard/types';
|
|
49
59
|
import Icon from '../icon';
|
|
50
60
|
import Itinerary from '../itinerary';
|
|
@@ -53,7 +63,7 @@ import FlyIn from '../../../shared/components/flyin/flyin';
|
|
|
53
63
|
import HotelAccommodationResults from '../hotel/hotel-accommodation-results';
|
|
54
64
|
import RoundTripResults from '../round-trip/round-trip-results';
|
|
55
65
|
import FlightResults from '../flight/flight-results';
|
|
56
|
-
import { findSortByType, getSortingName, getTranslations } from '../../../shared/utils/localization-util';
|
|
66
|
+
import { dateToDateStruct, findSortByType, getSortingName, getTranslations } from '../../../shared/utils/localization-util';
|
|
57
67
|
import { FlightSearchProvider } from '../flight/flight-search-context';
|
|
58
68
|
import FlightResultsContainer from './flight-search-results';
|
|
59
69
|
import Filters from '../filters/filters';
|
|
@@ -77,6 +87,20 @@ import {
|
|
|
77
87
|
} from '../../utils/query-utils';
|
|
78
88
|
import { getRequestRoomsFromPackagingSegments, getRoomIndexFromLine, getSelectedOptionsPerRoom } from '../../utils/packaging-utils';
|
|
79
89
|
import FullItinerary from '../itinerary/full-itinerary';
|
|
90
|
+
import { getFlightKey } from '../../utils/flight-utils';
|
|
91
|
+
import IndependentFlightOption from '../flight/flight-selection/independent-flight-option';
|
|
92
|
+
import { Spinner } from '../../..';
|
|
93
|
+
import { PackagingRequestBase } from '@qite/tide-client/build/types/booking-v2/request/packaging/packaging-request-base';
|
|
94
|
+
|
|
95
|
+
type BuildPackagingEntryPartialArgs = {
|
|
96
|
+
sourceEntry: PackagingEntry | null | undefined;
|
|
97
|
+
selectedHotelCode: string | null | undefined;
|
|
98
|
+
accommodationResults: PackagingAccommodationResponse[];
|
|
99
|
+
selectedFlight: PackagingFlightResponse | null;
|
|
100
|
+
seed: SearchSeed;
|
|
101
|
+
transactionId: string;
|
|
102
|
+
language: string;
|
|
103
|
+
};
|
|
80
104
|
|
|
81
105
|
const SearchResultsContainer: React.FC = () => {
|
|
82
106
|
const currentSearch = typeof window !== 'undefined' ? window.location.search : '';
|
|
@@ -93,6 +117,7 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
93
117
|
filteredPackagingAccoResults,
|
|
94
118
|
bookingPackageDetails,
|
|
95
119
|
isLoading,
|
|
120
|
+
flightsLoading,
|
|
96
121
|
filters,
|
|
97
122
|
selectedSortType,
|
|
98
123
|
selectedSearchResult,
|
|
@@ -102,7 +127,8 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
102
127
|
editablePackagingEntry,
|
|
103
128
|
transactionId,
|
|
104
129
|
accommodationFlyInStep,
|
|
105
|
-
itinerary
|
|
130
|
+
itinerary,
|
|
131
|
+
packagingFlightResults
|
|
106
132
|
} = useSelector((state: SearchResultsRootState) => state.searchResults);
|
|
107
133
|
|
|
108
134
|
const isMobile = useMediaQuery('(max-width: 1200px)');
|
|
@@ -119,6 +145,10 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
119
145
|
|
|
120
146
|
const [selectedAccommodationSeed, setSelectedAccommodationSeed] = useState<SearchSeed | null>(null);
|
|
121
147
|
|
|
148
|
+
const skipInitialPackagingAccoDetailsRef = useRef(false);
|
|
149
|
+
|
|
150
|
+
const [showAllOutwardFlights, setShowAllOutwardFlights] = useState(false);
|
|
151
|
+
|
|
122
152
|
const panelRef = useRef<HTMLDivElement | null>(null);
|
|
123
153
|
|
|
124
154
|
const sortByTypes: SortByType[] = [
|
|
@@ -276,7 +306,7 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
276
306
|
|
|
277
307
|
return {
|
|
278
308
|
transactionId: currentTransactionId,
|
|
279
|
-
officeId: 1,
|
|
309
|
+
officeId: context?.tideConnection?.officeId ?? 1,
|
|
280
310
|
agentId: context?.agentId ?? null,
|
|
281
311
|
catalogueId: context!.searchConfiguration.defaultCatalogueId ?? 0,
|
|
282
312
|
searchConfigurationId: context!.searchConfiguration.id,
|
|
@@ -299,6 +329,43 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
299
329
|
};
|
|
300
330
|
};
|
|
301
331
|
|
|
332
|
+
const buildPackagingFlightRequestFromSeed = (seed: SearchSeed, currentTransactionId: string): FlightSearchRequest => {
|
|
333
|
+
if (typeof window !== 'undefined') {
|
|
334
|
+
window.scrollTo(0, 0);
|
|
335
|
+
}
|
|
336
|
+
var adults = seed.rooms.flatMap((x) => x.pax).filter((x) => x.age! >= 18).length;
|
|
337
|
+
var kids = seed.rooms.flatMap((x) => x.pax).filter((x) => x.age! >= 2 && x.age! < 18).length;
|
|
338
|
+
var babies = seed.rooms.flatMap((x) => x.pax).filter((x) => x.age! < 2).length;
|
|
339
|
+
|
|
340
|
+
return {
|
|
341
|
+
transactionId: currentTransactionId,
|
|
342
|
+
officeId: context?.tideConnection?.officeId ?? 1,
|
|
343
|
+
catalogueId: first(context?.tideConnection?.catalogueIds) ?? 0,
|
|
344
|
+
departureAirportCode: seed.departureAirport,
|
|
345
|
+
arrivalAirportCode: seed.destinationAirport,
|
|
346
|
+
returnAirportCode: seed.returnAirport,
|
|
347
|
+
luggageIncluded: null,
|
|
348
|
+
maxStops: null,
|
|
349
|
+
travelClass: seed.travelClass,
|
|
350
|
+
pax: concat(
|
|
351
|
+
Array.from({ length: adults ?? 0 }, (_, index) => ({
|
|
352
|
+
id: index,
|
|
353
|
+
age: 31
|
|
354
|
+
})),
|
|
355
|
+
Array.from({ length: kids ?? 0 }, (_, index) => ({
|
|
356
|
+
id: index + (adults ?? 0),
|
|
357
|
+
age: 8
|
|
358
|
+
})),
|
|
359
|
+
Array.from({ length: babies ?? 0 }, (_, index) => ({
|
|
360
|
+
id: index + (adults ?? 0) + (kids ?? 0),
|
|
361
|
+
age: 1
|
|
362
|
+
}))
|
|
363
|
+
),
|
|
364
|
+
outward: seed.fromDate ? { date: dateToDateStruct(new Date(seed.fromDate)) } : null,
|
|
365
|
+
return: seed.toDate ? { date: dateToDateStruct(new Date(seed.toDate)) } : null
|
|
366
|
+
} as FlightSearchRequest;
|
|
367
|
+
};
|
|
368
|
+
|
|
302
369
|
const buildSearchSeedFromQueryParams = (params: URLSearchParams): SearchSeed | null => {
|
|
303
370
|
const from = getDateFromParams(params, 'fromDate');
|
|
304
371
|
const to = getDateFromParams(params, 'toDate');
|
|
@@ -311,6 +378,8 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
311
378
|
const tagId = getNumberFromParams(params, 'tagId');
|
|
312
379
|
const destinationAirport = getStringFromParams(params, 'destinationAirport');
|
|
313
380
|
const departureAirport = getStringFromParams(params, 'departureAirport');
|
|
381
|
+
const travelClass = getStringFromParams(params, 'travelClass');
|
|
382
|
+
const nationality = getStringFromParams(params, 'nationality');
|
|
314
383
|
|
|
315
384
|
if (!from || !to) {
|
|
316
385
|
return null;
|
|
@@ -328,10 +397,55 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
328
397
|
tagId,
|
|
329
398
|
destinationAirport,
|
|
330
399
|
departureAirport,
|
|
400
|
+
travelClass,
|
|
401
|
+
nationality,
|
|
331
402
|
rooms: getRequestRooms(rooms)
|
|
332
403
|
};
|
|
333
404
|
};
|
|
334
405
|
|
|
406
|
+
const buildSearchSeedFromAccommodationSegments = (entry: PackagingEntry, segments: PackagingEntryLine[]): SearchSeed | null => {
|
|
407
|
+
if (!segments?.length) return null;
|
|
408
|
+
|
|
409
|
+
const sortedSegments = [...segments].sort((a, b) => new Date(a.from).getTime() - new Date(b.from).getTime());
|
|
410
|
+
|
|
411
|
+
const firstSegment = first(sortedSegments);
|
|
412
|
+
const lastSegment = last(sortedSegments);
|
|
413
|
+
|
|
414
|
+
if (!firstSegment || !lastSegment) return null;
|
|
415
|
+
|
|
416
|
+
return {
|
|
417
|
+
fromDate: toDateOnlyString(firstSegment.from),
|
|
418
|
+
toDate: toDateOnlyString(lastSegment.to),
|
|
419
|
+
country: firstSegment.country?.id ?? null,
|
|
420
|
+
region: firstSegment.region?.id ?? null,
|
|
421
|
+
oord: firstSegment.oord?.id ?? null,
|
|
422
|
+
location: firstSegment.location?.id ?? null,
|
|
423
|
+
hotel: parseHotelId(firstSegment),
|
|
424
|
+
hotelCode: firstSegment.productCode ?? null,
|
|
425
|
+
tagId: null,
|
|
426
|
+
destinationAirport: getDestinationAirportFromEntry(entry.lines ?? []),
|
|
427
|
+
departureAirport: getDepartureAirportFromEntry(entry.lines ?? []),
|
|
428
|
+
rooms: getRequestRoomsFromPackagingSegments(entry, sortedSegments)
|
|
429
|
+
};
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
const handleEditAccommodation = async (segments: PackagingEntryLine[]) => {
|
|
433
|
+
const sourceEntry = editablePackagingEntry ?? context?.packagingEntry;
|
|
434
|
+
if (!sourceEntry) return;
|
|
435
|
+
|
|
436
|
+
const seed = buildSearchSeedFromAccommodationSegments(sourceEntry, segments);
|
|
437
|
+
if (!seed) return;
|
|
438
|
+
|
|
439
|
+
setDetailsIsLoading(true);
|
|
440
|
+
|
|
441
|
+
setSelectedAccommodationSeed(seed);
|
|
442
|
+
dispatch(setAccommodationFlyInStep('results'));
|
|
443
|
+
handleFlyInToggle(true);
|
|
444
|
+
const currentTransactionId = await getOrCreateTransactionId();
|
|
445
|
+
await runAccommodationFlow(seed, currentTransactionId ?? '');
|
|
446
|
+
setDetailsIsLoading(false);
|
|
447
|
+
};
|
|
448
|
+
|
|
335
449
|
const handleConfirmHotelSwap = () => {
|
|
336
450
|
const updatedEntry = swapHotelInPackagingEntry();
|
|
337
451
|
if (!updatedEntry) return;
|
|
@@ -431,6 +545,18 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
431
545
|
}
|
|
432
546
|
}, [filtersOpen]);
|
|
433
547
|
|
|
548
|
+
const getOrCreateTransactionId = async (): Promise<string | null> => {
|
|
549
|
+
if (context?.packagingEntry?.transactionId) {
|
|
550
|
+
return context.packagingEntry.transactionId;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
if (transactionId) {
|
|
554
|
+
return transactionId;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
return await runStartTransaction();
|
|
558
|
+
};
|
|
559
|
+
|
|
434
560
|
const runSearch = async () => {
|
|
435
561
|
try {
|
|
436
562
|
if (!context) return;
|
|
@@ -476,19 +602,21 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
476
602
|
try {
|
|
477
603
|
if (!context) return null;
|
|
478
604
|
|
|
605
|
+
dispatch(setIsLoading(true));
|
|
606
|
+
|
|
479
607
|
const config: TideClientConfig = {
|
|
480
608
|
host: context.tideConnection.host,
|
|
481
609
|
apiKey: context.tideConnection.apiKey
|
|
482
610
|
};
|
|
483
611
|
|
|
484
612
|
const transaction = await startTransaction(config);
|
|
485
|
-
console.log('Transaction started', transaction);
|
|
486
613
|
|
|
487
614
|
dispatch(setTransactionId(transaction.transactionId));
|
|
488
|
-
|
|
615
|
+
dispatch(setIsLoading(false));
|
|
489
616
|
return transaction.transactionId;
|
|
490
617
|
} catch (err) {
|
|
491
618
|
console.error('Transaction failed', err);
|
|
619
|
+
dispatch(setIsLoading(false));
|
|
492
620
|
return null;
|
|
493
621
|
}
|
|
494
622
|
};
|
|
@@ -520,28 +648,55 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
520
648
|
const initialFilteredResults = applyFiltersToPackageAccoResults(packageAccoSearchResults, filters, null);
|
|
521
649
|
dispatch(setFilteredPackagingAccoResults(initialFilteredResults));
|
|
522
650
|
|
|
651
|
+
if (initialFilteredResults.length > 0) {
|
|
652
|
+
skipInitialPackagingAccoDetailsRef.current = true;
|
|
653
|
+
dispatch(setSelectedPackagingAccoResult(first(initialFilteredResults)?.code ?? null));
|
|
654
|
+
}
|
|
655
|
+
|
|
523
656
|
dispatch(setIsLoading(false));
|
|
524
657
|
} catch (err) {
|
|
525
|
-
console.error('
|
|
658
|
+
console.error('HotelSearch failed', err);
|
|
526
659
|
dispatch(setIsLoading(false));
|
|
527
660
|
}
|
|
528
661
|
};
|
|
529
662
|
|
|
530
|
-
const runAccommodationFlow = async (seed: SearchSeed) => {
|
|
663
|
+
const runAccommodationFlow = async (seed: SearchSeed, currentTransactionId: string) => {
|
|
531
664
|
if (!context || context.showMockup) return;
|
|
665
|
+
await runHotelSearch(currentTransactionId, seed);
|
|
666
|
+
};
|
|
532
667
|
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
}
|
|
668
|
+
const runFlightSearch = async (currentTransactionId: string, seed: SearchSeed) => {
|
|
669
|
+
try {
|
|
670
|
+
if (!context) return;
|
|
671
|
+
dispatch(setFlightsLoading(true));
|
|
538
672
|
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
673
|
+
const config: TideClientConfig = {
|
|
674
|
+
host: context.tideConnection.host,
|
|
675
|
+
apiKey: context.tideConnection.apiKey
|
|
676
|
+
};
|
|
677
|
+
|
|
678
|
+
let searchRequest: FlightSearchRequest = buildPackagingFlightRequestFromSeed(seed, currentTransactionId);
|
|
679
|
+
searchRequest.agentId = context.agentId;
|
|
680
|
+
|
|
681
|
+
const packageFlightSearchResults = await searchPackagingFlights(config, searchRequest);
|
|
682
|
+
|
|
683
|
+
dispatch(setPackagingFlightResults(packageFlightSearchResults));
|
|
684
|
+
const firstResult = first(packageFlightSearchResults);
|
|
685
|
+
if (firstResult) {
|
|
686
|
+
setSelectedOutwardKey(getFlightKey(firstResult.outward.segments));
|
|
687
|
+
setSelectedReturnKey(getFlightKey(firstResult.return.segments));
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
dispatch(setFlightsLoading(false));
|
|
691
|
+
} catch (err) {
|
|
692
|
+
console.error('FlightSearch failed', err);
|
|
693
|
+
dispatch(setFlightsLoading(false));
|
|
542
694
|
}
|
|
695
|
+
};
|
|
543
696
|
|
|
544
|
-
|
|
697
|
+
const runFlightFlow = async (seed: SearchSeed, currentTransactionId: string) => {
|
|
698
|
+
if (!context || context.showMockup) return;
|
|
699
|
+
await runFlightSearch(currentTransactionId, seed);
|
|
545
700
|
};
|
|
546
701
|
|
|
547
702
|
// separate Search
|
|
@@ -555,20 +710,41 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
555
710
|
|
|
556
711
|
if (context?.searchConfiguration.qsmType === PortalQsmType.Accommodation) {
|
|
557
712
|
const seed = activeSearchSeed;
|
|
713
|
+
|
|
558
714
|
if (seed) {
|
|
559
|
-
|
|
715
|
+
(async () => {
|
|
716
|
+
const transactionId = await getOrCreateTransactionId();
|
|
717
|
+
if (!transactionId) return;
|
|
718
|
+
|
|
719
|
+
await runAccommodationFlow(seed, transactionId);
|
|
720
|
+
})();
|
|
560
721
|
}
|
|
561
722
|
}
|
|
562
723
|
|
|
563
724
|
if (
|
|
564
725
|
context?.searchConfiguration.qsmType === PortalQsmType.AccommodationAndFlight &&
|
|
565
726
|
context.searchConfiguration.enableManualPackaging &&
|
|
566
|
-
context.searchConfiguration.allowAccommodations &&
|
|
567
727
|
!context?.packagingEntry
|
|
568
728
|
) {
|
|
569
729
|
const seed = activeSearchSeed;
|
|
730
|
+
|
|
570
731
|
if (seed) {
|
|
571
|
-
|
|
732
|
+
(async () => {
|
|
733
|
+
const sharedTransactionId = await getOrCreateTransactionId();
|
|
734
|
+
if (!sharedTransactionId) return;
|
|
735
|
+
|
|
736
|
+
const tasks: Promise<void>[] = [];
|
|
737
|
+
|
|
738
|
+
if (context.searchConfiguration.allowAccommodations) {
|
|
739
|
+
tasks.push(runAccommodationFlow(seed, sharedTransactionId));
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
if (context.searchConfiguration.allowFlights) {
|
|
743
|
+
tasks.push(runFlightFlow(seed, sharedTransactionId));
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
await Promise.all(tasks);
|
|
747
|
+
})();
|
|
572
748
|
}
|
|
573
749
|
}
|
|
574
750
|
}, [
|
|
@@ -583,7 +759,6 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
583
759
|
|
|
584
760
|
useEffect(() => {
|
|
585
761
|
if (context?.packagingEntry) {
|
|
586
|
-
console.log('original packaging entry from context', context.packagingEntry);
|
|
587
762
|
dispatch(setEditablePackagingEntry(structuredClone(context.packagingEntry)));
|
|
588
763
|
dispatch(setTransactionId(context.packagingEntry.transactionId));
|
|
589
764
|
}
|
|
@@ -651,6 +826,12 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
651
826
|
|
|
652
827
|
const fetchPackagingAccoSearchDetails = async () => {
|
|
653
828
|
if (!selectedPackagingAccoResultCode || !context) return;
|
|
829
|
+
|
|
830
|
+
if (skipInitialPackagingAccoDetailsRef.current) {
|
|
831
|
+
skipInitialPackagingAccoDetailsRef.current = false;
|
|
832
|
+
return;
|
|
833
|
+
}
|
|
834
|
+
|
|
654
835
|
setDetailsIsLoading(true);
|
|
655
836
|
if (
|
|
656
837
|
context?.searchConfiguration.qsmType === PortalQsmType.Accommodation ||
|
|
@@ -759,52 +940,9 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
759
940
|
setInitialFiltersSet(false);
|
|
760
941
|
}, [activeSearchSeed]);
|
|
761
942
|
|
|
762
|
-
const handleEditAccommodation = async (segments: PackagingEntryLine[]) => {
|
|
763
|
-
const sourceEntry = editablePackagingEntry ?? context?.packagingEntry;
|
|
764
|
-
if (!sourceEntry) return;
|
|
765
|
-
|
|
766
|
-
const seed = buildSearchSeedFromAccommodationSegments(sourceEntry, segments);
|
|
767
|
-
if (!seed) return;
|
|
768
|
-
|
|
769
|
-
setDetailsIsLoading(true);
|
|
770
|
-
|
|
771
|
-
setSelectedAccommodationSeed(seed);
|
|
772
|
-
dispatch(setAccommodationFlyInStep('results'));
|
|
773
|
-
handleFlyInToggle(true);
|
|
774
|
-
|
|
775
|
-
await runAccommodationFlow(seed);
|
|
776
|
-
setDetailsIsLoading(false);
|
|
777
|
-
};
|
|
778
|
-
|
|
779
|
-
const buildSearchSeedFromAccommodationSegments = (entry: PackagingEntry, segments: PackagingEntryLine[]): SearchSeed | null => {
|
|
780
|
-
if (!segments?.length) return null;
|
|
781
|
-
|
|
782
|
-
const sortedSegments = [...segments].sort((a, b) => new Date(a.from).getTime() - new Date(b.from).getTime());
|
|
783
|
-
|
|
784
|
-
const firstSegment = first(sortedSegments);
|
|
785
|
-
const lastSegment = last(sortedSegments);
|
|
786
|
-
|
|
787
|
-
if (!firstSegment || !lastSegment) return null;
|
|
788
|
-
|
|
789
|
-
return {
|
|
790
|
-
fromDate: toDateOnlyString(firstSegment.from),
|
|
791
|
-
toDate: toDateOnlyString(lastSegment.to),
|
|
792
|
-
country: firstSegment.country?.id ?? null,
|
|
793
|
-
region: firstSegment.region?.id ?? null,
|
|
794
|
-
oord: firstSegment.oord?.id ?? null,
|
|
795
|
-
location: firstSegment.location?.id ?? null,
|
|
796
|
-
hotel: parseHotelId(firstSegment),
|
|
797
|
-
hotelCode: firstSegment.productCode ?? null,
|
|
798
|
-
tagId: null,
|
|
799
|
-
destinationAirport: getDestinationAirportFromEntry(entry.lines ?? []),
|
|
800
|
-
departureAirport: getDepartureAirportFromEntry(entry.lines ?? []),
|
|
801
|
-
rooms: getRequestRoomsFromPackagingSegments(entry, sortedSegments)
|
|
802
|
-
};
|
|
803
|
-
};
|
|
804
|
-
|
|
805
943
|
useEffect(() => {
|
|
806
944
|
const fetchPriceDetails = async () => {
|
|
807
|
-
if (!context || !editablePackagingEntry) return;
|
|
945
|
+
if (!context || !editablePackagingEntry || isEmpty(editablePackagingEntry.lines)) return;
|
|
808
946
|
setPricesAreLoading(true);
|
|
809
947
|
try {
|
|
810
948
|
const config: TideClientConfig = {
|
|
@@ -812,7 +950,15 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
812
950
|
apiKey: context.tideConnection.apiKey
|
|
813
951
|
};
|
|
814
952
|
|
|
815
|
-
const
|
|
953
|
+
const request = {
|
|
954
|
+
language: context.languageCode ?? 'en-GB',
|
|
955
|
+
officeId: context.tideConnection.officeId,
|
|
956
|
+
catalogueId: context.searchConfiguration.defaultCatalogueId ?? 0,
|
|
957
|
+
agentId: context.agentId,
|
|
958
|
+
payload: editablePackagingEntry
|
|
959
|
+
} as PackagingRequestBase<PackagingEntry>;
|
|
960
|
+
|
|
961
|
+
const priceDetails = await getPriceDetails(config, request);
|
|
816
962
|
dispatch(setPriceDetails(priceDetails));
|
|
817
963
|
setPricesAreLoading(false);
|
|
818
964
|
} catch (err) {
|
|
@@ -822,8 +968,7 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
822
968
|
};
|
|
823
969
|
|
|
824
970
|
const fetchItinerary = async () => {
|
|
825
|
-
|
|
826
|
-
if (!context || !editablePackagingEntry) return;
|
|
971
|
+
if (!context || !context.packagingEntry || !editablePackagingEntry || isEmpty(editablePackagingEntry.lines)) return;
|
|
827
972
|
setItineraryIsLoading(true);
|
|
828
973
|
try {
|
|
829
974
|
const config: TideClientConfig = {
|
|
@@ -831,7 +976,15 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
831
976
|
apiKey: context.tideConnection.apiKey
|
|
832
977
|
};
|
|
833
978
|
|
|
834
|
-
const
|
|
979
|
+
const request = {
|
|
980
|
+
language: context.languageCode ?? 'en-GB',
|
|
981
|
+
officeId: context.tideConnection.officeId,
|
|
982
|
+
catalogueId: context.searchConfiguration.defaultCatalogueId ?? 0,
|
|
983
|
+
agentId: context.agentId,
|
|
984
|
+
payload: editablePackagingEntry
|
|
985
|
+
} as PackagingRequestBase<PackagingEntry>;
|
|
986
|
+
|
|
987
|
+
const itinerary = await getItinerary(config, request);
|
|
835
988
|
console.log('Fetched itinerary', itinerary);
|
|
836
989
|
dispatch(setItinerary(itinerary));
|
|
837
990
|
setItineraryIsLoading(false);
|
|
@@ -845,6 +998,379 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
845
998
|
fetchItinerary();
|
|
846
999
|
}, [editablePackagingEntry]);
|
|
847
1000
|
|
|
1001
|
+
// Flight selection
|
|
1002
|
+
const [selectedOutwardKey, setSelectedOutwardKey] = useState<string | null>(null);
|
|
1003
|
+
const [selectedReturnKey, setSelectedReturnKey] = useState<string | null>(null);
|
|
1004
|
+
|
|
1005
|
+
const uniqueOutwardFlights: PackagingFlightResponse[] = React.useMemo(() => {
|
|
1006
|
+
const map = new Map();
|
|
1007
|
+
|
|
1008
|
+
packagingFlightResults.forEach((flight) => {
|
|
1009
|
+
const key = getFlightKey(flight.outward.segments);
|
|
1010
|
+
|
|
1011
|
+
if (!map.has(key)) {
|
|
1012
|
+
map.set(key, flight);
|
|
1013
|
+
}
|
|
1014
|
+
});
|
|
1015
|
+
|
|
1016
|
+
return Array.from(map.values());
|
|
1017
|
+
}, [packagingFlightResults]);
|
|
1018
|
+
|
|
1019
|
+
const [uniqueReturnFlights, setUniqueReturnFlights] = useState<PackagingFlightResponse[]>([]);
|
|
1020
|
+
|
|
1021
|
+
useEffect(() => {
|
|
1022
|
+
if (!selectedOutwardKey) {
|
|
1023
|
+
setUniqueReturnFlights([]);
|
|
1024
|
+
setSelectedReturnKey(null);
|
|
1025
|
+
return;
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
// Filter combinations that match selected outward fare
|
|
1029
|
+
const matchingCombinations = packagingFlightResults.filter((flight) => getFlightKey(flight.outward.segments) === selectedOutwardKey);
|
|
1030
|
+
const returnMap = new Map();
|
|
1031
|
+
|
|
1032
|
+
matchingCombinations.forEach((flight) => {
|
|
1033
|
+
const key = getFlightKey(flight.return.segments);
|
|
1034
|
+
|
|
1035
|
+
if (!returnMap.has(key)) {
|
|
1036
|
+
returnMap.set(key, flight);
|
|
1037
|
+
}
|
|
1038
|
+
});
|
|
1039
|
+
|
|
1040
|
+
const returns = Array.from(returnMap.values());
|
|
1041
|
+
|
|
1042
|
+
setUniqueReturnFlights(returns);
|
|
1043
|
+
const firstReturnKey = returns.length > 0 ? getFlightKey(first(returns)?.return.segments) : null;
|
|
1044
|
+
|
|
1045
|
+
if (firstReturnKey) {
|
|
1046
|
+
setSelectedReturnKey(firstReturnKey);
|
|
1047
|
+
}
|
|
1048
|
+
}, [selectedOutwardKey, packagingFlightResults]);
|
|
1049
|
+
|
|
1050
|
+
const selectedOutward = React.useMemo(() => {
|
|
1051
|
+
if (!selectedOutwardKey) return null;
|
|
1052
|
+
|
|
1053
|
+
return packagingFlightResults.find((flight) => getFlightKey(flight.outward.segments) === selectedOutwardKey) || null;
|
|
1054
|
+
}, [packagingFlightResults, selectedOutwardKey]);
|
|
1055
|
+
|
|
1056
|
+
const selectedReturn = React.useMemo(() => {
|
|
1057
|
+
if (!selectedReturnKey) return null;
|
|
1058
|
+
|
|
1059
|
+
return packagingFlightResults.find((flight) => getFlightKey(flight.return.segments) === selectedReturnKey) || null;
|
|
1060
|
+
}, [packagingFlightResults, selectedReturnKey]);
|
|
1061
|
+
|
|
1062
|
+
const selectedCombinationFlight = React.useMemo(() => {
|
|
1063
|
+
if (!selectedOutwardKey || !selectedReturnKey) return undefined;
|
|
1064
|
+
|
|
1065
|
+
return packagingFlightResults.find(
|
|
1066
|
+
(flight) => getFlightKey(flight.outward.segments) === selectedOutwardKey && getFlightKey(flight.return.segments) === selectedReturnKey
|
|
1067
|
+
);
|
|
1068
|
+
}, [packagingFlightResults, selectedOutwardKey, selectedReturnKey]);
|
|
1069
|
+
|
|
1070
|
+
// TODO: get details for selected combination flight and show in fly-in
|
|
1071
|
+
// useEffect(() => {
|
|
1072
|
+
// if (!selectedCombinationFlight) return;
|
|
1073
|
+
|
|
1074
|
+
// dispatch(setSelectedPackagingFlight(selectedCombinationFlight));
|
|
1075
|
+
// // onFlightSearch(selectedCombinationFlight); // Trigger search to update accommodation options based on selected flight
|
|
1076
|
+
// dispatch(setFlyInIsOpen(true));
|
|
1077
|
+
// }, [selectedCombinationFlight, dispatch]);
|
|
1078
|
+
|
|
1079
|
+
useEffect(() => {
|
|
1080
|
+
if (!context) return;
|
|
1081
|
+
|
|
1082
|
+
const seed = activeSearchSeed;
|
|
1083
|
+
if (!seed) return;
|
|
1084
|
+
|
|
1085
|
+
const nextEntry = buildOrUpdatePackagingEntryPartial({
|
|
1086
|
+
sourceEntry: editablePackagingEntry ?? context.packagingEntry ?? null,
|
|
1087
|
+
selectedHotelCode: selectedPackagingAccoResultCode,
|
|
1088
|
+
accommodationResults: packagingAccoResults,
|
|
1089
|
+
selectedFlight: selectedCombinationFlight ?? null,
|
|
1090
|
+
seed,
|
|
1091
|
+
transactionId: transactionId ?? context.packagingEntry?.transactionId ?? '',
|
|
1092
|
+
language: context.languageCode ?? 'en-GB'
|
|
1093
|
+
});
|
|
1094
|
+
|
|
1095
|
+
if (!nextEntry) return;
|
|
1096
|
+
|
|
1097
|
+
dispatch(setEditablePackagingEntry(nextEntry));
|
|
1098
|
+
|
|
1099
|
+
if (selectedCombinationFlight) {
|
|
1100
|
+
dispatch(setSelectedPackagingFlight(selectedCombinationFlight));
|
|
1101
|
+
}
|
|
1102
|
+
}, [
|
|
1103
|
+
context,
|
|
1104
|
+
activeSearchSeed,
|
|
1105
|
+
selectedPackagingAccoResultCode,
|
|
1106
|
+
packagingAccoResults,
|
|
1107
|
+
packagingAccoSearchDetails,
|
|
1108
|
+
selectedCombinationFlight,
|
|
1109
|
+
transactionId,
|
|
1110
|
+
dispatch
|
|
1111
|
+
]);
|
|
1112
|
+
|
|
1113
|
+
const removeAccommodationLines = (lines: PackagingEntryLine[]) => lines.filter((line) => line.serviceType !== ACCOMMODATION_SERVICE_TYPE);
|
|
1114
|
+
|
|
1115
|
+
const removeFlightLines = (lines: PackagingEntryLine[]) => lines.filter((line) => line.serviceType !== FLIGHT_SERVICE_TYPE);
|
|
1116
|
+
|
|
1117
|
+
const buildAccommodationLinesFromSelection = (selectedHotel: PackagingAccommodationResponse, seed: SearchSeed): PackagingEntryLine[] => {
|
|
1118
|
+
if (!selectedHotel) return [];
|
|
1119
|
+
const parentGuid = crypto.randomUUID();
|
|
1120
|
+
|
|
1121
|
+
return selectedHotel.rooms
|
|
1122
|
+
.filter((room) => room.options.some((o) => o.isSelected))
|
|
1123
|
+
.map((room, index) => {
|
|
1124
|
+
const option = room.options.find((o) => o.isSelected)!;
|
|
1125
|
+
|
|
1126
|
+
const pax =
|
|
1127
|
+
seed.rooms?.flatMap((room, roomIndex) =>
|
|
1128
|
+
room.pax.map((p, paxIndex) => ({
|
|
1129
|
+
paxId: p.id,
|
|
1130
|
+
room: roomIndex,
|
|
1131
|
+
order: paxIndex
|
|
1132
|
+
}))
|
|
1133
|
+
) ?? [];
|
|
1134
|
+
|
|
1135
|
+
return {
|
|
1136
|
+
guid: option.guid ?? crypto.randomUUID(),
|
|
1137
|
+
moment: '',
|
|
1138
|
+
parentGuid: index === 0 ? null : parentGuid,
|
|
1139
|
+
order: index,
|
|
1140
|
+
isChanged: true,
|
|
1141
|
+
from: selectedHotel.fromDate,
|
|
1142
|
+
to: selectedHotel.toDate,
|
|
1143
|
+
serviceType: ACCOMMODATION_SERVICE_TYPE,
|
|
1144
|
+
productName: selectedHotel.name,
|
|
1145
|
+
productCode: selectedHotel.code,
|
|
1146
|
+
accommodationName: option.accommodationName,
|
|
1147
|
+
accommodationCode: option.accommodationCode,
|
|
1148
|
+
regimeName: option.regimeName,
|
|
1149
|
+
regimeCode: option.regimeCode,
|
|
1150
|
+
country: selectedHotel.countryId ? { id: selectedHotel.countryId, name: selectedHotel.countryName, localizations: [] } : null,
|
|
1151
|
+
region: selectedHotel.regionId ? { id: selectedHotel.regionId, name: selectedHotel.regionName, localizations: [] } : null,
|
|
1152
|
+
oord: selectedHotel.oordId ? { id: selectedHotel.oordId, name: selectedHotel.oordName, localizations: [] } : null,
|
|
1153
|
+
location: selectedHotel.locationId ? { id: selectedHotel.locationId, name: selectedHotel.locationName, localizations: [] } : null,
|
|
1154
|
+
longitude: selectedHotel.longitude ?? null,
|
|
1155
|
+
latitude: selectedHotel.latitude ?? null,
|
|
1156
|
+
pax,
|
|
1157
|
+
flightInformation: null
|
|
1158
|
+
} satisfies PackagingEntryLine;
|
|
1159
|
+
});
|
|
1160
|
+
};
|
|
1161
|
+
|
|
1162
|
+
const toDateOnlyUtcString = (value: string | Date): string => {
|
|
1163
|
+
const date = new Date(value);
|
|
1164
|
+
|
|
1165
|
+
return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate())).toISOString();
|
|
1166
|
+
};
|
|
1167
|
+
|
|
1168
|
+
const toTimeOnlyString = (value: string | Date): string => {
|
|
1169
|
+
const date = new Date(value);
|
|
1170
|
+
|
|
1171
|
+
const hh = String(date.getUTCHours()).padStart(2, '0');
|
|
1172
|
+
const mm = String(date.getUTCMinutes()).padStart(2, '0');
|
|
1173
|
+
const ss = String(date.getUTCSeconds()).padStart(2, '0');
|
|
1174
|
+
|
|
1175
|
+
return `${hh}:${mm}:${ss}`;
|
|
1176
|
+
};
|
|
1177
|
+
|
|
1178
|
+
const mapFlightSegmentsToFlightLines = (segments: FlightSearchResponseFlightSegment[]): PackagingEntryLineFlightLine[] =>
|
|
1179
|
+
segments.map((segment) => ({
|
|
1180
|
+
airlineCode: segment.marketingAirlineCode,
|
|
1181
|
+
airlineDescription: segment.marketingAirlineName,
|
|
1182
|
+
operatingAirlineCode: segment.operatingAirlineCode,
|
|
1183
|
+
operatingAirlineDescription: segment.operatingAirlineName,
|
|
1184
|
+
flightNumber: segment.flightNumber,
|
|
1185
|
+
operatingFlightNumber: segment.operatingFlightNumber ?? null,
|
|
1186
|
+
departureDate: toDateOnlyUtcString(segment.departureDateTime),
|
|
1187
|
+
departureTime: toTimeOnlyString(segment.departureDateTime),
|
|
1188
|
+
departureAirportCode: segment.departureAirportCode,
|
|
1189
|
+
departureAirportDescription: segment.departureAirportName,
|
|
1190
|
+
arrivalDate: toDateOnlyUtcString(segment.arrivalDateTime),
|
|
1191
|
+
arrivalTime: toTimeOnlyString(segment.arrivalDateTime),
|
|
1192
|
+
arrivalAirportCode: segment.arrivalAirportCode,
|
|
1193
|
+
arrivalAirportDescription: segment.arrivalAirportName,
|
|
1194
|
+
durationInTicks: segment.durationInTicks
|
|
1195
|
+
}));
|
|
1196
|
+
|
|
1197
|
+
const buildFlightLabel = (segments: FlightSearchResponseFlightSegment[]) => {
|
|
1198
|
+
const firstSegment = first(segments);
|
|
1199
|
+
const lastSegment = last(segments);
|
|
1200
|
+
|
|
1201
|
+
if (!firstSegment || !lastSegment) {
|
|
1202
|
+
return { productName: 'Flight', productCode: 'FLIGHT' };
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
return {
|
|
1206
|
+
productName: `${firstSegment.departureAirportName} - ${lastSegment.arrivalAirportName} (${firstSegment.marketingAirlineName})`,
|
|
1207
|
+
productCode: `${firstSegment.departureAirportCode} ${lastSegment.arrivalAirportCode}/${firstSegment.marketingAirlineCode}`
|
|
1208
|
+
};
|
|
1209
|
+
};
|
|
1210
|
+
|
|
1211
|
+
const buildFlightLinesFromSelection = (selectedFlight: PackagingFlightResponse): PackagingEntryLine[] => {
|
|
1212
|
+
if (!selectedFlight) return [];
|
|
1213
|
+
|
|
1214
|
+
const outwardSegments = selectedFlight.outward?.segments ?? [];
|
|
1215
|
+
const returnSegments = selectedFlight.return?.segments ?? [];
|
|
1216
|
+
|
|
1217
|
+
if (!outwardSegments.length || !returnSegments.length) return [];
|
|
1218
|
+
|
|
1219
|
+
const outwardLabel = buildFlightLabel(outwardSegments);
|
|
1220
|
+
const returnLabel = buildFlightLabel(returnSegments);
|
|
1221
|
+
|
|
1222
|
+
const outwardFirst = first(outwardSegments);
|
|
1223
|
+
const outwardLast = last(outwardSegments);
|
|
1224
|
+
const returnFirst = first(returnSegments);
|
|
1225
|
+
const returnLast = last(returnSegments);
|
|
1226
|
+
|
|
1227
|
+
const outwardLine = {
|
|
1228
|
+
guid: selectedFlight.outwardGuid,
|
|
1229
|
+
parentGuid: null,
|
|
1230
|
+
order: 0,
|
|
1231
|
+
isChanged: true,
|
|
1232
|
+
from: outwardFirst?.departureDateTime.toString() ?? '',
|
|
1233
|
+
to: outwardLast?.arrivalDateTime.toString() ?? '',
|
|
1234
|
+
serviceType: FLIGHT_SERVICE_TYPE,
|
|
1235
|
+
productName: outwardLabel.productName,
|
|
1236
|
+
productCode: outwardLabel.productCode,
|
|
1237
|
+
accommodationName: outwardFirst?.metaData?.farePriceClassName,
|
|
1238
|
+
accommodationCode: outwardFirst?.metaData?.fareCode,
|
|
1239
|
+
regimeName: null,
|
|
1240
|
+
regimeCode: null,
|
|
1241
|
+
country: null,
|
|
1242
|
+
region: null,
|
|
1243
|
+
oord: null,
|
|
1244
|
+
location: null,
|
|
1245
|
+
longitude: null,
|
|
1246
|
+
latitude: null,
|
|
1247
|
+
// pax: allPaxAssignments,
|
|
1248
|
+
flightInformation: {
|
|
1249
|
+
pnr: '',
|
|
1250
|
+
flightLines: mapFlightSegmentsToFlightLines(outwardSegments)
|
|
1251
|
+
}
|
|
1252
|
+
} as PackagingEntryLine;
|
|
1253
|
+
|
|
1254
|
+
const returnLine = {
|
|
1255
|
+
guid: selectedFlight.returnGuid,
|
|
1256
|
+
parentGuid: selectedFlight.outwardGuid,
|
|
1257
|
+
order: 1,
|
|
1258
|
+
isChanged: true,
|
|
1259
|
+
from: returnFirst?.departureDateTime.toString() ?? '',
|
|
1260
|
+
to: returnLast?.departureDateTime.toString() ?? '',
|
|
1261
|
+
serviceType: FLIGHT_SERVICE_TYPE,
|
|
1262
|
+
productName: returnLabel.productName,
|
|
1263
|
+
productCode: returnLabel.productCode,
|
|
1264
|
+
accommodationName: returnFirst?.metaData?.farePriceClassName,
|
|
1265
|
+
accommodationCode: returnFirst?.metaData?.fareCode,
|
|
1266
|
+
regimeName: null,
|
|
1267
|
+
regimeCode: null,
|
|
1268
|
+
country: null,
|
|
1269
|
+
region: null,
|
|
1270
|
+
oord: null,
|
|
1271
|
+
location: null,
|
|
1272
|
+
longitude: null,
|
|
1273
|
+
latitude: null,
|
|
1274
|
+
// pax: allPaxAssignments,
|
|
1275
|
+
flightInformation: {
|
|
1276
|
+
pnr: '',
|
|
1277
|
+
flightLines: mapFlightSegmentsToFlightLines(returnSegments)
|
|
1278
|
+
}
|
|
1279
|
+
} as PackagingEntryLine;
|
|
1280
|
+
|
|
1281
|
+
return [outwardLine, returnLine];
|
|
1282
|
+
};
|
|
1283
|
+
|
|
1284
|
+
const buildOrUpdatePackagingEntryPartial = ({
|
|
1285
|
+
sourceEntry,
|
|
1286
|
+
selectedHotelCode,
|
|
1287
|
+
accommodationResults,
|
|
1288
|
+
selectedFlight,
|
|
1289
|
+
seed,
|
|
1290
|
+
transactionId,
|
|
1291
|
+
language
|
|
1292
|
+
}: BuildPackagingEntryPartialArgs): PackagingEntry | null => {
|
|
1293
|
+
if (!seed?.rooms?.length) return null;
|
|
1294
|
+
|
|
1295
|
+
const entry = buildBasePackagingEntry(sourceEntry, seed, transactionId, language);
|
|
1296
|
+
|
|
1297
|
+
let nextLines = [...(entry.lines ?? [])];
|
|
1298
|
+
|
|
1299
|
+
const selectedHotel = selectedHotelCode ? accommodationResults.find((r) => r.code === selectedHotelCode) : null;
|
|
1300
|
+
|
|
1301
|
+
// Update accommodation only when enough data exists
|
|
1302
|
+
if (selectedHotel) {
|
|
1303
|
+
const accommodationLines = buildAccommodationLinesFromSelection(selectedHotel, seed);
|
|
1304
|
+
|
|
1305
|
+
if (accommodationLines.length) {
|
|
1306
|
+
nextLines = removeAccommodationLines(nextLines);
|
|
1307
|
+
nextLines = [...nextLines, ...accommodationLines];
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
// Update flights only when full selected combination exists
|
|
1312
|
+
if (selectedFlight) {
|
|
1313
|
+
const flightLines = buildFlightLinesFromSelection(selectedFlight);
|
|
1314
|
+
if (flightLines.length) {
|
|
1315
|
+
nextLines = removeFlightLines(nextLines);
|
|
1316
|
+
nextLines = [...nextLines, ...flightLines];
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
nextLines = nextLines.map((line, index) => ({
|
|
1321
|
+
...line,
|
|
1322
|
+
order: index
|
|
1323
|
+
}));
|
|
1324
|
+
|
|
1325
|
+
return {
|
|
1326
|
+
...entry,
|
|
1327
|
+
language,
|
|
1328
|
+
transactionId,
|
|
1329
|
+
pax: entry.pax,
|
|
1330
|
+
lines: nextLines
|
|
1331
|
+
};
|
|
1332
|
+
};
|
|
1333
|
+
|
|
1334
|
+
const buildBasePackagingEntry = (
|
|
1335
|
+
sourceEntry: PackagingEntry | null | undefined,
|
|
1336
|
+
seed: SearchSeed,
|
|
1337
|
+
transactionId: string,
|
|
1338
|
+
language: string
|
|
1339
|
+
): PackagingEntry => {
|
|
1340
|
+
if (sourceEntry) {
|
|
1341
|
+
return structuredClone(sourceEntry);
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
let paxId = 1;
|
|
1345
|
+
|
|
1346
|
+
const pax =
|
|
1347
|
+
seed.rooms?.flatMap((room, roomIndex) =>
|
|
1348
|
+
room.pax.map((_, paxIndex) => ({
|
|
1349
|
+
id: paxId++,
|
|
1350
|
+
firstName: '',
|
|
1351
|
+
lastName: '',
|
|
1352
|
+
dateOfBirth: null,
|
|
1353
|
+
isMainBooker: roomIndex === 0 && paxIndex === 0
|
|
1354
|
+
}))
|
|
1355
|
+
) ?? [];
|
|
1356
|
+
|
|
1357
|
+
return {
|
|
1358
|
+
language,
|
|
1359
|
+
transactionId,
|
|
1360
|
+
dossierNumber: '',
|
|
1361
|
+
status: 0,
|
|
1362
|
+
bookingDate: null,
|
|
1363
|
+
price: 0,
|
|
1364
|
+
depositAmount: 0,
|
|
1365
|
+
pax,
|
|
1366
|
+
lines: []
|
|
1367
|
+
} as PackagingEntry;
|
|
1368
|
+
};
|
|
1369
|
+
|
|
1370
|
+
const visibleOutwardFlights = React.useMemo(() => {
|
|
1371
|
+
return showAllOutwardFlights ? uniqueOutwardFlights : uniqueOutwardFlights.slice(0, 3);
|
|
1372
|
+
}, [showAllOutwardFlights, uniqueOutwardFlights]);
|
|
1373
|
+
|
|
848
1374
|
return (
|
|
849
1375
|
<div id="tide-booking" className="search__bg">
|
|
850
1376
|
{context && (
|
|
@@ -920,11 +1446,9 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
920
1446
|
)}
|
|
921
1447
|
<div className="search__result-row">
|
|
922
1448
|
<span className="search__result-row-text">
|
|
923
|
-
{!isLoading &&
|
|
1449
|
+
{!isLoading && context.searchConfiguration.qsmType !== PortalQsmType.AccommodationAndFlight && (
|
|
924
1450
|
<>
|
|
925
|
-
{
|
|
926
|
-
context.searchConfiguration.enableManualPackaging) ||
|
|
927
|
-
context.searchConfiguration.qsmType === PortalQsmType.Accommodation) &&
|
|
1451
|
+
{context.searchConfiguration.qsmType === PortalQsmType.Accommodation &&
|
|
928
1452
|
filteredPackagingAccoResults?.length &&
|
|
929
1453
|
filteredPackagingAccoResults?.length}
|
|
930
1454
|
{context.searchConfiguration.qsmType !== PortalQsmType.Accommodation && filteredResults?.length && filteredResults.length}
|
|
@@ -957,17 +1481,105 @@ const SearchResultsContainer: React.FC = () => {
|
|
|
957
1481
|
|
|
958
1482
|
{context.searchConfiguration.qsmType === PortalQsmType.GroupTour && <GroupTourResults isLoading={isLoading} />}
|
|
959
1483
|
|
|
960
|
-
{context.searchConfiguration.qsmType === PortalQsmType.AccommodationAndFlight &&
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
1484
|
+
{context.searchConfiguration.qsmType === PortalQsmType.AccommodationAndFlight && !context.packagingEntry && context.showFlightResults && (
|
|
1485
|
+
// bookingPackageDetails?.outwardFlights &&
|
|
1486
|
+
// <FlightResults flights={bookingPackageDetails?.outwardFlights} isDeparture={true} />
|
|
1487
|
+
<>
|
|
1488
|
+
<div className="search__results__label search__results__label--secondary">
|
|
1489
|
+
<div className="search__results__label__date">
|
|
1490
|
+
<Icon name="ui-flight" height={16} fill="white" />
|
|
1491
|
+
</div>
|
|
1492
|
+
<div className="search__results__label__text">
|
|
1493
|
+
<h3>
|
|
1494
|
+
{translations.SRP.SELECT} <strong> {translations.SRP.DEPARTURE}</strong>
|
|
1495
|
+
</h3>
|
|
1496
|
+
</div>
|
|
1497
|
+
</div>
|
|
1498
|
+
|
|
1499
|
+
{flightsLoading ? (
|
|
1500
|
+
<Spinner />
|
|
1501
|
+
) : (
|
|
1502
|
+
<>
|
|
1503
|
+
<div className="search__results__cards search__results__cards--extended">
|
|
1504
|
+
{selectedOutwardKey && selectedOutward && (
|
|
1505
|
+
<IndependentFlightOption
|
|
1506
|
+
key={`flight-${selectedOutwardKey}`}
|
|
1507
|
+
item={selectedOutward.outward}
|
|
1508
|
+
guid={selectedOutward.outwardGuid}
|
|
1509
|
+
onSelect={() => setSelectedOutwardKey(null)}
|
|
1510
|
+
selectedGuid={selectedOutward.outwardGuid}
|
|
1511
|
+
isOutward={true}
|
|
1512
|
+
showSelectedState={true}
|
|
1513
|
+
price={selectedOutward.price}
|
|
1514
|
+
/>
|
|
1515
|
+
)}
|
|
1516
|
+
{visibleOutwardFlights.map((result) => (
|
|
1517
|
+
<IndependentFlightOption
|
|
1518
|
+
key={`flight-${result.outwardGuid}`}
|
|
1519
|
+
item={result.outward}
|
|
1520
|
+
onSelect={() => setSelectedOutwardKey(getFlightKey(result.outward.segments))}
|
|
1521
|
+
guid={result.outwardGuid}
|
|
1522
|
+
isOutward={true}
|
|
1523
|
+
price={result.price}
|
|
1524
|
+
currentSelectedPrice={selectedOutward?.price}
|
|
1525
|
+
/>
|
|
1526
|
+
))}
|
|
1527
|
+
</div>
|
|
1528
|
+
{uniqueOutwardFlights && uniqueOutwardFlights.length > 3 && (
|
|
1529
|
+
<div className="search__results__cards__actions">
|
|
1530
|
+
<button className="cta cta--secondary" onClick={() => setShowAllOutwardFlights(!showAllOutwardFlights)}>
|
|
1531
|
+
{showAllOutwardFlights ? translations.SRP.SHOW_LESS : translations.SRP.SHOW_MORE}
|
|
1532
|
+
</button>
|
|
1533
|
+
</div>
|
|
1534
|
+
)}
|
|
1535
|
+
</>
|
|
1536
|
+
)}
|
|
1537
|
+
</>
|
|
1538
|
+
)}
|
|
964
1539
|
|
|
965
1540
|
{context.showHotelAccommodationResults && !context.packagingEntry && <HotelAccommodationResults isLoading={isLoading} />}
|
|
966
1541
|
|
|
967
|
-
{context.searchConfiguration.qsmType === PortalQsmType.AccommodationAndFlight &&
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
1542
|
+
{context.searchConfiguration.qsmType === PortalQsmType.AccommodationAndFlight && !context.packagingEntry && context.showFlightResults && (
|
|
1543
|
+
// bookingPackageDetails?.returnFlights &&
|
|
1544
|
+
// <FlightResults flights={bookingPackageDetails?.returnFlights} isDeparture={false} />
|
|
1545
|
+
<>
|
|
1546
|
+
<div className="search__results__label search__results__label--secondary">
|
|
1547
|
+
<div className="search__results__label__date">
|
|
1548
|
+
<Icon name="ui-flight" height={16} fill="white" />
|
|
1549
|
+
</div>
|
|
1550
|
+
<div className="search__results__label__text">
|
|
1551
|
+
<h3>
|
|
1552
|
+
{translations.SRP.SELECT} <strong> {translations.SRP.RETURN}</strong>
|
|
1553
|
+
</h3>
|
|
1554
|
+
</div>
|
|
1555
|
+
</div>
|
|
1556
|
+
|
|
1557
|
+
<div className="search__results__cards search__results__cards--extended">
|
|
1558
|
+
{selectedReturnKey && selectedReturn && (
|
|
1559
|
+
<IndependentFlightOption
|
|
1560
|
+
key={`flight-${selectedReturnKey}`}
|
|
1561
|
+
item={selectedReturn.return}
|
|
1562
|
+
guid={selectedReturn.outwardGuid}
|
|
1563
|
+
selectedGuid={selectedReturn.outwardGuid}
|
|
1564
|
+
isOutward={false}
|
|
1565
|
+
showSelectedState={true}
|
|
1566
|
+
price={selectedReturn.price}
|
|
1567
|
+
/>
|
|
1568
|
+
)}
|
|
1569
|
+
{uniqueReturnFlights.map((result) => (
|
|
1570
|
+
<IndependentFlightOption
|
|
1571
|
+
key={`flight-${result.outwardGuid}`}
|
|
1572
|
+
item={result.return}
|
|
1573
|
+
onSelect={() => setSelectedReturnKey(getFlightKey(result.return.segments))}
|
|
1574
|
+
guid={result.outwardGuid}
|
|
1575
|
+
isOutward={false}
|
|
1576
|
+
currentSelectedPrice={selectedReturn?.price}
|
|
1577
|
+
price={result.price}
|
|
1578
|
+
/>
|
|
1579
|
+
))}
|
|
1580
|
+
</div>
|
|
1581
|
+
</>
|
|
1582
|
+
)}
|
|
971
1583
|
|
|
972
1584
|
{context.searchConfiguration.qsmType === PortalQsmType.AccommodationAndFlight && context.packagingEntry && itinerary && (
|
|
973
1585
|
<FullItinerary isLoading={itineraryIsLoading} />
|