@qite/tide-components 1.0.3 → 1.0.4

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.
@@ -27370,7 +27370,7 @@ const initialState$1 = {
27370
27370
  currentStep: 0,
27371
27371
  bookingNumber: undefined,
27372
27372
  trajectNodeAvailability: {},
27373
- trajectFlightsResolved: false
27373
+ trajectFlightAvailability: {}
27374
27374
  };
27375
27375
  const searchResultsSlice = toolkit.createSlice({
27376
27376
  name: 'searchResults',
@@ -27592,16 +27592,38 @@ const searchResultsSlice = toolkit.createSlice({
27592
27592
  const guids = new Set(action.payload);
27593
27593
  state.editablePackagingEntry.lines = (state.editablePackagingEntry.lines ?? []).filter((x) => !guids.has(x.guid));
27594
27594
  },
27595
- setTrajectFlightsResolved(state, action) {
27596
- state.trajectFlightsResolved = action.payload;
27595
+ setTrajectFlightLoading(state, action) {
27596
+ const lineGuid = action.payload;
27597
+ state.trajectFlightAvailability[lineGuid] = {
27598
+ lineGuid,
27599
+ status: 'loading',
27600
+ results: state.trajectFlightAvailability[lineGuid]?.results ?? [],
27601
+ selectedGuid: state.trajectFlightAvailability[lineGuid]?.selectedGuid ?? null
27602
+ };
27603
+ },
27604
+ setTrajectFlightAvailability(state, action) {
27605
+ const { lineGuid, results, selectedGuid } = action.payload;
27606
+ state.trajectFlightAvailability[lineGuid] = {
27607
+ lineGuid,
27608
+ status: selectedGuid ? 'resolved' : 'unavailable',
27609
+ results,
27610
+ selectedGuid
27611
+ };
27612
+ },
27613
+ setTrajectFlightSelection(state, action) {
27614
+ const flight = state.trajectFlightAvailability[action.payload.lineGuid];
27615
+ if (!flight)
27616
+ return;
27617
+ flight.selectedGuid = action.payload.guid;
27618
+ flight.status = 'resolved';
27597
27619
  },
27598
27620
  resetTrajectAvailability(state) {
27599
27621
  state.trajectNodeAvailability = {};
27600
- state.trajectFlightsResolved = false;
27622
+ state.trajectFlightAvailability = {};
27601
27623
  }
27602
27624
  }
27603
27625
  });
27604
- 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, setTrajectFlightsResolved, resetTrajectAvailability, updateEditableEntryLine, removeEditableEntryLines } = searchResultsSlice.actions;
27626
+ const { setResults, setFilteredResults, setSelectedSearchResult, setPackagingAccoResults, setFilteredPackagingAccoResults, setFilteredPackagingFlightResults, setPackagingAccoSearchDetails, setSelectedPackagingAccoResult, setPackagingFlightResults, setSelectedPackagingFlight, setSelectedFlight, setSelectedFlightDetails, setBookingPackageDetails, selectFlight, setIsLoading, setFlightsLoading, setInitialFilters, setFilters, resetFilters, setInitialFlightFilters, setFlightFilters, resetFlightFilters, setSortType, setFlightSortType, setActiveTab, setCurrentPage, resetSearchState, setFlyInIsOpen, setEditablePackagingEntry, setTransactionId, setFlyInType, setPriceDetails, setItinerary, setSelectedOutwardKey, setSelectedReturnKey, resetFlightSelection, setExcursionSearchParams, setSelectedExcursionSearchResult, confirmExcursionForDay, removeConfirmedExcursionForDay, clearConfirmedExcursionsForDay, setBookPackagingEntry, setCurrentStep, setBookingNumber, setTrajectNodeLoading, setTrajectNodeAvailability, setTrajectNodeSelection, setTrajectFlightLoading, setTrajectFlightAvailability, setTrajectFlightSelection, resetTrajectAvailability, updateEditableEntryLine, removeEditableEntryLines } = searchResultsSlice.actions;
27605
27627
  var searchResultsReducer = searchResultsSlice.reducer;
27606
27628
 
27607
27629
  const ItemPicker = ({ items, selection, selectedSortByType, label, placeholder, classModifier, onPick, valueFormatter }) => {
@@ -33072,8 +33094,26 @@ const mapFlightSegmentsToFlightLines = (segments) => segments.map((segment) => (
33072
33094
  arrivalAirportDescription: segment.arrivalAirportName,
33073
33095
  durationInTicks: segment.durationInTicks
33074
33096
  }));
