@voyant-travel/catalog-react 0.117.2

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 (220) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +36 -0
  3. package/dist/admin/catalog-vertical-host.d.ts +45 -0
  4. package/dist/admin/catalog-vertical-host.d.ts.map +1 -0
  5. package/dist/admin/catalog-vertical-host.js +230 -0
  6. package/dist/admin/cruise-detail-host.d.ts +11 -0
  7. package/dist/admin/cruise-detail-host.d.ts.map +1 -0
  8. package/dist/admin/cruise-detail-host.js +33 -0
  9. package/dist/admin/dynamic-catalog-host.d.ts +13 -0
  10. package/dist/admin/dynamic-catalog-host.d.ts.map +1 -0
  11. package/dist/admin/dynamic-catalog-host.js +17 -0
  12. package/dist/admin/index.d.ts +133 -0
  13. package/dist/admin/index.d.ts.map +1 -0
  14. package/dist/admin/index.js +144 -0
  15. package/dist/admin/open-in-new-tab.d.ts +7 -0
  16. package/dist/admin/open-in-new-tab.d.ts.map +1 -0
  17. package/dist/admin/open-in-new-tab.js +10 -0
  18. package/dist/admin/pages/catalog-accommodations-detail-page.d.ts +4 -0
  19. package/dist/admin/pages/catalog-accommodations-detail-page.d.ts.map +1 -0
  20. package/dist/admin/pages/catalog-accommodations-detail-page.js +7 -0
  21. package/dist/admin/pages/catalog-accommodations-index-page.d.ts +8 -0
  22. package/dist/admin/pages/catalog-accommodations-index-page.d.ts.map +1 -0
  23. package/dist/admin/pages/catalog-accommodations-index-page.js +17 -0
  24. package/dist/admin/pages/catalog-cruises-detail-page.d.ts +4 -0
  25. package/dist/admin/pages/catalog-cruises-detail-page.d.ts.map +1 -0
  26. package/dist/admin/pages/catalog-cruises-detail-page.js +7 -0
  27. package/dist/admin/pages/catalog-cruises-index-page.d.ts +8 -0
  28. package/dist/admin/pages/catalog-cruises-index-page.d.ts.map +1 -0
  29. package/dist/admin/pages/catalog-cruises-index-page.js +19 -0
  30. package/dist/admin/pages/catalog-excursions-detail-page.d.ts +4 -0
  31. package/dist/admin/pages/catalog-excursions-detail-page.d.ts.map +1 -0
  32. package/dist/admin/pages/catalog-excursions-detail-page.js +7 -0
  33. package/dist/admin/pages/catalog-excursions-index-page.d.ts +8 -0
  34. package/dist/admin/pages/catalog-excursions-index-page.d.ts.map +1 -0
  35. package/dist/admin/pages/catalog-excursions-index-page.js +12 -0
  36. package/dist/admin/pages/catalog-products-detail-page.d.ts +8 -0
  37. package/dist/admin/pages/catalog-products-detail-page.d.ts.map +1 -0
  38. package/dist/admin/pages/catalog-products-detail-page.js +12 -0
  39. package/dist/admin/pages/catalog-products-index-page.d.ts +8 -0
  40. package/dist/admin/pages/catalog-products-index-page.d.ts.map +1 -0
  41. package/dist/admin/pages/catalog-products-index-page.js +12 -0
  42. package/dist/admin/pages/catalog-tours-detail-page.d.ts +4 -0
  43. package/dist/admin/pages/catalog-tours-detail-page.d.ts.map +1 -0
  44. package/dist/admin/pages/catalog-tours-detail-page.js +7 -0
  45. package/dist/admin/pages/catalog-tours-index-page.d.ts +8 -0
  46. package/dist/admin/pages/catalog-tours-index-page.d.ts.map +1 -0
  47. package/dist/admin/pages/catalog-tours-index-page.js +12 -0
  48. package/dist/admin/product-detail-host.d.ts +18 -0
  49. package/dist/admin/product-detail-host.d.ts.map +1 -0
  50. package/dist/admin/product-detail-host.js +40 -0
  51. package/dist/admin/scheduled-catalog-host.d.ts +15 -0
  52. package/dist/admin/scheduled-catalog-host.d.ts.map +1 -0
  53. package/dist/admin/scheduled-catalog-host.js +19 -0
  54. package/dist/admin/vertical-detail-host.d.ts +13 -0
  55. package/dist/admin/vertical-detail-host.d.ts.map +1 -0
  56. package/dist/admin/vertical-detail-host.js +62 -0
  57. package/dist/booking-engine/index.d.ts +26 -0
  58. package/dist/booking-engine/index.d.ts.map +1 -0
  59. package/dist/booking-engine/index.js +25 -0
  60. package/dist/booking-engine/use-booking-commit.d.ts +61 -0
  61. package/dist/booking-engine/use-booking-commit.d.ts.map +1 -0
  62. package/dist/booking-engine/use-booking-commit.js +47 -0
  63. package/dist/booking-engine/use-booking-draft-shape.d.ts +20 -0
  64. package/dist/booking-engine/use-booking-draft-shape.d.ts.map +1 -0
  65. package/dist/booking-engine/use-booking-draft-shape.js +9 -0
  66. package/dist/booking-engine/use-booking-draft.d.ts +102 -0
  67. package/dist/booking-engine/use-booking-draft.d.ts.map +1 -0
  68. package/dist/booking-engine/use-booking-draft.js +93 -0
  69. package/dist/booking-engine/use-booking-hold.d.ts +30 -0
  70. package/dist/booking-engine/use-booking-hold.d.ts.map +1 -0
  71. package/dist/booking-engine/use-booking-hold.js +44 -0
  72. package/dist/booking-engine/use-booking-journey-api.d.ts +23 -0
  73. package/dist/booking-engine/use-booking-journey-api.d.ts.map +1 -0
  74. package/dist/booking-engine/use-booking-journey-api.js +24 -0
  75. package/dist/booking-engine/use-booking-quote.d.ts +711 -0
  76. package/dist/booking-engine/use-booking-quote.d.ts.map +1 -0
  77. package/dist/booking-engine/use-booking-quote.js +122 -0
  78. package/dist/catalog-enrichment-mappers.d.ts +162 -0
  79. package/dist/catalog-enrichment-mappers.d.ts.map +1 -0
  80. package/dist/catalog-enrichment-mappers.js +190 -0
  81. package/dist/catalog-enrichment.d.ts +203 -0
  82. package/dist/catalog-enrichment.d.ts.map +1 -0
  83. package/dist/catalog-enrichment.js +130 -0
  84. package/dist/catalog-offers-client.d.ts +58 -0
  85. package/dist/catalog-offers-client.d.ts.map +1 -0
  86. package/dist/catalog-offers-client.js +61 -0
  87. package/dist/catalog-search-params.d.ts +45 -0
  88. package/dist/catalog-search-params.d.ts.map +1 -0
  89. package/dist/catalog-search-params.js +30 -0
  90. package/dist/catalog-surfaces.d.ts +17 -0
  91. package/dist/catalog-surfaces.d.ts.map +1 -0
  92. package/dist/catalog-surfaces.js +26 -0
  93. package/dist/client.d.ts +20 -0
  94. package/dist/client.d.ts.map +1 -0
  95. package/dist/client.js +65 -0
  96. package/dist/components/availability-calendar.d.ts +33 -0
  97. package/dist/components/availability-calendar.d.ts.map +1 -0
  98. package/dist/components/availability-calendar.js +65 -0
  99. package/dist/components/catalog-browse-page.d.ts +41 -0
  100. package/dist/components/catalog-browse-page.d.ts.map +1 -0
  101. package/dist/components/catalog-browse-page.js +47 -0
  102. package/dist/components/catalog-card.d.ts +68 -0
  103. package/dist/components/catalog-card.d.ts.map +1 -0
  104. package/dist/components/catalog-card.js +52 -0
  105. package/dist/components/catalog-detail-cruise-cards.d.ts +16 -0
  106. package/dist/components/catalog-detail-cruise-cards.d.ts.map +1 -0
  107. package/dist/components/catalog-detail-cruise-cards.js +54 -0
  108. package/dist/components/catalog-detail-departures.d.ts +25 -0
  109. package/dist/components/catalog-detail-departures.d.ts.map +1 -0
  110. package/dist/components/catalog-detail-departures.js +240 -0
  111. package/dist/components/catalog-detail-parts.d.ts +70 -0
  112. package/dist/components/catalog-detail-parts.d.ts.map +1 -0
  113. package/dist/components/catalog-detail-parts.js +282 -0
  114. package/dist/components/catalog-detail-sheet.d.ts +93 -0
  115. package/dist/components/catalog-detail-sheet.d.ts.map +1 -0
  116. package/dist/components/catalog-detail-sheet.js +68 -0
  117. package/dist/components/catalog-detail-view.d.ts +39 -0
  118. package/dist/components/catalog-detail-view.d.ts.map +1 -0
  119. package/dist/components/catalog-detail-view.js +157 -0
  120. package/dist/components/catalog-enrichment-fetchers.d.ts +8 -0
  121. package/dist/components/catalog-enrichment-fetchers.d.ts.map +1 -0
  122. package/dist/components/catalog-enrichment-fetchers.js +7 -0
  123. package/dist/components/catalog-faceted-filter.d.ts +30 -0
  124. package/dist/components/catalog-faceted-filter.d.ts.map +1 -0
  125. package/dist/components/catalog-faceted-filter.js +24 -0
  126. package/dist/components/catalog-filter-rail.d.ts +25 -0
  127. package/dist/components/catalog-filter-rail.d.ts.map +1 -0
  128. package/dist/components/catalog-filter-rail.js +88 -0
  129. package/dist/components/catalog-gallery.d.ts +27 -0
  130. package/dist/components/catalog-gallery.d.ts.map +1 -0
  131. package/dist/components/catalog-gallery.js +66 -0
  132. package/dist/components/catalog-hit.d.ts +27 -0
  133. package/dist/components/catalog-hit.d.ts.map +1 -0
  134. package/dist/components/catalog-hit.js +57 -0
  135. package/dist/components/catalog-page-cards.d.ts +21 -0
  136. package/dist/components/catalog-page-cards.d.ts.map +1 -0
  137. package/dist/components/catalog-page-cards.js +174 -0
  138. package/dist/components/catalog-page-config.d.ts +17 -0
  139. package/dist/components/catalog-page-config.d.ts.map +1 -0
  140. package/dist/components/catalog-page-config.js +369 -0
  141. package/dist/components/catalog-page.d.ts +88 -0
  142. package/dist/components/catalog-page.d.ts.map +1 -0
  143. package/dist/components/catalog-page.js +148 -0
  144. package/dist/components/catalog-range-filter.d.ts +34 -0
  145. package/dist/components/catalog-range-filter.d.ts.map +1 -0
  146. package/dist/components/catalog-range-filter.js +72 -0
  147. package/dist/components/catalog-search-page.d.ts +239 -0
  148. package/dist/components/catalog-search-page.d.ts.map +1 -0
  149. package/dist/components/catalog-search-page.js +63 -0
  150. package/dist/components/catalog-search-tab-panel.d.ts +42 -0
  151. package/dist/components/catalog-search-tab-panel.d.ts.map +1 -0
  152. package/dist/components/catalog-search-tab-panel.js +199 -0
  153. package/dist/components/catalog-vertical-detail-page.d.ts +33 -0
  154. package/dist/components/catalog-vertical-detail-page.d.ts.map +1 -0
  155. package/dist/components/catalog-vertical-detail-page.js +100 -0
  156. package/dist/components/cruise-detail-page-parts.d.ts +72 -0
  157. package/dist/components/cruise-detail-page-parts.d.ts.map +1 -0
  158. package/dist/components/cruise-detail-page-parts.js +146 -0
  159. package/dist/components/cruise-detail-page.d.ts +21 -0
  160. package/dist/components/cruise-detail-page.d.ts.map +1 -0
  161. package/dist/components/cruise-detail-page.js +201 -0
  162. package/dist/components/dynamic-catalog-page-parts.d.ts +40 -0
  163. package/dist/components/dynamic-catalog-page-parts.d.ts.map +1 -0
  164. package/dist/components/dynamic-catalog-page-parts.js +43 -0
  165. package/dist/components/dynamic-catalog-page.d.ts +23 -0
  166. package/dist/components/dynamic-catalog-page.d.ts.map +1 -0
  167. package/dist/components/dynamic-catalog-page.js +270 -0
  168. package/dist/components/media-gallery.d.ts +13 -0
  169. package/dist/components/media-gallery.d.ts.map +1 -0
  170. package/dist/components/media-gallery.js +42 -0
  171. package/dist/components/product-detail-page-parts.d.ts +106 -0
  172. package/dist/components/product-detail-page-parts.d.ts.map +1 -0
  173. package/dist/components/product-detail-page-parts.js +130 -0
  174. package/dist/components/product-detail-page.d.ts +57 -0
  175. package/dist/components/product-detail-page.d.ts.map +1 -0
  176. package/dist/components/product-detail-page.js +175 -0
  177. package/dist/components/scheduled-catalog-page.d.ts +34 -0
  178. package/dist/components/scheduled-catalog-page.d.ts.map +1 -0
  179. package/dist/components/scheduled-catalog-page.js +6 -0
  180. package/dist/hooks/index.d.ts +3 -0
  181. package/dist/hooks/index.d.ts.map +1 -0
  182. package/dist/hooks/index.js +2 -0
  183. package/dist/hooks/use-catalog-offers.d.ts +186 -0
  184. package/dist/hooks/use-catalog-offers.d.ts.map +1 -0
  185. package/dist/hooks/use-catalog-offers.js +105 -0
  186. package/dist/hooks/use-catalog-search.d.ts +109 -0
  187. package/dist/hooks/use-catalog-search.d.ts.map +1 -0
  188. package/dist/hooks/use-catalog-search.js +52 -0
  189. package/dist/i18n/en.d.ts +397 -0
  190. package/dist/i18n/en.d.ts.map +1 -0
  191. package/dist/i18n/en.js +396 -0
  192. package/dist/i18n/index.d.ts +5 -0
  193. package/dist/i18n/index.d.ts.map +1 -0
  194. package/dist/i18n/index.js +3 -0
  195. package/dist/i18n/messages.d.ts +335 -0
  196. package/dist/i18n/messages.d.ts.map +1 -0
  197. package/dist/i18n/messages.js +1 -0
  198. package/dist/i18n/provider.d.ts +816 -0
  199. package/dist/i18n/provider.d.ts.map +1 -0
  200. package/dist/i18n/provider.js +44 -0
  201. package/dist/i18n/ro.d.ts +397 -0
  202. package/dist/i18n/ro.d.ts.map +1 -0
  203. package/dist/i18n/ro.js +396 -0
  204. package/dist/index.d.ts +9 -0
  205. package/dist/index.d.ts.map +1 -0
  206. package/dist/index.js +17 -0
  207. package/dist/provider.d.ts +2 -0
  208. package/dist/provider.d.ts.map +1 -0
  209. package/dist/provider.js +1 -0
  210. package/dist/schemas-catalog-offers.d.ts +290 -0
  211. package/dist/schemas-catalog-offers.d.ts.map +1 -0
  212. package/dist/schemas-catalog-offers.js +155 -0
  213. package/dist/schemas.d.ts +143 -0
  214. package/dist/schemas.d.ts.map +1 -0
  215. package/dist/schemas.js +76 -0
  216. package/dist/ui.d.ts +19 -0
  217. package/dist/ui.d.ts.map +1 -0
  218. package/dist/ui.js +18 -0
  219. package/package.json +150 -0
  220. package/src/styles.css +11 -0
