@qite/tide-booking-component 1.4.95 → 1.4.97

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.
Files changed (453) hide show
  1. package/README.md +24 -24
  2. package/build/build-cjs/index.js +48813 -29709
  3. package/build/build-cjs/src/booking-product/components/age-select.d.ts +3 -3
  4. package/build/build-cjs/src/booking-product/components/amount-input.d.ts +5 -5
  5. package/build/build-cjs/src/booking-product/components/date-range-picker/calendar-day.d.ts +8 -8
  6. package/build/build-cjs/src/booking-product/components/date-range-picker/calendar.d.ts +14 -14
  7. package/build/build-cjs/src/booking-product/components/date-range-picker/index.d.ts +16 -19
  8. package/build/build-cjs/src/booking-product/components/dates.d.ts +8 -8
  9. package/build/build-cjs/src/booking-product/components/footer.d.ts +5 -5
  10. package/build/build-cjs/src/booking-product/components/header.d.ts +6 -6
  11. package/build/build-cjs/src/booking-product/components/icon.d.ts +5 -5
  12. package/build/build-cjs/src/booking-product/components/list-view.d.ts +2 -2
  13. package/build/build-cjs/src/booking-product/components/product.d.ts +4 -4
  14. package/build/build-cjs/src/booking-product/components/rating.d.ts +1 -1
  15. package/build/build-cjs/src/booking-product/components/rooms.d.ts +4 -4
  16. package/build/build-cjs/src/booking-product/constants.d.ts +1 -1
  17. package/build/build-cjs/src/booking-product/index.d.ts +4 -4
  18. package/build/build-cjs/src/booking-product/settings-context.d.ts +1 -2
  19. package/build/build-cjs/src/booking-product/types.d.ts +21 -21
  20. package/build/build-cjs/src/booking-product/utils/api.d.ts +11 -2
  21. package/build/build-cjs/src/booking-product/utils/price.d.ts +10 -1
  22. package/build/build-cjs/src/booking-wizard/api-settings-slice.d.ts +3 -2
  23. package/build/build-cjs/src/booking-wizard/components/icon.d.ts +5 -5
  24. package/build/build-cjs/src/booking-wizard/components/labeled-input.d.ts +13 -13
  25. package/build/build-cjs/src/booking-wizard/components/labeled-select.d.ts +16 -16
  26. package/build/build-cjs/src/booking-wizard/components/message.d.ts +4 -4
  27. package/build/build-cjs/src/booking-wizard/components/multi-range-filter.d.ts +6 -6
  28. package/build/build-cjs/src/booking-wizard/components/phone-input.d.ts +11 -11
  29. package/build/build-cjs/src/booking-wizard/components/print-offer-button.d.ts +11 -11
  30. package/build/build-cjs/src/booking-wizard/components/product-card.d.ts +3 -3
  31. package/build/build-cjs/src/booking-wizard/components/step-indicator.d.ts +1 -1
  32. package/build/build-cjs/src/booking-wizard/components/step-route.d.ts +3 -3
  33. package/build/build-cjs/src/booking-wizard/features/booking/api.d.ts +26 -7
  34. package/build/build-cjs/src/booking-wizard/features/booking/booking-self-contained.d.ts +3 -3
  35. package/build/build-cjs/src/booking-wizard/features/booking/booking-slice.d.ts +102 -43
  36. package/build/build-cjs/src/booking-wizard/features/booking/booking.d.ts +3 -3
  37. package/build/build-cjs/src/booking-wizard/features/booking/constants.d.ts +8 -1
  38. package/build/build-cjs/src/booking-wizard/features/booking/selectors.d.ts +644 -479
  39. package/build/build-cjs/src/booking-wizard/features/confirmation/confirmation.d.ts +1 -2
  40. package/build/build-cjs/src/booking-wizard/features/error/error.d.ts +1 -2
  41. package/build/build-cjs/src/booking-wizard/features/flight-options/flight-filter.d.ts +3 -3
  42. package/build/build-cjs/src/booking-wizard/features/flight-options/flight-option-flight.d.ts +2 -2
  43. package/build/build-cjs/src/booking-wizard/features/flight-options/flight-option.d.ts +4 -4
  44. package/build/build-cjs/src/booking-wizard/features/flight-options/flight-utils.d.ts +9 -2
  45. package/build/build-cjs/src/booking-wizard/features/flight-options/index.d.ts +1 -2
  46. package/build/build-cjs/src/booking-wizard/features/price-details/price-details-api.d.ts +6 -1
  47. package/build/build-cjs/src/booking-wizard/features/price-details/price-details-slice.d.ts +15 -10
  48. package/build/build-cjs/src/booking-wizard/features/price-details/selectors.d.ts +302 -287
  49. package/build/build-cjs/src/booking-wizard/features/product-options/none-option.d.ts +3 -3
  50. package/build/build-cjs/src/booking-wizard/features/product-options/option-booking-airline-group.d.ts +2 -2
  51. package/build/build-cjs/src/booking-wizard/features/product-options/option-booking-group.d.ts +6 -6
  52. package/build/build-cjs/src/booking-wizard/features/product-options/option-item.d.ts +5 -5
  53. package/build/build-cjs/src/booking-wizard/features/product-options/option-pax-card.d.ts +4 -4
  54. package/build/build-cjs/src/booking-wizard/features/product-options/option-pax-group.d.ts +7 -7
  55. package/build/build-cjs/src/booking-wizard/features/product-options/option-room.d.ts +5 -5
  56. package/build/build-cjs/src/booking-wizard/features/product-options/option-unit-group.d.ts +7 -7
  57. package/build/build-cjs/src/booking-wizard/features/product-options/option-units-card.d.ts +3 -3
  58. package/build/build-cjs/src/booking-wizard/features/product-options/options-form.d.ts +1 -2
  59. package/build/build-cjs/src/booking-wizard/features/room-options/index.d.ts +1 -2
  60. package/build/build-cjs/src/booking-wizard/features/room-options/room-utils.d.ts +19 -6
  61. package/build/build-cjs/src/booking-wizard/features/room-options/room.d.ts +6 -6
  62. package/build/build-cjs/src/booking-wizard/features/room-options/traveler-rooms.d.ts +3 -3
  63. package/build/build-cjs/src/booking-wizard/features/sidebar/index.d.ts +2 -2
  64. package/build/build-cjs/src/booking-wizard/features/sidebar/sidebar-flight.d.ts +2 -2
  65. package/build/build-cjs/src/booking-wizard/features/sidebar/sidebar-util.d.ts +4 -2
  66. package/build/build-cjs/src/booking-wizard/features/sidebar/sidebar.d.ts +24 -24
  67. package/build/build-cjs/src/booking-wizard/features/summary/summary-booking-option-pax.d.ts +1 -1
  68. package/build/build-cjs/src/booking-wizard/features/summary/summary-booking-option-unit.d.ts +1 -1
  69. package/build/build-cjs/src/booking-wizard/features/summary/summary-flight.d.ts +2 -2
  70. package/build/build-cjs/src/booking-wizard/features/summary/summary-per-booking-option-group.d.ts +1 -1
  71. package/build/build-cjs/src/booking-wizard/features/summary/summary-per-pax-option-group.d.ts +1 -1
  72. package/build/build-cjs/src/booking-wizard/features/summary/summary-per-unit-option-group.d.ts +1 -1
  73. package/build/build-cjs/src/booking-wizard/features/summary/summary-slice.d.ts +3 -3
  74. package/build/build-cjs/src/booking-wizard/features/summary/summary.d.ts +1 -2
  75. package/build/build-cjs/src/booking-wizard/features/travelers-form/travelers-form-slice.d.ts +75 -75
  76. package/build/build-cjs/src/booking-wizard/features/travelers-form/travelers-form-util.d.ts +4 -4
  77. package/build/build-cjs/src/booking-wizard/features/travelers-form/travelers-form.d.ts +1 -2
  78. package/build/build-cjs/src/booking-wizard/features/travelers-form/type-ahead-input.d.ts +11 -11
  79. package/build/build-cjs/src/booking-wizard/features/travelers-form/validate-form.d.ts +8 -1
  80. package/build/build-cjs/src/booking-wizard/index.d.ts +6 -6
  81. package/build/build-cjs/src/booking-wizard/settings-context.d.ts +1 -2
  82. package/build/build-cjs/src/booking-wizard/store.d.ts +40 -22
  83. package/build/build-cjs/src/booking-wizard/types.d.ts +240 -240
  84. package/build/build-cjs/src/booking-wizard/use-offer-printer.d.ts +8 -8
  85. package/build/build-cjs/src/content/components/LanguageSwitcher.d.ts +5 -5
  86. package/build/build-cjs/src/content/components/accordion.d.ts +4 -4
  87. package/build/build-cjs/src/content/components/breadcrumb.d.ts +7 -7
  88. package/build/build-cjs/src/content/components/faq.d.ts +4 -4
  89. package/build/build-cjs/src/content/components/gallery.d.ts +6 -6
  90. package/build/build-cjs/src/content/components/icon.d.ts +5 -5
  91. package/build/build-cjs/src/content/components/image-with-text.d.ts +18 -18
  92. package/build/build-cjs/src/content/components/slider.d.ts +5 -5
  93. package/build/build-cjs/src/content/featured-trips/types.d.ts +8 -8
  94. package/build/build-cjs/src/content/features/content-page/content-page-self-contained.d.ts +1 -1
  95. package/build/build-cjs/src/content/footer/types.d.ts +17 -17
  96. package/build/build-cjs/src/content/header/types.d.ts +23 -20
  97. package/build/build-cjs/src/content/image-card-grid/types.d.ts +8 -8
  98. package/build/build-cjs/src/content/image-with-text-section/types.d.ts +15 -15
  99. package/build/build-cjs/src/content/login/login-services.d.ts +6 -1
  100. package/build/build-cjs/src/content/login/types.d.ts +19 -19
  101. package/build/build-cjs/src/content/navbar/placeholderData.d.ts +2 -2
  102. package/build/build-cjs/src/content/navbar/types.d.ts +22 -22
  103. package/build/build-cjs/src/index.d.ts +17 -1
  104. package/build/build-cjs/src/qsm/components/date-range-picker/calendar-day.d.ts +7 -7
  105. package/build/build-cjs/src/qsm/components/date-range-picker/calendar.d.ts +18 -18
  106. package/build/build-cjs/src/qsm/components/date-range-picker/index.d.ts +5 -5
  107. package/build/build-cjs/src/qsm/components/double-search-input-group/index.d.ts +2 -2
  108. package/build/build-cjs/src/qsm/components/icon.d.ts +5 -5
  109. package/build/build-cjs/src/qsm/components/item-picker/index.d.ts +7 -7
  110. package/build/build-cjs/src/qsm/components/search-input/index.d.ts +9 -9
  111. package/build/build-cjs/src/qsm/components/search-input-group/index.d.ts +7 -7
  112. package/build/build-cjs/src/qsm/index.d.ts +1 -1
  113. package/build/build-cjs/src/qsm/store/qsm-slice.d.ts +110 -58
  114. package/build/build-cjs/src/qsm/store/qsm-store.d.ts +20 -7
  115. package/build/build-cjs/src/qsm/types.d.ts +59 -59
  116. package/build/build-cjs/src/search-results/components/filters/filters.d.ts +5 -5
  117. package/build/build-cjs/src/search-results/components/filters/flight-filters.d.ts +3 -3
  118. package/build/build-cjs/src/search-results/components/flight/flight-banner.d.ts +2 -2
  119. package/build/build-cjs/src/search-results/components/flight/flight-card.d.ts +1 -1
  120. package/build/build-cjs/src/search-results/components/flight/flight-leg.d.ts +1 -1
  121. package/build/build-cjs/src/search-results/components/flight/flight-path.d.ts +1 -1
  122. package/build/build-cjs/src/search-results/components/flight/flight-results.d.ts +2 -2
  123. package/build/build-cjs/src/search-results/components/flight/flight-search-context/index.d.ts +29 -29
  124. package/build/build-cjs/src/search-results/components/flight/flight-selection/independent-flight-option.d.ts +5 -5
  125. package/build/build-cjs/src/search-results/components/flight/flight-selection/independent-flight-selection.d.ts +1 -1
  126. package/build/build-cjs/src/search-results/components/flight/flight-selection/index.d.ts +2 -2
  127. package/build/build-cjs/src/search-results/components/flight/flight-selection/paired-flight-option.d.ts +1 -1
  128. package/build/build-cjs/src/search-results/components/flight/flight-selection/paired-flight-selection.d.ts +1 -1
  129. package/build/build-cjs/src/search-results/components/group-tour/group-tour-card.d.ts +3 -3
  130. package/build/build-cjs/src/search-results/components/group-tour/group-tour-results.d.ts +1 -1
  131. package/build/build-cjs/src/search-results/components/hotel/hotel-accommodation-results.d.ts +1 -1
  132. package/build/build-cjs/src/search-results/components/hotel/hotel-card.d.ts +2 -2
  133. package/build/build-cjs/src/search-results/components/icon.d.ts +6 -6
  134. package/build/build-cjs/src/search-results/components/item-picker/index.d.ts +8 -8
  135. package/build/build-cjs/src/search-results/components/itinerary/index.d.ts +3 -3
  136. package/build/build-cjs/src/search-results/components/multi-range-filter.d.ts +6 -6
  137. package/build/build-cjs/src/search-results/components/round-trip/round-trip-results.d.ts +1 -2
  138. package/build/build-cjs/src/search-results/components/search-results-container/flight-search-results.d.ts +1 -1
  139. package/build/build-cjs/src/search-results/components/tab-views/index.d.ts +1 -2
  140. package/build/build-cjs/src/search-results/features/flights/flight-search-results-self-contained.d.ts +1 -2
  141. package/build/build-cjs/src/search-results/features/hotels/hotel-flight-search-results-self-contained.d.ts +1 -2
  142. package/build/build-cjs/src/search-results/features/hotels/hotel-search-results-self-contained.d.ts +1 -2
  143. package/build/build-cjs/src/search-results/features/roundtrips/roundtrip-search-results-self-contained.d.ts +1 -2
  144. package/build/build-cjs/src/search-results/index.d.ts +1 -1
  145. package/build/build-cjs/src/search-results/store/search-results-slice.d.ts +63 -26
  146. package/build/build-cjs/src/search-results/store/search-results-store.d.ts +20 -7
  147. package/build/build-cjs/src/search-results/types.d.ts +104 -104
  148. package/build/build-cjs/src/search-results/utils/flight-utils.d.ts +6 -1
  149. package/build/build-cjs/src/search-results/utils/search-results-utils.d.ts +10 -2
  150. package/build/build-cjs/src/shared/components/flyin/accommodation-flyin.d.ts +3 -3
  151. package/build/build-cjs/src/shared/components/flyin/flights-flyin.d.ts +2 -2
  152. package/build/build-cjs/src/shared/components/flyin/flyin.d.ts +7 -7
  153. package/build/build-cjs/src/shared/components/flyin/group-tour-flyin.d.ts +3 -3
  154. package/build/build-cjs/src/shared/components/icon.d.ts +5 -5
  155. package/build/build-cjs/src/shared/components/loader.d.ts +1 -1
  156. package/build/build-cjs/src/shared/types.d.ts +9 -9
  157. package/build/build-cjs/src/shared/utils/localization-util.d.ts +395 -396
  158. package/build/build-esm/index.js +48531 -29650
  159. package/build/build-esm/src/booking-product/components/age-select.d.ts +3 -3
  160. package/build/build-esm/src/booking-product/components/amount-input.d.ts +5 -5
  161. package/build/build-esm/src/booking-product/components/date-range-picker/calendar-day.d.ts +8 -8
  162. package/build/build-esm/src/booking-product/components/date-range-picker/calendar.d.ts +14 -14
  163. package/build/build-esm/src/booking-product/components/date-range-picker/index.d.ts +16 -19
  164. package/build/build-esm/src/booking-product/components/dates.d.ts +8 -8
  165. package/build/build-esm/src/booking-product/components/footer.d.ts +5 -5
  166. package/build/build-esm/src/booking-product/components/header.d.ts +6 -6
  167. package/build/build-esm/src/booking-product/components/icon.d.ts +5 -5
  168. package/build/build-esm/src/booking-product/components/list-view.d.ts +2 -2
  169. package/build/build-esm/src/booking-product/components/product.d.ts +4 -4
  170. package/build/build-esm/src/booking-product/components/rating.d.ts +1 -1
  171. package/build/build-esm/src/booking-product/components/rooms.d.ts +4 -4
  172. package/build/build-esm/src/booking-product/constants.d.ts +1 -1
  173. package/build/build-esm/src/booking-product/index.d.ts +4 -4
  174. package/build/build-esm/src/booking-product/settings-context.d.ts +1 -2
  175. package/build/build-esm/src/booking-product/types.d.ts +21 -21
  176. package/build/build-esm/src/booking-product/utils/api.d.ts +11 -2
  177. package/build/build-esm/src/booking-product/utils/price.d.ts +10 -1
  178. package/build/build-esm/src/booking-wizard/api-settings-slice.d.ts +3 -2
  179. package/build/build-esm/src/booking-wizard/components/icon.d.ts +5 -5
  180. package/build/build-esm/src/booking-wizard/components/labeled-input.d.ts +13 -13
  181. package/build/build-esm/src/booking-wizard/components/labeled-select.d.ts +16 -16
  182. package/build/build-esm/src/booking-wizard/components/message.d.ts +4 -4
  183. package/build/build-esm/src/booking-wizard/components/multi-range-filter.d.ts +6 -6
  184. package/build/build-esm/src/booking-wizard/components/phone-input.d.ts +11 -11
  185. package/build/build-esm/src/booking-wizard/components/print-offer-button.d.ts +11 -11
  186. package/build/build-esm/src/booking-wizard/components/product-card.d.ts +3 -3
  187. package/build/build-esm/src/booking-wizard/components/step-indicator.d.ts +1 -1
  188. package/build/build-esm/src/booking-wizard/components/step-route.d.ts +3 -3
  189. package/build/build-esm/src/booking-wizard/features/booking/api.d.ts +26 -7
  190. package/build/build-esm/src/booking-wizard/features/booking/booking-self-contained.d.ts +3 -3
  191. package/build/build-esm/src/booking-wizard/features/booking/booking-slice.d.ts +102 -43
  192. package/build/build-esm/src/booking-wizard/features/booking/booking.d.ts +3 -3
  193. package/build/build-esm/src/booking-wizard/features/booking/constants.d.ts +8 -1
  194. package/build/build-esm/src/booking-wizard/features/booking/selectors.d.ts +644 -479
  195. package/build/build-esm/src/booking-wizard/features/confirmation/confirmation.d.ts +1 -2
  196. package/build/build-esm/src/booking-wizard/features/error/error.d.ts +1 -2
  197. package/build/build-esm/src/booking-wizard/features/flight-options/flight-filter.d.ts +3 -3
  198. package/build/build-esm/src/booking-wizard/features/flight-options/flight-option-flight.d.ts +2 -2
  199. package/build/build-esm/src/booking-wizard/features/flight-options/flight-option.d.ts +4 -4
  200. package/build/build-esm/src/booking-wizard/features/flight-options/flight-utils.d.ts +9 -2
  201. package/build/build-esm/src/booking-wizard/features/flight-options/index.d.ts +1 -2
  202. package/build/build-esm/src/booking-wizard/features/price-details/price-details-api.d.ts +6 -1
  203. package/build/build-esm/src/booking-wizard/features/price-details/price-details-slice.d.ts +15 -10
  204. package/build/build-esm/src/booking-wizard/features/price-details/selectors.d.ts +302 -287
  205. package/build/build-esm/src/booking-wizard/features/product-options/none-option.d.ts +3 -3
  206. package/build/build-esm/src/booking-wizard/features/product-options/option-booking-airline-group.d.ts +2 -2
  207. package/build/build-esm/src/booking-wizard/features/product-options/option-booking-group.d.ts +6 -6
  208. package/build/build-esm/src/booking-wizard/features/product-options/option-item.d.ts +5 -5
  209. package/build/build-esm/src/booking-wizard/features/product-options/option-pax-card.d.ts +4 -4
  210. package/build/build-esm/src/booking-wizard/features/product-options/option-pax-group.d.ts +7 -7
  211. package/build/build-esm/src/booking-wizard/features/product-options/option-room.d.ts +5 -5
  212. package/build/build-esm/src/booking-wizard/features/product-options/option-unit-group.d.ts +7 -7
  213. package/build/build-esm/src/booking-wizard/features/product-options/option-units-card.d.ts +3 -3
  214. package/build/build-esm/src/booking-wizard/features/product-options/options-form.d.ts +1 -2
  215. package/build/build-esm/src/booking-wizard/features/room-options/index.d.ts +1 -2
  216. package/build/build-esm/src/booking-wizard/features/room-options/room-utils.d.ts +19 -6
  217. package/build/build-esm/src/booking-wizard/features/room-options/room.d.ts +6 -6
  218. package/build/build-esm/src/booking-wizard/features/room-options/traveler-rooms.d.ts +3 -3
  219. package/build/build-esm/src/booking-wizard/features/sidebar/index.d.ts +2 -2
  220. package/build/build-esm/src/booking-wizard/features/sidebar/sidebar-flight.d.ts +2 -2
  221. package/build/build-esm/src/booking-wizard/features/sidebar/sidebar-util.d.ts +4 -2
  222. package/build/build-esm/src/booking-wizard/features/sidebar/sidebar.d.ts +24 -24
  223. package/build/build-esm/src/booking-wizard/features/summary/summary-booking-option-pax.d.ts +1 -1
  224. package/build/build-esm/src/booking-wizard/features/summary/summary-booking-option-unit.d.ts +1 -1
  225. package/build/build-esm/src/booking-wizard/features/summary/summary-flight.d.ts +2 -2
  226. package/build/build-esm/src/booking-wizard/features/summary/summary-per-booking-option-group.d.ts +1 -1
  227. package/build/build-esm/src/booking-wizard/features/summary/summary-per-pax-option-group.d.ts +1 -1
  228. package/build/build-esm/src/booking-wizard/features/summary/summary-per-unit-option-group.d.ts +1 -1
  229. package/build/build-esm/src/booking-wizard/features/summary/summary-slice.d.ts +3 -3
  230. package/build/build-esm/src/booking-wizard/features/summary/summary.d.ts +1 -2
  231. package/build/build-esm/src/booking-wizard/features/travelers-form/travelers-form-slice.d.ts +75 -75
  232. package/build/build-esm/src/booking-wizard/features/travelers-form/travelers-form-util.d.ts +4 -4
  233. package/build/build-esm/src/booking-wizard/features/travelers-form/travelers-form.d.ts +1 -2
  234. package/build/build-esm/src/booking-wizard/features/travelers-form/type-ahead-input.d.ts +11 -11
  235. package/build/build-esm/src/booking-wizard/features/travelers-form/validate-form.d.ts +8 -1
  236. package/build/build-esm/src/booking-wizard/index.d.ts +6 -6
  237. package/build/build-esm/src/booking-wizard/settings-context.d.ts +1 -2
  238. package/build/build-esm/src/booking-wizard/store.d.ts +40 -22
  239. package/build/build-esm/src/booking-wizard/types.d.ts +240 -240
  240. package/build/build-esm/src/booking-wizard/use-offer-printer.d.ts +8 -8
  241. package/build/build-esm/src/content/components/LanguageSwitcher.d.ts +5 -5
  242. package/build/build-esm/src/content/components/accordion.d.ts +4 -4
  243. package/build/build-esm/src/content/components/breadcrumb.d.ts +7 -7
  244. package/build/build-esm/src/content/components/faq.d.ts +4 -4
  245. package/build/build-esm/src/content/components/gallery.d.ts +6 -6
  246. package/build/build-esm/src/content/components/icon.d.ts +5 -5
  247. package/build/build-esm/src/content/components/image-with-text.d.ts +18 -18
  248. package/build/build-esm/src/content/components/slider.d.ts +5 -5
  249. package/build/build-esm/src/content/featured-trips/types.d.ts +8 -8
  250. package/build/build-esm/src/content/features/content-page/content-page-self-contained.d.ts +1 -1
  251. package/build/build-esm/src/content/footer/types.d.ts +17 -17
  252. package/build/build-esm/src/content/header/types.d.ts +23 -20
  253. package/build/build-esm/src/content/image-card-grid/types.d.ts +8 -8
  254. package/build/build-esm/src/content/image-with-text-section/types.d.ts +15 -15
  255. package/build/build-esm/src/content/login/login-services.d.ts +6 -1
  256. package/build/build-esm/src/content/login/types.d.ts +19 -19
  257. package/build/build-esm/src/content/navbar/placeholderData.d.ts +2 -2
  258. package/build/build-esm/src/content/navbar/types.d.ts +22 -22
  259. package/build/build-esm/src/index.d.ts +17 -1
  260. package/build/build-esm/src/qsm/components/date-range-picker/calendar-day.d.ts +7 -7
  261. package/build/build-esm/src/qsm/components/date-range-picker/calendar.d.ts +18 -18
  262. package/build/build-esm/src/qsm/components/date-range-picker/index.d.ts +5 -5
  263. package/build/build-esm/src/qsm/components/double-search-input-group/index.d.ts +2 -2
  264. package/build/build-esm/src/qsm/components/icon.d.ts +5 -5
  265. package/build/build-esm/src/qsm/components/item-picker/index.d.ts +7 -7
  266. package/build/build-esm/src/qsm/components/search-input/index.d.ts +9 -9
  267. package/build/build-esm/src/qsm/components/search-input-group/index.d.ts +7 -7
  268. package/build/build-esm/src/qsm/index.d.ts +1 -1
  269. package/build/build-esm/src/qsm/store/qsm-slice.d.ts +110 -58
  270. package/build/build-esm/src/qsm/store/qsm-store.d.ts +20 -7
  271. package/build/build-esm/src/qsm/types.d.ts +59 -59
  272. package/build/build-esm/src/search-results/components/filters/filters.d.ts +5 -5
  273. package/build/build-esm/src/search-results/components/filters/flight-filters.d.ts +3 -3
  274. package/build/build-esm/src/search-results/components/flight/flight-banner.d.ts +2 -2
  275. package/build/build-esm/src/search-results/components/flight/flight-card.d.ts +1 -1
  276. package/build/build-esm/src/search-results/components/flight/flight-leg.d.ts +1 -1
  277. package/build/build-esm/src/search-results/components/flight/flight-path.d.ts +1 -1
  278. package/build/build-esm/src/search-results/components/flight/flight-results.d.ts +2 -2
  279. package/build/build-esm/src/search-results/components/flight/flight-search-context/index.d.ts +29 -29
  280. package/build/build-esm/src/search-results/components/flight/flight-selection/independent-flight-option.d.ts +5 -5
  281. package/build/build-esm/src/search-results/components/flight/flight-selection/independent-flight-selection.d.ts +1 -1
  282. package/build/build-esm/src/search-results/components/flight/flight-selection/index.d.ts +2 -2
  283. package/build/build-esm/src/search-results/components/flight/flight-selection/paired-flight-option.d.ts +1 -1
  284. package/build/build-esm/src/search-results/components/flight/flight-selection/paired-flight-selection.d.ts +1 -1
  285. package/build/build-esm/src/search-results/components/group-tour/group-tour-card.d.ts +3 -3
  286. package/build/build-esm/src/search-results/components/group-tour/group-tour-results.d.ts +1 -1
  287. package/build/build-esm/src/search-results/components/hotel/hotel-accommodation-results.d.ts +1 -1
  288. package/build/build-esm/src/search-results/components/hotel/hotel-card.d.ts +2 -2
  289. package/build/build-esm/src/search-results/components/icon.d.ts +6 -6
  290. package/build/build-esm/src/search-results/components/item-picker/index.d.ts +8 -8
  291. package/build/build-esm/src/search-results/components/itinerary/index.d.ts +3 -3
  292. package/build/build-esm/src/search-results/components/multi-range-filter.d.ts +6 -6
  293. package/build/build-esm/src/search-results/components/round-trip/round-trip-results.d.ts +1 -2
  294. package/build/build-esm/src/search-results/components/search-results-container/flight-search-results.d.ts +1 -1
  295. package/build/build-esm/src/search-results/components/tab-views/index.d.ts +1 -2
  296. package/build/build-esm/src/search-results/features/flights/flight-search-results-self-contained.d.ts +1 -2
  297. package/build/build-esm/src/search-results/features/hotels/hotel-flight-search-results-self-contained.d.ts +1 -2
  298. package/build/build-esm/src/search-results/features/hotels/hotel-search-results-self-contained.d.ts +1 -2
  299. package/build/build-esm/src/search-results/features/roundtrips/roundtrip-search-results-self-contained.d.ts +1 -2
  300. package/build/build-esm/src/search-results/index.d.ts +1 -1
  301. package/build/build-esm/src/search-results/store/search-results-slice.d.ts +63 -26
  302. package/build/build-esm/src/search-results/store/search-results-store.d.ts +20 -7
  303. package/build/build-esm/src/search-results/types.d.ts +104 -104
  304. package/build/build-esm/src/search-results/utils/flight-utils.d.ts +6 -1
  305. package/build/build-esm/src/search-results/utils/search-results-utils.d.ts +10 -2
  306. package/build/build-esm/src/shared/components/flyin/accommodation-flyin.d.ts +3 -3
  307. package/build/build-esm/src/shared/components/flyin/flights-flyin.d.ts +2 -2
  308. package/build/build-esm/src/shared/components/flyin/flyin.d.ts +7 -7
  309. package/build/build-esm/src/shared/components/flyin/group-tour-flyin.d.ts +3 -3
  310. package/build/build-esm/src/shared/components/icon.d.ts +5 -5
  311. package/build/build-esm/src/shared/components/loader.d.ts +1 -1
  312. package/build/build-esm/src/shared/types.d.ts +9 -9
  313. package/build/build-esm/src/shared/utils/localization-util.d.ts +395 -396
  314. package/package.json +83 -83
  315. package/rollup.config.js +16 -16
  316. package/src/booking-product/components/date-range-picker/calendar-day.tsx +46 -46
  317. package/src/booking-product/components/date-range-picker/index.tsx +185 -185
  318. package/src/booking-product/components/dates.tsx +153 -153
  319. package/src/booking-product/components/product.tsx +379 -379
  320. package/src/booking-wizard/components/step-indicator.tsx +57 -57
  321. package/src/booking-wizard/components/step-route.tsx +26 -26
  322. package/src/booking-wizard/features/booking/api.ts +44 -44
  323. package/src/booking-wizard/features/booking/booking-self-contained.tsx +318 -318
  324. package/src/booking-wizard/features/booking/booking-slice.ts +633 -633
  325. package/src/booking-wizard/features/booking/booking.tsx +342 -342
  326. package/src/booking-wizard/features/booking/constants.ts +16 -16
  327. package/src/booking-wizard/features/booking/selectors.ts +411 -411
  328. package/src/booking-wizard/features/confirmation/confirmation.tsx +90 -90
  329. package/src/booking-wizard/features/error/error.tsx +71 -71
  330. package/src/booking-wizard/features/flight-options/index.tsx +166 -166
  331. package/src/booking-wizard/features/price-details/price-details-slice.ts +79 -79
  332. package/src/booking-wizard/features/price-details/selectors.ts +118 -118
  333. package/src/booking-wizard/features/product-options/option-units-card.tsx +148 -148
  334. package/src/booking-wizard/features/product-options/options-form.tsx +382 -382
  335. package/src/booking-wizard/features/room-options/index.tsx +132 -132
  336. package/src/booking-wizard/features/sidebar/index.tsx +83 -83
  337. package/src/booking-wizard/features/sidebar/sidebar-util.ts +147 -147
  338. package/src/booking-wizard/features/sidebar/sidebar.tsx +344 -332
  339. package/src/booking-wizard/features/summary/summary.tsx +562 -562
  340. package/src/booking-wizard/features/travelers-form/controls/gender-control.tsx +60 -60
  341. package/src/booking-wizard/features/travelers-form/travelers-form.tsx +1101 -1101
  342. package/src/booking-wizard/settings-context.ts +64 -64
  343. package/src/booking-wizard/types.ts +332 -332
  344. package/src/content/components/breadcrumb.tsx +67 -67
  345. package/src/content/components/faq.tsx +42 -42
  346. package/src/content/components/gallery.tsx +153 -153
  347. package/src/content/components/icon.tsx +695 -695
  348. package/src/content/components/image-with-text.tsx +120 -120
  349. package/src/content/components/login.tsx +162 -162
  350. package/src/content/components/slider.tsx +237 -237
  351. package/src/content/featured-trips/featured-trip-card.tsx +48 -48
  352. package/src/content/featured-trips/index.tsx +19 -19
  353. package/src/content/featured-trips/types.ts +13 -13
  354. package/src/content/features/content-page/content-page-self-contained.tsx +895 -895
  355. package/src/content/footer/index.tsx +159 -159
  356. package/src/content/footer/types.ts +36 -36
  357. package/src/content/image-card-grid/index.tsx +34 -34
  358. package/src/content/image-card-grid/types.ts +13 -13
  359. package/src/content/image-with-text-section/card.tsx +58 -58
  360. package/src/content/image-with-text-section/index.tsx +22 -22
  361. package/src/content/image-with-text-section/types.ts +20 -20
  362. package/src/content/login/confirm-component.tsx +149 -149
  363. package/src/content/login/index.tsx +70 -70
  364. package/src/content/login/login-component.tsx +159 -159
  365. package/src/content/login/login-services.ts +109 -109
  366. package/src/content/login/reset-password-component.tsx +191 -191
  367. package/src/content/login/types.ts +29 -29
  368. package/src/content/navbar/index.tsx +354 -354
  369. package/src/content/navbar/placeholderData.tsx +173 -173
  370. package/src/content/navbar/types.ts +43 -43
  371. package/src/index.ts +44 -44
  372. package/src/qsm/components/QSMContainer/qsm-container.tsx +512 -512
  373. package/src/qsm/components/double-search-input-group/index.tsx +78 -78
  374. package/src/qsm/components/icon.tsx +354 -354
  375. package/src/qsm/components/item-picker/index.tsx +69 -69
  376. package/src/qsm/components/mobile-filter-modal/index.tsx +307 -307
  377. package/src/qsm/components/search-input/index.tsx +91 -91
  378. package/src/qsm/components/search-input-group/index.tsx +199 -199
  379. package/src/qsm/components/travel-class-picker/index.tsx +28 -28
  380. package/src/qsm/components/travel-input/index.tsx +243 -243
  381. package/src/qsm/components/travel-input-group/index.tsx +114 -114
  382. package/src/qsm/components/travel-nationality-picker/index.tsx +28 -28
  383. package/src/qsm/components/travel-type-picker/index.tsx +28 -28
  384. package/src/qsm/qsm-configuration-context.ts +31 -31
  385. package/src/qsm/store/qsm-slice.ts +275 -275
  386. package/src/qsm/types.ts +110 -110
  387. package/src/search-results/components/filters/filters.tsx +230 -230
  388. package/src/search-results/components/filters/flight-filters.tsx +671 -671
  389. package/src/search-results/components/flight/flight-banner.tsx +35 -35
  390. package/src/search-results/components/flight/flight-results.tsx +208 -208
  391. package/src/search-results/components/flight/flight-search-context/index.tsx +628 -628
  392. package/src/search-results/components/flight/flight-selection/independent-flight-option.tsx +147 -147
  393. package/src/search-results/components/flight/flight-selection/independent-flight-selection.tsx +172 -172
  394. package/src/search-results/components/flight/flight-selection/index.tsx +19 -19
  395. package/src/search-results/components/flight/flight-selection/paired-flight-option.tsx +255 -255
  396. package/src/search-results/components/flight/flight-selection/paired-flight-selection.tsx +38 -38
  397. package/src/search-results/components/group-tour/group-tour-card.tsx +105 -105
  398. package/src/search-results/components/group-tour/group-tour-results.tsx +62 -62
  399. package/src/search-results/components/hotel/hotel-accommodation-results.tsx +176 -176
  400. package/src/search-results/components/hotel/hotel-card.tsx +113 -113
  401. package/src/search-results/components/icon.tsx +680 -680
  402. package/src/search-results/components/item-picker/index.tsx +81 -81
  403. package/src/search-results/components/itinerary/index.tsx +310 -310
  404. package/src/search-results/components/round-trip/round-trip-results.tsx +199 -199
  405. package/src/search-results/components/search-results-container/flight-search-results.tsx +137 -137
  406. package/src/search-results/components/search-results-container/search-results-container.tsx +893 -893
  407. package/src/search-results/components/spinner/spinner.tsx +16 -16
  408. package/src/search-results/components/tab-views/index.tsx +53 -53
  409. package/src/search-results/features/flights/flight-search-results-self-contained.tsx +294 -294
  410. package/src/search-results/features/hotels/hotel-flight-search-results-self-contained.tsx +143 -143
  411. package/src/search-results/features/hotels/hotel-search-results-self-contained.tsx +220 -220
  412. package/src/search-results/features/roundtrips/roundtrip-search-results-self-contained.tsx +65 -65
  413. package/src/search-results/store/search-results-slice.ts +158 -158
  414. package/src/search-results/types.ts +181 -181
  415. package/src/search-results/utils/flight-utils.ts +93 -93
  416. package/src/search-results/utils/search-results-utils.ts +251 -251
  417. package/src/shared/components/flyin/accommodation-flyin.tsx +422 -422
  418. package/src/shared/components/flyin/flights-flyin.tsx +503 -503
  419. package/src/shared/components/flyin/flyin.tsx +82 -82
  420. package/src/shared/components/flyin/group-tour-flyin.tsx +293 -293
  421. package/src/shared/components/icon.tsx +826 -826
  422. package/src/shared/translations/ar-SA.json +382 -382
  423. package/src/shared/translations/da-DK.json +382 -382
  424. package/src/shared/translations/de-DE.json +382 -382
  425. package/src/shared/translations/en-GB.json +386 -386
  426. package/src/shared/translations/es-ES.json +382 -382
  427. package/src/shared/translations/fr-BE.json +386 -386
  428. package/src/shared/translations/fr-FR.json +382 -382
  429. package/src/shared/translations/is-IS.json +382 -382
  430. package/src/shared/translations/it-IT.json +382 -382
  431. package/src/shared/translations/ja-JP.json +382 -382
  432. package/src/shared/translations/nl-BE.json +386 -386
  433. package/src/shared/translations/nl-NL.json +382 -382
  434. package/src/shared/translations/no-NO.json +382 -382
  435. package/src/shared/translations/pl-PL.json +382 -382
  436. package/src/shared/translations/pt-PT.json +382 -382
  437. package/src/shared/translations/sv-SE.json +382 -382
  438. package/src/shared/types.ts +31 -31
  439. package/src/shared/utils/localization-util.ts +275 -275
  440. package/styles/booking-search-results-variables.scss +728 -728
  441. package/styles/booking-search-results.scss +53 -53
  442. package/styles/components/_booking.scss +872 -872
  443. package/styles/components/_content.scss +336 -336
  444. package/styles/components/_flyin.scss +727 -727
  445. package/styles/components/_footer.scss +141 -141
  446. package/styles/components/_form.scss +1634 -1634
  447. package/styles/components/_image-with-text.scss +206 -206
  448. package/styles/components/_login.scss +140 -140
  449. package/styles/components/_navbar.scss +765 -765
  450. package/styles/components/_pricing-summary.scss +163 -163
  451. package/styles/components/_search.scss +2009 -2009
  452. package/styles/components/_tree.scss +648 -648
  453. package/styles/content-blocks.scss +64 -64
