@qite/tide-booking-component 1.4.100 → 1.4.101

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.
@@ -13418,10 +13418,10 @@ PERFORMANCE OF THIS SOFTWARE.
13418
13418
  var ENDPOINT_START_TRANSACTION = ENDPOINT + '/start';
13419
13419
  var ENDPOINT_ACCOMMODATIONS = ENDPOINT + '/accommodations';
13420
13420
  var ENDPOINT_PRICE_DETAILS = ENDPOINT + '/price-details';
13421
+ var ENDPOINT_ITINERARY = ENDPOINT + '/itinerary';
13421
13422
  var ENDPOINT_ENTRY = function (magicLinkCode) {
13422
13423
  return ENDPOINT + '/entry/' + magicLinkCode;
13423
13424
  };
13424
- // MANUAL PACKAGING SEARCH
13425
13425
  var startTransaction = function (config, signal) {
13426
13426
  var apiKey = config.apiKey;
13427
13427
  var url = '' + config.host + ENDPOINT_START_TRANSACTION;
@@ -13444,6 +13444,12 @@ PERFORMANCE OF THIS SOFTWARE.
13444
13444
  var body = JSON.stringify(request);
13445
13445
  return post(url, apiKey, body, config.token, signal, true);
13446
13446
  };
13447
+ var getItinerary = function (config, request, signal) {
13448
+ var url = '' + config.host + ENDPOINT_ITINERARY;
13449
+ var apiKey = config.apiKey;
13450
+ var body = JSON.stringify(request);
13451
+ return post(url, apiKey, body, config.token, signal, true);
13452
+ };
13447
13453
 
13448
13454
  exports.AllotmentType = AllotmentType;
13449
13455
  exports.ContactForm = ContactForm;
@@ -13496,6 +13502,7 @@ PERFORMANCE OF THIS SOFTWARE.
13496
13502
  exports.getEntryStatus = getEntryStatus;
13497
13503
  exports.getEntryTotals = getEntryTotals;
13498
13504
  exports.getInvoiceList = getInvoiceList;
13505
+ exports.getItinerary = getItinerary;
13499
13506
  exports.getLocations = getLocations;
13500
13507
  exports.getMolliePayment = getMolliePayment;
13501
13508
  exports.getPortal = getPortal;
@@ -33821,7 +33828,8 @@ var initialState$1 = {
33821
33828
  editablePackagingEntry: null,
33822
33829
  transactionId: null,
33823
33830
  accommodationFlyInStep: 'details',
33824
- priceDetails: null
33831
+ priceDetails: null,
33832
+ itinerary: null
33825
33833
  };
33826
33834
  var searchResultsSlice = toolkit.createSlice({
33827
33835
  name: 'searchResults',
@@ -33917,6 +33925,9 @@ var searchResultsSlice = toolkit.createSlice({
33917
33925
  },
33918
33926
  setPriceDetails: function (state, action) {
33919
33927
  state.priceDetails = action.payload;
33928
+ },
33929
+ setItinerary: function (state, action) {
33930
+ state.itinerary = action.payload;
33920
33931
  }
33921
33932
  }
33922
33933
  });
@@ -33942,7 +33953,8 @@ var setFlyInIsOpen = _a.setFlyInIsOpen,
33942
33953
  setEditablePackagingEntry = _a.setEditablePackagingEntry,
33943
33954
  setTransactionId = _a.setTransactionId,
33944
33955
  setAccommodationFlyInStep = _a.setAccommodationFlyInStep,
33945
- setPriceDetails = _a.setPriceDetails;
33956
+ setPriceDetails = _a.setPriceDetails,
33957
+ setItinerary = _a.setItinerary;
33946
33958
  var searchResultsReducer = searchResultsSlice.reducer;
33947
33959
 
33948
33960
  var ItemPicker = function (_a) {
@@ -47322,6 +47334,118 @@ var getRequestRoomsFromPackagingSegments = function (entry, segments) {
47322
47334
  });
47323
47335
  };
47324
47336
 