33075
- const applyFlightToLine = (line, flight, isOutbound) => {
33076
- const segments = (isOutbound ? flight.outward?.segments : flight.return?.segments) ?? [];
33097
+ const buildTrajectFlightRequest = ({ node, line }, context, pax) => ({
33098
+ transactionId: context.transactionId,
33099
+ officeId: context.officeId,
33100
+ catalogueId: context.catalogueId,
33101
+ agentId: context.agentId ?? null,
33102
+ language: context.language,
33103
+ departureAirportCode: node.departureAirportCode ?? '',
33104
+ arrivalAirportCode: node.arrivalAirportCode ?? '',
33105
+ returnAirportCode: null,
33106
+ luggageIncluded: null,
33107
+ maxStops: null,
33108
+ travelClass: null,
33109
+ vendorConfigurationId: node.externalVendorId ?? null,
33110
+ pax,
33111
+ outward: { date: dateToDateStruct(new Date(line.from)) },
33112
+ return: null
33113
+ });
33114
+ const pickCheapestFlight = (flights) => [...flights].sort((a, b) => a.price - b.price)[0] ?? null;
33115
+ const applyFlightToLine = (line, flight) => {
33116
+ const segments = flight.outward?.segments ?? [];
33077
33117
  if (!segments.length)
33078
33118
  return line;
33079
33119
  const firstSegment = segments[0];
@@ -33093,14 +33133,6 @@ const applyFlightToLine = (line, flight, isOutbound) => {
33093
33133
  isChanged: true
33094
33134
  };
33095
33135
  };
33096
- const pairTrajectFlights = (nodes) => {
33097
- const flights = nodes.filter((x) => isTrajectFlightNode(x.node));
33098
- return {
33099
- outbound: flights[0] ?? null,
33100
- inbound: flights.length > 1 ? flights[flights.length - 1] : null,
33101
- unsupported: flights.slice(1, Math.max(flights.length - 1, 1))
33102
- };
33103
- };
33104
33136
 
33105
33137
  const getLocation = (result) => {
33106
33138
  const place = result.locationName || result.regionName || result.oordName;
@@ -33178,15 +33210,10 @@ const TrajectResults = ({ isLoading }) => {
33178
33210
  const dispatch = reactRedux.useDispatch();
33179
33211
  const translations = getTranslations(context?.languageCode ?? 'en-GB');
33180
33212
  const locale = getLocale(context?.languageCode ?? 'en-GB');
33181
- const { trajectNodeAvailability, flightsLoading, trajectFlightsResolved, selectedOutwardKey, selectedReturnKey } = reactRedux.useSelector((state) => state.searchResults);
33182
- const uniqueOutwardFlights = reactRedux.useSelector(selectUniqueOutwardFlights);
33183
- const uniqueReturnFlights = reactRedux.useSelector(selectUniqueReturnFlights);
33184
- const selectedOutward = reactRedux.useSelector(selectSelectedOutward);
33185
- const selectedReturn = reactRedux.useSelector(selectSelectedReturn);
33213
+ const { trajectNodeAvailability, trajectFlightAvailability } = reactRedux.useSelector((state) => state.searchResults);
33186
33214
  const [expandedNodes, setExpandedNodes] = React.useState({});
33187
33215
  const trajectEntry = context?.trajectEntry;
33188
33216
  const nodesWithLines = React.useMemo(() => (trajectEntry ? getTrajectNodesWithLines(trajectEntry) : []), [trajectEntry]);
33189
- const flightPairing = React.useMemo(() => pairTrajectFlights(nodesWithLines), [nodesWithLines]);
33190
33217
  const lineByNodeId = React.useMemo(() => new Map(nodesWithLines.map((x) => [x.node.nodeId, x])), [nodesWithLines]);
33191
33218
  const nodesByDay = React.useMemo(() => {
33192
33219
  const map = new Map();
@@ -33211,6 +33238,7 @@ const TrajectResults = ({ isLoading }) => {
33211
33238
  dispatch(updateEditableEntryLine(applyResultToLine(item.line, result)));
33212
33239
  };
33213
33240
  const renderServiceNode = (item) => {
33241
+ console.log('renderServiceNode', item);
33214
33242
  const { node, line } = item;
33215
33243
  const availability = trajectNodeAvailability[line.guid];
33216
33244
  const isExpanded = !!expandedNodes[line.guid];
@@ -33239,30 +33267,19 @@ const TrajectResults = ({ isLoading }) => {
33239
33267
  others.length > 0 && (React__default["default"].createElement("div", { className: "search__results__cards__actions" },
33240
33268
  React__default["default"].createElement("button", { type: "button", className: "cta cta--secondary", onClick: () => toggleExpanded(line.guid) }, isExpanded ? translations.SRP.SHOW_LESS : `${translations.SRP.SHOW_MORE} (${others.length})`)))))));
33241
33269
  };
33270
+ const handleSelectFlight = (item, flight) => {
33271
+ dispatch(setTrajectFlightSelection({ lineGuid: item.line.guid, guid: flight.outwardGuid }));
33272
+ dispatch(updateEditableEntryLine(applyFlightToLine(item.line, flight)));
33273
+ };
33242
33274
  const renderFlightNode = (item) => {
33243
33275
  const { node, line } = item;
33244
- const isOutbound = flightPairing.outbound?.node.nodeId === node.nodeId;
33245
- const isInbound = flightPairing.inbound?.node.nodeId === node.nodeId;
33246
- // A middle leg of a multi-flight traject: the round-trip flight search cannot express it.
33247
- if (!isOutbound && !isInbound) {
33248
- return (React__default["default"].createElement(React__default["default"].Fragment, { key: line.guid },
33249
- React__default["default"].createElement("div", { className: "search__results__label search__results__label--secondary" },
33250
- React__default["default"].createElement("div", { className: "search__results__label__date" },
33251
- React__default["default"].createElement("p", { className: "search__results__label__date-date" }, dateFns.format(new Date(line.from), 'd', { locale })),
33252
- React__default["default"].createElement("p", null, dateFns.format(new Date(line.from), 'MMM', { locale }))),
33253
- React__default["default"].createElement("div", { className: "search__results__label__text" },
33254
- React__default["default"].createElement(Icon, { name: "ui-flight", height: 16 }),
33255
- React__default["default"].createElement("h3", null,
33256
- React__default["default"].createElement("strong", null, node.name)))),
33257
- React__default["default"].createElement("div", { className: "no-results" },
33258
- node.departureAirportCode,
33259
- " \u2192 ",
33260
- node.arrivalAirportCode)));
33261
- }
33262
- const flights = isOutbound ? uniqueOutwardFlights : uniqueReturnFlights;
33263
- const selectedFlight = isOutbound ? selectedOutward : selectedReturn;
33264
- const selectedKey = isOutbound ? selectedOutwardKey : selectedReturnKey;
33265
- const visible = flights.filter((x) => getFlightKey(isOutbound ? x.outward.segments : x.return.segments) !== selectedKey);
33276
+ const availability = trajectFlightAvailability[line.guid];
33277
+ const status = availability?.status ?? 'loading';
33278
+ const flights = availability?.results ?? [];
33279
+ const selected = flights.find((x) => x.outwardGuid === availability?.selectedGuid) ?? null;
33280
+ const others = flights.filter((x) => x.outwardGuid !== availability?.selectedGuid);
33281
+ const isExpanded = !!expandedNodes[line.guid];
33282
+ const visible = isExpanded ? others : others.slice(0, 2);
33266
33283
  return (React__default["default"].createElement(React__default["default"].Fragment, { key: line.guid },
33267
33284
  React__default["default"].createElement("div", { className: "search__results__label search__results__label--secondary" },
33268
33285
  React__default["default"].createElement("div", { className: "search__results__label__date" },
@@ -33272,11 +33289,19 @@ const TrajectResults = ({ isLoading }) => {
33272
33289
  React__default["default"].createElement(Icon, { name: "ui-flight", height: 16 }),
33273
33290
  React__default["default"].createElement("h3", null,
33274
33291
  translations.SRP.SELECT,
33275
- " ",
33276
- React__default["default"].createElement("strong", null, isOutbound ? translations.SRP.DEPARTURE : translations.SRP.RETURN)))),
33277
- flightsLoading || !trajectFlightsResolved ? (React__default["default"].createElement(Spinner, { label: translations.SRP.LOADING_FLIGHTS })) : flights.length === 0 ? (React__default["default"].createElement("div", { className: "no-results" }, translations.SRP.NO_RESULTS)) : (React__default["default"].createElement("div", { className: "search__results__cards search__results__cards--extended" },
33278
- selectedFlight && (React__default["default"].createElement(IndependentFlightOption, { key: `flight-${selectedKey}`, item: isOutbound ? selectedFlight.outward : selectedFlight.return, guid: selectedFlight.outwardGuid, selectedGuid: selectedFlight.outwardGuid, isOutward: isOutbound, showSelectedState: true, price: selectedFlight.price, onSelect: isOutbound ? () => dispatch(setSelectedOutwardKey(null)) : undefined })),
33279
- visible.map((result) => (React__default["default"].createElement(IndependentFlightOption, { key: `flight-${result.outwardGuid}`, item: isOutbound ? result.outward : result.return, guid: result.outwardGuid, isOutward: isOutbound, price: result.price, currentSelectedPrice: selectedFlight?.price, onSelect: () => dispatch(isOutbound ? setSelectedOutwardKey(getFlightKey(result.outward.segments)) : setSelectedReturnKey(getFlightKey(result.return.segments))) })))))));
33292
+ ' ',
33293
+ React__default["default"].createElement("strong", null,
33294
+ node.departureAirportCode,
33295
+ " - ",
33296
+ node.arrivalAirportCode)))),
33297
+ status === 'loading' && React__default["default"].createElement(Spinner, { label: translations.SRP.LOADING_FLIGHTS }),
33298
+ status === 'unavailable' && React__default["default"].createElement("div", { className: "no-results" }, translations.SRP.NO_RESULTS),
33299
+ status === 'resolved' && selected && (React__default["default"].createElement(React__default["default"].Fragment, null,
33300
+ React__default["default"].createElement("div", { className: "search__results__cards search__results__cards--extended" },
33301
+ React__default["default"].createElement(IndependentFlightOption, { key: `flight-${selected.outwardGuid}`, item: selected.outward, guid: selected.outwardGuid, selectedGuid: selected.outwardGuid, isOutward: true, showSelectedState: true, price: selected.price }),
33302
+ visible.map((result) => (React__default["default"].createElement(IndependentFlightOption, { key: `flight-${result.outwardGuid}`, item: result.outward, guid: result.outwardGuid, isOutward: true, price: result.price, currentSelectedPrice: selected.price, onSelect: () => handleSelectFlight(item, result) })))),
33303
+ others.length > 2 && (React__default["default"].createElement("div", { className: "search__results__cards__actions" },
33304
+ React__default["default"].createElement("button", { type: "button", className: "cta cta--secondary", onClick: () => toggleExpanded(line.guid) }, isExpanded ? translations.SRP.SHOW_LESS : `${translations.SRP.SHOW_MORE} (${others.length - 2})`)))))));
33280
33305
  };