@@ -1,671 +1,671 @@
1
- import React, { useContext, useEffect, useState } from 'react';
2
- import { ExtendedFlightSearchResponseItem, FilterValue } from '../../types';
3
- import MultiRangeFilter from '../multi-range-filter';
4
- import SearchResultsConfigurationContext from '../../search-results-configuration-context';
5
- import Icon from '../icon';
6
- import { durationInTicksInMinutes, getTranslations, minutesToHoursString, rangeFromDateTimeInMinutes } from '../../../shared/utils/localization-util';
7
- import { useFlightSearch } from '../flight/flight-search-context';
8
- import { first, isEmpty, last, orderBy } from 'lodash';
9
- import { getArrivalSegment, getDepartureRangeName, getDepartureSegment, getNumberOfStops } from '../../utils/flight-utils';
10
- import { DepartureRange } from '../../../shared/types';
11
-
12
- interface FiltersProps {
13
- isOpen: boolean;
14
- handleSetIsOpen: () => void;
15
- isLoading?: boolean;
16
- }
17
-
18
- const FlightFilters: React.FC<FiltersProps> = ({ isOpen, handleSetIsOpen, isLoading }) => {
19
- const context = useContext(SearchResultsConfigurationContext);
20
- const translations = getTranslations(context?.languageCode ?? 'en-GB');
21
- const {
22
- flightSearchResults,
23
- filteredResults,
24
- searchRequestId,
25
- filters,
26
- setFilters,
27
- resetFilters,
28
- airlineResults,
29
- numberOfStopsResults,
30
- departureRangeResults,
31
- departureAirportsResults,
32
- arrivalAirportsResults,
33
- valuesManuallyChanged,
34
- setValuesManuallyChanged
35
- } = useFlightSearch();
36
-
37
- const [airlineFilterExpanded, setAirlineFilterExpanded] = useState<boolean>(true);
38
- const [airlineFilter, setAirlineFilter] = useState<FilterValue[]>([]);
39
-
40
- const [stopFilterExpanded, setStopFilterExpanded] = useState<boolean>(true);
41
- const [numberOfStopsFilter, setNumberOfStopsFilter] = useState<FilterValue[]>([]);
42
-
43
- const [departureFilterExpanded, setDepartureFilterExpanded] = useState<boolean>(true);
44
- const [departureRangeFilter, setDepartureRangeFilter] = useState<FilterValue[]>([]);
45
-
46
- const [departureAirportFilterExpanded, setDepartureAirportFilterExpanded] = useState<boolean>(true);
47
- const [departureAirportFilter, setDepartureAirportFilter] = useState<FilterValue[]>([]);
48
-
49
- const [arrivalAirportFilterExpanded, setArrivalAirportFilterExpanded] = useState<boolean>(true);
50
- const [arrivalAirportFilter, setArrivalAirportFilter] = useState<FilterValue[]>([]);
51
-
52
- const [durationFilterExpanded, setDurationFilterExpanded] = useState<boolean>(true);
53
- const [minTravelTime, setMinTravelTime] = useState<number>(0);
54
- const [maxTravelTime, setMaxTravelTime] = useState<number>(0);
55
- const [travelTimeRangeValues, setTravelTimeRangeValues] = useState<number[]>([]);
56
-
57
- const [priceFilterExpanded, setPriceFilterExpanded] = useState<boolean>(true);
58
- const [minPrice, setMinPrice] = useState<number>(0);
59
- const [maxPrice, setMaxPrice] = useState<number>(0);
60
- const [priceRangeValues, setPriceRangeValues] = useState<number[]>([]);
61
-
62
- useEffect(() => {
63
- const results = filteredResults.filter((item) => item.requestId === searchRequestId);
64
- if (!isEmpty(results)) {
65
- // airlines
66
- const airlinesMap = new Map<string, ExtendedFlightSearchResponseItem>();
67
- if (!isEmpty(airlineResults)) {
68
- airlineResults.map((result) => {
69
- const airline = result.airlineCode;
70
-
71
- if (!airlinesMap.has(airline) || result.price < airlinesMap.get(airline)!.price) {
72
- airlinesMap.set(airline, result);
73
- }
74
- });
75
-
76
- // check if filters.airlines has values, if so, make sure all selected filters are present in the airlinesMap
77
- const validAirlines = filters.airlines.filter((filter) => airlinesMap.has(filter.id.toString()));
78
- if (validAirlines.length !== filters.airlines.length) {
79
- setFilters({
80
- ...filters,
81
- airlines: validAirlines
82
- });
83
- }
84
- }
85
- // Convert the Map to an array of FilterValue
86
- const uniqueAirlines = Array.from(airlinesMap.entries()).map(
87
- ([airlineCode, flightItem]) =>
88
- ({
89
- id: airlineCode,
90
- name: flightItem.airlineName, // Use the airline name directly
91
- lowestPrice: flightItem.price
92
- } as FilterValue)
93
- );
94
- setAirlineFilter(orderBy(uniqueAirlines, ['name'], ['asc']));
95
-
96
- // number of stops
97
- // Group by numberOfStops and find the lowest price
98
- const stopsMap = new Map<number, number>();
99
- if (!isEmpty(numberOfStopsResults)) {
100
- numberOfStopsResults.map((result) => {
101
- let numberOfStops = getNumberOfStops(result.outward);
102
- if (numberOfStops > 2) {
103
- numberOfStops = 2;
104
- }
105
-
106
- if (!stopsMap.has(numberOfStops) || result.price < stopsMap.get(numberOfStops)!) {
107
- stopsMap.set(numberOfStops, result.price);
108
- }
109
- });
110
- // check if filters.numberOfStops has values, if so, make sure all selected filters are present in the stopsMap
111
- const validNumberOfStops = filters.numberOfStops.filter((filter) => stopsMap.has(Number(filter.id)));
112
- if (validNumberOfStops.length !== filters.numberOfStops.length) {
113
- setFilters({
114
- ...filters,
115
- numberOfStops: validNumberOfStops
116
- });
117
- }
118
- }
119
- // Convert the Map to an array of FilterValue
120
- const uniqueStops = Array.from(stopsMap.entries()).map(
121
- ([id, lowestPrice]) =>
122
- ({
123
- id: id,
124
- name: id.toString(), // Format the name
125
- lowestPrice: lowestPrice
126
- } as FilterValue)
127
- );
128
- setNumberOfStopsFilter(orderBy(uniqueStops, ['id'], ['asc']));
129
-
130
- // departureRange
131
- const departureRangeMap = new Map<DepartureRange, number>();
132
- if (!isEmpty(departureRangeResults)) {
133
- departureRangeResults.map((result) => {
134
- const departureRange = rangeFromDateTimeInMinutes(getDepartureSegment(result.outward)?.departureDateTime);
135
- if (!departureRangeMap.has(departureRange) || result.price < departureRangeMap.get(departureRange)!) {
136
- departureRangeMap.set(departureRange, result.price);
137
- }
138
- });
139
- // check if filters.departureRanges has values, if so, make sure all selected filters are present in the departureRangeMap
140
- const validDepartureRanges = filters.departureRanges.filter((filter) => departureRangeMap.has(Number(filter.id)));
141
- if (validDepartureRanges.length !== filters.departureRanges.length) {
142
- setFilters({
143
- ...filters,
144
- departureRanges: validDepartureRanges
145
- });
146
- }
147
- }
148
- // Convert the Map to an array of FilterValue
149
- const uniqueDepartureRanges = Array.from(departureRangeMap.entries()).map(
150
- ([range, lowestPrice]) =>
151
- ({
152
- id: range,
153
- name: getDepartureRangeName(translations, range),
154
- lowestPrice: lowestPrice
155
- } as FilterValue)
156
- );
157
- setDepartureRangeFilter(orderBy(uniqueDepartureRanges, ['id'], ['asc']));
158
-
159
- // departureAirports
160
- const departureAirportsMap = new Map<string, ExtendedFlightSearchResponseItem>();
161
- if (!isEmpty(departureAirportsResults)) {
162
- departureAirportsResults.map((result) => {
163
- const departureAirport = getDepartureSegment(result.outward)?.departureAirportCode;
164
-
165
- if (departureAirport) {
166
- if (!departureAirportsMap.has(departureAirport) || result.price < departureAirportsMap.get(departureAirport)!.price) {
167
- departureAirportsMap.set(departureAirport, result);
168
- }
169
- }
170
- });
171
-
172
- // check if filters.departureAirports has values, if so, make sure all selected filters are present in the departureAirportsMap
173
- const validDepartureAirports = filters.departureAirports.filter((filter) => departureAirportsMap.has(filter.id.toString()));
174
- if (validDepartureAirports.length !== filters.departureAirports.length) {
175
- setFilters({
176
- ...filters,
177
- departureAirports: validDepartureAirports
178
- });
179
- }
180
- }
181
- // Convert the Map to an array of FilterValue
182
- const uniqueDepartureAirports = Array.from(departureAirportsMap.entries()).map(
183
- ([departureAirport, flightItem]) =>
184
- ({
185
- id: departureAirport,
186
- name: getDepartureSegment(flightItem.outward)?.departureAirportName + ' (' + departureAirport + ')', // Use the departure airport name directly
187
- lowestPrice: flightItem.price
188
- } as FilterValue)
189
- );
190
- setDepartureAirportFilter(orderBy(uniqueDepartureAirports, ['name'], ['asc']));
191
-
192
- // arrivalAirports
193
- const arrivalAirportsMap = new Map<string, ExtendedFlightSearchResponseItem>();
194
- if (!isEmpty(arrivalAirportsResults)) {
195
- arrivalAirportsResults.map((result) => {
196
- const arrivalAirport = getArrivalSegment(result.outward)?.arrivalAirportCode;
197
-
198
- if (arrivalAirport) {
199
- if (!arrivalAirportsMap.has(arrivalAirport) || result.price < arrivalAirportsMap.get(arrivalAirport)!.price) {
200
- arrivalAirportsMap.set(arrivalAirport, result);
201
- }
202
- }
203
- });
204
-
205
- // check if filters.arrivalAirports has values, if so, make sure all selected filters are present in the arrivalAirportsMap
206
- const validArrivalAirports = filters.arrivalAirports.filter((filter) => arrivalAirportsMap.has(filter.id.toString()));
207
- if (validArrivalAirports.length !== filters.arrivalAirports.length) {
208
- setFilters({
209
- ...filters,
210
- arrivalAirports: validArrivalAirports
211
- });
212
- }
213
- }
214
- // Convert the Map to an array of FilterValue
215
- const uniqueArrivalAirports = Array.from(arrivalAirportsMap.entries()).map(
216
- ([arrivalAirport, flightItem]) =>
217
- ({
218
- id: arrivalAirport,
219
- name: getArrivalSegment(flightItem.outward)?.arrivalAirportName + ' (' + arrivalAirport + ')', // Use the arrival airport name directly
220
- lowestPrice: flightItem.price
221
- } as FilterValue)
222
- );
223
- setArrivalAirportFilter(orderBy(uniqueArrivalAirports, ['name'], ['asc']));
224
- } else {
225
- setNumberOfStopsFilter([]);
226
- setDepartureRangeFilter([]);
227
- setAirlineFilter([]);
228
- setDepartureAirportFilter([]);
229
- }
230
- }, [filteredResults, searchRequestId]);
231
-
232
- useEffect(() => {
233
- const results = flightSearchResults.filter((item) => item.requestId === searchRequestId);
234
- if (!isEmpty(results)) {
235
- // Travel time filter
236
- const minTravelTimeDuration = Math.min(...results.map((result) => result.outward.durationInTicks));
237
- const maxTravelTimeDuration = Math.max(...results.map((result) => result.outward.durationInTicks));
238
- const minTravelTimeValue = durationInTicksInMinutes(minTravelTimeDuration);
239
- const maxTravelTimeValue = durationInTicksInMinutes(maxTravelTimeDuration);
240
- setMinTravelTime(minTravelTimeValue);
241
- setMaxTravelTime(maxTravelTimeValue);
242
- if (!valuesManuallyChanged) {
243
- setTravelTimeRangeValues([minTravelTimeValue, maxTravelTimeValue]);
244
- }
245
- // price filter
246
- const minPriceValue = Math.min(...results.map((result) => result.price));
247
- const maxPriceValue = Math.max(...results.map((result) => result.price));
248
- setMinPrice(minPriceValue);
249
- setMaxPrice(maxPriceValue);
250
- if (!valuesManuallyChanged) {
251
- setPriceRangeValues([minPriceValue, maxPriceValue]);
252
- }
253
- }
254
- }, [flightSearchResults]);
255
-
256
- const adjustFilters = (type: string, value: FilterValue, checked: boolean) => {
257
- switch (type) {
258
- case 'Airlines':
259
- setFilters({
260
- ...filters,
261
- airlines: checked ? [...filters.airlines, value] : filters.airlines.filter((n) => n.id !== value.id)
262
- });
263
- return;
264
- case 'NumberOfStops':
265
- setFilters({
266
- ...filters,
267
- numberOfStops: checked ? [...filters.numberOfStops, value] : filters.numberOfStops.filter((n) => n.id !== value.id)
268
- });
269
- return;
270
- case 'DepartureRange':
271
- setFilters({
272
- ...filters,
273
- departureRanges: checked ? [...filters.departureRanges, value] : filters.departureRanges.filter((n) => n.id !== value.id)
274
- });
275
- return;
276
- case 'DepartureAirports':
277
- setFilters({
278
- ...filters,
279
- departureAirports: checked ? [...filters.departureAirports, value] : filters.departureAirports.filter((n) => n.id !== value.id)
280
- });
281
- return;
282
- case 'ArrivalAirports':
283
- setFilters({
284
- ...filters,
285
- arrivalAirports: checked ? [...filters.arrivalAirports, value] : filters.arrivalAirports.filter((n) => n.id !== value.id)
286
- });
287
- return;
288
- }
289
- };
290
-
291
- const changeCheckbox = (type: string, value: FilterValue, event: React.ChangeEvent<HTMLInputElement>) => {
292
- adjustFilters(type, value, event.target.checked);
293
- };
294
-
295
- const handleTravelTimeSliderChange = (values: number[] | number) => {
296
- setTravelTimeRangeValues(Array.isArray(values) ? values : [values, values]);
297
- setValuesManuallyChanged(true);
298
-
299
- onCompleteTravelTimeSliderChange(values);
300
- };
301
-
302
- const onCompleteTravelTimeSliderChange = (values: number[] | number) => {
303
- const ranges = Array.isArray(values) ? values : [values, values];
304
- const rangeFilters = ranges.map((value, index) => {
305
- return {
306
- id: index,
307
- name: 'travelTime',
308
- lowestPrice: value
309
- } as FilterValue;
310
- });
311
- setFilters({
312
- ...filters,
313
- travelTimes: rangeFilters
314
- });
315
- };
316
-
317
- const handlePriceSliderChange = (values: number[] | number) => {
318
- setPriceRangeValues(Array.isArray(values) ? values : [values, values]);
319
- setValuesManuallyChanged(true);
320
-
321
- onCompletePriceSliderChange(values);
322
- };
323
-
324
- const onCompletePriceSliderChange = (values: number[] | number) => {
325
- const ranges = Array.isArray(values) ? values : [values, values];
326
- const rangeFilters = ranges.map((value, index) => {
327
- return {
328
- id: index,
329
- name: 'price',
330
- lowestPrice: value
331
- } as FilterValue;
332
- });
333
- setFilters({
334
- ...filters,
335
- prices: rangeFilters
336
- });
337
- };
338
-
339
- const handleResetFilters = () => {
340
- resetFilters();
341
- setValuesManuallyChanged(false);
342
- setTravelTimeRangeValues([minTravelTime, maxTravelTime]);
343
- setPriceRangeValues([minPrice, maxPrice]);
344
- };
345
-
346
- return (
347
- <div className={`search__filters--modal ${isOpen ? 'is-open' : ''}`}>
348
- <div className="search__filters--background" onClick={() => handleSetIsOpen()}></div>
349
- <button className="search__filters--close" onClick={() => handleSetIsOpen()}>
350
- <Icon name="ui-close" height={24} />
351
- </button>
352
- <div className="search__filters">
353
- <div className="search__filter-row search__filter__header">
354
- <div className="search__filter-row-flex-title">
355
- <p className="search__filter-small-title">{translations.SRP.FILTERS}</p>
356
- </div>
357
- {!isLoading && (
358
- <a className="search__filter-reset" onClick={() => handleResetFilters()}>
359
- {translations.SRP.RESET}
360
- </a>
361
- )}
362
- </div>
363
- <>
364
- <div className="search__filters__group-container">
365
- {/* Airlines */}
366
- <div className="search__filter-group">
367
- <div
368
- className="search__filter-row search__filter-row--underline"
369
- onClick={() => setAirlineFilterExpanded(!airlineFilterExpanded)}
370
- role="button"
371
- tabIndex={0}>
372
- <h6 className="search__filter-large-title">{translations.FLIGHTS_FORM.AIRLINES}</h6>
373
- <svg
374
- id="search-chevron-up-icon"
375
- className={`search__filter-chevron-icon ${airlineFilterExpanded ? 'search__filter-chevron-icon--flipped' : ''} `}
376
- viewBox="0 0 10 6.063"
377
- width={10}
378
- height={6.063}>
379
- <path
380
- id="Path_62"
381
- data-name="Path 62"
382
- d="M245-617.937l-5-5L241.063-624,245-620.062,248.938-624,250-622.937Z"
383
- transform="translate(-240 624)"
384
- fill="#707070"
385
- />
386
- </svg>
387
- </div>
388
- {airlineFilterExpanded && (
389
- <div className="search__filter-rows">
390
- {airlineFilter.map((airline, airlineIndex) => (
391
- <div className="search__filter-row search__filter-row--checkbox" key={`${airline.name}-${airlineIndex}`}>
392
- <div className="checkbox">
393
- <label className="checkbox__label">
394
- <input
395
- type="checkbox"
396
- className="checkbox__input checkbox__input--parent"
397
- checked={filters.airlines?.map((a) => a.id).includes(airline.id)}
398
- onChange={(event) => changeCheckbox('Airlines', airline, event)}
399
- />
400
- <span className="radiobutton__label-text">{airline.name}</span>
401
- </label>
402
- </div>
403
- </div>
404
- ))}
405
- </div>
406
- )}
407
- </div>
408
- {/* NumberOfStops */}
409
- <div className="search__filter-group">
410
- <div
411
- className="search__filter-row search__filter-row--underline"
412
- onClick={() => setStopFilterExpanded(!stopFilterExpanded)}
413
- role="button"
414
- tabIndex={0}>
415
- <h6 className="search__filter-large-title">{translations.FLIGHTS_FORM.NUMBER_OF_STOPS}</h6>
416
- <svg
417
- id="search-chevron-up-icon"
418
- className={`search__filter-chevron-icon ${stopFilterExpanded ? 'search__filter-chevron-icon--flipped' : ''} `}
419
- viewBox="0 0 10 6.063"
420
- width={10}
421
- height={6.063}>
422
- <path
423
- id="Path_62"
424
- data-name="Path 62"
425
- d="M245-617.937l-5-5L241.063-624,245-620.062,248.938-624,250-622.937Z"
426
- transform="translate(-240 624)"
427
- fill="#707070"
428
- />
429
- </svg>
430
- </div>
431
- {stopFilterExpanded && (
432
- <div className="search__filter-rows">
433
- {numberOfStopsFilter.map((numberOfStops, stopIndex) => (
434
- <div className="search__filter-row search__filter-row--checkbox" key={`${numberOfStops.name}-${stopIndex}`}>
435
- <div className="checkbox">
436
- <label className="checkbox__label">
437
- <input
438
- type="checkbox"
439
- className="checkbox__input checkbox__input--parent"
440
- checked={filters.numberOfStops?.map((s) => s.id).includes(numberOfStops.id)}
441
- onChange={(event) => changeCheckbox('NumberOfStops', numberOfStops, event)}
442
- />
443
- <span className="radiobutton__label-text">{Number(numberOfStops.name) > 0 ? `${numberOfStops.name} Stop(s)` : 'Direct'}</span>
444
- </label>
445
- </div>
446
- </div>
447
- ))}
448
- </div>
449
- )}
450
- </div>
451
- {/* DepartureRange */}
452
- <div className="search__filter-group">
453
- <div
454
- className="search__filter-row search__filter-row--underline"
455
- onClick={() => setDepartureFilterExpanded(!departureFilterExpanded)}
456
- role="button"
457
- tabIndex={0}>
458
- <h6 className="search__filter-large-title">{translations.SRP.DEPARTURE_RANGE}</h6>
459
- <svg
460
- id="search-chevron-up-icon"
461
- className={`search__filter-chevron-icon ${departureFilterExpanded ? 'search__filter-chevron-icon--flipped' : ''} `}
462
- viewBox="0 0 10 6.063"
463
- width={10}
464
- height={6.063}>
465
- <path
466
- id="Path_62"
467
- data-name="Path 62"
468
- d="M245-617.937l-5-5L241.063-624,245-620.062,248.938-624,250-622.937Z"
469
- transform="translate(-240 624)"
470
- fill="#707070"
471
- />
472
- </svg>
473
- </div>
474
- {departureFilterExpanded && (
475
- <div className="search__filter-rows">
476
- {departureRangeFilter.map((departureRange, rangeIndex) => (
477
- <div className="search__filter-row search__filter-row--checkbox" key={`${departureRange.name}-${rangeIndex}`}>
478
- <div className="checkbox">
479
- <label className="checkbox__label">
480
- <input
481
- type="checkbox"
482
- className="checkbox__input checkbox__input--parent"
483
- checked={filters.departureRanges?.map((s) => s.id).includes(departureRange.id)}
484
- onChange={(event) => changeCheckbox('DepartureRange', departureRange, event)}
485
- />
486
- <span className="radiobutton__label-text">{departureRange.name}</span>
487
- </label>
488
- </div>
489
- </div>
490
- ))}
491
- </div>
492
- )}
493
- </div>
494
- {/* DepartureAirport */}
495
- <div className="search__filter-group">
496
- <div
497
- className="search__filter-row search__filter-row--underline"
498
- onClick={() => setDepartureAirportFilterExpanded(!departureAirportFilterExpanded)}
499
- role="button"
500
- tabIndex={0}>
501
- <h6 className="search__filter-large-title">{translations.SRP.DEPARTURE_AIRPORTS}</h6>
502
- <svg
503
- id="search-chevron-up-icon"
504
- className={`search__filter-chevron-icon ${departureAirportFilterExpanded ? 'search__filter-chevron-icon--flipped' : ''} `}
505
- viewBox="0 0 10 6.063"
506
- width={10}
507
- height={6.063}>
508
- <path
509
- id="Path_62"
510
- data-name="Path 62"
511
- d="M245-617.937l-5-5L241.063-624,245-620.062,248.938-624,250-622.937Z"
512
- transform="translate(-240 624)"
513
- fill="#707070"
514
- />
515
- </svg>
516
- </div>
517
- {departureAirportFilterExpanded && (
518
- <div className="search__filter-rows">
519
- {departureAirportFilter.map((departureAirport, airportIndex) => (
520
- <div className="search__filter-row search__filter-row--checkbox" key={`${departureAirport.name}-${airportIndex}`}>
521
- <div className="checkbox">
522
- <label className="checkbox__label">
523
- <input
524
- type="checkbox"
525
- className="checkbox__input checkbox__input--parent"
526
- checked={filters.departureAirports?.map((s) => s.id).includes(departureAirport.id)}
527
- onChange={(event) => changeCheckbox('DepartureAirports', departureAirport, event)}
528
- />
529
- <span className="radiobutton__label-text">{departureAirport.name}</span>
530
- </label>
531
- </div>
532
- </div>
533
- ))}
534
- </div>
535
- )}
536
- </div>
537
- {/* ArrivalAirport */}
538
- <div className="search__filter-group">
539
- <div
540
- className="search__filter-row search__filter-row--underline"
541
- onClick={() => setArrivalAirportFilterExpanded(!arrivalAirportFilterExpanded)}
542
- role="button"
543
- tabIndex={0}>
544
- <h6 className="search__filter-large-title">{translations.SRP.ARRIVAL_AIRPORTS}</h6>
545
- <svg
546
- id="search-chevron-up-icon"
547
- className={`search__filter-chevron-icon ${arrivalAirportFilterExpanded ? 'search__filter-chevron-icon--flipped' : ''} `}
548
- viewBox="0 0 10 6.063"
549
- width={10}
550
- height={6.063}>
551
- <path
552
- id="Path_62"
553
- data-name="Path 62"
554
- d="M245-617.937l-5-5L241.063-624,245-620.062,248.938-624,250-622.937Z"
555
- transform="translate(-240 624)"
556
- fill="#707070"
557
- />
558
- </svg>
559
- </div>
560
- {arrivalAirportFilterExpanded && (
561
- <div className="search__filter-rows">
562
- {arrivalAirportFilter.map((arrivalAirport, airportIndex) => (
563
- <div className="search__filter-row search__filter-row--checkbox" key={`${arrivalAirport.name}-${airportIndex}`}>
564
- <div className="checkbox">
565
- <label className="checkbox__label">
566
- <input
567
- type="checkbox"
568
- className="checkbox__input checkbox__input--parent"
569
- checked={filters.arrivalAirports?.map((s) => s.id).includes(arrivalAirport.id)}
570
- onChange={(event) => changeCheckbox('ArrivalAirports', arrivalAirport, event)}
571
- />
572
- <span className="radiobutton__label-text">{arrivalAirport.name}</span>
573
- </label>
574
- </div>
575
- </div>
576
- ))}
577
- </div>
578
- )}
579
- </div>
580
- {/* Duration */}
581
- <div className="search__filter-group">
582
- <div
583
- className="search__filter-row search__filter-row--underline"
584
- onClick={() => setDurationFilterExpanded(!durationFilterExpanded)}
585
- role="button"
586
- tabIndex={0}>
587
- <h6 className="search__filter-large-title">{translations.FLIGHTS_FORM.TRAVEL_DURATION}</h6>
588
- <svg
589
- id="search-chevron-up-icon"
590
- className={`search__filter-chevron-icon ${durationFilterExpanded ? 'search__filter-chevron-icon--flipped' : ''} `}
591
- viewBox="0 0 10 6.063"
592
- width={10}
593
- height={6.063}>
594
- <path
595
- id="Path_62"
596
- data-name="Path 62"
597
- d="M245-617.937l-5-5L241.063-624,245-620.062,248.938-624,250-622.937Z"
598
- transform="translate(-240 624)"
599
- fill="#707070"
600
- />
601
- </svg>
602
- </div>
603
- {durationFilterExpanded &&
604
- !isEmpty(travelTimeRangeValues) &&
605
- (() => {
606
- return (
607
- <div className="search__filter-rows">
608
- <MultiRangeFilter
609
- min={minTravelTime}
610
- max={maxTravelTime}
611
- selectedMin={first(travelTimeRangeValues) ?? minTravelTime}
612
- selectedMax={last(travelTimeRangeValues) ?? maxTravelTime}
613
- valueFormatter={(value) => `${minutesToHoursString(value)}`}
614
- onChange={(newMin, newMax) => {
615
- handleTravelTimeSliderChange([newMin, newMax]);
616
- }}
617
- />
618
- </div>
619
- );
620
- })()}
621
- </div>
622
- {/* Price */}
623
- <div className="search__filter-group">
624
- <div
625
- className="search__filter-row search__filter-row--underline"
626
- onClick={() => setPriceFilterExpanded(!priceFilterExpanded)}
627
- role="button"
628
- tabIndex={0}>
629
- <h6 className="search__filter-large-title">{translations.SRP.PRICE}</h6>
630
- <svg
631
- id="search-chevron-up-icon"
632
- className={`search__filter-chevron-icon ${priceFilterExpanded ? 'search__filter-chevron-icon--flipped' : ''} `}
633
- viewBox="0 0 10 6.063"
634
- width={10}
635
- height={6.063}>
636
- <path
637
- id="Path_62"
638
- data-name="Path 62"
639
- d="M245-617.937l-5-5L241.063-624,245-620.062,248.938-624,250-622.937Z"
640
- transform="translate(-240 624)"
641
- fill="#707070"
642
- />
643
- </svg>
644
- </div>
645
- {priceFilterExpanded &&
646
- !isEmpty(priceRangeValues) &&
647
- (() => {
648
- return (
649
- <div className="search__filter-rows">
650
- <MultiRangeFilter
651
- min={minPrice}
652
- max={maxPrice}
653
- selectedMin={first(priceRangeValues) ?? minPrice}
654
- selectedMax={last(priceRangeValues) ?? maxPrice}
655
- valueFormatter={(value) => `€${value}`}
656
- onChange={(newMin, newMax) => {
657
- handlePriceSliderChange([newMin, newMax]);
658
- }}
659
- />
660
- </div>
661
- );
662
- })()}
663
- </div>
664
- </div>
665
- </>
666
- </div>
667
- </div>
668
- );
669
- };
670
-
671
- export default FlightFilters;
1
+ import React, { useContext, useEffect, useState } from 'react';
2
+ import { ExtendedFlightSearchResponseItem, FilterValue } from '../../types';
3
+ import MultiRangeFilter from '../multi-range-filter';
4
+ import SearchResultsConfigurationContext from '../../search-results-configuration-context';
5
+ import Icon from '../icon';
6
+ import { durationInTicksInMinutes, getTranslations, minutesToHoursString, rangeFromDateTimeInMinutes } from '../../../shared/utils/localization-util';
7
+ import { useFlightSearch } from '../flight/flight-search-context';
8
+ import { first, isEmpty, last, orderBy } from 'lodash';
9
+ import { getArrivalSegment, getDepartureRangeName, getDepartureSegment, getNumberOfStops } from '../../utils/flight-utils';
10
+ import { DepartureRange } from '../../../shared/types';
11
+
12
+ interface FiltersProps {
13
+ isOpen: boolean;
14
+ handleSetIsOpen: () => void;
15
+ isLoading?: boolean;
16
+ }
17
+
18
+ const FlightFilters: React.FC<FiltersProps> = ({ isOpen, handleSetIsOpen, isLoading }) => {
19
+ const context = useContext(SearchResultsConfigurationContext);
20
+ const translations = getTranslations(context?.languageCode ?? 'en-GB');
21
+ const {
22
+ flightSearchResults,
23
+ filteredResults,
24
+ searchRequestId,
25
+ filters,
26
+ setFilters,
27
+ resetFilters,
28
+ airlineResults,
29
+ numberOfStopsResults,
30
+ departureRangeResults,
31
+ departureAirportsResults,
32
+ arrivalAirportsResults,
33
+ valuesManuallyChanged,
34
+ setValuesManuallyChanged
35
+ } = useFlightSearch();
36
+
37
+ const [airlineFilterExpanded, setAirlineFilterExpanded] = useState<boolean>(true);
38
+ const [airlineFilter, setAirlineFilter] = useState<FilterValue[]>([]);
39
+
40
+ const [stopFilterExpanded, setStopFilterExpanded] = useState<boolean>(true);
41
+ const [numberOfStopsFilter, setNumberOfStopsFilter] = useState<FilterValue[]>([]);
42
+
43
+ const [departureFilterExpanded, setDepartureFilterExpanded] = useState<boolean>(true);
44
+ const [departureRangeFilter, setDepartureRangeFilter] = useState<FilterValue[]>([]);
45
+
46
+ const [departureAirportFilterExpanded, setDepartureAirportFilterExpanded] = useState<boolean>(true);
47
+ const [departureAirportFilter, setDepartureAirportFilter] = useState<FilterValue[]>([]);
48
+
49
+ const [arrivalAirportFilterExpanded, setArrivalAirportFilterExpanded] = useState<boolean>(true);
50
+ const [arrivalAirportFilter, setArrivalAirportFilter] = useState<FilterValue[]>([]);
51
+
52
+ const [durationFilterExpanded, setDurationFilterExpanded] = useState<boolean>(true);
53
+ const [minTravelTime, setMinTravelTime] = useState<number>(0);
54
+ const [maxTravelTime, setMaxTravelTime] = useState<number>(0);
55
+ const [travelTimeRangeValues, setTravelTimeRangeValues] = useState<number[]>([]);
56
+
57
+ const [priceFilterExpanded, setPriceFilterExpanded] = useState<boolean>(true);
58
+ const [minPrice, setMinPrice] = useState<number>(0);
59
+ const [maxPrice, setMaxPrice] = useState<number>(0);
60
+ const [priceRangeValues, setPriceRangeValues] = useState<number[]>([]);
61
+
62
+ useEffect(() => {
63
+ const results = filteredResults.filter((item) => item.requestId === searchRequestId);
64
+ if (!isEmpty(results)) {
65
+ // airlines
66
+ const airlinesMap = new Map<string, ExtendedFlightSearchResponseItem>();
67
+ if (!isEmpty(airlineResults)) {
68
+ airlineResults.map((result) => {
69
+ const airline = result.airlineCode;
70
+
71
+ if (!airlinesMap.has(airline) || result.price < airlinesMap.get(airline)!.price) {
72
+ airlinesMap.set(airline, result);
73
+ }
74
+ });
75
+
76
+ // check if filters.airlines has values, if so, make sure all selected filters are present in the airlinesMap
77
+ const validAirlines = filters.airlines.filter((filter) => airlinesMap.has(filter.id.toString()));
78
+ if (validAirlines.length !== filters.airlines.length) {
79
+ setFilters({
80
+ ...filters,
81
+ airlines: validAirlines
82
+ });
83
+ }
84
+ }
85
+ // Convert the Map to an array of FilterValue
86
+ const uniqueAirlines = Array.from(airlinesMap.entries()).map(
87
+ ([airlineCode, flightItem]) =>
88
+ ({
89
+ id: airlineCode,
90
+ name: flightItem.airlineName, // Use the airline name directly
91
+ lowestPrice: flightItem.price
92
+ } as FilterValue)
93
+ );
94
+ setAirlineFilter(orderBy(uniqueAirlines, ['name'], ['asc']));
95
+
96
+ // number of stops
97
+ // Group by numberOfStops and find the lowest price
98
+ const stopsMap = new Map<number, number>();
99
+ if (!isEmpty(numberOfStopsResults)) {
100
+ numberOfStopsResults.map((result) => {
101
+ let numberOfStops = getNumberOfStops(result.outward);
102
+ if (numberOfStops > 2) {
103
+ numberOfStops = 2;
104
+ }
105
+
106
+ if (!stopsMap.has(numberOfStops) || result.price < stopsMap.get(numberOfStops)!) {
107
+ stopsMap.set(numberOfStops, result.price);
108
+ }
109
+ });
110
+ // check if filters.numberOfStops has values, if so, make sure all selected filters are present in the stopsMap
111
+ const validNumberOfStops = filters.numberOfStops.filter((filter) => stopsMap.has(Number(filter.id)));
112
+ if (validNumberOfStops.length !== filters.numberOfStops.length) {
113
+ setFilters({
114
+ ...filters,
115
+ numberOfStops: validNumberOfStops
116
+ });
117
+ }
118
+ }
119
+ // Convert the Map to an array of FilterValue
120
+ const uniqueStops = Array.from(stopsMap.entries()).map(
121
+ ([id, lowestPrice]) =>
122
+ ({
123
+ id: id,
124
+ name: id.toString(), // Format the name
125
+ lowestPrice: lowestPrice
126
+ } as FilterValue)
127
+ );
128
+ setNumberOfStopsFilter(orderBy(uniqueStops, ['id'], ['asc']));
129
+
130
+ // departureRange
131
+ const departureRangeMap = new Map<DepartureRange, number>();
132
+ if (!isEmpty(departureRangeResults)) {
133
+ departureRangeResults.map((result) => {
134
+ const departureRange = rangeFromDateTimeInMinutes(getDepartureSegment(result.outward)?.departureDateTime);
135
+ if (!departureRangeMap.has(departureRange) || result.price < departureRangeMap.get(departureRange)!) {
136
+ departureRangeMap.set(departureRange, result.price);
137
+ }
138
+ });
139
+ // check if filters.departureRanges has values, if so, make sure all selected filters are present in the departureRangeMap
140
+ const validDepartureRanges = filters.departureRanges.filter((filter) => departureRangeMap.has(Number(filter.id)));
141
+ if (validDepartureRanges.length !== filters.departureRanges.length) {
142
+ setFilters({
143
+ ...filters,
144
+ departureRanges: validDepartureRanges
145
+ });
146
+ }
147
+ }
148
+ // Convert the Map to an array of FilterValue
149
+ const uniqueDepartureRanges = Array.from(departureRangeMap.entries()).map(
150
+ ([range, lowestPrice]) =>
151
+ ({
152
+ id: range,
153
+ name: getDepartureRangeName(translations, range),
154
+ lowestPrice: lowestPrice
155
+ } as FilterValue)
156
+ );
157
+ setDepartureRangeFilter(orderBy(uniqueDepartureRanges, ['id'], ['asc']));
158
+
159
+ // departureAirports
160
+ const departureAirportsMap = new Map<string, ExtendedFlightSearchResponseItem>();
161
+ if (!isEmpty(departureAirportsResults)) {
162
+ departureAirportsResults.map((result) => {
163
+ const departureAirport = getDepartureSegment(result.outward)?.departureAirportCode;
164
+
165
+ if (departureAirport) {
166
+ if (!departureAirportsMap.has(departureAirport) || result.price < departureAirportsMap.get(departureAirport)!.price) {
167
+ departureAirportsMap.set(departureAirport, result);
168
+ }
169
+ }
170
+ });
171
+
172
+ // check if filters.departureAirports has values, if so, make sure all selected filters are present in the departureAirportsMap
173
+ const validDepartureAirports = filters.departureAirports.filter((filter) => departureAirportsMap.has(filter.id.toString()));
174
+ if (validDepartureAirports.length !== filters.departureAirports.length) {
175
+ setFilters({
176
+ ...filters,
177
+ departureAirports: validDepartureAirports
178
+ });
179
+ }
180
+ }
181
+ // Convert the Map to an array of FilterValue
182
+ const uniqueDepartureAirports = Array.from(departureAirportsMap.entries()).map(
183
+ ([departureAirport, flightItem]) =>
184
+ ({
185
+ id: departureAirport,
186
+ name: getDepartureSegment(flightItem.outward)?.departureAirportName + ' (' + departureAirport + ')', // Use the departure airport name directly
187
+ lowestPrice: flightItem.price
188
+ } as FilterValue)
189
+ );
190
+ setDepartureAirportFilter(orderBy(uniqueDepartureAirports, ['name'], ['asc']));
191
+
192
+ // arrivalAirports
193
+ const arrivalAirportsMap = new Map<string, ExtendedFlightSearchResponseItem>();
194
+ if (!isEmpty(arrivalAirportsResults)) {
195
+ arrivalAirportsResults.map((result) => {
196
+ const arrivalAirport = getArrivalSegment(result.outward)?.arrivalAirportCode;
197
+
198
+ if (arrivalAirport) {
199
+ if (!arrivalAirportsMap.has(arrivalAirport) || result.price < arrivalAirportsMap.get(arrivalAirport)!.price) {
200
+ arrivalAirportsMap.set(arrivalAirport, result);
201
+ }
202
+ }
203
+ });
204
+
205
+ // check if filters.arrivalAirports has values, if so, make sure all selected filters are present in the arrivalAirportsMap
206
+ const validArrivalAirports = filters.arrivalAirports.filter((filter) => arrivalAirportsMap.has(filter.id.toString()));
207
+ if (validArrivalAirports.length !== filters.arrivalAirports.length) {
208
+ setFilters({
209
+ ...filters,
210
+ arrivalAirports: validArrivalAirports
211
+ });
212
+ }
213
+ }
214
+ // Convert the Map to an array of FilterValue
215
+ const uniqueArrivalAirports = Array.from(arrivalAirportsMap.entries()).map(
216
+ ([arrivalAirport, flightItem]) =>
217
+ ({
218
+ id: arrivalAirport,
219
+ name: getArrivalSegment(flightItem.outward)?.arrivalAirportName + ' (' + arrivalAirport + ')', // Use the arrival airport name directly
220
+ lowestPrice: flightItem.price
221
+ } as FilterValue)
222
+ );
223
+ setArrivalAirportFilter(orderBy(uniqueArrivalAirports, ['name'], ['asc']));
224
+ } else {
225
+ setNumberOfStopsFilter([]);
226
+ setDepartureRangeFilter([]);
227
+ setAirlineFilter([]);
228
+ setDepartureAirportFilter([]);
229
+ }
230
+ }, [filteredResults, searchRequestId]);
231
+
232
+ useEffect(() => {
233
+ const results = flightSearchResults.filter((item) => item.requestId === searchRequestId);
234
+ if (!isEmpty(results)) {
235
+ // Travel time filter
236
+ const minTravelTimeDuration = Math.min(...results.map((result) => result.outward.durationInTicks));
237
+ const maxTravelTimeDuration = Math.max(...results.map((result) => result.outward.durationInTicks));
238
+ const minTravelTimeValue = durationInTicksInMinutes(minTravelTimeDuration);
239
+ const maxTravelTimeValue = durationInTicksInMinutes(maxTravelTimeDuration);
240
+ setMinTravelTime(minTravelTimeValue);
241
+ setMaxTravelTime(maxTravelTimeValue);
242
+ if (!valuesManuallyChanged) {
243
+ setTravelTimeRangeValues([minTravelTimeValue, maxTravelTimeValue]);
244
+ }
245
+ // price filter
246
+ const minPriceValue = Math.min(...results.map((result) => result.price));
247
+ const maxPriceValue = Math.max(...results.map((result) => result.price));
248
+ setMinPrice(minPriceValue);
249
+ setMaxPrice(maxPriceValue);
250
+ if (!valuesManuallyChanged) {
251
+ setPriceRangeValues([minPriceValue, maxPriceValue]);
252
+ }
253
+ }
254
+ }, [flightSearchResults]);
255
+
256
+ const adjustFilters = (type: string, value: FilterValue, checked: boolean) => {
257
+ switch (type) {
258
+ case 'Airlines':
259
+ setFilters({
260
+ ...filters,
261
+ airlines: checked ? [...filters.airlines, value] : filters.airlines.filter((n) => n.id !== value.id)
262
+ });
263
+ return;
264
+ case 'NumberOfStops':
265
+ setFilters({
266
+ ...filters,
267
+ numberOfStops: checked ? [...filters.numberOfStops, value] : filters.numberOfStops.filter((n) => n.id !== value.id)
268
+ });
269
+ return;
270
+ case 'DepartureRange':
271
+ setFilters({
272
+ ...filters,
273
+ departureRanges: checked ? [...filters.departureRanges, value] : filters.departureRanges.filter((n) => n.id !== value.id)
274
+ });
275
+ return;
276
+ case 'DepartureAirports':
277
+ setFilters({
278
+ ...filters,
279
+ departureAirports: checked ? [...filters.departureAirports, value] : filters.departureAirports.filter((n) => n.id !== value.id)
280
+ });
281
+ return;
282
+ case 'ArrivalAirports':
283
+ setFilters({
284
+ ...filters,
285
+ arrivalAirports: checked ? [...filters.arrivalAirports, value] : filters.arrivalAirports.filter((n) => n.id !== value.id)
286
+ });
287
+ return;
288
+ }
289
+ };
290
+
291
+ const changeCheckbox = (type: string, value: FilterValue, event: React.ChangeEvent<HTMLInputElement>) => {
292
+ adjustFilters(type, value, event.target.checked);
293
+ };
294
+
295
+ const handleTravelTimeSliderChange = (values: number[] | number) => {
296
+ setTravelTimeRangeValues(Array.isArray(values) ? values : [values, values]);
297
+ setValuesManuallyChanged(true);
298
+
299
+ onCompleteTravelTimeSliderChange(values);
300
+ };
301
+
302
+ const onCompleteTravelTimeSliderChange = (values: number[] | number) => {
303
+ const ranges = Array.isArray(values) ? values : [values, values];
304
+ const rangeFilters = ranges.map((value, index) => {
305
+ return {
306
+ id: index,
307
+ name: 'travelTime',
308
+ lowestPrice: value
309
+ } as FilterValue;
310
+ });
311
+ setFilters({
312
+ ...filters,
313
+ travelTimes: rangeFilters
314
+ });
315
+ };
316
+
317
+ const handlePriceSliderChange = (values: number[] | number) => {
318
+ setPriceRangeValues(Array.isArray(values) ? values : [values, values]);
319
+ setValuesManuallyChanged(true);
320
+
321
+ onCompletePriceSliderChange(values);
322
+ };
323
+
324
+ const onCompletePriceSliderChange = (values: number[] | number) => {
325
+ const ranges = Array.isArray(values) ? values : [values, values];
326
+ const rangeFilters = ranges.map((value, index) => {
327
+ return {
328
+ id: index,
329
+ name: 'price',
330
+ lowestPrice: value
331
+ } as FilterValue;
332
+ });
333
+ setFilters({
334
+ ...filters,
335
+ prices: rangeFilters
336
+ });
337
+ };
338
+
339
+ const handleResetFilters = () => {
340
+ resetFilters();
341
+ setValuesManuallyChanged(false);
342
+ setTravelTimeRangeValues([minTravelTime, maxTravelTime]);
343
+ setPriceRangeValues([minPrice, maxPrice]);
344
+ };
345
+
346
+ return (
347
+ <div className={`search__filters--modal ${isOpen ? 'is-open' : ''}`}>
348
+ <div className="search__filters--background" onClick={() => handleSetIsOpen()}></div>
349
+ <button className="search__filters--close" onClick={() => handleSetIsOpen()}>
350
+ <Icon name="ui-close" height={24} />
351
+ </button>
352
+ <div className="search__filters">
353
+ <div className="search__filter-row search__filter__header">
354
+ <div className="search__filter-row-flex-title">
355
+ <p className="search__filter-small-title">{translations.SRP.FILTERS}</p>
356
+ </div>
357
+ {!isLoading && (
358
+ <a className="search__filter-reset" onClick={() => handleResetFilters()}>
359
+ {translations.SRP.RESET}
360
+ </a>
361
+ )}
362
+ </div>
363
+ <>
364
+ <div className="search__filters__group-container">
365
+ {/* Airlines */}
366
+ <div className="search__filter-group">
367
+ <div
368
+ className="search__filter-row search__filter-row--underline"
369
+ onClick={() => setAirlineFilterExpanded(!airlineFilterExpanded)}
370
+ role="button"
371
+ tabIndex={0}>
372
+ <h6 className="search__filter-large-title">{translations.FLIGHTS_FORM.AIRLINES}</h6>
373
+ <svg
374
+ id="search-chevron-up-icon"
375
+ className={`search__filter-chevron-icon ${airlineFilterExpanded ? 'search__filter-chevron-icon--flipped' : ''} `}
376
+ viewBox="0 0 10 6.063"
377
+ width={10}
378
+ height={6.063}>
379
+ <path
380
+ id="Path_62"
381
+ data-name="Path 62"
382
+ d="M245-617.937l-5-5L241.063-624,245-620.062,248.938-624,250-622.937Z"
383
+ transform="translate(-240 624)"
384
+ fill="#707070"
385
+ />
386
+ </svg>
387
+ </div>
388
+ {airlineFilterExpanded && (
389
+ <div className="search__filter-rows">
390
+ {airlineFilter.map((airline, airlineIndex) => (
391
+ <div className="search__filter-row search__filter-row--checkbox" key={`${airline.name}-${airlineIndex}`}>
392
+ <div className="checkbox">
393
+ <label className="checkbox__label">
394
+ <input
395
+ type="checkbox"
396
+ className="checkbox__input checkbox__input--parent"
397
+ checked={filters.airlines?.map((a) => a.id).includes(airline.id)}
398
+ onChange={(event) => changeCheckbox('Airlines', airline, event)}
399
+ />
400
+ <span className="radiobutton__label-text">{airline.name}</span>
401
+ </label>
402
+ </div>
403
+ </div>
404
+ ))}
405
+ </div>
406
+ )}
407
+ </div>
408
+ {/* NumberOfStops */}
409
+ <div className="search__filter-group">
410
+ <div
411
+ className="search__filter-row search__filter-row--underline"
412
+ onClick={() => setStopFilterExpanded(!stopFilterExpanded)}
413
+ role="button"
414
+ tabIndex={0}>
415
+ <h6 className="search__filter-large-title">{translations.FLIGHTS_FORM.NUMBER_OF_STOPS}</h6>
416
+ <svg
417
+ id="search-chevron-up-icon"
418
+ className={`search__filter-chevron-icon ${stopFilterExpanded ? 'search__filter-chevron-icon--flipped' : ''} `}
419
+ viewBox="0 0 10 6.063"
420
+ width={10}
421
+ height={6.063}>
422
+ <path
423
+ id="Path_62"
424
+ data-name="Path 62"
425
+ d="M245-617.937l-5-5L241.063-624,245-620.062,248.938-624,250-622.937Z"
426
+ transform="translate(-240 624)"
427
+ fill="#707070"
428
+ />
429
+ </svg>
430
+ </div>
431
+ {stopFilterExpanded && (
432
+ <div className="search__filter-rows">
433
+ {numberOfStopsFilter.map((numberOfStops, stopIndex) => (
434
+ <div className="search__filter-row search__filter-row--checkbox" key={`${numberOfStops.name}-${stopIndex}`}>
435
+ <div className="checkbox">
436
+ <label className="checkbox__label">
437
+ <input
438
+ type="checkbox"
439
+ className="checkbox__input checkbox__input--parent"
440
+ checked={filters.numberOfStops?.map((s) => s.id).includes(numberOfStops.id)}
441
+ onChange={(event) => changeCheckbox('NumberOfStops', numberOfStops, event)}
442
+ />
443
+ <span className="radiobutton__label-text">{Number(numberOfStops.name) > 0 ? `${numberOfStops.name} Stop(s)` : 'Direct'}</span>
444
+ </label>
445
+ </div>
446
+ </div>
447
+ ))}
448
+ </div>
449
+ )}
450
+ </div>
451
+ {/* DepartureRange */}
452
+ <div className="search__filter-group">
453
+ <div
454
+ className="search__filter-row search__filter-row--underline"
455
+ onClick={() => setDepartureFilterExpanded(!departureFilterExpanded)}
456
+ role="button"
457
+ tabIndex={0}>
458
+ <h6 className="search__filter-large-title">{translations.SRP.DEPARTURE_RANGE}</h6>
459
+ <svg
460
+ id="search-chevron-up-icon"
461
+ className={`search__filter-chevron-icon ${departureFilterExpanded ? 'search__filter-chevron-icon--flipped' : ''} `}
462
+ viewBox="0 0 10 6.063"
463
+ width={10}
464
+ height={6.063}>
465
+ <path
466
+ id="Path_62"
467
+ data-name="Path 62"
468
+ d="M245-617.937l-5-5L241.063-624,245-620.062,248.938-624,250-622.937Z"
469
+ transform="translate(-240 624)"
470
+ fill="#707070"
471
+ />
472
+ </svg>
473
+ </div>
474
+ {departureFilterExpanded && (
475
+ <div className="search__filter-rows">
476
+ {departureRangeFilter.map((departureRange, rangeIndex) => (
477
+ <div className="search__filter-row search__filter-row--checkbox" key={`${departureRange.name}-${rangeIndex}`}>
478
+ <div className="checkbox">
479
+ <label className="checkbox__label">
480
+ <input
481
+ type="checkbox"
482
+ className="checkbox__input checkbox__input--parent"
483
+ checked={filters.departureRanges?.map((s) => s.id).includes(departureRange.id)}
484
+ onChange={(event) => changeCheckbox('DepartureRange', departureRange, event)}
485
+ />
486
+ <span className="radiobutton__label-text">{departureRange.name}</span>
487
+ </label>
488
+ </div>
489
+ </div>
490
+ ))}
491
+ </div>
492
+ )}
493
+ </div>
494
+ {/* DepartureAirport */}
495
+ <div className="search__filter-group">
496
+ <div
497
+ className="search__filter-row search__filter-row--underline"
498
+ onClick={() => setDepartureAirportFilterExpanded(!departureAirportFilterExpanded)}
499
+ role="button"
500
+ tabIndex={0}>
501
+ <h6 className="search__filter-large-title">{translations.SRP.DEPARTURE_AIRPORTS}</h6>
502
+ <svg
503
+ id="search-chevron-up-icon"
504
+ className={`search__filter-chevron-icon ${departureAirportFilterExpanded ? 'search__filter-chevron-icon--flipped' : ''} `}
505
+ viewBox="0 0 10 6.063"
506
+ width={10}
507
+ height={6.063}>
508
+ <path
509
+ id="Path_62"
510
+ data-name="Path 62"
511
+ d="M245-617.937l-5-5L241.063-624,245-620.062,248.938-624,250-622.937Z"
512
+ transform="translate(-240 624)"
513
+ fill="#707070"
514
+ />
515
+ </svg>
516
+ </div>
517
+ {departureAirportFilterExpanded && (
518
+ <div className="search__filter-rows">
519
+ {departureAirportFilter.map((departureAirport, airportIndex) => (
520
+ <div className="search__filter-row search__filter-row--checkbox" key={`${departureAirport.name}-${airportIndex}`}>
521
+ <div className="checkbox">
522
+ <label className="checkbox__label">
523
+ <input
524
+ type="checkbox"
525
+ className="checkbox__input checkbox__input--parent"
526
+ checked={filters.departureAirports?.map((s) => s.id).includes(departureAirport.id)}
527
+ onChange={(event) => changeCheckbox('DepartureAirports', departureAirport, event)}
528
+ />
529
+ <span className="radiobutton__label-text">{departureAirport.name}</span>
530
+ </label>
531
+ </div>
532
+ </div>
533
+ ))}
534
+ </div>
535
+ )}
536
+ </div>
537
+ {/* ArrivalAirport */}
538
+ <div className="search__filter-group">
539
+ <div
540
+ className="search__filter-row search__filter-row--underline"
541
+ onClick={() => setArrivalAirportFilterExpanded(!arrivalAirportFilterExpanded)}
542
+ role="button"
543
+ tabIndex={0}>
544
+ <h6 className="search__filter-large-title">{translations.SRP.ARRIVAL_AIRPORTS}</h6>
545
+ <svg
546
+ id="search-chevron-up-icon"
547
+ className={`search__filter-chevron-icon ${arrivalAirportFilterExpanded ? 'search__filter-chevron-icon--flipped' : ''} `}
548
+ viewBox="0 0 10 6.063"
549
+ width={10}
550
+ height={6.063}>
551
+ <path
552
+ id="Path_62"
553
+ data-name="Path 62"
554
+ d="M245-617.937l-5-5L241.063-624,245-620.062,248.938-624,250-622.937Z"
555
+ transform="translate(-240 624)"
556
+ fill="#707070"
557
+ />
558
+ </svg>
559
+ </div>
560
+ {arrivalAirportFilterExpanded && (
561
+ <div className="search__filter-rows">
562
+ {arrivalAirportFilter.map((arrivalAirport, airportIndex) => (
563
+ <div className="search__filter-row search__filter-row--checkbox" key={`${arrivalAirport.name}-${airportIndex}`}>
564
+ <div className="checkbox">
565
+ <label className="checkbox__label">
566
+ <input
567
+ type="checkbox"
568
+ className="checkbox__input checkbox__input--parent"
569
+ checked={filters.arrivalAirports?.map((s) => s.id).includes(arrivalAirport.id)}
570
+ onChange={(event) => changeCheckbox('ArrivalAirports', arrivalAirport, event)}
571
+ />
572
+ <span className="radiobutton__label-text">{arrivalAirport.name}</span>
573
+ </label>
574
+ </div>
575
+ </div>
576
+ ))}
577
+ </div>
578
+ )}
579
+ </div>
580
+ {/* Duration */}
581
+ <div className="search__filter-group">
582
+ <div
583
+ className="search__filter-row search__filter-row--underline"
584
+ onClick={() => setDurationFilterExpanded(!durationFilterExpanded)}
585
+ role="button"
586
+ tabIndex={0}>
587
+ <h6 className="search__filter-large-title">{translations.FLIGHTS_FORM.TRAVEL_DURATION}</h6>
588
+ <svg
589
+ id="search-chevron-up-icon"
590
+ className={`search__filter-chevron-icon ${durationFilterExpanded ? 'search__filter-chevron-icon--flipped' : ''} `}
591
+ viewBox="0 0 10 6.063"
592
+ width={10}
593
+ height={6.063}>
594
+ <path
595
+ id="Path_62"
596
+ data-name="Path 62"
597
+ d="M245-617.937l-5-5L241.063-624,245-620.062,248.938-624,250-622.937Z"
598
+ transform="translate(-240 624)"
599
+ fill="#707070"
600
+ />
601
+ </svg>
602
+ </div>
603
+ {durationFilterExpanded &&
604
+ !isEmpty(travelTimeRangeValues) &&
605
+ (() => {
606
+ return (
607
+ <div className="search__filter-rows">
608
+ <MultiRangeFilter
609
+ min={minTravelTime}
610
+ max={maxTravelTime}
611
+ selectedMin={first(travelTimeRangeValues) ?? minTravelTime}
612
+ selectedMax={last(travelTimeRangeValues) ?? maxTravelTime}
613
+ valueFormatter={(value) => `${minutesToHoursString(value)}`}
614
+ onChange={(newMin, newMax) => {
615
+ handleTravelTimeSliderChange([newMin, newMax]);
616
+ }}
617
+ />
618
+ </div>
619
+ );
620
+ })()}
621
+ </div>
622
+ {/* Price */}
623
+ <div className="search__filter-group">
624
+ <div
625
+ className="search__filter-row search__filter-row--underline"
626
+ onClick={() => setPriceFilterExpanded(!priceFilterExpanded)}
627
+ role="button"
628
+ tabIndex={0}>
629
+ <h6 className="search__filter-large-title">{translations.SRP.PRICE}</h6>
630
+ <svg
631
+ id="search-chevron-up-icon"
632
+ className={`search__filter-chevron-icon ${priceFilterExpanded ? 'search__filter-chevron-icon--flipped' : ''} `}
633
+ viewBox="0 0 10 6.063"
634
+ width={10}
635
+ height={6.063}>
636
+ <path
637
+ id="Path_62"
638
+ data-name="Path 62"
639
+ d="M245-617.937l-5-5L241.063-624,245-620.062,248.938-624,250-622.937Z"
640
+ transform="translate(-240 624)"
641
+ fill="#707070"
642
+ />
643
+ </svg>
644
+ </div>
645
+ {priceFilterExpanded &&
646
+ !isEmpty(priceRangeValues) &&
647
+ (() => {
648
+ return (
649
+ <div className="search__filter-rows">
650
+ <MultiRangeFilter
651
+ min={minPrice}
652
+ max={maxPrice}
653
+ selectedMin={first(priceRangeValues) ?? minPrice}
654
+ selectedMax={last(priceRangeValues) ?? maxPrice}
655
+ valueFormatter={(value) => `€${value}`}
656
+ onChange={(newMin, newMax) => {
657
+ handlePriceSliderChange([newMin, newMax]);
658
+ }}
659
+ />
660
+ </div>
661
+ );
662
+ })()}
663
+ </div>
664
+ </div>
665
+ </>
666
+ </div>
667
+ </div>
668
+ );
669
+ };
670
+
671
+ export default FlightFilters;