47337
+ var formatNodeDate = function (date) {
47338
+ if (!date) return '';
47339
+ try {
47340
+ return new Intl.DateTimeFormat('nl-BE', {
47341
+ weekday: 'long',
47342
+ day: '2-digit',
47343
+ month: '2-digit',
47344
+ year: 'numeric'
47345
+ }).format(date);
47346
+ } catch (_a) {
47347
+ return '';
47348
+ }
47349
+ };
47350
+ var escapeHtml = function (value) {
47351
+ if (!value) return '';
47352
+ return value.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#39;');
47353
+ };
47354
+ var buildItineraryHtml = function (itinerary) {
47355
+ var _a;
47356
+ if (!itinerary) {
47357
+ return '\n <div class="itinerary-shell">\n <div class="itinerary-empty">Geen reisroute beschikbaar.</div>\n </div>\n ';
47358
+ }
47359
+ var nodesHtml = ((_a = itinerary.nodes) !== null && _a !== void 0 ? _a : [])
47360
+ .map(function (node) {
47361
+ var hasItems = Array.isArray(node.items) && node.items.length > 0;
47362
+ var itemsHtml = hasItems
47363
+ ? node.items
47364
+ .map(function (item) {
47365
+ var _a;
47366
+ return '\n <article class="itinerary-item" data-template="'
47367
+ .concat(escapeHtml(item.templateName), '">\n ')
47368
+ .concat((_a = item.contents) !== null && _a !== void 0 ? _a : '', '\n </article>\n ');
47369
+ })
47370
+ .join('')
47371
+ : '<div class="itinerary-node__empty">Geen items voor deze dag.</div>';
47372
+ return '\n <section class="itinerary-node">\n <header class="itinerary-node__header">\n <div class="itinerary-node__day">Dag '
47373
+ .concat(node.startDay)
47374
+ .concat(
47375
+ node.endDay > node.startDay ? ' - '.concat(node.endDay) : '',
47376
+ '</div>\n <div class="itinerary-node__meta">\n <h2 class="itinerary-node__title">'
47377
+ )
47378
+ .concat(escapeHtml(node.title), '</h2>\n <div class="itinerary-node__date">')
47379
+ .concat(
47380
+ escapeHtml(formatNodeDate(node.startDate)),
47381
+ '</div>\n </div>\n </header>\n\n <div class="itinerary-node__content">\n '
47382
+ )
47383
+ .concat(itemsHtml, '\n </div>\n </section>\n ');
47384
+ })
47385
+ .join('');
47386
+ var defaultItemsHtml =
47387
+ itinerary.defaultItems && itinerary.defaultItems.length > 0
47388
+ ? '\n <section class="itinerary-default-items">\n <h2 class="itinerary-default-items__title">Algemene info</h2>\n '.concat(
47389
+ itinerary.defaultItems
47390
+ .map(function (item) {
47391
+ var _a;
47392
+ return '\n <article class="itinerary-item" data-template="'
47393
+ .concat(escapeHtml(item.templateName), '">\n ')
47394
+ .concat((_a = item.contents) !== null && _a !== void 0 ? _a : '', '\n </article>\n ');
47395
+ })
47396
+ .join(''),
47397
+ '\n </section>\n '
47398
+ )
47399
+ : '';
47400
+ return '\n <div class="itinerary-shell">\n '
47401
+ .concat(
47402
+ itinerary.title
47403
+ ? '<header class="itinerary-shell__header"><h1 class="itinerary-shell__title">'.concat(escapeHtml(itinerary.title), '</h1></header>')
47404
+ : '',
47405
+ '\n\n <div class="itinerary-shell__content">\n '
47406
+ )
47407
+ .concat(nodesHtml, '\n ')
47408
+ .concat(defaultItemsHtml, '\n </div>\n </div>\n ');
47409
+ };
47410
+ var FullItinerary = function (_a) {
47411
+ var isLoading = _a.isLoading;
47412
+ if (isLoading) {
47413
+ return React__default['default'].createElement(Spinner, null);
47414
+ }
47415
+ var itinerary = reactRedux.useSelector(function (state) {
47416
+ return state.searchResults;
47417
+ }).itinerary;
47418
+ var hostRef = React.useRef(null);
47419
+ var shadowRootRef = React.useRef(null);
47420
+ var html = React.useMemo(
47421
+ function () {
47422
+ return buildItineraryHtml(itinerary);
47423
+ },
47424
+ [itinerary]
47425
+ );
47426
+ React.useEffect(
47427
+ function () {
47428
+ var _a;
47429
+ if (!hostRef.current) {
47430
+ return;
47431
+ }
47432
+ if (!shadowRootRef.current) {
47433
+ shadowRootRef.current = hostRef.current.attachShadow({ mode: 'open' });
47434
+ }
47435
+ var shadowRoot = shadowRootRef.current;
47436
+ shadowRoot.innerHTML =
47437
+ "\n <style>\n :host {\n all: initial;\n }\n\n *,\n *::before,\n *::after {\n box-sizing: border-box;\n }\n\n .itinerary-shell {\n width: 100%;\n margin: 0 auto;\n font-family: 'Outfit', sans-serif;\n color: #222;\n }\n\n .itinerary-shell__header {\n margin-bottom: 24px;\n }\n\n .itinerary-shell__title {\n margin: 0;\n font-size: 32px;\n line-height: 1.2;\n }\n\n .itinerary-shell__content {\n display: grid;\n gap: 24px;\n }\n\n .itinerary-node {\n border: 1px solid #e5e7eb;\n border-radius: 16px;\n overflow: hidden;\n background: #fff;\n box-shadow: 0 2px 10px rgba(0, 0, 0, 0.04);\n }\n\n .itinerary-node__header {\n display: flex;\n gap: 16px;\n align-items: flex-start;\n padding: 20px 20px 16px;\n border-bottom: 1px solid #eef0f2;\n background: #fafafa;\n }\n\n .itinerary-node__day {\n flex: 0 0 auto;\n min-width: 72px;\n padding: 8px 12px;\n border-radius: 999px;\n font-size: 14px;\n font-weight: 700;\n line-height: 1;\n background: #111827;\n color: #fff;\n text-align: center;\n }\n\n .itinerary-node__meta {\n min-width: 0;\n }\n\n .itinerary-node__title {\n margin: 0;\n font-size: 22px;\n line-height: 1.25;\n }\n\n .itinerary-node__date {\n margin-top: 6px;\n font-size: 14px;\n color: #6b7280;\n }\n\n .itinerary-node__content {\n display: grid;\n gap: 20px;\n padding: 20px;\n }\n\n .itinerary-item {\n display: block;\n border-radius: 12px;\n overflow: hidden;\n background: #fff;\n }\n\n .itinerary-node__empty,\n .itinerary-empty {\n padding: 20px;\n border: 1px dashed #d1d5db;\n border-radius: 12px;\n color: #6b7280;\n background: #fafafa;\n }\n\n .itinerary-default-items {\n border: 1px solid #e5e7eb;\n border-radius: 16px;\n padding: 20px;\n background: #fff;\n box-shadow: 0 2px 10px rgba(0, 0, 0, 0.04);\n }\n\n .itinerary-default-items__title {\n margin: 0 0 16px;\n font-size: 22px;\n }\n\n @media (max-width: 768px) {\n .itinerary-shell {\n padding: 16px;\n }\n\n .itinerary-node__header {\n flex-direction: column;\n }\n\n .itinerary-node__day {\n min-width: auto;\n }\n }\n\n "
47438
+ .concat(
47439
+ (_a = itinerary === null || itinerary === void 0 ? void 0 : itinerary.styleSheetBody) !== null && _a !== void 0 ? _a : '',
47440
+ '\n </style>\n\n '
47441
+ )
47442
+ .concat(html, '\n ');
47443
+ },
47444
+ [html, itinerary === null || itinerary === void 0 ? void 0 : itinerary.styleSheetBody]
47445
+ );
47446
+ return React__default['default'].createElement('div', { ref: hostRef });
47447
+ };
47448
+
47325
47449
  var SearchResultsContainer = function () {
47326
47450
  var _a, _b;
47327
47451
  var currentSearch = typeof window !== 'undefined' ? window.location.search : '';
@@ -47345,7 +47469,8 @@ var SearchResultsContainer = function () {
47345
47469
  packagingAccoSearchDetails = _c.packagingAccoSearchDetails,
47346
47470
  editablePackagingEntry = _c.editablePackagingEntry,
47347
47471
  transactionId = _c.transactionId,
47348
- accommodationFlyInStep = _c.accommodationFlyInStep;
47472
+ accommodationFlyInStep = _c.accommodationFlyInStep,
47473
+ itinerary = _c.itinerary;
47349
47474
  var isMobile = useMediaQuery('(max-width: 1200px)');
47350
47475
  var _d = React.useState(false),
47351
47476
  initialFiltersSet = _d[0],
@@ -47363,11 +47488,14 @@ var SearchResultsContainer = function () {
47363
47488
  pricesAreLoading = _h[0],
47364
47489
  setPricesAreLoading = _h[1];
47365
47490
  var _j = React.useState(false),
47366
- itineraryOpen = _j[0],
47367
- setItineraryOpen = _j[1];
47368
- var _k = React.useState(null),
47369
- selectedAccommodationSeed = _k[0],
47370
- setSelectedAccommodationSeed = _k[1];
47491
+ itineraryIsLoading = _j[0],
47492
+ setItineraryIsLoading = _j[1];
47493
+ var _k = React.useState(false),
47494
+ itineraryOpen = _k[0],
47495
+ setItineraryOpen = _k[1];
47496
+ var _l = React.useState(null),
47497
+ selectedAccommodationSeed = _l[0],
47498
+ setSelectedAccommodationSeed = _l[1];
47371
47499
  var panelRef = React.useRef(null);
47372
47500
  var sortByTypes = [
47373
47501
  { direction: 'asc', label: 'default' },
@@ -47569,7 +47697,6 @@ var SearchResultsContainer = function () {
47569
47697
  };
47570
47698
  var handleConfirmHotelSwap = function () {
47571
47699
  var updatedEntry = swapHotelInPackagingEntry();
47572
- console.log('Updated entry after hotel swap', updatedEntry);
47573
47700
  if (!updatedEntry) return;
47574
47701
  dispatch(setEditablePackagingEntry(updatedEntry));
47575
47702
  handleFlyInToggle(false);
@@ -47584,7 +47711,6 @@ var SearchResultsContainer = function () {
47584
47711
  var details = packagingAccoSearchDetails;
47585
47712
  if (!sourceEntry || !(details === null || details === void 0 ? void 0 : details.length)) return null;
47586
47713
  var selectedOptionsPerRoom = getSelectedOptionsPerRoom(details);
47587
- console.log('Selected options per room', selectedOptionsPerRoom);
47588
47714
  if (!selectedOptionsPerRoom.length) return null;
47589
47715
  var selectedHotel = details[0];
47590
47716
  var roomIndex = 0;
@@ -47597,7 +47723,6 @@ var SearchResultsContainer = function () {
47597
47723
  return x.roomIndex === roomIndex;
47598
47724
  });
47599
47725
  var selectedOption = selectedRoom === null || selectedRoom === void 0 ? void 0 : selectedRoom.option;
47600
- console.log('selectedRoom and selectedOption for line', line, selectedRoom, selectedOption);
47601
47726
  roomIndex++;
47602
47727
  if (!selectedOption) {
47603
47728
  return line;
@@ -47692,7 +47817,6 @@ var SearchResultsContainer = function () {
47692
47817
  return [4 /*yield*/, build.search(config, searchRequest)];
47693
47818
  case 1:
47694
47819
  packageSearchResults = _b.sent();
47695
- console.log('Search results', packageSearchResults);
47696
47820
  enrichedFilters = enrichFiltersWithResults(packageSearchResults, context.filters, (_a = context.tags) !== null && _a !== void 0 ? _a : []);
47697
47821
  if (!initialFiltersSet) {
47698
47822
  dispatch(resetFilters(enrichedFilters));
@@ -47766,7 +47890,6 @@ var SearchResultsContainer = function () {
47766
47890
  searchRequest = buildPackagingAccommodationRequestFromSeed(seed, currentTransactionId);
47767
47891
  searchRequest.portalId = context.portalId;
47768
47892
  searchRequest.agentId = context.agentId;
47769
- console.log('Packaging accommodation search request', searchRequest);
47770
47893
  return [4 /*yield*/, build.searchPackagingAccommodations(config, searchRequest)];
47771
47894
  case 1:
47772
47895
  packageAccoSearchResults = _b.sent();
@@ -47807,7 +47930,6 @@ var SearchResultsContainer = function () {
47807
47930
  currentTransactionId =
47808
47931
  ((_a = context === null || context === void 0 ? void 0 : context.packagingEntry) === null || _a === void 0 ? void 0 : _a.transactionId) ||
47809
47932
  transactionId;
47810
- console.log('Current transaction ID', currentTransactionId);
47811
47933
  if (!!currentTransactionId) return [3 /*break*/, 2];
47812
47934
  dispatch(setIsLoading(true));
47813
47935
  return [4 /*yield*/, runStartTransaction()];
@@ -48053,7 +48175,6 @@ var SearchResultsContainer = function () {
48053
48175
  return [4 /*yield*/, build.searchPackagingAccommodations(config, detailSearchRequest)];
48054
48176
  case 2:
48055
48177
  packageAccoSearchDetails = _f.sent();
48056
- console.log('Packaging Acco Search details', packageAccoSearchDetails);
48057
48178
  dispatch(setPackagingAccoSearchDetails(packageAccoSearchDetails));
48058
48179
  setDetailsIsLoading(false);
48059
48180
  return [3 /*break*/, 4];
@@ -48156,14 +48277,6 @@ var SearchResultsContainer = function () {
48156
48277
  return __generator(this, function (_a) {
48157
48278
  switch (_a.label) {
48158
48279
  case 0:
48159
- console.log(
48160
- 'Fetching price details for entry',
48161
- editablePackagingEntry !== null && editablePackagingEntry !== void 0
48162
- ? editablePackagingEntry
48163
- : context === null || context === void 0
48164
- ? void 0
48165
- : context.packagingEntry
48166
- );
48167
48280
  if (!context || !editablePackagingEntry) return [2 /*return*/];
48168
48281
  setPricesAreLoading(true);
48169
48282
  _a.label = 1;
@@ -48190,7 +48303,42 @@ var SearchResultsContainer = function () {
48190
48303
  });
48191
48304
  });
48192
48305
  };
48306
+ var fetchItinerary = function () {
48307
+ return __awaiter(void 0, void 0, void 0, function () {
48308
+ var config, itinerary_1, err_7;
48309
+ return __generator(this, function (_a) {
48310
+ switch (_a.label) {
48311
+ case 0:
48312
+ console.log('Fetching itinerary for entry', editablePackagingEntry);
48313
+ if (!context || !editablePackagingEntry) return [2 /*return*/];
48314
+ setItineraryIsLoading(true);
48315
+ _a.label = 1;
48316
+ case 1:
48317
+ _a.trys.push([1, 3, , 4]);
48318
+ config = {
48319
+ host: context.tideConnection.host,
48320
+ apiKey: context.tideConnection.apiKey
48321
+ };
48322
+ return [4 /*yield*/, build.getItinerary(config, editablePackagingEntry)];
48323
+ case 2:
48324
+ itinerary_1 = _a.sent();
48325
+ console.log('Fetched itinerary', itinerary_1);
48326
+ dispatch(setItinerary(itinerary_1));
48327
+ setItineraryIsLoading(false);
48328
+ return [3 /*break*/, 4];
48329
+ case 3:
48330
+ err_7 = _a.sent();
48331
+ console.error('Error fetching itinerary', err_7);
48332
+ setItineraryIsLoading(false);
48333
+ return [3 /*break*/, 4];
48334
+ case 4:
48335
+ return [2 /*return*/];
48336
+ }
48337
+ });
48338
+ });
48339
+ };
48193
48340
  fetchPriceDetails();
48341
+ fetchItinerary();
48194
48342
  },
48195
48343
  [editablePackagingEntry]
48196
48344
  );
@@ -48384,7 +48532,8 @@ var SearchResultsContainer = function () {
48384
48532
  }),
48385
48533
  context.searchConfiguration.qsmType === build.PortalQsmType.AccommodationAndFlight &&
48386
48534
  context.packagingEntry &&
48387
- React__default['default'].createElement('span', null, 'TODO: Show Full Itinerary here')
48535
+ itinerary &&
48536
+ React__default['default'].createElement(FullItinerary, { isLoading: itineraryIsLoading })
48388
48537
  )
48389
48538
  ),
48390
48539
  React__default['default'].createElement(FlyIn, {
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ interface FullItineraryProps {
3
+ isLoading: boolean;
4
+ }
5
+ declare const FullItinerary: React.FC<FullItineraryProps>;
6
+ export default FullItinerary;
@@ -1,5 +1,12 @@
1
1
  import { AccommodationFlyInStep, ExtendedFlightSearchResponseItem, Filter, SortByType } from '../types';
2
- import { BookingPackage, BookingPackageItem, BookingPriceDetails, PackagingAccommodationResponse, PackagingEntry } from '@qite/tide-client/build/types';
2
+ import {
3
+ BookingPackage,
4
+ BookingPackageItem,
5
+ BookingPriceDetails,
6
+ ClientPortalItinerary,
7
+ PackagingAccommodationResponse,
8
+ PackagingEntry
9
+ } from '@qite/tide-client/build/types';
3
10
  export interface SearchResultsState {
4
11
  results: BookingPackageItem[];
5
12
  filteredResults: BookingPackageItem[];
@@ -21,6 +28,7 @@ export interface SearchResultsState {
21
28
  transactionId: string | null;
22
29
  accommodationFlyInStep: AccommodationFlyInStep;
23
30
  priceDetails: BookingPriceDetails | null;
31
+ itinerary: ClientPortalItinerary | null;
24
32
  }
25
33
  export declare const setResults: import('@reduxjs/toolkit').ActionCreatorWithPayload<BookingPackageItem[], 'searchResults/setResults'>,
26
34
  setFilteredResults: import('@reduxjs/toolkit').ActionCreatorWithPayload<BookingPackageItem[], 'searchResults/setFilteredResults'>,
@@ -64,6 +72,7 @@ export declare const setResults: import('@reduxjs/toolkit').ActionCreatorWithPay
64
72
  setEditablePackagingEntry: import('@reduxjs/toolkit').ActionCreatorWithPayload<PackagingEntry | null, 'searchResults/setEditablePackagingEntry'>,
65
73
  setTransactionId: import('@reduxjs/toolkit').ActionCreatorWithPayload<string | null, 'searchResults/setTransactionId'>,
66
74
  setAccommodationFlyInStep: import('@reduxjs/toolkit').ActionCreatorWithPayload<AccommodationFlyInStep, 'searchResults/setAccommodationFlyInStep'>,
67
- setPriceDetails: import('@reduxjs/toolkit').ActionCreatorWithPayload<BookingPriceDetails | null, 'searchResults/setPriceDetails'>;
75
+ setPriceDetails: import('@reduxjs/toolkit').ActionCreatorWithPayload<BookingPriceDetails | null, 'searchResults/setPriceDetails'>,
76
+ setItinerary: import('@reduxjs/toolkit').ActionCreatorWithPayload<ClientPortalItinerary | null, 'searchResults/setItinerary'>;
68
77
  declare const _default: import('@reduxjs/toolkit').Reducer<SearchResultsState>;
69
78
  export default _default;
@@ -13450,10 +13450,10 @@ PERFORMANCE OF THIS SOFTWARE.
13450
13450
  var ENDPOINT_START_TRANSACTION = ENDPOINT + '/start';
13451
13451
  var ENDPOINT_ACCOMMODATIONS = ENDPOINT + '/accommodations';
13452
13452
  var ENDPOINT_PRICE_DETAILS = ENDPOINT + '/price-details';
13453
+ var ENDPOINT_ITINERARY = ENDPOINT + '/itinerary';
13453
13454
  var ENDPOINT_ENTRY = function (magicLinkCode) {
13454
13455
  return ENDPOINT + '/entry/' + magicLinkCode;
13455
13456
  };
13456
- // MANUAL PACKAGING SEARCH
13457
13457
  var startTransaction = function (config, signal) {
13458
13458
  var apiKey = config.apiKey;
13459
13459
  var url = '' + config.host + ENDPOINT_START_TRANSACTION;
@@ -13476,6 +13476,12 @@ PERFORMANCE OF THIS SOFTWARE.
13476
13476
  var body = JSON.stringify(request);
13477
13477
  return post(url, apiKey, body, config.token, signal, true);
13478
13478
  };
13479
+ var getItinerary = function (config, request, signal) {
13480
+ var url = '' + config.host + ENDPOINT_ITINERARY;
13481
+ var apiKey = config.apiKey;
13482
+ var body = JSON.stringify(request);
13483
+ return post(url, apiKey, body, config.token, signal, true);
13484
+ };
13479
13485
 
13480
13486
  exports.AllotmentType = AllotmentType;
13481
13487
  exports.ContactForm = ContactForm;
@@ -13528,6 +13534,7 @@ PERFORMANCE OF THIS SOFTWARE.
13528
13534
  exports.getEntryStatus = getEntryStatus;
13529
13535
  exports.getEntryTotals = getEntryTotals;
13530
13536
  exports.getInvoiceList = getInvoiceList;
13537
+ exports.getItinerary = getItinerary;
13531
13538
  exports.getLocations = getLocations;
13532
13539
  exports.getMolliePayment = getMolliePayment;
13533
13540
  exports.getPortal = getPortal;
@@ -33680,7 +33687,8 @@ var initialState$1 = {
33680
33687
  editablePackagingEntry: null,
33681
33688
  transactionId: null,
33682
33689
  accommodationFlyInStep: 'details',
33683
- priceDetails: null
33690
+ priceDetails: null,
33691
+ itinerary: null
33684
33692
  };
33685
33693
  var searchResultsSlice = createSlice({
33686
33694
  name: 'searchResults',
@@ -33776,6 +33784,9 @@ var searchResultsSlice = createSlice({
33776
33784
  },
33777
33785
  setPriceDetails: function (state, action) {
33778
33786
  state.priceDetails = action.payload;
33787
+ },
33788
+ setItinerary: function (state, action) {
33789
+ state.itinerary = action.payload;
33779
33790
  }
33780
33791
  }
33781
33792
  });
@@ -33801,7 +33812,8 @@ var setFlyInIsOpen = _a.setFlyInIsOpen,
33801
33812
  setEditablePackagingEntry = _a.setEditablePackagingEntry,
33802
33813
  setTransactionId = _a.setTransactionId,
33803
33814
  setAccommodationFlyInStep = _a.setAccommodationFlyInStep,
33804
- setPriceDetails = _a.setPriceDetails;
33815
+ setPriceDetails = _a.setPriceDetails,
33816
+ setItinerary = _a.setItinerary;
33805
33817
  var searchResultsReducer = searchResultsSlice.reducer;
33806
33818
 
33807
33819
  var ItemPicker = function (_a) {
@@ -47076,6 +47088,118 @@ var getRequestRoomsFromPackagingSegments = function (entry, segments) {
47076
47088
  });
47077
47089
  };
47078
47090
 
47091
+ var formatNodeDate = function (date) {
47092
+ if (!date) return '';
47093
+ try {
47094
+ return new Intl.DateTimeFormat('nl-BE', {
47095
+ weekday: 'long',
47096
+ day: '2-digit',
47097
+ month: '2-digit',
47098
+ year: 'numeric'
47099
+ }).format(date);
47100
+ } catch (_a) {
47101
+ return '';
47102
+ }
47103
+ };
47104
+ var escapeHtml = function (value) {
47105
+ if (!value) return '';
47106
+ return value.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#39;');
47107
+ };
47108
+ var buildItineraryHtml = function (itinerary) {
47109
+ var _a;
47110
+ if (!itinerary) {
47111
+ return '\n <div class="itinerary-shell">\n <div class="itinerary-empty">Geen reisroute beschikbaar.</div>\n </div>\n ';
47112
+ }
47113
+ var nodesHtml = ((_a = itinerary.nodes) !== null && _a !== void 0 ? _a : [])
47114
+ .map(function (node) {
47115
+ var hasItems = Array.isArray(node.items) && node.items.length > 0;
47116
+ var itemsHtml = hasItems
47117
+ ? node.items
47118
+ .map(function (item) {
47119
+ var _a;
47120
+ return '\n <article class="itinerary-item" data-template="'
47121
+ .concat(escapeHtml(item.templateName), '">\n ')
47122
+ .concat((_a = item.contents) !== null && _a !== void 0 ? _a : '', '\n </article>\n ');
47123
+ })
47124
+ .join('')
47125
+ : '<div class="itinerary-node__empty">Geen items voor deze dag.</div>';
47126
+ return '\n <section class="itinerary-node">\n <header class="itinerary-node__header">\n <div class="itinerary-node__day">Dag '
47127
+ .concat(node.startDay)
47128
+ .concat(
47129
+ node.endDay > node.startDay ? ' - '.concat(node.endDay) : '',
47130
+ '</div>\n <div class="itinerary-node__meta">\n <h2 class="itinerary-node__title">'
47131
+ )
47132
+ .concat(escapeHtml(node.title), '</h2>\n <div class="itinerary-node__date">')
47133
+ .concat(
47134
+ escapeHtml(formatNodeDate(node.startDate)),
47135
+ '</div>\n </div>\n </header>\n\n <div class="itinerary-node__content">\n '
47136
+ )
47137
+ .concat(itemsHtml, '\n </div>\n </section>\n ');
47138
+ })
47139
+ .join('');
47140
+ var defaultItemsHtml =
47141
+ itinerary.defaultItems && itinerary.defaultItems.length > 0
47142
+ ? '\n <section class="itinerary-default-items">\n <h2 class="itinerary-default-items__title">Algemene info</h2>\n '.concat(
47143
+ itinerary.defaultItems
47144
+ .map(function (item) {
47145
+ var _a;
47146
+ return '\n <article class="itinerary-item" data-template="'
47147
+ .concat(escapeHtml(item.templateName), '">\n ')
47148
+ .concat((_a = item.contents) !== null && _a !== void 0 ? _a : '', '\n </article>\n ');
47149
+ })
47150
+ .join(''),
47151
+ '\n </section>\n '
47152
+ )
47153
+ : '';
47154
+ return '\n <div class="itinerary-shell">\n '
47155
+ .concat(
47156
+ itinerary.title
47157
+ ? '<header class="itinerary-shell__header"><h1 class="itinerary-shell__title">'.concat(escapeHtml(itinerary.title), '</h1></header>')
47158
+ : '',
47159
+ '\n\n <div class="itinerary-shell__content">\n '
47160
+ )
47161
+ .concat(nodesHtml, '\n ')
47162
+ .concat(defaultItemsHtml, '\n </div>\n </div>\n ');
47163
+ };
47164
+ var FullItinerary = function (_a) {
47165
+ var isLoading = _a.isLoading;
47166
+ if (isLoading) {
47167
+ return React__default.createElement(Spinner, null);
47168
+ }
47169
+ var itinerary = useSelector(function (state) {
47170
+ return state.searchResults;
47171
+ }).itinerary;
47172
+ var hostRef = useRef(null);
47173
+ var shadowRootRef = useRef(null);
47174
+ var html = useMemo(
47175
+ function () {
47176
+ return buildItineraryHtml(itinerary);
47177
+ },
47178
+ [itinerary]
47179
+ );
47180
+ useEffect(
47181
+ function () {
47182
+ var _a;
47183
+ if (!hostRef.current) {
47184
+ return;
47185
+ }
47186
+ if (!shadowRootRef.current) {
47187
+ shadowRootRef.current = hostRef.current.attachShadow({ mode: 'open' });
47188
+ }
47189
+ var shadowRoot = shadowRootRef.current;
47190
+ shadowRoot.innerHTML =
47191
+ "\n <style>\n :host {\n all: initial;\n }\n\n *,\n *::before,\n *::after {\n box-sizing: border-box;\n }\n\n .itinerary-shell {\n width: 100%;\n margin: 0 auto;\n font-family: 'Outfit', sans-serif;\n color: #222;\n }\n\n .itinerary-shell__header {\n margin-bottom: 24px;\n }\n\n .itinerary-shell__title {\n margin: 0;\n font-size: 32px;\n line-height: 1.2;\n }\n\n .itinerary-shell__content {\n display: grid;\n gap: 24px;\n }\n\n .itinerary-node {\n border: 1px solid #e5e7eb;\n border-radius: 16px;\n overflow: hidden;\n background: #fff;\n box-shadow: 0 2px 10px rgba(0, 0, 0, 0.04);\n }\n\n .itinerary-node__header {\n display: flex;\n gap: 16px;\n align-items: flex-start;\n padding: 20px 20px 16px;\n border-bottom: 1px solid #eef0f2;\n background: #fafafa;\n }\n\n .itinerary-node__day {\n flex: 0 0 auto;\n min-width: 72px;\n padding: 8px 12px;\n border-radius: 999px;\n font-size: 14px;\n font-weight: 700;\n line-height: 1;\n background: #111827;\n color: #fff;\n text-align: center;\n }\n\n .itinerary-node__meta {\n min-width: 0;\n }\n\n .itinerary-node__title {\n margin: 0;\n font-size: 22px;\n line-height: 1.25;\n }\n\n .itinerary-node__date {\n margin-top: 6px;\n font-size: 14px;\n color: #6b7280;\n }\n\n .itinerary-node__content {\n display: grid;\n gap: 20px;\n padding: 20px;\n }\n\n .itinerary-item {\n display: block;\n border-radius: 12px;\n overflow: hidden;\n background: #fff;\n }\n\n .itinerary-node__empty,\n .itinerary-empty {\n padding: 20px;\n border: 1px dashed #d1d5db;\n border-radius: 12px;\n color: #6b7280;\n background: #fafafa;\n }\n\n .itinerary-default-items {\n border: 1px solid #e5e7eb;\n border-radius: 16px;\n padding: 20px;\n background: #fff;\n box-shadow: 0 2px 10px rgba(0, 0, 0, 0.04);\n }\n\n .itinerary-default-items__title {\n margin: 0 0 16px;\n font-size: 22px;\n }\n\n @media (max-width: 768px) {\n .itinerary-shell {\n padding: 16px;\n }\n\n .itinerary-node__header {\n flex-direction: column;\n }\n\n .itinerary-node__day {\n min-width: auto;\n }\n }\n\n "
47192
+ .concat(
47193
+ (_a = itinerary === null || itinerary === void 0 ? void 0 : itinerary.styleSheetBody) !== null && _a !== void 0 ? _a : '',
47194
+ '\n </style>\n\n '
47195
+ )
47196
+ .concat(html, '\n ');
47197
+ },
47198
+ [html, itinerary === null || itinerary === void 0 ? void 0 : itinerary.styleSheetBody]
47199
+ );
47200
+ return React__default.createElement('div', { ref: hostRef });
47201
+ };
47202
+
47079
47203
  var SearchResultsContainer = function () {
47080
47204
  var _a, _b;
47081
47205
  var currentSearch = typeof window !== 'undefined' ? window.location.search : '';
@@ -47099,7 +47223,8 @@ var SearchResultsContainer = function () {
47099
47223
  packagingAccoSearchDetails = _c.packagingAccoSearchDetails,
47100
47224
  editablePackagingEntry = _c.editablePackagingEntry,
47101
47225
  transactionId = _c.transactionId,
47102
- accommodationFlyInStep = _c.accommodationFlyInStep;
47226
+ accommodationFlyInStep = _c.accommodationFlyInStep,
47227
+ itinerary = _c.itinerary;
47103
47228
  var isMobile = useMediaQuery('(max-width: 1200px)');
47104
47229
  var _d = useState(false),
47105
47230
  initialFiltersSet = _d[0],
@@ -47117,11 +47242,14 @@ var SearchResultsContainer = function () {
47117
47242
  pricesAreLoading = _h[0],
47118
47243
  setPricesAreLoading = _h[1];
47119
47244
  var _j = useState(false),
47120
- itineraryOpen = _j[0],
47121
- setItineraryOpen = _j[1];
47122
- var _k = useState(null),
47123
- selectedAccommodationSeed = _k[0],
47124
- setSelectedAccommodationSeed = _k[1];
47245
+ itineraryIsLoading = _j[0],
47246
+ setItineraryIsLoading = _j[1];
47247
+ var _k = useState(false),
47248
+ itineraryOpen = _k[0],
47249
+ setItineraryOpen = _k[1];
47250
+ var _l = useState(null),
47251
+ selectedAccommodationSeed = _l[0],
47252
+ setSelectedAccommodationSeed = _l[1];
47125
47253
  var panelRef = useRef(null);
47126
47254
  var sortByTypes = [
47127
47255
  { direction: 'asc', label: 'default' },
@@ -47323,7 +47451,6 @@ var SearchResultsContainer = function () {
47323
47451
  };
47324
47452
  var handleConfirmHotelSwap = function () {
47325
47453
  var updatedEntry = swapHotelInPackagingEntry();
47326
- console.log('Updated entry after hotel swap', updatedEntry);
47327
47454
  if (!updatedEntry) return;
47328
47455
  dispatch(setEditablePackagingEntry(updatedEntry));
47329
47456
  handleFlyInToggle(false);
@@ -47338,7 +47465,6 @@ var SearchResultsContainer = function () {
47338
47465
  var details = packagingAccoSearchDetails;
47339
47466
  if (!sourceEntry || !(details === null || details === void 0 ? void 0 : details.length)) return null;
47340
47467
  var selectedOptionsPerRoom = getSelectedOptionsPerRoom(details);
47341
- console.log('Selected options per room', selectedOptionsPerRoom);
47342
47468
  if (!selectedOptionsPerRoom.length) return null;
47343
47469
  var selectedHotel = details[0];
47344
47470
  var roomIndex = 0;
@@ -47351,7 +47477,6 @@ var SearchResultsContainer = function () {
47351
47477
  return x.roomIndex === roomIndex;
47352
47478
  });
47353
47479
  var selectedOption = selectedRoom === null || selectedRoom === void 0 ? void 0 : selectedRoom.option;
47354
- console.log('selectedRoom and selectedOption for line', line, selectedRoom, selectedOption);
47355
47480
  roomIndex++;
47356
47481
  if (!selectedOption) {
47357
47482
  return line;
@@ -47446,7 +47571,6 @@ var SearchResultsContainer = function () {
47446
47571
  return [4 /*yield*/, build.search(config, searchRequest)];
47447
47572
  case 1:
47448
47573
  packageSearchResults = _b.sent();
47449
- console.log('Search results', packageSearchResults);
47450
47574
  enrichedFilters = enrichFiltersWithResults(packageSearchResults, context.filters, (_a = context.tags) !== null && _a !== void 0 ? _a : []);
47451
47575
  if (!initialFiltersSet) {
47452
47576
  dispatch(resetFilters(enrichedFilters));
@@ -47520,7 +47644,6 @@ var SearchResultsContainer = function () {
47520
47644
  searchRequest = buildPackagingAccommodationRequestFromSeed(seed, currentTransactionId);
47521
47645
  searchRequest.portalId = context.portalId;
47522
47646
  searchRequest.agentId = context.agentId;
47523
- console.log('Packaging accommodation search request', searchRequest);
47524
47647
  return [4 /*yield*/, build.searchPackagingAccommodations(config, searchRequest)];
47525
47648
  case 1:
47526
47649
  packageAccoSearchResults = _b.sent();
@@ -47561,7 +47684,6 @@ var SearchResultsContainer = function () {
47561
47684
  currentTransactionId =
47562
47685
  ((_a = context === null || context === void 0 ? void 0 : context.packagingEntry) === null || _a === void 0 ? void 0 : _a.transactionId) ||
47563
47686
  transactionId;
47564
- console.log('Current transaction ID', currentTransactionId);
47565
47687
  if (!!currentTransactionId) return [3 /*break*/, 2];
47566
47688
  dispatch(setIsLoading(true));
47567
47689
  return [4 /*yield*/, runStartTransaction()];
@@ -47807,7 +47929,6 @@ var SearchResultsContainer = function () {
47807
47929
  return [4 /*yield*/, build.searchPackagingAccommodations(config, detailSearchRequest)];
47808
47930
  case 2:
47809
47931
  packageAccoSearchDetails = _f.sent();
47810
- console.log('Packaging Acco Search details', packageAccoSearchDetails);
47811
47932
  dispatch(setPackagingAccoSearchDetails(packageAccoSearchDetails));
47812
47933
  setDetailsIsLoading(false);
47813
47934
  return [3 /*break*/, 4];
@@ -47910,14 +48031,6 @@ var SearchResultsContainer = function () {
47910
48031
  return __generator(this, function (_a) {
47911
48032
  switch (_a.label) {
47912
48033
  case 0:
47913
- console.log(
47914
- 'Fetching price details for entry',
47915
- editablePackagingEntry !== null && editablePackagingEntry !== void 0
47916
- ? editablePackagingEntry
47917
- : context === null || context === void 0
47918
- ? void 0
47919
- : context.packagingEntry
47920
- );
47921
48034
  if (!context || !editablePackagingEntry) return [2 /*return*/];
47922
48035
  setPricesAreLoading(true);
47923
48036
  _a.label = 1;
@@ -47944,7 +48057,42 @@ var SearchResultsContainer = function () {
47944
48057
  });
47945
48058
  });
47946
48059
  };
48060
+ var fetchItinerary = function () {
48061
+ return __awaiter(void 0, void 0, void 0, function () {
48062
+ var config, itinerary_1, err_7;
48063
+ return __generator(this, function (_a) {
48064
+ switch (_a.label) {
48065
+ case 0:
48066
+ console.log('Fetching itinerary for entry', editablePackagingEntry);
48067
+ if (!context || !editablePackagingEntry) return [2 /*return*/];
48068
+ setItineraryIsLoading(true);
48069
+ _a.label = 1;
48070
+ case 1:
48071
+ _a.trys.push([1, 3, , 4]);
48072
+ config = {
48073
+ host: context.tideConnection.host,
48074
+ apiKey: context.tideConnection.apiKey
48075
+ };
48076
+ return [4 /*yield*/, build.getItinerary(config, editablePackagingEntry)];
48077
+ case 2:
48078
+ itinerary_1 = _a.sent();
48079
+ console.log('Fetched itinerary', itinerary_1);
48080
+ dispatch(setItinerary(itinerary_1));
48081
+ setItineraryIsLoading(false);
48082
+ return [3 /*break*/, 4];
48083
+ case 3:
48084
+ err_7 = _a.sent();
48085
+ console.error('Error fetching itinerary', err_7);
48086
+ setItineraryIsLoading(false);
48087
+ return [3 /*break*/, 4];
48088
+ case 4:
48089
+ return [2 /*return*/];
48090
+ }
48091
+ });
48092
+ });
48093
+ };
47947
48094
  fetchPriceDetails();
48095
+ fetchItinerary();
47948
48096
  },
47949
48097
  [editablePackagingEntry]
47950
48098
  );
@@ -48138,7 +48286,8 @@ var SearchResultsContainer = function () {
48138
48286
  }),
48139
48287
  context.searchConfiguration.qsmType === build.PortalQsmType.AccommodationAndFlight &&
48140
48288
  context.packagingEntry &&
48141
- React__default.createElement('span', null, 'TODO: Show Full Itinerary here')
48289
+ itinerary &&
48290
+ React__default.createElement(FullItinerary, { isLoading: itineraryIsLoading })
48142
48291
  )
48143
48292
  ),
48144
48293
  React__default.createElement(FlyIn, {
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ interface FullItineraryProps {
3
+ isLoading: boolean;
4
+ }
5
+ declare const FullItinerary: React.FC<FullItineraryProps>;
6
+ export default FullItinerary;
@@ -1,5 +1,12 @@
1
1
  import { AccommodationFlyInStep, ExtendedFlightSearchResponseItem, Filter, SortByType } from '../types';
2
- import { BookingPackage, BookingPackageItem, BookingPriceDetails, PackagingAccommodationResponse, PackagingEntry } from '@qite/tide-client/build/types';
2
+ import {
3
+ BookingPackage,
4
+ BookingPackageItem,
5
+ BookingPriceDetails,
6
+ ClientPortalItinerary,
7
+ PackagingAccommodationResponse,
8
+ PackagingEntry
9
+ } from '@qite/tide-client/build/types';
3
10
  export interface SearchResultsState {
4
11
  results: BookingPackageItem[];
5
12
  filteredResults: BookingPackageItem[];
@@ -21,6 +28,7 @@ export interface SearchResultsState {
21
28
  transactionId: string | null;
22
29
  accommodationFlyInStep: AccommodationFlyInStep;
23
30
  priceDetails: BookingPriceDetails | null;
31
+ itinerary: ClientPortalItinerary | null;
24
32
  }
25
33
  export declare const setResults: import('@reduxjs/toolkit').ActionCreatorWithPayload<BookingPackageItem[], 'searchResults/setResults'>,
26
34
  setFilteredResults: import('@reduxjs/toolkit').ActionCreatorWithPayload<BookingPackageItem[], 'searchResults/setFilteredResults'>,
@@ -64,6 +72,7 @@ export declare const setResults: import('@reduxjs/toolkit').ActionCreatorWithPay
64
72
  setEditablePackagingEntry: import('@reduxjs/toolkit').ActionCreatorWithPayload<PackagingEntry | null, 'searchResults/setEditablePackagingEntry'>,
65
73
  setTransactionId: import('@reduxjs/toolkit').ActionCreatorWithPayload<string | null, 'searchResults/setTransactionId'>,
66
74
  setAccommodationFlyInStep: import('@reduxjs/toolkit').ActionCreatorWithPayload<AccommodationFlyInStep, 'searchResults/setAccommodationFlyInStep'>,
67
- setPriceDetails: import('@reduxjs/toolkit').ActionCreatorWithPayload<BookingPriceDetails | null, 'searchResults/setPriceDetails'>;
75
+ setPriceDetails: import('@reduxjs/toolkit').ActionCreatorWithPayload<BookingPriceDetails | null, 'searchResults/setPriceDetails'>,
76
+ setItinerary: import('@reduxjs/toolkit').ActionCreatorWithPayload<ClientPortalItinerary | null, 'searchResults/setItinerary'>;
68
77
  declare const _default: import('@reduxjs/toolkit').Reducer<SearchResultsState>;
69
78
  export default _default;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qite/tide-booking-component",
3
- "version": "1.4.100",
3
+ "version": "1.4.101",
4
4
  "description": "React Booking wizard & Booking product component for Tide",
5
5
  "main": "build/build-cjs/index.js",
6
6
  "types": "build/build-cjs/src/index.d.ts",
@@ -29,7 +29,7 @@
29
29
  "devDependencies": {
30
30
  "@jsonurl/jsonurl": "^1.1.4",
31
31
  "@popperjs/core": "^2.10.2",
32
- "@qite/tide-client": "^1.1.157",
32
+ "@qite/tide-client": "^1.1.158",
33
33
  "@reduxjs/toolkit": "^2.8.2",
34
34
  "@rollup/plugin-commonjs": "^19.0.1",
35
35
  "@rollup/plugin-json": "^4.1.0",
@@ -0,0 +1,266 @@
1
+ import React, { useEffect, useMemo, useRef } from 'react';
2
+ import { useSelector } from 'react-redux';
3
+ import { SearchResultsRootState } from '../../store/search-results-store';
4
+ import { ClientPortalItinerary } from '@qite/tide-client';
5
+ import Spinner from '../spinner/spinner';
6
+
7
+ const formatNodeDate = (date?: Date | null) => {
8
+ if (!date) return '';
9
+
10
+ try {
11
+ return new Intl.DateTimeFormat('nl-BE', {
12
+ weekday: 'long',
13
+ day: '2-digit',
14
+ month: '2-digit',
15
+ year: 'numeric'
16
+ }).format(date);
17
+ } catch {
18
+ return '';
19
+ }
20
+ };
21
+
22
+ const escapeHtml = (value?: string | null) => {
23
+ if (!value) return '';
24
+
25
+ return value.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#39;');
26
+ };
27
+
28
+ const buildItineraryHtml = (itinerary?: ClientPortalItinerary | null) => {
29
+ if (!itinerary) {
30
+ return `
31
+ <div class="itinerary-shell">
32
+ <div class="itinerary-empty">Geen reisroute beschikbaar.</div>
33
+ </div>
34
+ `;
35
+ }
36
+
37
+ const nodesHtml = (itinerary.nodes ?? [])
38
+ .map((node) => {
39
+ const hasItems = Array.isArray(node.items) && node.items.length > 0;
40
+
41
+ const itemsHtml = hasItems
42
+ ? node.items
43
+ .map(
44
+ (item) => `
45
+ <article class="itinerary-item" data-template="${escapeHtml(item.templateName)}">
46
+ ${item.contents ?? ''}
47
+ </article>
48
+ `
49
+ )
50
+ .join('')
51
+ : `<div class="itinerary-node__empty">Geen items voor deze dag.</div>`;
52
+
53
+ return `
54
+ <section class="itinerary-node">
55
+ <header class="itinerary-node__header">
56
+ <div class="itinerary-node__day">Dag ${node.startDay}${node.endDay > node.startDay ? ` - ${node.endDay}` : ''}</div>
57
+ <div class="itinerary-node__meta">
58
+ <h2 class="itinerary-node__title">${escapeHtml(node.title)}</h2>
59
+ <div class="itinerary-node__date">${escapeHtml(formatNodeDate(node.startDate))}</div>
60
+ </div>
61
+ </header>
62
+
63
+ <div class="itinerary-node__content">
64
+ ${itemsHtml}
65
+ </div>
66
+ </section>
67
+ `;
68
+ })
69
+ .join('');
70
+
71
+ const defaultItemsHtml =
72
+ itinerary.defaultItems && itinerary.defaultItems.length > 0
73
+ ? `
74
+ <section class="itinerary-default-items">
75
+ <h2 class="itinerary-default-items__title">Algemene info</h2>
76
+ ${itinerary.defaultItems
77
+ .map(
78
+ (item) => `
79
+ <article class="itinerary-item" data-template="${escapeHtml(item.templateName)}">
80
+ ${item.contents ?? ''}
81
+ </article>
82
+ `
83
+ )
84
+ .join('')}
85
+ </section>
86
+ `
87
+ : '';
88
+
89
+ return `
90
+ <div class="itinerary-shell">
91
+ ${itinerary.title ? `<header class="itinerary-shell__header"><h1 class="itinerary-shell__title">${escapeHtml(itinerary.title)}</h1></header>` : ''}
92
+
93
+ <div class="itinerary-shell__content">
94
+ ${nodesHtml}
95
+ ${defaultItemsHtml}
96
+ </div>
97
+ </div>
98
+ `;
99
+ };
100
+
101
+ interface FullItineraryProps {
102
+ isLoading: boolean;
103
+ }
104
+
105
+ const FullItinerary: React.FC<FullItineraryProps> = ({ isLoading }) => {
106
+ if (isLoading) {
107
+ return <Spinner />;
108
+ }
109
+
110
+ const { itinerary } = useSelector((state: SearchResultsRootState) => state.searchResults);
111
+ const hostRef = useRef<HTMLDivElement | null>(null);
112
+ const shadowRootRef = useRef<ShadowRoot | null>(null);
113
+
114
+ const html = useMemo(() => buildItineraryHtml(itinerary as ClientPortalItinerary | null), [itinerary]);
115
+
116
+ useEffect(() => {
117
+ if (!hostRef.current) {
118
+ return;
119
+ }
120
+
121
+ if (!shadowRootRef.current) {
122
+ shadowRootRef.current = hostRef.current.attachShadow({ mode: 'open' });
123
+ }
124
+
125
+ const shadowRoot = shadowRootRef.current;
126
+
127
+ shadowRoot.innerHTML = `
128
+ <style>
129
+ :host {
130
+ all: initial;
131
+ }
132
+
133
+ *,
134
+ *::before,
135
+ *::after {
136
+ box-sizing: border-box;
137
+ }
138
+
139
+ .itinerary-shell {
140
+ width: 100%;
141
+ margin: 0 auto;
142
+ font-family: 'Outfit', sans-serif;
143
+ color: #222;
144
+ }
145
+
146
+ .itinerary-shell__header {
147
+ margin-bottom: 24px;
148
+ }
149
+
150
+ .itinerary-shell__title {
151
+ margin: 0;
152
+ font-size: 32px;
153
+ line-height: 1.2;
154
+ }
155
+
156
+ .itinerary-shell__content {
157
+ display: grid;
158
+ gap: 24px;
159
+ }
160
+
161
+ .itinerary-node {
162
+ border: 1px solid #e5e7eb;
163
+ border-radius: 16px;
164
+ overflow: hidden;
165
+ background: #fff;
166
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.04);
167
+ }
168
+
169
+ .itinerary-node__header {
170
+ display: flex;
171
+ gap: 16px;
172
+ align-items: flex-start;
173
+ padding: 20px 20px 16px;
174
+ border-bottom: 1px solid #eef0f2;
175
+ background: #fafafa;
176
+ }
177
+
178
+ .itinerary-node__day {
179
+ flex: 0 0 auto;
180
+ min-width: 72px;
181
+ padding: 8px 12px;
182
+ border-radius: 999px;
183
+ font-size: 14px;
184
+ font-weight: 700;
185
+ line-height: 1;
186
+ background: #111827;
187
+ color: #fff;
188
+ text-align: center;
189
+ }
190
+
191
+ .itinerary-node__meta {
192
+ min-width: 0;
193
+ }
194
+
195
+ .itinerary-node__title {
196
+ margin: 0;
197
+ font-size: 22px;
198
+ line-height: 1.25;
199
+ }
200
+
201
+ .itinerary-node__date {
202
+ margin-top: 6px;
203
+ font-size: 14px;
204
+ color: #6b7280;
205
+ }
206
+
207
+ .itinerary-node__content {
208
+ display: grid;
209
+ gap: 20px;
210
+ padding: 20px;
211
+ }
212
+
213
+ .itinerary-item {
214
+ display: block;
215
+ border-radius: 12px;
216
+ overflow: hidden;
217
+ background: #fff;
218
+ }
219
+
220
+ .itinerary-node__empty,
221
+ .itinerary-empty {
222
+ padding: 20px;
223
+ border: 1px dashed #d1d5db;
224
+ border-radius: 12px;
225
+ color: #6b7280;
226
+ background: #fafafa;
227
+ }
228
+
229
+ .itinerary-default-items {
230
+ border: 1px solid #e5e7eb;
231
+ border-radius: 16px;
232
+ padding: 20px;
233
+ background: #fff;
234
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.04);
235
+ }
236
+
237
+ .itinerary-default-items__title {
238
+ margin: 0 0 16px;
239
+ font-size: 22px;
240
+ }
241
+
242
+ @media (max-width: 768px) {
243
+ .itinerary-shell {
244
+ padding: 16px;
245
+ }
246
+
247
+ .itinerary-node__header {
248
+ flex-direction: column;
249
+ }
250
+
251
+ .itinerary-node__day {
252
+ min-width: auto;
253
+ }
254
+ }
255
+
256
+ ${itinerary?.styleSheetBody ?? ''}
257
+ </style>
258
+
259
+ ${html}
260
+ `;
261
+ }, [html, itinerary?.styleSheetBody]);
262
+
263
+ return <div ref={hostRef} />;
264
+ };
265
+
266
+ export default FullItinerary;
@@ -17,7 +17,8 @@ import {
17
17
  setEditablePackagingEntry,
18
18
  setTransactionId,
19
19
  setAccommodationFlyInStep,
20
- setPriceDetails
20
+ setPriceDetails,
21
+ setItinerary
21
22
  } from '../../store/search-results-slice';
22
23
  import { AccommodationFlyInStep, Filter, SearchSeed, SortByType } from '../../types';
23
24
  import useMediaQuery from '../../../shared/utils/use-media-query-util';
@@ -39,7 +40,8 @@ import {
39
40
  PackagingEntry,
40
41
  startTransaction,
41
42
  PackagingEntryLine,
42
- getPriceDetails
43
+ getPriceDetails,
44
+ getItinerary
43
45
  } from '@qite/tide-client';
44
46
  import { getDateFromParams, getNumberFromParams, getRoomsFromParams, getStringFromParams } from '../../../shared/utils/query-string-util';
45
47
  import { first, last, range } from 'lodash';
@@ -74,6 +76,7 @@ import {
74
76
  toDateOnlyString
75
77
  } from '../../utils/query-utils';
76
78
  import { getRequestRoomsFromPackagingSegments, getRoomIndexFromLine, getSelectedOptionsPerRoom } from '../../utils/packaging-utils';
79
+ import FullItinerary from '../itinerary/full-itinerary';
77
80
 
78
81
  const SearchResultsContainer: React.FC = () => {
79
82
  const currentSearch = typeof window !== 'undefined' ? window.location.search : '';
@@ -98,7 +101,8 @@ const SearchResultsContainer: React.FC = () => {
98
101
  packagingAccoSearchDetails,
99
102
  editablePackagingEntry,
100
103
  transactionId,
101
- accommodationFlyInStep
104
+ accommodationFlyInStep,
105
+ itinerary
102
106
  } = useSelector((state: SearchResultsRootState) => state.searchResults);
103
107
 
104
108
  const isMobile = useMediaQuery('(max-width: 1200px)');
@@ -109,6 +113,7 @@ const SearchResultsContainer: React.FC = () => {
109
113
 
110
114
  const [detailsIsLoading, setDetailsIsLoading] = useState(false);
111
115
  const [pricesAreLoading, setPricesAreLoading] = useState(false);
116
+ const [itineraryIsLoading, setItineraryIsLoading] = useState(false);
112
117
 
113
118
  const [itineraryOpen, setItineraryOpen] = useState(false);
114
119
 
@@ -816,7 +821,28 @@ const SearchResultsContainer: React.FC = () => {
816
821
  }
817
822
  };
818
823
 
824
+ const fetchItinerary = async () => {
825
+ console.log('Fetching itinerary for entry', editablePackagingEntry);
826
+ if (!context || !editablePackagingEntry) return;
827
+ setItineraryIsLoading(true);
828
+ try {
829
+ const config: TideClientConfig = {
830
+ host: context.tideConnection.host,
831
+ apiKey: context.tideConnection.apiKey
832
+ };
833
+
834
+ const itinerary = await getItinerary(config, editablePackagingEntry);
835
+ console.log('Fetched itinerary', itinerary);
836
+ dispatch(setItinerary(itinerary));
837
+ setItineraryIsLoading(false);
838
+ } catch (err) {
839
+ console.error('Error fetching itinerary', err);
840
+ setItineraryIsLoading(false);
841
+ }
842
+ };
843
+
819
844
  fetchPriceDetails();
845
+ fetchItinerary();
820
846
  }, [editablePackagingEntry]);
821
847
 
822
848
  return (
@@ -943,8 +969,8 @@ const SearchResultsContainer: React.FC = () => {
943
969
  context.showFlightResults &&
944
970
  bookingPackageDetails?.returnFlights && <FlightResults flights={bookingPackageDetails?.returnFlights} isDeparture={false} />}
945
971
 
946
- {context.searchConfiguration.qsmType === PortalQsmType.AccommodationAndFlight && context.packagingEntry && (
947
- <span>TODO: Show Full Itinerary here</span>
972
+ {context.searchConfiguration.qsmType === PortalQsmType.AccommodationAndFlight && context.packagingEntry && itinerary && (
973
+ <FullItinerary isLoading={itineraryIsLoading} />
948
974
  )}
949
975
  </div>
950
976
  </div>
@@ -1,6 +1,13 @@
1
1
  import { createSlice, PayloadAction } from '@reduxjs/toolkit';
2
2
  import { AccommodationFlyInStep, ExtendedFlightSearchResponseItem, Filter, SortByType } from '../types';
3
- import { BookingPackage, BookingPackageItem, BookingPriceDetails, PackagingAccommodationResponse, PackagingEntry } from '@qite/tide-client/build/types';
3
+ import {
4
+ BookingPackage,
5
+ BookingPackageItem,
6
+ BookingPriceDetails,
7
+ ClientPortalItinerary,
8
+ PackagingAccommodationResponse,
9
+ PackagingEntry
10
+ } from '@qite/tide-client/build/types';
4
11
 
5
12
  export interface SearchResultsState {
6
13
  results: BookingPackageItem[];
@@ -23,6 +30,7 @@ export interface SearchResultsState {
23
30
  transactionId: string | null;
24
31
  accommodationFlyInStep: AccommodationFlyInStep;
25
32
  priceDetails: BookingPriceDetails | null;
33
+ itinerary: ClientPortalItinerary | null;
26
34
  }
27
35
 
28
36
  const initialState: SearchResultsState = {
@@ -45,7 +53,8 @@ const initialState: SearchResultsState = {
45
53
  editablePackagingEntry: null,
46
54
  transactionId: null,
47
55
  accommodationFlyInStep: 'details',
48
- priceDetails: null
56
+ priceDetails: null,
57
+ itinerary: null
49
58
  };
50
59
 
51
60
  const searchResultsSlice = createSlice({
@@ -143,6 +152,9 @@ const searchResultsSlice = createSlice({
143
152
  },
144
153
  setPriceDetails(state, action: PayloadAction<BookingPriceDetails | null>) {
145
154
  state.priceDetails = action.payload;
155
+ },
156
+ setItinerary(state, action: PayloadAction<ClientPortalItinerary | null>) {
157
+ state.itinerary = action.payload;
146
158
  }
147
159
  }
148
160
  });
@@ -170,7 +182,8 @@ export const {
170
182
  setEditablePackagingEntry,
171
183
  setTransactionId,
172
184
  setAccommodationFlyInStep,
173
- setPriceDetails
185
+ setPriceDetails,
186
+ setItinerary
174
187
  } = searchResultsSlice.actions;
175
188
 
176
189
  export default searchResultsSlice.reducer;