33281
33306
  if (isLoading)
33282
33307
  return React__default["default"].createElement(Spinner, { label: translations.SRP.LOADING_ITINERARY });
@@ -33302,7 +33327,6 @@ const useTrajectAvailability = () => {
33302
33327
  const context = React.useContext(SearchResultsConfigurationContext);
33303
33328
  const dispatch = reactRedux.useDispatch();
33304
33329
  const trajectEntry = context?.trajectEntry;
33305
- const selectedCombinationFlight = reactRedux.useSelector(selectSelectedCombinationFlight);
33306
33330
  React.useEffect(() => {
33307
33331
  if (!context || !trajectEntry)
33308
33332
  return;
@@ -33322,6 +33346,13 @@ const useTrajectAvailability = () => {
33322
33346
  language: context.languageCode ?? 'en-GB',
33323
33347
  rooms: getPackagingRoomsFromEntry(trajectEntry.entry)
33324
33348
  };
33349
+ // Flight searches take passengers rather than rooms; the API buckets by age.
33350
+ const pax = trajectEntry.entry.pax ?? [];
33351
+ const ageOf = (p) => p.age ?? 30;
33352
+ const adults = pax.filter((p) => ageOf(p) >= 12).length;
33353
+ const kids = pax.filter((p) => ageOf(p) >= 2 && ageOf(p) < 12).length;
33354
+ const babies = pax.filter((p) => ageOf(p) < 2).length;
33355
+ const flightPax = lodash.concat(Array.from({ length: adults }, (_, index) => ({ id: index, age: 31 })), Array.from({ length: kids }, (_, index) => ({ id: index + adults, age: 8 })), Array.from({ length: babies }, (_, index) => ({ id: index + adults + kids, age: 1 })));
33325
33356
  const nodesWithLines = getTrajectNodesWithLines(trajectEntry);
33326
33357
  const resolveNode = async (item) => {
33327
33358
  const lineGuid = item.line.guid;
@@ -33371,59 +33402,36 @@ const useTrajectAvailability = () => {
33371
33402
  return lineGuid;
33372
33403
  }
33373
33404
  };
33374
- const resolveFlights = async () => {
33375
- const { outbound, inbound } = pairTrajectFlights(nodesWithLines);
33376
- if (!outbound) {
33377
- dispatch(setTrajectFlightsResolved(true));
33378
- return;
33405
+ const resolveFlightNode = async (item) => {
33406
+ const lineGuid = item.line.guid;
33407
+ if (!item.node.departureAirportCode || !item.node.arrivalAirportCode) {
33408
+ dispatch(setTrajectFlightAvailability({ lineGuid, results: [], selectedGuid: null }));
33409
+ return lineGuid;
33379
33410
  }
33380
- dispatch(setFlightsLoading(true));
33411
+ dispatch(setTrajectFlightLoading(lineGuid));
33381
33412
  try {
33382
- const pax = trajectEntry.entry.pax ?? [];
33383
- const ageOf = (p) => p.age ?? 30;
33384
- const adults = pax.filter((p) => ageOf(p) >= 12).length;
33385
- const kids = pax.filter((p) => ageOf(p) >= 2 && ageOf(p) < 12).length;
33386
- const babies = pax.filter((p) => ageOf(p) < 2).length;
33387
- const request = {
33388
- transactionId: trajectEntry.entry.transactionId,
33389
- officeId: searchContext.officeId,
33390
- catalogueId: searchContext.catalogueId,
33391
- agentId: searchContext.agentId,
33392
- language: searchContext.language,
33393
- departureAirportCode: outbound.node.departureAirportCode ?? '',
33394
- arrivalAirportCode: outbound.node.arrivalAirportCode ?? '',
33395
- returnAirportCode: inbound?.node.arrivalAirportCode ?? outbound.node.departureAirportCode ?? null,
33396
- luggageIncluded: null,
33397
- maxStops: null,
33398
- travelClass: null,
33399
- pax: lodash.concat(Array.from({ length: adults }, (_, index) => ({ id: index, age: 31 })), Array.from({ length: kids }, (_, index) => ({ id: index + adults, age: 8 })), Array.from({ length: babies }, (_, index) => ({ id: index + adults + kids, age: 1 }))),
33400
- outward: { date: dateToDateStruct(new Date(outbound.line.from)) },
33401
- return: { date: dateToDateStruct(new Date((inbound ?? outbound).line.from)) }
33402
- };
33403
- const flights = (await build.searchPackagingFlights(config, request, controller.signal)) ?? [];
33413
+ const flights = (await build.searchPackagingFlights(config, buildTrajectFlightRequest(item, searchContext, flightPax), controller.signal)) ?? [];
33404
33414
  if (controller.signal.aborted)
33405
- return;
33406
- dispatch(setPackagingFlightResults(flights));
33407
- const firstFlight = lodash.first(flights);
33408
- if (firstFlight) {
33409
- dispatch(setSelectedOutwardKey(getFlightKey(firstFlight.outward.segments)));
33410
- dispatch(setSelectedReturnKey(getFlightKey(firstFlight.return.segments)));
33411
- }
33415
+ return null;
33416
+ const picked = pickCheapestFlight(flights);
33417
+ dispatch(setTrajectFlightAvailability({ lineGuid, results: flights, selectedGuid: picked?.outwardGuid ?? null }));
33418
+ if (!picked)
33419
+ return lineGuid;
33420
+ dispatch(updateEditableEntryLine(applyFlightToLine(item.line, picked)));
33421
+ return null;
33412
33422
  }
33413
33423
  catch (error) {
33414
- if (!controller.signal.aborted)
33415
- console.error('Failed to resolve traject flights', error);
33416
- }
33417
- finally {
33418
- if (!controller.signal.aborted) {
33419
- dispatch(setFlightsLoading(false));
33420
- dispatch(setTrajectFlightsResolved(true));
33421
- }
33424
+ if (controller.signal.aborted)
33425
+ return null;
33426
+ console.error('Failed to resolve traject flight', item.node.name, error);
33427
+ dispatch(setTrajectFlightAvailability({ lineGuid, results: [], selectedGuid: null }));
33428
+ return lineGuid;
33422
33429
  }
33423
33430
  };
33424
33431
  (async () => {
33425
33432
  const searchable = nodesWithLines.filter((x) => isSearchableTrajectNode(x.node));
33426
- const [unresolved] = await Promise.all([Promise.all(searchable.map(resolveNode)), resolveFlights()]);
33433
+ const flightNodes = nodesWithLines.filter((x) => isTrajectFlightNode(x.node));
33434
+ const unresolved = await Promise.all([...searchable.map(resolveNode), ...flightNodes.map(resolveFlightNode)]);
33427
33435
  if (controller.signal.aborted)
33428
33436
  return;
33429
33437
  // A node with no availability anywhere cannot be priced or booked, so its line comes off
@@ -33434,16 +33442,6 @@ const useTrajectAvailability = () => {
33434
33442
  })();
33435
33443
  return () => controller.abort();
33436
33444
  }, [trajectEntry]);
33437
- // Keep the traject's own flight lines in step with whatever outward/return the user picks.
33438
- React.useEffect(() => {
33439
- if (!trajectEntry || !selectedCombinationFlight)
33440
- return;
33441
- const { outbound, inbound } = pairTrajectFlights(getTrajectNodesWithLines(trajectEntry));
33442
- if (outbound)
33443
- dispatch(updateEditableEntryLine(applyFlightToLine(outbound.line, selectedCombinationFlight, true)));
33444
- if (inbound)
33445
- dispatch(updateEditableEntryLine(applyFlightToLine(inbound.line, selectedCombinationFlight, false)));
33446
- }, [trajectEntry, selectedCombinationFlight]);
33447
33445
  };
33448
33446
 
33449
33447
  const SearchResultsContainer = ({ onBookingStarted }) => {
@@ -40,7 +40,8 @@ export interface SearchResultsState {
40
40
  bookingNumber?: string;
41
41
  /** Per-traject-node availability, keyed by the entry line guid the node maps to. */
42
42
  trajectNodeAvailability: Record<string, TrajectNodeAvailability>;
43
- trajectFlightsResolved: boolean;
43
+ /** Per-flight-node one-way search results, keyed by the entry line guid. */
44
+ trajectFlightAvailability: Record<string, TrajectFlightAvailability>;
44
45
  }
45
46
  export type TrajectNodeStatus = 'idle' | 'loading' | 'resolved' | 'unavailable';
46
47
  export interface TrajectNodeAvailability {
@@ -50,6 +51,12 @@ export interface TrajectNodeAvailability {
50
51
  selectedCode: string | null;
51
52
  preferredUnavailable: boolean;
52
53
  }
54
+ export interface TrajectFlightAvailability {
55
+ lineGuid: string;
56
+ status: TrajectNodeStatus;
57
+ results: PackagingFlightResponse[];
58
+ selectedGuid: string | null;
59
+ }
53
60
  export declare const setResults: import("@reduxjs/toolkit").ActionCreatorWithPayload<BookingPackageItem[], "searchResults/setResults">, setFilteredResults: import("@reduxjs/toolkit").ActionCreatorWithPayload<BookingPackageItem[], "searchResults/setFilteredResults">, setSelectedSearchResult: import("@reduxjs/toolkit").ActionCreatorWithPayload<BookingPackageItem | null, "searchResults/setSelectedSearchResult">, setPackagingAccoResults: import("@reduxjs/toolkit").ActionCreatorWithPayload<PackagingAccommodationResponse[], "searchResults/setPackagingAccoResults">, setFilteredPackagingAccoResults: import("@reduxjs/toolkit").ActionCreatorWithPayload<PackagingAccommodationResponse[], "searchResults/setFilteredPackagingAccoResults">, setFilteredPackagingFlightResults: import("@reduxjs/toolkit").ActionCreatorWithPayload<PackagingFlightResponse[], "searchResults/setFilteredPackagingFlightResults">, setPackagingAccoSearchDetails: import("@reduxjs/toolkit").ActionCreatorWithPayload<PackagingAccommodationResponse[], "searchResults/setPackagingAccoSearchDetails">, setSelectedPackagingAccoResult: import("@reduxjs/toolkit").ActionCreatorWithPayload<string | null, "searchResults/setSelectedPackagingAccoResult">, setPackagingFlightResults: import("@reduxjs/toolkit").ActionCreatorWithPayload<PackagingFlightResponse[], "searchResults/setPackagingFlightResults">, setSelectedPackagingFlight: import("@reduxjs/toolkit").ActionCreatorWithPayload<PackagingFlightResponse | null, "searchResults/setSelectedPackagingFlight">, setSelectedFlight: import("@reduxjs/toolkit").ActionCreatorWithPayload<ExtendedFlightSearchResponseItem | null, "searchResults/setSelectedFlight">, setSelectedFlightDetails: import("@reduxjs/toolkit").ActionCreatorWithPayload<ExtendedFlightSearchResponseItem | null, "searchResults/setSelectedFlightDetails">, setBookingPackageDetails: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
54
61
  details: BookingPackage;
55
62
  }, "searchResults/setBookingPackageDetails">, selectFlight: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
@@ -71,6 +78,13 @@ export declare const setResults: import("@reduxjs/toolkit").ActionCreatorWithPay
71
78
  }, "searchResults/setTrajectNodeAvailability">, setTrajectNodeSelection: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
72
79
  lineGuid: string;
73
80
  code: string;
74
- }, "searchResults/setTrajectNodeSelection">, setTrajectFlightsResolved: import("@reduxjs/toolkit").ActionCreatorWithPayload<boolean, "searchResults/setTrajectFlightsResolved">, resetTrajectAvailability: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<"searchResults/resetTrajectAvailability">, updateEditableEntryLine: import("@reduxjs/toolkit").ActionCreatorWithPayload<PackagingEntryLine, "searchResults/updateEditableEntryLine">, removeEditableEntryLines: import("@reduxjs/toolkit").ActionCreatorWithPayload<string[], "searchResults/removeEditableEntryLines">;
81
+ }, "searchResults/setTrajectNodeSelection">, setTrajectFlightLoading: import("@reduxjs/toolkit").ActionCreatorWithPayload<string, "searchResults/setTrajectFlightLoading">, setTrajectFlightAvailability: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
82
+ lineGuid: string;
83
+ results: PackagingFlightResponse[];
84
+ selectedGuid: string | null;
85
+ }, "searchResults/setTrajectFlightAvailability">, setTrajectFlightSelection: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
86
+ lineGuid: string;
87
+ guid: string;
88
+ }, "searchResults/setTrajectFlightSelection">, resetTrajectAvailability: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<"searchResults/resetTrajectAvailability">, updateEditableEntryLine: import("@reduxjs/toolkit").ActionCreatorWithPayload<PackagingEntryLine, "searchResults/updateEditableEntryLine">, removeEditableEntryLines: import("@reduxjs/toolkit").ActionCreatorWithPayload<string[], "searchResults/removeEditableEntryLines">;
75
89
  declare const _default: import("@reduxjs/toolkit").Reducer<SearchResultsState>;