@@ -0,0 +1,369 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Badge } from "@voyant-travel/ui/components/badge";
3
+ import { Image as ImageIcon } from "lucide-react";
4
+ import { formatHitPrice, numberField, resolveHitPriceUnit, stringField, } from "./catalog-hit.js";
5
+ import { formatBoard, formatCountry, formatDepartureMonth, formatStars, formatTransport, } from "./catalog-page-cards.js";
6
+ export function makeProductColumns(formatSupplier, messages) {
7
+ return [
8
+ nameColumn(messages.fallbacks.productName, messages),
9
+ statusColumn(messages),
10
+ sourceColumn(messages),
11
+ lookupColumn("supplierId", messages.columns.supplier, formatSupplier, messages),
12
+ daysColumn(messages),
13
+ nightsColumn(messages),
14
+ priceColumn("sellAmountCents", "sellCurrency", messages.columns.price, messages),
15
+ ];
16
+ }
17
+ export function makeExtraColumns(formatSupplier, messages) {
18
+ return [
19
+ nameColumn(messages.fallbacks.extraName, messages),
20
+ activeColumn(messages),
21
+ sourceColumn(messages),
22
+ lookupColumn("supplierId", messages.columns.supplier, formatSupplier, messages),
23
+ textColumn("selectionType", messages.columns.selection, messages),
24
+ textColumn("pricingMode", messages.columns.pricing, messages),
25
+ textColumn("defaultQuantity", messages.columns.defaultQuantity, messages),
26
+ ];
27
+ }
28
+ export function makeCruiseColumns(formatSupplier, messages) {
29
+ return [
30
+ nameColumn(messages.fallbacks.cruiseName, messages),
31
+ statusColumn(messages),
32
+ sourceColumn(messages),
33
+ textColumn("cruiseType", messages.columns.type, messages),
34
+ lookupColumn("lineSupplierId", messages.columns.supplier, formatSupplier, messages),
35
+ textColumn("nights", messages.columns.nights, messages),
36
+ priceColumn("lowestPriceCached", "lowestPriceCurrencyCached", messages.columns.price, messages, "major", "lowestPriceUnit"),
37
+ ];
38
+ }
39
+ export function makeCharterColumns(formatSupplier, messages) {
40
+ return [
41
+ nameColumn(messages.fallbacks.charterName, messages, "heroImageUrl"),
42
+ statusColumn(messages),
43
+ sourceColumn(messages),
44
+ lookupColumn("lineSupplierId", messages.columns.supplier, formatSupplier, messages),
45
+ textColumn("defaultYachtId", messages.columns.yacht, messages),
46
+ priceColumn("lowestPriceCachedAmount", "lowestPriceCachedCurrency", messages.columns.from, messages),
47
+ ];
48
+ }
49
+ export function makeAccommodationColumns(formatSupplier, messages) {
50
+ return [
51
+ nameColumn(messages.fallbacks.roomName, messages),
52
+ activeColumn(messages),
53
+ sourceColumn(messages),
54
+ lookupColumn("supplierId", messages.columns.supplier, formatSupplier, messages),
55
+ textColumn("roomClass", messages.columns.class, messages),
56
+ textColumn("maxOccupancy", messages.columns.maxPax, messages),
57
+ textColumn("bedroomCount", messages.columns.bedrooms, messages),
58
+ ];
59
+ }
60
+ export function makeProductFilters(formatSupplier, messages) {
61
+ return [
62
+ { field: "status", label: messages.filters.status },
63
+ { field: "countryCodes", label: messages.filters.country, formatValue: formatCountry },
64
+ { field: "destinations", label: messages.filters.destination },
65
+ {
66
+ field: "board",
67
+ label: messages.filters.board,
68
+ formatValue: (value) => formatBoard(String(value), messages) ?? String(value),
69
+ },
70
+ {
71
+ field: "stars",
72
+ label: messages.filters.stars,
73
+ formatValue: (value) => formatStars(value) ?? String(value),
74
+ // Show 5★ → 0, not by item count.
75
+ sortValues: "value-desc",
76
+ },
77
+ {
78
+ field: "transport",
79
+ label: messages.filters.transport,
80
+ formatValue: (value) => formatTransport(String(value), messages) ?? String(value),
81
+ },
82
+ {
83
+ field: "source.kind",
84
+ label: messages.filters.source,
85
+ formatValue: (value) => formatSourceKind(value, messages),
86
+ },
87
+ { field: "supplierId", label: messages.filters.supplier, formatValue: formatSupplier },
88
+ { field: "bookingMode", label: messages.filters.bookingMode },
89
+ { field: "productTypeId", label: messages.filters.type },
90
+ { field: "capacityMode", label: messages.filters.capacity },
91
+ { field: "visibility", label: messages.filters.visibility },
92
+ { field: "facilityId", label: messages.filters.facility },
93
+ {
94
+ kind: "range",
95
+ field: "sellAmountCents",
96
+ label: messages.filters.price,
97
+ format: "currency",
98
+ currency: "EUR",
99
+ step: 100,
100
+ minPlaceholder: "0",
101
+ maxPlaceholder: messages.filters.any,
102
+ },
103
+ {
104
+ kind: "range",
105
+ field: "pax",
106
+ label: messages.filters.pax,
107
+ minPlaceholder: "0",
108
+ maxPlaceholder: messages.filters.any,
109
+ },
110
+ ];
111
+ }
112
+ export function makeExtraFilters(formatSupplier, messages) {
113
+ return [
114
+ { field: "active", label: messages.filters.active },
115
+ {
116
+ field: "source.kind",
117
+ label: messages.filters.source,
118
+ formatValue: (value) => formatSourceKind(value, messages),
119
+ },
120
+ { field: "supplierId", label: messages.filters.supplier, formatValue: formatSupplier },
121
+ { field: "selectionType", label: messages.filters.selection },
122
+ { field: "pricingMode", label: messages.filters.pricingMode },
123
+ { field: "pricedPerPerson", label: messages.filters.perPerson },
124
+ {
125
+ kind: "range",
126
+ field: "minQuantity",
127
+ label: messages.filters.minQuantity,
128
+ minPlaceholder: "0",
129
+ maxPlaceholder: messages.filters.any,
130
+ },
131
+ {
132
+ kind: "range",
133
+ field: "maxQuantity",
134
+ label: messages.filters.maxQuantity,
135
+ minPlaceholder: "0",
136
+ maxPlaceholder: messages.filters.any,
137
+ },
138
+ ];
139
+ }
140
+ export function makeCruiseFilters(formatSupplier, messages) {
141
+ return [
142
+ { field: "status", label: messages.filters.status },
143
+ {
144
+ field: "source.kind",
145
+ label: messages.filters.source,
146
+ formatValue: (value) => formatSourceKind(value, messages),
147
+ },
148
+ { field: "cruiseType", label: messages.filters.type },
149
+ {
150
+ // Departure month/year facet — `YYYY-MM` values sorted chronologically,
151
+ // shown as "Mar 2027".
152
+ field: "departureMonths",
153
+ label: messages.filters.departureMonth,
154
+ formatValue: formatDepartureMonth,
155
+ sortValues: "value-asc",
156
+ },
157
+ { field: "lineSupplierId", label: messages.filters.supplier, formatValue: formatSupplier },
158
+ { field: "defaultShipId", label: messages.filters.ship },
159
+ { field: "embarkPortFacilityId", label: messages.filters.embark },
160
+ { field: "disembarkPortFacilityId", label: messages.filters.disembark },
161
+ { field: "regions", label: messages.filters.region },
162
+ { field: "themes", label: messages.filters.theme },
163
+ {
164
+ kind: "range",
165
+ field: "nights",
166
+ label: messages.filters.nights,
167
+ minPlaceholder: "0",
168
+ maxPlaceholder: messages.filters.any,
169
+ },
170
+ ];
171
+ }
172
+ export function makeCharterFilters(formatSupplier, messages) {
173
+ return [
174
+ { field: "status", label: messages.filters.status },
175
+ {
176
+ field: "source.kind",
177
+ label: messages.filters.source,
178
+ formatValue: (value) => formatSourceKind(value, messages),
179
+ },
180
+ { field: "lineSupplierId", label: messages.filters.supplier, formatValue: formatSupplier },
181
+ { field: "defaultYachtId", label: messages.filters.yacht },
182
+ { field: "defaultBookingModes", label: messages.filters.bookingMode },
183
+ { field: "regions", label: messages.filters.region },
184
+ { field: "themes", label: messages.filters.theme },
185
+ {
186
+ kind: "range",
187
+ field: "lowestPriceCachedAmount",
188
+ label: messages.filters.price,
189
+ format: "currency",
190
+ currency: "EUR",
191
+ step: 100,
192
+ minPlaceholder: "0",
193
+ maxPlaceholder: messages.filters.any,
194
+ },
195
+ {
196
+ kind: "range",
197
+ field: "defaultApaPercent",
198
+ label: messages.filters.apaPercent,
199
+ step: 1,
200
+ minPlaceholder: "0",
201
+ maxPlaceholder: "100",
202
+ },
203
+ ];
204
+ }
205
+ export function makeAccommodationFilters(formatSupplier, messages) {
206
+ return [
207
+ { field: "active", label: messages.filters.active },
208
+ {
209
+ field: "source.kind",
210
+ label: messages.filters.source,
211
+ formatValue: (value) => formatSourceKind(value, messages),
212
+ },
213
+ { field: "supplierId", label: messages.filters.supplier, formatValue: formatSupplier },
214
+ { field: "inventoryMode", label: messages.filters.inventory },
215
+ { field: "roomClass", label: messages.filters.class },
216
+ { field: "smokingAllowed", label: messages.filters.smoking },
217
+ { field: "propertyId", label: messages.filters.property },
218
+ {
219
+ kind: "range",
220
+ field: "maxOccupancy",
221
+ label: messages.filters.maxPax,
222
+ minPlaceholder: "0",
223
+ maxPlaceholder: messages.filters.any,
224
+ },
225
+ {
226
+ kind: "range",
227
+ field: "bedroomCount",
228
+ label: messages.filters.bedrooms,
229
+ minPlaceholder: "0",
230
+ maxPlaceholder: messages.filters.any,
231
+ },
232
+ {
233
+ kind: "range",
234
+ field: "bathroomCount",
235
+ label: messages.filters.bathrooms,
236
+ minPlaceholder: "0",
237
+ maxPlaceholder: messages.filters.any,
238
+ },
239
+ ];
240
+ }
241
+ function nameColumn(fallback, messages, urlField = "thumbnailUrl") {
242
+ return {
243
+ id: "name",
244
+ header: messages.columns.name,
245
+ cell: ({ row }) => (_jsxs("div", { className: "flex items-center gap-3", children: [_jsx(CatalogThumbnail, { hit: row.original, urlField: urlField }), _jsx("span", { className: "font-medium", children: stringField(row.original, "name", fallback) })] })),
246
+ };
247
+ }
248
+ function statusColumn(messages) {
249
+ return {
250
+ id: "status",
251
+ header: messages.columns.status,
252
+ cell: ({ row }) => {
253
+ const status = stringField(row.original, "status", null);
254
+ if (!status)
255
+ return null;
256
+ return (_jsx(Badge, { variant: status === "active" ? "default" : "secondary", className: "capitalize", children: status }));
257
+ },
258
+ };
259
+ }
260
+ function activeColumn(messages) {
261
+ return {
262
+ id: "active",
263
+ header: messages.columns.active,
264
+ cell: ({ row }) => {
265
+ const v = stringField(row.original, "active", null);
266
+ if (v == null)
267
+ return null;
268
+ const isActive = v === "true" || v === "1";
269
+ return (_jsx(Badge, { variant: isActive ? "default" : "secondary", children: isActive ? messages.values.active : messages.values.inactive }));
270
+ },
271
+ };
272
+ }
273
+ function textColumn(field, header, messages) {
274
+ return {
275
+ id: field,
276
+ header,
277
+ cell: ({ row }) => (_jsx("span", { className: "text-muted-foreground text-sm", children: stringField(row.original, field, messages.values.empty) })),
278
+ };
279
+ }
280
+ function daysColumn(messages) {
281
+ return {
282
+ id: "durationDays",
283
+ header: messages.columns.days,
284
+ cell: ({ row }) => {
285
+ const days = numberField(row.original, "durationDays");
286
+ if (days == null)
287
+ return _jsx("span", { className: "text-muted-foreground", children: messages.values.empty });
288
+ return _jsx("span", { className: "tabular-nums", children: days });
289
+ },
290
+ };
291
+ }
292
+ function nightsColumn(messages) {
293
+ return {
294
+ id: "nights",
295
+ header: messages.columns.nights,
296
+ cell: ({ row }) => {
297
+ const days = numberField(row.original, "durationDays");
298
+ if (days == null || days < 1)
299
+ return _jsx("span", { className: "text-muted-foreground", children: messages.values.empty });
300
+ return _jsx("span", { className: "tabular-nums", children: Math.max(0, days - 1) });
301
+ },
302
+ };
303
+ }
304
+ function sourceColumn(messages) {
305
+ return {
306
+ id: "source.kind",
307
+ header: messages.columns.source,
308
+ cell: ({ row }) => {
309
+ const kind = stringField(row.original, "source.kind", null);
310
+ if (!kind)
311
+ return _jsx("span", { className: "text-muted-foreground", children: messages.values.empty });
312
+ return (_jsx(Badge, { variant: kind === "owned" ? "secondary" : "outline", children: formatSourceKind(kind, messages) }));
313
+ },
314
+ };
315
+ }
316
+ export function formatSourceKind(value, messages) {
317
+ const raw = String(value);
318
+ const known = {
319
+ owned: messages.sourceKinds.owned,
320
+ "voyant-connect": messages.sourceKinds.voyantConnect,
321
+ manual: messages.sourceKinds.manual,
322
+ "gds:amadeus": messages.sourceKinds.gdsAmadeus,
323
+ "gds:sabre": messages.sourceKinds.gdsSabre,
324
+ "gds:travelport": messages.sourceKinds.gdsTravelport,
325
+ "bedbank:hotelbeds": messages.sourceKinds.bedbankHotelbeds,
326
+ "bedbank:expedia": messages.sourceKinds.bedbankExpedia,
327
+ };
328
+ if (known[raw])
329
+ return known[raw];
330
+ if (raw.startsWith("direct:")) {
331
+ return `${raw.slice("direct:".length).toUpperCase()} ${messages.sourceKinds.directSuffix}`;
332
+ }
333
+ return raw
334
+ .split(/[:\-_]/)
335
+ .filter(Boolean)
336
+ .map((s) => s.charAt(0).toUpperCase() + s.slice(1))
337
+ .join(" ");
338
+ }
339
+ function lookupColumn(field, header, format, messages) {
340
+ return {
341
+ id: field,
342
+ header,
343
+ cell: ({ row }) => {
344
+ const id = stringField(row.original, field, null);
345
+ if (!id)
346
+ return _jsx("span", { className: "text-muted-foreground", children: messages.values.empty });
347
+ return _jsx("span", { children: format(id) });
348
+ },
349
+ };
350
+ }
351
+ function priceColumn(amountField, currencyField, header, messages, unit = "minor", unitField) {
352
+ return {
353
+ id: amountField,
354
+ header,
355
+ cell: ({ row }) => {
356
+ const resolvedUnit = resolveHitPriceUnit(row.original, unit, unitField);
357
+ const formatted = formatHitPrice(row.original, amountField, currencyField, resolvedUnit);
358
+ return (_jsx("span", { className: "font-medium", children: formatted ?? _jsx("span", { className: "text-muted-foreground", children: messages.values.empty }) }));
359
+ },
360
+ };
361
+ }
362
+ function CatalogThumbnail({ hit, urlField }) {
363
+ const url = stringField(hit, urlField, null);
364
+ const name = stringField(hit, "name", "");
365
+ if (url) {
366
+ return (_jsx("img", { src: url, alt: name, className: "h-9 w-9 shrink-0 rounded-md object-cover", loading: "lazy" }));
367
+ }
368
+ return (_jsx("div", { className: "flex h-9 w-9 shrink-0 items-center justify-center rounded-md bg-muted text-muted-foreground", "aria-hidden": "true", children: _jsx(ImageIcon, { className: "h-4 w-4" }) }));
369
+ }
@@ -0,0 +1,88 @@
1
+ import type { ReactNode } from "react";
2
+ import type { CatalogSearchHit } from "../index.js";
3
+ import type { CatalogDetailEnrichment, CatalogDetailRenderSlot, CatalogDetailSheetProps } from "./catalog-detail-sheet.js";
4
+ import type { CatalogEnrichmentFetchers } from "./catalog-enrichment-fetchers.js";
5
+ import { type CatalogFilterSelections, type CatalogSortOption } from "./catalog-search-page.js";
6
+ export interface CatalogPageSearchState {
7
+ tab?: string;
8
+ q?: string;
9
+ page?: number;
10
+ market?: string;
11
+ locale?: string;
12
+ view?: "grid" | "list";
13
+ sort?: CatalogSortOption;
14
+ filters?: CatalogFilterSelections;
15
+ }
16
+ export interface CatalogPageProps {
17
+ search?: CatalogPageSearchState;
18
+ onTabChange?: (tabId: string) => void;
19
+ onQueryChange?: (query: string) => void;
20
+ onPageChange?: (page: number) => void;
21
+ onViewChange?: (view: "grid" | "list") => void;
22
+ onSortChange?: (sort: CatalogSortOption) => void;
23
+ onFiltersChange?: (filters: CatalogFilterSelections) => void;
24
+ toolbarEnd?: ReactNode;
25
+ formatSupplier?: (id: string | number) => string;
26
+ onBookHit?: (hit: CatalogSearchHit, entityModule: string) => void;
27
+ onBookDeparture?: (hit: CatalogSearchHit, entityModule: string, departure: NonNullable<CatalogDetailEnrichment["departures"]>[number]) => void;
28
+ onBookOption?: (hit: CatalogSearchHit, entityModule: string, departure: NonNullable<CatalogDetailEnrichment["departures"]>[number], option: NonNullable<CatalogDetailEnrichment["options"]>[number]) => void;
29
+ onOpenProductEditor?: (hit: CatalogSearchHit) => void;
30
+ /**
31
+ * Open the full, URL-addressable detail page for a result (e.g. in a new
32
+ * tab) instead of the in-page detail sheet. Bound per vertical and passed
33
+ * the clicked hit + its vertical; return nothing. Provide it only for
34
+ * verticals that have a dedicated detail page — others keep the sheet.
35
+ */
36
+ onOpenProductDetail?: (hit: CatalogSearchHit, vertical: string) => void;
37
+ /**
38
+ * Explicit detail-enrichment callback. When set, takes precedence over
39
+ * `enrichmentFetchers` — pass this when you need full control over the
40
+ * request (auth headers, locale resolution, side-channel data). The
41
+ * default integration uses `enrichmentFetchers` for the common case.
42
+ */
43
+ onLoadProductDetail?: (hit: CatalogSearchHit, vertical?: string) => Promise<CatalogDetailEnrichment | null>;
44
+ /**
45
+ * Declarative detail-enrichment fetchers. Build with
46
+ * `createCatalogEnrichmentFetchers({ baseUrl, … })`. When provided
47
+ * (and `onLoadProductDetail` is not), the detail sheet calls
48
+ * `fetchers.loadProductDetail` on open. This is the recommended way
49
+ * to wire up the sheet — it pins the route contract with
50
+ * `createProductContentRoutes` so a missing server-side mount is
51
+ * caught immediately instead of rendering an empty sheet.
52
+ */
53
+ enrichmentFetchers?: CatalogEnrichmentFetchers;
54
+ detailSheetWidth?: CatalogDetailSheetProps["width"];
55
+ detailHeaderExtras?: CatalogDetailSheetProps["headerExtras"];
56
+ renderDetailBrochure?: CatalogDetailRenderSlot;
57
+ renderDetailMedia?: CatalogDetailRenderSlot;
58
+ renderDetailItineraryDay?: CatalogDetailSheetProps["renderItineraryDay"];
59
+ renderDetailExtraSections?: CatalogDetailRenderSlot;
60
+ /**
61
+ * Renders the supplier value in the detail sheet's Attributes tab as
62
+ * a clickable link to the supplier record. Templates wire this with
63
+ * their router's `Link` component. When omitted, the supplier shows
64
+ * as plain text (the resolved supplier name via `formatSupplier`).
65
+ */
66
+ renderSupplierLink?: CatalogDetailSheetProps["renderSupplierLink"];
67
+ /**
68
+ * Inline tags editor for the detail sheet. When set, the Tags row in
69
+ * the Overview tab becomes editable; the callback persists the next
70
+ * tag list (e.g. a product PATCH). Owned products only on the
71
+ * operator side — sourced rows pass through without an editor.
72
+ */
73
+ onTagsChange?: CatalogDetailSheetProps["onTagsChange"];
74
+ /**
75
+ * Restrict the page to one catalog vertical and hide the vertical tab
76
+ * switcher. Use this when routing owns the vertical selection.
77
+ */
78
+ vertical?: string;
79
+ title?: ReactNode;
80
+ className?: string;
81
+ /**
82
+ * Hide the built-in search input. Use when an embedding surface provides its
83
+ * own unified search box and drives `search.q`/`onQueryChange` externally.
84
+ */
85
+ hideSearchInput?: boolean;
86
+ }
87
+ export declare function CatalogPage({ search, onTabChange, onQueryChange, onPageChange, onViewChange, onSortChange, onFiltersChange, toolbarEnd, hideSearchInput, formatSupplier, onBookHit, onBookDeparture, onBookOption, onOpenProductEditor, onOpenProductDetail, onLoadProductDetail, enrichmentFetchers, detailSheetWidth, detailHeaderExtras, renderDetailBrochure, renderDetailMedia, renderDetailItineraryDay, renderDetailExtraSections, renderSupplierLink, onTagsChange, vertical, title, className, }: CatalogPageProps): import("react/jsx-runtime").JSX.Element;
88
+ //# sourceMappingURL=catalog-page.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog-page.d.ts","sourceRoot":"","sources":["../../src/components/catalog-page.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,148 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { cn } from "@voyant-travel/ui/lib/utils";
4
+ import { useMemo } from "react";
5
+ import { useCatalogUiMessagesOrDefault } from "../i18n/index.js";
6
+ import { stringField } from "./catalog-hit.js";
7
+ import { makeAccommodationCard, makeCharterCard, makeCruiseCard, makeProductCard, } from "./catalog-page-cards.js";
8
+ import { formatSourceKind, makeAccommodationColumns, makeAccommodationFilters, makeCharterColumns, makeCharterFilters, makeCruiseColumns, makeCruiseFilters, makeExtraColumns, makeExtraFilters, makeProductColumns, makeProductFilters, } from "./catalog-page-config.js";
9
+ import { CatalogSearchPage, } from "./catalog-search-page.js";
10
+ export function CatalogPage({ search = {}, onTabChange, onQueryChange, onPageChange, onViewChange, onSortChange, onFiltersChange, toolbarEnd, hideSearchInput, formatSupplier = (id) => String(id), onBookHit, onBookDeparture, onBookOption, onOpenProductEditor, onOpenProductDetail, onLoadProductDetail, enrichmentFetchers, detailSheetWidth, detailHeaderExtras, renderDetailBrochure, renderDetailMedia, renderDetailItineraryDay, renderDetailExtraSections, renderSupplierLink, onTagsChange, vertical, title, className, }) {
11
+ const messages = useCatalogUiMessagesOrDefault().catalogPage;
12
+ const resolvedLoadProductDetail = useMemo(() => onLoadProductDetail ?? enrichmentFetchers?.loadProductDetail, [onLoadProductDetail, enrichmentFetchers]);
13
+ // Each tab binds the loader to its vertical so the detail sheet fetches
14
+ // from the right content route (e.g. cruises → /v1/admin/cruises). Without
15
+ // this, only the products tab loaded enrichment and every other vertical's
16
+ // sheet rendered the bare projection.
17
+ const detailLoaderFor = (vertical) => resolvedLoadProductDetail
18
+ ? (hit) => resolvedLoadProductDetail(hit, vertical)
19
+ : undefined;
20
+ // Open the dedicated detail page (e.g. new tab), bound per vertical. Only
21
+ // verticals with a real detail page get it; the rest fall back to the sheet.
22
+ const detailOpenerFor = (vertical) => onOpenProductDetail ? (hit) => onOpenProductDetail(hit, vertical) : undefined;
23
+ // Lazy per-cabin pricing loader, bound per vertical (cruises only today).
24
+ const loadDeparturePricing = enrichmentFetchers?.loadDeparturePricing;
25
+ const departurePricingLoaderFor = (vertical) => loadDeparturePricing
26
+ ? (hit, sailingRef) => loadDeparturePricing(hit, sailingRef, vertical)
27
+ : undefined;
28
+ const supplierFormatter = (value) => typeof value === "string" ? formatSupplier(value) : String(value ?? "");
29
+ const sourceKindFormatter = (value) => typeof value === "string" || typeof value === "number"
30
+ ? formatSourceKind(value, messages)
31
+ : String(value ?? "");
32
+ const tabs = [
33
+ {
34
+ id: "products",
35
+ label: messages.tabs.products,
36
+ vertical: "products",
37
+ columns: makeProductColumns(formatSupplier, messages),
38
+ filterFields: makeProductFilters(formatSupplier, messages),
39
+ card: makeProductCard(formatSupplier, messages),
40
+ sorts: ["price-asc", "price-desc", "departure-asc", "newest"],
41
+ detailFormatters: {
42
+ supplierId: supplierFormatter,
43
+ "source.kind": sourceKindFormatter,
44
+ },
45
+ detailActions: [
46
+ ...(onBookHit
47
+ ? [
48
+ {
49
+ label: messages.actions.bookThis,
50
+ onClick: (hit) => onBookHit(hit, "products"),
51
+ },
52
+ ]
53
+ : []),
54
+ ...(onOpenProductEditor
55
+ ? [
56
+ {
57
+ label: messages.actions.openEditor,
58
+ onClick: onOpenProductEditor,
59
+ visible: (hit) => {
60
+ const kind = stringField(hit, "source.kind", null);
61
+ // Owned products are the only ones that have an editor —
62
+ // sourced rows are read-only mirrors of the upstream.
63
+ return kind === "owned" || kind == null;
64
+ },
65
+ },
66
+ ]
67
+ : []),
68
+ ],
69
+ onLoadDetail: detailLoaderFor("products"),
70
+ onBookDeparture: onBookDeparture
71
+ ? (hit, departure) => onBookDeparture(hit, "products", departure)
72
+ : undefined,
73
+ onBookOption: onBookOption
74
+ ? (hit, departure, option) => onBookOption(hit, "products", departure, option)
75
+ : undefined,
76
+ },
77
+ {
78
+ id: "extras",
79
+ label: messages.tabs.extras,
80
+ vertical: "extras",
81
+ columns: makeExtraColumns(formatSupplier, messages),
82
+ filterFields: makeExtraFilters(formatSupplier, messages),
83
+ detailFormatters: {
84
+ supplierId: supplierFormatter,
85
+ "source.kind": sourceKindFormatter,
86
+ },
87
+ onLoadDetail: detailLoaderFor("extras"),
88
+ },
89
+ {
90
+ id: "cruises",
91
+ label: messages.tabs.cruises,
92
+ vertical: "cruises",
93
+ columns: makeCruiseColumns(formatSupplier, messages),
94
+ filterFields: makeCruiseFilters(formatSupplier, messages),
95
+ imageField: "thumbnailUrl",
96
+ card: makeCruiseCard(formatSupplier, messages),
97
+ detailFormatters: {
98
+ lineSupplierId: supplierFormatter,
99
+ "source.kind": sourceKindFormatter,
100
+ },
101
+ onLoadDetail: detailLoaderFor("cruises"),
102
+ onLoadDeparturePricing: departurePricingLoaderFor("cruises"),
103
+ },
104
+ {
105
+ id: "charters",
106
+ label: messages.tabs.charters,
107
+ vertical: "charters",
108
+ columns: makeCharterColumns(formatSupplier, messages),
109
+ filterFields: makeCharterFilters(formatSupplier, messages),
110
+ imageField: "heroImageUrl",
111
+ card: makeCharterCard(formatSupplier),
112
+ sorts: ["price-asc", "price-desc", "newest"],
113
+ detailFormatters: {
114
+ lineSupplierId: supplierFormatter,
115
+ "source.kind": sourceKindFormatter,
116
+ },
117
+ onLoadDetail: detailLoaderFor("charters"),
118
+ },
119
+ {
120
+ id: "accommodations",
121
+ label: messages.tabs.accommodations,
122
+ vertical: "accommodations",
123
+ columns: makeAccommodationColumns(formatSupplier, messages),
124
+ filterFields: makeAccommodationFilters(formatSupplier, messages),
125
+ card: makeAccommodationCard(formatSupplier),
126
+ detailFormatters: {
127
+ supplierId: supplierFormatter,
128
+ "source.kind": sourceKindFormatter,
129
+ },
130
+ onLoadDetail: detailLoaderFor("accommodations"),
131
+ },
132
+ ];
133
+ // Bind the new-tab detail opener per vertical on every tab — when the host
134
+ // provides `onOpenProductDetail`, results open the dedicated detail page
135
+ // (new tab) instead of the in-page sheet, for whichever vertical is shown.
136
+ const tabsWithDetail = tabs.map((tab) => ({
137
+ ...tab,
138
+ onOpenDetail: detailOpenerFor(tab.vertical),
139
+ }));
140
+ const visibleTabs = vertical
141
+ ? tabsWithDetail.filter((tab) => tab.id === vertical || tab.vertical === vertical)
142
+ : tabsWithDetail;
143
+ const activeTab = vertical ? visibleTabs[0]?.id : (search.tab ?? tabs[0]?.id);
144
+ return (_jsx("div", { className: cn("mx-auto w-full max-w-screen-2xl px-6 py-6 lg:px-8", className), children: _jsx(CatalogSearchPage, { tabs: visibleTabs, activeTab: activeTab, onActiveTabChange: (id) => {
145
+ if (!vertical)
146
+ onTabChange?.(id);
147
+ }, showTabs: !vertical, hideSearchInput: hideSearchInput, query: search.q ?? "", onQueryChange: (q) => onQueryChange?.(q), page: search.page ?? 1, onPageChange: (p) => onPageChange?.(p), view: search.view, onViewChange: onViewChange, sort: search.sort, onSortChange: onSortChange, filters: search.filters, onFiltersChange: onFiltersChange, market: search.market, locale: search.locale, toolbarEnd: toolbarEnd, detailSheetWidth: detailSheetWidth, detailHeaderExtras: detailHeaderExtras, renderDetailBrochure: renderDetailBrochure, renderDetailMedia: renderDetailMedia, renderDetailItineraryDay: renderDetailItineraryDay, renderDetailExtraSections: renderDetailExtraSections, renderSupplierLink: renderSupplierLink, onTagsChange: onTagsChange, title: title ?? (_jsxs("div", { children: [_jsx("h1", { className: "font-semibold text-2xl", children: messages.title }), _jsx("p", { className: "text-muted-foreground text-sm", children: messages.description })] })), searchPlaceholder: messages.searchPlaceholder }) }));
148
+ }
@@ -0,0 +1,34 @@
1
+ export interface CatalogRangeFilterValue {
2
+ gte?: number;
3
+ lte?: number;
4
+ }
5
+ export interface CatalogRangeFilterProps {
6
+ /** Field name (matches the indexer document field). */
7
+ field: string;
8
+ /** Display label for the trigger. */
9
+ label: string;
10
+ /** Current selection. `undefined` means no filter. */
11
+ value: CatalogRangeFilterValue | undefined;
12
+ /** Apply a new range. */
13
+ onChange: (next: CatalogRangeFilterValue | undefined) => void;
14
+ /** Number-input step (default 1). */
15
+ step?: number;
16
+ /** Optional placeholder min/max — purely visual. */
17
+ minPlaceholder?: string;
18
+ maxPlaceholder?: string;
19
+ /**
20
+ * Format the active selection in the trigger badge. When the field
21
+ * stores money in cents, use `format="currency"` + `currency` so the
22
+ * trigger displays e.g. "€100 – €500".
23
+ */
24
+ format?: "number" | "currency";
25
+ /** ISO 4217 currency code when `format === "currency"`. */
26
+ currency?: string;
27
+ }
28
+ /**
29
+ * Numeric-range filter — popover trigger styled like the faceted filter
30
+ * for visual consistency. When active, the trigger shows the range as a
31
+ * compact badge ("≥ €100", "≤ €500", or "€100 – €500").
32
+ */
33
+ export declare function CatalogRangeFilter({ label, value, onChange, step, minPlaceholder, maxPlaceholder, format, currency, }: CatalogRangeFilterProps): import("react/jsx-runtime").JSX.Element;
34
+ //# sourceMappingURL=catalog-range-filter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog-range-filter.d.ts","sourceRoot":"","sources":["../../src/components/catalog-range-filter.tsx"],"names":[],"mappings":"AAWA,MAAM,WAAW,uBAAuB;IACtC,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,uBAAuB;IACtC,uDAAuD;IACvD,KAAK,EAAE,MAAM,CAAA;IACb,qCAAqC;IACrC,KAAK,EAAE,MAAM,CAAA;IACb,sDAAsD;IACtD,KAAK,EAAE,uBAAuB,GAAG,SAAS,CAAA;IAC1C,yBAAyB;IACzB,QAAQ,EAAE,CAAC,IAAI,EAAE,uBAAuB,GAAG,SAAS,KAAK,IAAI,CAAA;IAC7D,qCAAqC;IACrC,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,oDAAoD;IACpD,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB;;;;OAIG;IACH,MAAM,CAAC,EAAE,QAAQ,GAAG,UAAU,CAAA;IAC9B,2DAA2D;IAC3D,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,KAAK,EACL,KAAK,EACL,QAAQ,EACR,IAAQ,EACR,cAAc,EACd,cAAc,EACd,MAAiB,EACjB,QAAQ,GACT,EAAE,uBAAuB,2CAwFzB"}