76
90
  export default _default;
@@ -1,4 +1,4 @@
1
- import { PackagingAccommodationRequest, PackagingAccommodationResponse, PackagingEntry, PackagingEntryLine, PackagingFlightResponse, PackagingRoom, PackagingTrajectAlternative, PackagingTrajectNode, PackagingTrajectResponse } from '@qite/tide-client';
1
+ import { FlightSearchRequest, PackagingAccommodationRequest, PackagingAccommodationResponse, PackagingEntry, PackagingEntryLine, PackagingFlightResponse, PackagingRoom, PackagingTrajectAlternative, PackagingTrajectNode, PackagingTrajectResponse } from '@qite/tide-client';
2
2
  export declare const REGULAR_TRAJECT_NODE_TYPE = 0;
3
3
  export declare const CONNECTING_TRAJECT_NODE_TYPE = 1;
4
4
  export interface TrajectNodeWithLine {
@@ -25,9 +25,6 @@ export declare const matchesCode: (result: PackagingAccommodationResponse, code:
25
25
  export declare const getPackagingRoomsFromEntry: (entry: PackagingEntry) => PackagingRoom[];
26
26
  export declare const pickCheapest: (results: PackagingAccommodationResponse[]) => PackagingAccommodationResponse | null;
27
27
  export declare const applyResultToLine: (line: PackagingEntryLine, result: PackagingAccommodationResponse) => PackagingEntryLine;
28
- export declare const applyFlightToLine: (line: PackagingEntryLine, flight: PackagingFlightResponse, isOutbound: boolean) => PackagingEntryLine;
29
- export declare const pairTrajectFlights: (nodes: TrajectNodeWithLine[]) => {
30
- outbound: TrajectNodeWithLine;
31
- inbound: TrajectNodeWithLine | null;
32
- unsupported: TrajectNodeWithLine[];
33
- };
28
+ export declare const buildTrajectFlightRequest: ({ node, line }: TrajectNodeWithLine, context: TrajectSearchContext, pax: FlightSearchRequest["pax"]) => FlightSearchRequest;
29
+ export declare const pickCheapestFlight: (flights: PackagingFlightResponse[]) => PackagingFlightResponse | null;
30
+ export declare const applyFlightToLine: (line: PackagingEntryLine, flight: PackagingFlightResponse) => PackagingEntryLine;
@@ -27341,7 +27341,7 @@ const initialState$1 = {
27341
27341
  currentStep: 0,
27342
27342
  bookingNumber: undefined,
27343
27343
  trajectNodeAvailability: {},
27344
- trajectFlightsResolved: false
27344
+ trajectFlightAvailability: {}
27345
27345
  };
27346
27346
  const searchResultsSlice = createSlice({
27347
27347
  name: 'searchResults',
@@ -27563,16 +27563,38 @@ const searchResultsSlice = createSlice({
27563
27563
  const guids = new Set(action.payload);
27564
27564
  state.editablePackagingEntry.lines = (state.editablePackagingEntry.lines ?? []).filter((x) => !guids.has(x.guid));
27565
27565
  },
27566
- setTrajectFlightsResolved(state, action) {
27567
- state.trajectFlightsResolved = action.payload;
27566
+ setTrajectFlightLoading(state, action) {
27567
+ const lineGuid = action.payload;
27568
+ state.trajectFlightAvailability[lineGuid] = {
27569
+ lineGuid,
27570
+ status: 'loading',
27571
+ results: state.trajectFlightAvailability[lineGuid]?.results ?? [],
27572
+ selectedGuid: state.trajectFlightAvailability[lineGuid]?.selectedGuid ?? null
27573
+ };
27574
+ },
27575
+ setTrajectFlightAvailability(state, action) {
27576
+ const { lineGuid, results, selectedGuid } = action.payload;
27577
+ state.trajectFlightAvailability[lineGuid] = {
27578
+ lineGuid,
27579
+ status: selectedGuid ? 'resolved' : 'unavailable',
27580
+ results,
27581
+ selectedGuid
27582
+ };
27583
+ },
27584
+ setTrajectFlightSelection(state, action) {
27585
+ const flight = state.trajectFlightAvailability[action.payload.lineGuid];
27586
+ if (!flight)
27587
+ return;
27588
+ flight.selectedGuid = action.payload.guid;
27589
+ flight.status = 'resolved';
27568
27590
  },
27569
27591
  resetTrajectAvailability(state) {
27570
27592
  state.trajectNodeAvailability = {};
27571
- state.trajectFlightsResolved = false;
27593
+ state.trajectFlightAvailability = {};
27572
27594
  }
27573
27595
  }
27574
27596
  });
27575
- 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, setTrajectFlightsResolved, resetTrajectAvailability, updateEditableEntryLine, removeEditableEntryLines } = searchResultsSlice.actions;
27597
+ const { setResults, setFilteredResults, setSelectedSearchResult, setPackagingAccoResults, setFilteredPackagingAccoResults, setFilteredPackagingFlightResults, setPackagingAccoSearchDetails, setSelectedPackagingAccoResult, setPackagingFlightResults, setSelectedPackagingFlight, setSelectedFlight, setSelectedFlightDetails, setBookingPackageDetails, selectFlight, setIsLoading, setFlightsLoading, setInitialFilters, setFilters, resetFilters, setInitialFlightFilters, setFlightFilters, resetFlightFilters, setSortType, setFlightSortType, setActiveTab, setCurrentPage, resetSearchState, setFlyInIsOpen, setEditablePackagingEntry, setTransactionId, setFlyInType, setPriceDetails, setItinerary, setSelectedOutwardKey, setSelectedReturnKey, resetFlightSelection, setExcursionSearchParams, setSelectedExcursionSearchResult, confirmExcursionForDay, removeConfirmedExcursionForDay, clearConfirmedExcursionsForDay, setBookPackagingEntry, setCurrentStep, setBookingNumber, setTrajectNodeLoading, setTrajectNodeAvailability, setTrajectNodeSelection, setTrajectFlightLoading, setTrajectFlightAvailability, setTrajectFlightSelection, resetTrajectAvailability, updateEditableEntryLine, removeEditableEntryLines } = searchResultsSlice.actions;
27576
27598
  var searchResultsReducer = searchResultsSlice.reducer;
27577
27599
 
27578
27600
  const ItemPicker = ({ items, selection, selectedSortByType, label, placeholder, classModifier, onPick, valueFormatter }) => {
@@ -33043,8 +33065,26 @@ const mapFlightSegmentsToFlightLines = (segments) => segments.map((segment) => (
33043
33065
  arrivalAirportDescription: segment.arrivalAirportName,
33044
33066
  durationInTicks: segment.durationInTicks
33045
33067
  }));
33046
- const applyFlightToLine = (line, flight, isOutbound) => {
33047
- const segments = (isOutbound ? flight.outward?.segments : flight.return?.segments) ?? [];
33068
+ const buildTrajectFlightRequest = ({ node, line }, context, pax) => ({
33069
+ transactionId: context.transactionId,
33070
+ officeId: context.officeId,
33071
+ catalogueId: context.catalogueId,
33072
+ agentId: context.agentId ?? null,
33073
+ language: context.language,
33074
+ departureAirportCode: node.departureAirportCode ?? '',
33075
+ arrivalAirportCode: node.arrivalAirportCode ?? '',
33076
+ returnAirportCode: null,
33077
+ luggageIncluded: null,
33078
+ maxStops: null,
33079
+ travelClass: null,
33080
+ vendorConfigurationId: node.externalVendorId ?? null,
33081
+ pax,
33082
+ outward: { date: dateToDateStruct(new Date(line.from)) },
33083
+ return: null
33084
+ });
33085
+ const pickCheapestFlight = (flights) => [...flights].sort((a, b) => a.price - b.price)[0] ?? null;
33086
+ const applyFlightToLine = (line, flight) => {
33087
+ const segments = flight.outward?.segments ?? [];
33048
33088
  if (!segments.length)
33049
33089
  return line;
33050
33090
  const firstSegment = segments[0];
@@ -33064,14 +33104,6 @@ const applyFlightToLine = (line, flight, isOutbound) => {
33064
33104
  isChanged: true
33065
33105
  };
33066
33106
  };
33067
- const pairTrajectFlights = (nodes) => {
33068
- const flights = nodes.filter((x) => isTrajectFlightNode(x.node));
33069
- return {
33070
- outbound: flights[0] ?? null,
33071
- inbound: flights.length > 1 ? flights[flights.length - 1] : null,
33072
- unsupported: flights.slice(1, Math.max(flights.length - 1, 1))
33073
- };
33074
- };
33075
33107
 
33076
33108
  const getLocation = (result) => {
33077
33109
  const place = result.locationName || result.regionName || result.oordName;
@@ -33149,15 +33181,10 @@ const TrajectResults = ({ isLoading }) => {
33149
33181
  const dispatch = useDispatch();
33150
33182
  const translations = getTranslations(context?.languageCode ?? 'en-GB');
33151
33183
  const locale = getLocale(context?.languageCode ?? 'en-GB');
33152
- const { trajectNodeAvailability, flightsLoading, trajectFlightsResolved, selectedOutwardKey, selectedReturnKey } = useSelector((state) => state.searchResults);
33153
- const uniqueOutwardFlights = useSelector(selectUniqueOutwardFlights);
33154
- const uniqueReturnFlights = useSelector(selectUniqueReturnFlights);
33155
- const selectedOutward = useSelector(selectSelectedOutward);
33156
- const selectedReturn = useSelector(selectSelectedReturn);
33184
+ const { trajectNodeAvailability, trajectFlightAvailability } = useSelector((state) => state.searchResults);
33157
33185
  const [expandedNodes, setExpandedNodes] = useState({});
33158
33186
  const trajectEntry = context?.trajectEntry;
33159
33187
  const nodesWithLines = useMemo(() => (trajectEntry ? getTrajectNodesWithLines(trajectEntry) : []), [trajectEntry]);
33160
- const flightPairing = useMemo(() => pairTrajectFlights(nodesWithLines), [nodesWithLines]);
33161
33188
  const lineByNodeId = useMemo(() => new Map(nodesWithLines.map((x) => [x.node.nodeId, x])), [nodesWithLines]);
33162
33189
  const nodesByDay = useMemo(() => {
33163
33190
  const map = new Map();
@@ -33182,6 +33209,7 @@ const TrajectResults = ({ isLoading }) => {
33182
33209
  dispatch(updateEditableEntryLine(applyResultToLine(item.line, result)));
33183
33210
  };
33184
33211
  const renderServiceNode = (item) => {
33212
+ console.log('renderServiceNode', item);
33185
33213
  const { node, line } = item;
33186
33214
  const availability = trajectNodeAvailability[line.guid];
33187
33215
  const isExpanded = !!expandedNodes[line.guid];
@@ -33210,30 +33238,19 @@ const TrajectResults = ({ isLoading }) => {
33210
33238
  others.length > 0 && (React__default.createElement("div", { className: "search__results__cards__actions" },
33211
33239
  React__default.createElement("button", { type: "button", className: "cta cta--secondary", onClick: () => toggleExpanded(line.guid) }, isExpanded ? translations.SRP.SHOW_LESS : `${translations.SRP.SHOW_MORE} (${others.length})`)))))));
33212
33240
  };
33241
+ const handleSelectFlight = (item, flight) => {
33242
+ dispatch(setTrajectFlightSelection({ lineGuid: item.line.guid, guid: flight.outwardGuid }));
33243
+ dispatch(updateEditableEntryLine(applyFlightToLine(item.line, flight)));
33244
+ };
33213
33245
  const renderFlightNode = (item) => {
33214
33246
  const { node, line } = item;
33215
- const isOutbound = flightPairing.outbound?.node.nodeId === node.nodeId;
33216
- const isInbound = flightPairing.inbound?.node.nodeId === node.nodeId;
33217
- // A middle leg of a multi-flight traject: the round-trip flight search cannot express it.
33218
- if (!isOutbound && !isInbound) {
33219
- return (React__default.createElement(React__default.Fragment, { key: line.guid },
33220
- React__default.createElement("div", { className: "search__results__label search__results__label--secondary" },
33221
- React__default.createElement("div", { className: "search__results__label__date" },
33222
- React__default.createElement("p", { className: "search__results__label__date-date" }, format$2(new Date(line.from), 'd', { locale })),
33223
- React__default.createElement("p", null, format$2(new Date(line.from), 'MMM', { locale }))),
33224
- React__default.createElement("div", { className: "search__results__label__text" },
33225
- React__default.createElement(Icon, { name: "ui-flight", height: 16 }),
33226
- React__default.createElement("h3", null,
33227
- React__default.createElement("strong", null, node.name)))),
33228
- React__default.createElement("div", { className: "no-results" },
33229
- node.departureAirportCode,
33230
- " \u2192 ",
33231
- node.arrivalAirportCode)));
33232
- }
33233
- const flights = isOutbound ? uniqueOutwardFlights : uniqueReturnFlights;
33234
- const selectedFlight = isOutbound ? selectedOutward : selectedReturn;
33235
- const selectedKey = isOutbound ? selectedOutwardKey : selectedReturnKey;
33236
- const visible = flights.filter((x) => getFlightKey(isOutbound ? x.outward.segments : x.return.segments) !== selectedKey);
33247
+ const availability = trajectFlightAvailability[line.guid];
33248
+ const status = availability?.status ?? 'loading';
33249
+ const flights = availability?.results ?? [];
33250
+ const selected = flights.find((x) => x.outwardGuid === availability?.selectedGuid) ?? null;
33251
+ const others = flights.filter((x) => x.outwardGuid !== availability?.selectedGuid);
33252
+ const isExpanded = !!expandedNodes[line.guid];
33253
+ const visible = isExpanded ? others : others.slice(0, 2);
33237
33254
  return (React__default.createElement(React__default.Fragment, { key: line.guid },
33238
33255
  React__default.createElement("div", { className: "search__results__label search__results__label--secondary" },
33239
33256
  React__default.createElement("div", { className: "search__results__label__date" },
@@ -33243,11 +33260,19 @@ const TrajectResults = ({ isLoading }) => {
33243
33260
  React__default.createElement(Icon, { name: "ui-flight", height: 16 }),
33244
33261
  React__default.createElement("h3", null,
33245
33262
  translations.SRP.SELECT,
33246
- " ",
33247
- React__default.createElement("strong", null, isOutbound ? translations.SRP.DEPARTURE : translations.SRP.RETURN)))),
33248
- flightsLoading || !trajectFlightsResolved ? (React__default.createElement(Spinner, { label: translations.SRP.LOADING_FLIGHTS })) : flights.length === 0 ? (React__default.createElement("div", { className: "no-results" }, translations.SRP.NO_RESULTS)) : (React__default.createElement("div", { className: "search__results__cards search__results__cards--extended" },
33249
- selectedFlight && (React__default.createElement(IndependentFlightOption, { key: `flight-${selectedKey}`, item: isOutbound ? selectedFlight.outward : selectedFlight.return, guid: selectedFlight.outwardGuid, selectedGuid: selectedFlight.outwardGuid, isOutward: isOutbound, showSelectedState: true, price: selectedFlight.price, onSelect: isOutbound ? () => dispatch(setSelectedOutwardKey(null)) : undefined })),
33250
- visible.map((result) => (React__default.createElement(IndependentFlightOption, { key: `flight-${result.outwardGuid}`, item: isOutbound ? result.outward : result.return, guid: result.outwardGuid, isOutward: isOutbound, price: result.price, currentSelectedPrice: selectedFlight?.price, onSelect: () => dispatch(isOutbound ? setSelectedOutwardKey(getFlightKey(result.outward.segments)) : setSelectedReturnKey(getFlightKey(result.return.segments))) })))))));
33263
+ ' ',
33264
+ React__default.createElement("strong", null,
33265
+ node.departureAirportCode,
33266
+ " - ",
33267
+ node.arrivalAirportCode)))),
33268
+ status === 'loading' && React__default.createElement(Spinner, { label: translations.SRP.LOADING_FLIGHTS }),
33269
+ status === 'unavailable' && React__default.createElement("div", { className: "no-results" }, translations.SRP.NO_RESULTS),
33270
+ status === 'resolved' && selected && (React__default.createElement(React__default.Fragment, null,
33271
+ React__default.createElement("div", { className: "search__results__cards search__results__cards--extended" },
33272
+ React__default.createElement(IndependentFlightOption, { key: `flight-${selected.outwardGuid}`, item: selected.outward, guid: selected.outwardGuid, selectedGuid: selected.outwardGuid, isOutward: true, showSelectedState: true, price: selected.price }),
33273
+ visible.map((result) => (React__default.createElement(IndependentFlightOption, { key: `flight-${result.outwardGuid}`, item: result.outward, guid: result.outwardGuid, isOutward: true, price: result.price, currentSelectedPrice: selected.price, onSelect: () => handleSelectFlight(item, result) })))),
33274
+ others.length > 2 && (React__default.createElement("div", { className: "search__results__cards__actions" },
33275
+ React__default.createElement("button", { type: "button", className: "cta cta--secondary", onClick: () => toggleExpanded(line.guid) }, isExpanded ? translations.SRP.SHOW_LESS : `${translations.SRP.SHOW_MORE} (${others.length - 2})`)))))));
33251
33276
  };
33252
33277
  if (isLoading)
33253
33278
  return React__default.createElement(Spinner, { label: translations.SRP.LOADING_ITINERARY });
@@ -33273,7 +33298,6 @@ const useTrajectAvailability = () => {
33273
33298
  const context = useContext(SearchResultsConfigurationContext);
33274
33299
  const dispatch = useDispatch();
33275
33300
  const trajectEntry = context?.trajectEntry;
33276
- const selectedCombinationFlight = useSelector(selectSelectedCombinationFlight);
33277
33301
  useEffect(() => {
33278
33302
  if (!context || !trajectEntry)
33279
33303
  return;
@@ -33293,6 +33317,13 @@ const useTrajectAvailability = () => {
33293
33317
  language: context.languageCode ?? 'en-GB',
33294
33318
  rooms: getPackagingRoomsFromEntry(trajectEntry.entry)
33295
33319
  };
33320
+ // Flight searches take passengers rather than rooms; the API buckets by age.
33321
+ const pax = trajectEntry.entry.pax ?? [];
33322
+ const ageOf = (p) => p.age ?? 30;
33323
+ const adults = pax.filter((p) => ageOf(p) >= 12).length;
33324
+ const kids = pax.filter((p) => ageOf(p) >= 2 && ageOf(p) < 12).length;
33325
+ const babies = pax.filter((p) => ageOf(p) < 2).length;
33326
+ const flightPax = concat(Array.from({ length: adults }, (_, index) => ({ id: index, age: 31 })), Array.from({ length: kids }, (_, index) => ({ id: index + adults, age: 8 })), Array.from({ length: babies }, (_, index) => ({ id: index + adults + kids, age: 1 })));
33296
33327
  const nodesWithLines = getTrajectNodesWithLines(trajectEntry);
33297
33328
  const resolveNode = async (item) => {
33298
33329
  const lineGuid = item.line.guid;
@@ -33342,59 +33373,36 @@ const useTrajectAvailability = () => {
33342
33373
  return lineGuid;
33343
33374
  }
33344
33375
  };
33345
- const resolveFlights = async () => {
33346
- const { outbound, inbound } = pairTrajectFlights(nodesWithLines);
33347
- if (!outbound) {
33348
- dispatch(setTrajectFlightsResolved(true));
33349
- return;
33376
+ const resolveFlightNode = async (item) => {
33377
+ const lineGuid = item.line.guid;
33378
+ if (!item.node.departureAirportCode || !item.node.arrivalAirportCode) {
33379
+ dispatch(setTrajectFlightAvailability({ lineGuid, results: [], selectedGuid: null }));
33380
+ return lineGuid;
33350
33381
  }
33351
- dispatch(setFlightsLoading(true));
33382
+ dispatch(setTrajectFlightLoading(lineGuid));
33352
33383
  try {
33353
- const pax = trajectEntry.entry.pax ?? [];
33354
- const ageOf = (p) => p.age ?? 30;
33355
- const adults = pax.filter((p) => ageOf(p) >= 12).length;
33356
- const kids = pax.filter((p) => ageOf(p) >= 2 && ageOf(p) < 12).length;
33357
- const babies = pax.filter((p) => ageOf(p) < 2).length;
33358
- const request = {
33359
- transactionId: trajectEntry.entry.transactionId,
33360
- officeId: searchContext.officeId,
33361
- catalogueId: searchContext.catalogueId,
33362
- agentId: searchContext.agentId,
33363
- language: searchContext.language,
33364
- departureAirportCode: outbound.node.departureAirportCode ?? '',
33365
- arrivalAirportCode: outbound.node.arrivalAirportCode ?? '',
33366
- returnAirportCode: inbound?.node.arrivalAirportCode ?? outbound.node.departureAirportCode ?? null,
33367
- luggageIncluded: null,
33368
- maxStops: null,
33369
- travelClass: null,
33370
- pax: concat(Array.from({ length: adults }, (_, index) => ({ id: index, age: 31 })), Array.from({ length: kids }, (_, index) => ({ id: index + adults, age: 8 })), Array.from({ length: babies }, (_, index) => ({ id: index + adults + kids, age: 1 }))),
33371
- outward: { date: dateToDateStruct(new Date(outbound.line.from)) },
33372
- return: { date: dateToDateStruct(new Date((inbound ?? outbound).line.from)) }
33373
- };
33374
- const flights = (await build.searchPackagingFlights(config, request, controller.signal)) ?? [];
33384
+ const flights = (await build.searchPackagingFlights(config, buildTrajectFlightRequest(item, searchContext, flightPax), controller.signal)) ?? [];
33375
33385
  if (controller.signal.aborted)
33376
- return;
33377
- dispatch(setPackagingFlightResults(flights));
33378
- const firstFlight = first(flights);
33379
- if (firstFlight) {
33380
- dispatch(setSelectedOutwardKey(getFlightKey(firstFlight.outward.segments)));
33381
- dispatch(setSelectedReturnKey(getFlightKey(firstFlight.return.segments)));
33382
- }
33386
+ return null;
33387
+ const picked = pickCheapestFlight(flights);
33388
+ dispatch(setTrajectFlightAvailability({ lineGuid, results: flights, selectedGuid: picked?.outwardGuid ?? null }));
33389
+ if (!picked)
33390
+ return lineGuid;
33391
+ dispatch(updateEditableEntryLine(applyFlightToLine(item.line, picked)));
33392
+ return null;
33383
33393
  }
33384
33394
  catch (error) {
33385
- if (!controller.signal.aborted)
33386
- console.error('Failed to resolve traject flights', error);
33387
- }
33388
- finally {
33389
- if (!controller.signal.aborted) {
33390
- dispatch(setFlightsLoading(false));
33391
- dispatch(setTrajectFlightsResolved(true));
33392
- }
33395
+ if (controller.signal.aborted)
33396
+ return null;
33397
+ console.error('Failed to resolve traject flight', item.node.name, error);
33398
+ dispatch(setTrajectFlightAvailability({ lineGuid, results: [], selectedGuid: null }));
33399
+ return lineGuid;
33393
33400
  }
33394
33401
  };
33395
33402
  (async () => {
33396
33403
  const searchable = nodesWithLines.filter((x) => isSearchableTrajectNode(x.node));
33397
- const [unresolved] = await Promise.all([Promise.all(searchable.map(resolveNode)), resolveFlights()]);
33404
+ const flightNodes = nodesWithLines.filter((x) => isTrajectFlightNode(x.node));
33405
+ const unresolved = await Promise.all([...searchable.map(resolveNode), ...flightNodes.map(resolveFlightNode)]);
33398
33406
  if (controller.signal.aborted)
33399
33407
  return;
33400
33408
  // A node with no availability anywhere cannot be priced or booked, so its line comes off
@@ -33405,16 +33413,6 @@ const useTrajectAvailability = () => {
33405
33413
  })();
33406
33414
  return () => controller.abort();
33407
33415
  }, [trajectEntry]);
33408
- // Keep the traject's own flight lines in step with whatever outward/return the user picks.
33409
- useEffect(() => {
33410
- if (!trajectEntry || !selectedCombinationFlight)
33411
- return;
33412
- const { outbound, inbound } = pairTrajectFlights(getTrajectNodesWithLines(trajectEntry));
33413
- if (outbound)
33414
- dispatch(updateEditableEntryLine(applyFlightToLine(outbound.line, selectedCombinationFlight, true)));
33415
- if (inbound)
33416
- dispatch(updateEditableEntryLine(applyFlightToLine(inbound.line, selectedCombinationFlight, false)));
33417
- }, [trajectEntry, selectedCombinationFlight]);
33418
33416
  };
33419
33417
 
33420
33418
  const SearchResultsContainer = ({ onBookingStarted }) => {
@@ -40,7 +40,8 @@ export interface SearchResultsState {
40
40
  bookingNumber?: string;
41
41
  /** Per-traject-node availability, keyed by the entry line guid the node maps to. */
42
42
  trajectNodeAvailability: Record<string, TrajectNodeAvailability>;
43
- trajectFlightsResolved: boolean;
43
+ /** Per-flight-node one-way search results, keyed by the entry line guid. */
44
+ trajectFlightAvailability: Record<string, TrajectFlightAvailability>;
44
45
  }
45
46
  export type TrajectNodeStatus = 'idle' | 'loading' | 'resolved' | 'unavailable';
46
47
  export interface TrajectNodeAvailability {
@@ -50,6 +51,12 @@ export interface TrajectNodeAvailability {
50
51
  selectedCode: string | null;
51
52
  preferredUnavailable: boolean;
52
53
  }
54
+ export interface TrajectFlightAvailability {
55
+ lineGuid: string;
56
+ status: TrajectNodeStatus;
57
+ results: PackagingFlightResponse[];
58
+ selectedGuid: string | null;
59
+ }
53
60
  export declare const setResults: import("@reduxjs/toolkit").ActionCreatorWithPayload<BookingPackageItem[], "searchResults/setResults">, setFilteredResults: import("@reduxjs/toolkit").ActionCreatorWithPayload<BookingPackageItem[], "searchResults/setFilteredResults">, setSelectedSearchResult: import("@reduxjs/toolkit").ActionCreatorWithPayload<BookingPackageItem | null, "searchResults/setSelectedSearchResult">, setPackagingAccoResults: import("@reduxjs/toolkit").ActionCreatorWithPayload<PackagingAccommodationResponse[], "searchResults/setPackagingAccoResults">, setFilteredPackagingAccoResults: import("@reduxjs/toolkit").ActionCreatorWithPayload<PackagingAccommodationResponse[], "searchResults/setFilteredPackagingAccoResults">, setFilteredPackagingFlightResults: import("@reduxjs/toolkit").ActionCreatorWithPayload<PackagingFlightResponse[], "searchResults/setFilteredPackagingFlightResults">, setPackagingAccoSearchDetails: import("@reduxjs/toolkit").ActionCreatorWithPayload<PackagingAccommodationResponse[], "searchResults/setPackagingAccoSearchDetails">, setSelectedPackagingAccoResult: import("@reduxjs/toolkit").ActionCreatorWithPayload<string | null, "searchResults/setSelectedPackagingAccoResult">, setPackagingFlightResults: import("@reduxjs/toolkit").ActionCreatorWithPayload<PackagingFlightResponse[], "searchResults/setPackagingFlightResults">, setSelectedPackagingFlight: import("@reduxjs/toolkit").ActionCreatorWithPayload<PackagingFlightResponse | null, "searchResults/setSelectedPackagingFlight">, setSelectedFlight: import("@reduxjs/toolkit").ActionCreatorWithPayload<ExtendedFlightSearchResponseItem | null, "searchResults/setSelectedFlight">, setSelectedFlightDetails: import("@reduxjs/toolkit").ActionCreatorWithPayload<ExtendedFlightSearchResponseItem | null, "searchResults/setSelectedFlightDetails">, setBookingPackageDetails: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
54
61
  details: BookingPackage;
55
62
  }, "searchResults/setBookingPackageDetails">, selectFlight: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
@@ -71,6 +78,13 @@ export declare const setResults: import("@reduxjs/toolkit").ActionCreatorWithPay
71
78
  }, "searchResults/setTrajectNodeAvailability">, setTrajectNodeSelection: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
72
79
  lineGuid: string;
73
80
  code: string;
74
- }, "searchResults/setTrajectNodeSelection">, setTrajectFlightsResolved: import("@reduxjs/toolkit").ActionCreatorWithPayload<boolean, "searchResults/setTrajectFlightsResolved">, resetTrajectAvailability: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<"searchResults/resetTrajectAvailability">, updateEditableEntryLine: import("@reduxjs/toolkit").ActionCreatorWithPayload<PackagingEntryLine, "searchResults/updateEditableEntryLine">, removeEditableEntryLines: import("@reduxjs/toolkit").ActionCreatorWithPayload<string[], "searchResults/removeEditableEntryLines">;
81
+ }, "searchResults/setTrajectNodeSelection">, setTrajectFlightLoading: import("@reduxjs/toolkit").ActionCreatorWithPayload<string, "searchResults/setTrajectFlightLoading">, setTrajectFlightAvailability: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
82
+ lineGuid: string;
83
+ results: PackagingFlightResponse[];
84
+ selectedGuid: string | null;
85
+ }, "searchResults/setTrajectFlightAvailability">, setTrajectFlightSelection: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
86
+ lineGuid: string;
87
+ guid: string;
88
+ }, "searchResults/setTrajectFlightSelection">, resetTrajectAvailability: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<"searchResults/resetTrajectAvailability">, updateEditableEntryLine: import("@reduxjs/toolkit").ActionCreatorWithPayload<PackagingEntryLine, "searchResults/updateEditableEntryLine">, removeEditableEntryLines: import("@reduxjs/toolkit").ActionCreatorWithPayload<string[], "searchResults/removeEditableEntryLines">;
75
89
  declare const _default: import("@reduxjs/toolkit").Reducer<SearchResultsState>;
76
90
  export default _default;
@@ -1,4 +1,4 @@
1
- import { PackagingAccommodationRequest, PackagingAccommodationResponse, PackagingEntry, PackagingEntryLine, PackagingFlightResponse, PackagingRoom, PackagingTrajectAlternative, PackagingTrajectNode, PackagingTrajectResponse } from '@qite/tide-client';
1
+ import { FlightSearchRequest, PackagingAccommodationRequest, PackagingAccommodationResponse, PackagingEntry, PackagingEntryLine, PackagingFlightResponse, PackagingRoom, PackagingTrajectAlternative, PackagingTrajectNode, PackagingTrajectResponse } from '@qite/tide-client';
2
2
  export declare const REGULAR_TRAJECT_NODE_TYPE = 0;
3
3
  export declare const CONNECTING_TRAJECT_NODE_TYPE = 1;
4
4
  export interface TrajectNodeWithLine {
@@ -25,9 +25,6 @@ export declare const matchesCode: (result: PackagingAccommodationResponse, code:
25
25
  export declare const getPackagingRoomsFromEntry: (entry: PackagingEntry) => PackagingRoom[];
26
26
  export declare const pickCheapest: (results: PackagingAccommodationResponse[]) => PackagingAccommodationResponse | null;
27
27
  export declare const applyResultToLine: (line: PackagingEntryLine, result: PackagingAccommodationResponse) => PackagingEntryLine;
28
- export declare const applyFlightToLine: (line: PackagingEntryLine, flight: PackagingFlightResponse, isOutbound: boolean) => PackagingEntryLine;
29
- export declare const pairTrajectFlights: (nodes: TrajectNodeWithLine[]) => {
30
- outbound: TrajectNodeWithLine;
31
- inbound: TrajectNodeWithLine | null;
32
- unsupported: TrajectNodeWithLine[];
33
- };
28
+ export declare const buildTrajectFlightRequest: ({ node, line }: TrajectNodeWithLine, context: TrajectSearchContext, pax: FlightSearchRequest["pax"]) => FlightSearchRequest;
29
+ export declare const pickCheapestFlight: (flights: PackagingFlightResponse[]) => PackagingFlightResponse | null;
30
+ export declare const applyFlightToLine: (line: PackagingEntryLine, flight: PackagingFlightResponse) => PackagingEntryLine;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qite/tide-components",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "React components for Tide (booking, product availability, form, ...)",
5
5
  "main": "build/build-cjs/index.js",
6
6
  "types": "build/build-cjs/src/index.d.ts",
@@ -36,7 +36,7 @@
36
36
  "devDependencies": {
37
37
  "@jsonurl/jsonurl": "^1.1.4",
38
38
  "@popperjs/core": "^2.10.2",
39
- "@qite/tide-client": "^2.0.12",
39
+ "@qite/tide-client": "^2.0.13",
40
40
  "@reduxjs/toolkit": "^2.8.2",
41
41
  "@rollup/plugin-commonjs": "^19.0.1",
42
42
  "@rollup/plugin-json": "^4.1.0",