@vendure/dashboard 3.4.3-master-202509260228 → 3.5.0-minor-202510012036

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 (295) hide show
  1. package/README.md +4 -0
  2. package/dist/plugin/api/api-extensions.js +11 -14
  3. package/dist/plugin/api/metrics.resolver.d.ts +2 -2
  4. package/dist/plugin/api/metrics.resolver.js +2 -2
  5. package/dist/plugin/config/metrics-strategies.d.ts +9 -9
  6. package/dist/plugin/config/metrics-strategies.js +6 -6
  7. package/dist/plugin/constants.d.ts +2 -0
  8. package/dist/plugin/constants.js +3 -1
  9. package/dist/plugin/dashboard.plugin.js +13 -0
  10. package/dist/plugin/service/metrics.service.d.ts +3 -3
  11. package/dist/plugin/service/metrics.service.js +37 -53
  12. package/dist/plugin/types.d.ts +9 -12
  13. package/dist/plugin/types.js +7 -11
  14. package/dist/vite/vite-plugin-config.js +13 -9
  15. package/dist/vite/vite-plugin-translations.d.ts +22 -0
  16. package/dist/vite/vite-plugin-translations.js +66 -0
  17. package/dist/vite/vite-plugin-vendure-dashboard.js +10 -8
  18. package/lingui.config.js +25 -2
  19. package/package.json +159 -156
  20. package/src/app/app-providers.tsx +0 -4
  21. package/src/app/common/delete-bulk-action.tsx +6 -5
  22. package/src/app/common/duplicate-bulk-action.tsx +4 -5
  23. package/src/app/common/duplicate-entity-dialog.tsx +1 -1
  24. package/src/app/common/set-document-direction.ts +7 -0
  25. package/src/app/main.tsx +50 -17
  26. package/src/app/routes/_authenticated/_administrators/administrators.tsx +8 -6
  27. package/src/app/routes/_authenticated/_administrators/administrators_.$id.tsx +17 -6
  28. package/src/app/routes/_authenticated/_administrators/components/role-permissions-display.tsx +2 -2
  29. package/src/app/routes/_authenticated/_assets/assets.tsx +1 -1
  30. package/src/app/routes/_authenticated/_assets/assets_.$id.tsx +4 -4
  31. package/src/app/routes/_authenticated/_assets/components/asset-bulk-actions.tsx +8 -6
  32. package/src/app/routes/_authenticated/_assets/components/asset-tag-filter.tsx +1 -1
  33. package/src/app/routes/_authenticated/_assets/components/asset-tags-editor.tsx +1 -1
  34. package/src/app/routes/_authenticated/_assets/components/manage-tags-dialog.tsx +3 -8
  35. package/src/app/routes/_authenticated/_channels/channels.tsx +3 -6
  36. package/src/app/routes/_authenticated/_channels/channels_.$id.tsx +5 -5
  37. package/src/app/routes/_authenticated/_collections/collections.tsx +10 -6
  38. package/src/app/routes/_authenticated/_collections/collections_.$id.tsx +16 -5
  39. package/src/app/routes/_authenticated/_collections/components/collection-bulk-actions.tsx +1 -1
  40. package/src/app/routes/_authenticated/_collections/components/collection-contents-sheet.tsx +1 -1
  41. package/src/app/routes/_authenticated/_collections/components/move-collections-dialog.tsx +6 -6
  42. package/src/app/routes/_authenticated/_countries/countries.graphql.ts +2 -0
  43. package/src/app/routes/_authenticated/_countries/countries.tsx +2 -3
  44. package/src/app/routes/_authenticated/_countries/countries_.$id.tsx +4 -4
  45. package/src/app/routes/_authenticated/_customer-groups/components/customer-group-members-sheet.tsx +1 -1
  46. package/src/app/routes/_authenticated/_customer-groups/components/customer-group-members-table.tsx +4 -4
  47. package/src/app/routes/_authenticated/_customer-groups/customer-groups.tsx +2 -4
  48. package/src/app/routes/_authenticated/_customer-groups/customer-groups_.$id.tsx +13 -6
  49. package/src/app/routes/_authenticated/_customers/components/customer-address-card.tsx +8 -8
  50. package/src/app/routes/_authenticated/_customers/components/customer-address-form.tsx +3 -3
  51. package/src/app/routes/_authenticated/_customers/components/customer-history/customer-history-container.tsx +1 -1
  52. package/src/app/routes/_authenticated/_customers/components/customer-history/customer-history-utils.tsx +1 -1
  53. package/src/app/routes/_authenticated/_customers/components/customer-history/default-customer-history-components.tsx +1 -1
  54. package/src/app/routes/_authenticated/_customers/components/customer-history/use-customer-history.ts +1 -1
  55. package/src/app/routes/_authenticated/_customers/components/customer-status-badge.tsx +1 -1
  56. package/src/app/routes/_authenticated/_customers/customers.graphql.ts +4 -0
  57. package/src/app/routes/_authenticated/_customers/customers.tsx +23 -11
  58. package/src/app/routes/_authenticated/_customers/customers_.$id.tsx +10 -8
  59. package/src/app/routes/_authenticated/_facets/components/edit-facet-value.tsx +1 -1
  60. package/src/app/routes/_authenticated/_facets/components/facet-bulk-actions.tsx +6 -5
  61. package/src/app/routes/_authenticated/_facets/components/facet-values-sheet.tsx +1 -1
  62. package/src/app/routes/_authenticated/_facets/components/facet-values-table.tsx +1 -1
  63. package/src/app/routes/_authenticated/_facets/facets.tsx +5 -5
  64. package/src/app/routes/_authenticated/_facets/facets_.$facetId.values_.$id.tsx +7 -5
  65. package/src/app/routes/_authenticated/_facets/facets_.$id.tsx +18 -6
  66. package/src/app/routes/_authenticated/_global-settings/global-settings.tsx +5 -5
  67. package/src/app/routes/_authenticated/_orders/components/add-manual-payment-dialog.tsx +19 -21
  68. package/src/app/routes/_authenticated/_orders/components/customer-address-selector.tsx +1 -1
  69. package/src/app/routes/_authenticated/_orders/components/edit-order-table.tsx +22 -22
  70. package/src/app/routes/_authenticated/_orders/components/fulfill-order-dialog.tsx +6 -6
  71. package/src/app/routes/_authenticated/_orders/components/fulfillment-details.tsx +15 -9
  72. package/src/app/routes/_authenticated/_orders/components/order-address.tsx +1 -1
  73. package/src/app/routes/_authenticated/_orders/components/order-detail-shared.tsx +11 -9
  74. package/src/app/routes/_authenticated/_orders/components/order-history/default-order-history-components.tsx +1 -1
  75. package/src/app/routes/_authenticated/_orders/components/order-history/order-history-container.tsx +1 -1
  76. package/src/app/routes/_authenticated/_orders/components/order-history/order-history-utils.tsx +1 -1
  77. package/src/app/routes/_authenticated/_orders/components/order-history/use-order-history.ts +1 -1
  78. package/src/app/routes/_authenticated/_orders/components/order-line-custom-fields-form.tsx +1 -1
  79. package/src/app/routes/_authenticated/_orders/components/order-modification-preview-dialog.tsx +4 -4
  80. package/src/app/routes/_authenticated/_orders/components/order-modification-summary.tsx +1 -1
  81. package/src/app/routes/_authenticated/_orders/components/order-table-totals.tsx +27 -27
  82. package/src/app/routes/_authenticated/_orders/components/order-table.tsx +2 -2
  83. package/src/app/routes/_authenticated/_orders/components/order-tax-summary.tsx +1 -1
  84. package/src/app/routes/_authenticated/_orders/components/payment-details.tsx +26 -20
  85. package/src/app/routes/_authenticated/_orders/components/seller-orders-card.tsx +3 -1
  86. package/src/app/routes/_authenticated/_orders/components/settle-refund-dialog.tsx +6 -6
  87. package/src/app/routes/_authenticated/_orders/components/shipping-method-selector.tsx +1 -1
  88. package/src/app/routes/_authenticated/_orders/components/state-transition-control.tsx +1 -1
  89. package/src/app/routes/_authenticated/_orders/components/use-transition-order-to-state.tsx +3 -2
  90. package/src/app/routes/_authenticated/_orders/orders.tsx +5 -9
  91. package/src/app/routes/_authenticated/_orders/orders_.$aggregateOrderId_.seller-orders.$sellerOrderId.tsx +1 -1
  92. package/src/app/routes/_authenticated/_orders/orders_.$id.tsx +1 -1
  93. package/src/app/routes/_authenticated/_orders/orders_.$id_.modify.tsx +4 -4
  94. package/src/app/routes/_authenticated/_orders/orders_.draft.$id.tsx +17 -17
  95. package/src/app/routes/_authenticated/_orders/utils/order-detail-loaders.tsx +1 -1
  96. package/src/app/routes/_authenticated/_payment-methods/payment-methods.tsx +5 -6
  97. package/src/app/routes/_authenticated/_payment-methods/payment-methods_.$id.tsx +13 -6
  98. package/src/app/routes/_authenticated/_product-variants/components/product-variant-bulk-actions.tsx +1 -1
  99. package/src/app/routes/_authenticated/_product-variants/components/variant-price-detail.tsx +1 -1
  100. package/src/app/routes/_authenticated/_product-variants/product-variants.graphql.ts +10 -0
  101. package/src/app/routes/_authenticated/_product-variants/product-variants.tsx +9 -2
  102. package/src/app/routes/_authenticated/_product-variants/product-variants_.$id.tsx +13 -6
  103. package/src/app/routes/_authenticated/_products/components/add-option-group-dialog.tsx +5 -5
  104. package/src/app/routes/_authenticated/_products/components/add-product-variant-dialog.tsx +5 -5
  105. package/src/app/routes/_authenticated/_products/components/assign-facet-values-dialog.tsx +5 -4
  106. package/src/app/routes/_authenticated/_products/components/create-product-options-dialog.tsx +9 -12
  107. package/src/app/routes/_authenticated/_products/components/create-product-variants-dialog.tsx +1 -1
  108. package/src/app/routes/_authenticated/_products/components/create-product-variants.tsx +4 -4
  109. package/src/app/routes/_authenticated/_products/components/option-groups-editor.tsx +1 -1
  110. package/src/app/routes/_authenticated/_products/components/product-bulk-actions.tsx +1 -1
  111. package/src/app/routes/_authenticated/_products/components/product-option-group-badge.tsx +19 -0
  112. package/src/app/routes/_authenticated/_products/components/product-option-select.tsx +3 -3
  113. package/src/app/routes/_authenticated/_products/components/product-options-table.tsx +114 -0
  114. package/src/app/routes/_authenticated/_products/product-option-groups.graphql.ts +103 -0
  115. package/src/app/routes/_authenticated/_products/products.graphql.ts +44 -32
  116. package/src/app/routes/_authenticated/_products/products.tsx +34 -5
  117. package/src/app/routes/_authenticated/_products/products_.$id.tsx +29 -12
  118. package/src/app/routes/_authenticated/_products/products_.$id_.variants.tsx +11 -11
  119. package/src/app/routes/_authenticated/_products/products_.$productId.option-groups.$id.tsx +177 -0
  120. package/src/app/routes/_authenticated/_products/products_.$productId.option-groups.$productOptionGroupId.options_.$id.tsx +208 -0
  121. package/src/app/routes/_authenticated/_profile/profile.tsx +4 -4
  122. package/src/app/routes/_authenticated/_promotions/promotions.tsx +2 -4
  123. package/src/app/routes/_authenticated/_promotions/promotions_.$id.tsx +16 -9
  124. package/src/app/routes/_authenticated/_roles/components/permissions-table-grid.tsx +1 -1
  125. package/src/app/routes/_authenticated/_roles/roles.tsx +3 -6
  126. package/src/app/routes/_authenticated/_roles/roles_.$id.tsx +4 -6
  127. package/src/app/routes/_authenticated/_sellers/sellers.tsx +3 -4
  128. package/src/app/routes/_authenticated/_sellers/sellers_.$id.tsx +4 -4
  129. package/src/app/routes/_authenticated/_shipping-methods/components/price-display.tsx +5 -5
  130. package/src/app/routes/_authenticated/_shipping-methods/components/shipping-method-test-result-wrapper.tsx +1 -1
  131. package/src/app/routes/_authenticated/_shipping-methods/components/test-address-form.tsx +11 -11
  132. package/src/app/routes/_authenticated/_shipping-methods/components/test-order-builder.tsx +1 -1
  133. package/src/app/routes/_authenticated/_shipping-methods/components/test-shipping-methods-result.tsx +8 -8
  134. package/src/app/routes/_authenticated/_shipping-methods/components/test-shipping-methods-sheet.tsx +1 -1
  135. package/src/app/routes/_authenticated/_shipping-methods/components/test-single-method-result.tsx +8 -8
  136. package/src/app/routes/_authenticated/_shipping-methods/components/test-single-shipping-method-sheet.tsx +4 -4
  137. package/src/app/routes/_authenticated/_shipping-methods/shipping-methods.tsx +2 -3
  138. package/src/app/routes/_authenticated/_shipping-methods/shipping-methods_.$id.tsx +2 -2
  139. package/src/app/routes/_authenticated/_stock-locations/stock-locations.tsx +3 -4
  140. package/src/app/routes/_authenticated/_stock-locations/stock-locations_.$id.tsx +13 -6
  141. package/src/app/routes/_authenticated/_system/healthchecks.tsx +10 -4
  142. package/src/app/routes/_authenticated/_system/job-queue.tsx +10 -13
  143. package/src/app/routes/_authenticated/_system/scheduled-tasks.tsx +18 -16
  144. package/src/app/routes/_authenticated/_tax-categories/tax-categories.tsx +2 -4
  145. package/src/app/routes/_authenticated/_tax-categories/tax-categories_.$id.tsx +13 -6
  146. package/src/app/routes/_authenticated/_tax-rates/tax-rates.tsx +8 -12
  147. package/src/app/routes/_authenticated/_tax-rates/tax-rates_.$id.tsx +6 -4
  148. package/src/app/routes/_authenticated/_zones/components/zone-countries-sheet.tsx +4 -1
  149. package/src/app/routes/_authenticated/_zones/zones.tsx +4 -4
  150. package/src/app/routes/_authenticated/_zones/zones_.$id.tsx +8 -5
  151. package/src/app/routes/_authenticated/index.tsx +46 -25
  152. package/src/app/styles.css +4 -0
  153. package/src/i18n/common-strings.ts +111 -0
  154. package/src/i18n/locales/ar.po +4777 -0
  155. package/src/i18n/locales/cs.po +4777 -0
  156. package/src/i18n/locales/de.po +4299 -1101
  157. package/src/i18n/locales/en.po +3857 -659
  158. package/src/i18n/locales/es.po +4777 -0
  159. package/src/i18n/locales/fa.po +4777 -0
  160. package/src/i18n/locales/fr.po +4777 -0
  161. package/src/i18n/locales/he.po +4777 -0
  162. package/src/i18n/locales/hr.po +4777 -0
  163. package/src/i18n/locales/it.po +4777 -0
  164. package/src/i18n/locales/ja.po +4777 -0
  165. package/src/i18n/locales/ko.po +4628 -0
  166. package/src/i18n/locales/nb.po +4777 -0
  167. package/src/i18n/locales/ne.po +4777 -0
  168. package/src/i18n/locales/nl.po +4628 -0
  169. package/src/i18n/locales/pl.po +4777 -0
  170. package/src/i18n/locales/pt_BR.po +4777 -0
  171. package/src/i18n/locales/pt_PT.po +4777 -0
  172. package/src/i18n/locales/ru.po +4777 -0
  173. package/src/i18n/locales/sv.po +4777 -0
  174. package/src/i18n/locales/tr.po +4777 -0
  175. package/src/i18n/locales/uk.po +4777 -0
  176. package/src/i18n/locales/zh_Hans.po +4777 -0
  177. package/src/i18n/locales/zh_Hant.po +4777 -0
  178. package/src/lib/components/data-display/json.tsx +16 -1
  179. package/src/lib/components/data-input/combination-mode-input.tsx +1 -1
  180. package/src/lib/components/data-input/custom-field-list-input.tsx +11 -7
  181. package/src/lib/components/data-input/customer-group-input.tsx +27 -33
  182. package/src/lib/components/data-input/datetime-input.tsx +40 -1
  183. package/src/lib/components/data-input/default-relation-input.tsx +5 -4
  184. package/src/lib/components/data-input/index.ts +3 -0
  185. package/src/lib/components/data-input/product-multi-selector-input.tsx +14 -14
  186. package/src/lib/components/data-input/relation-selector.tsx +1 -1
  187. package/src/lib/components/data-input/select-with-options.tsx +1 -1
  188. package/src/lib/components/data-input/slug-input.tsx +290 -0
  189. package/src/lib/components/data-table/add-filter-menu.tsx +17 -10
  190. package/src/lib/components/data-table/data-table-bulk-action-item.tsx +45 -8
  191. package/src/lib/components/data-table/data-table-bulk-actions.tsx +4 -4
  192. package/src/lib/components/data-table/data-table-column-header.tsx +13 -8
  193. package/src/lib/components/data-table/data-table-context.tsx +91 -0
  194. package/src/lib/components/data-table/data-table-faceted-filter.tsx +2 -1
  195. package/src/lib/components/data-table/data-table-filter-badge.tsx +9 -5
  196. package/src/lib/components/data-table/data-table-filter-dialog.tsx +1 -1
  197. package/src/lib/components/data-table/data-table-utils.ts +21 -4
  198. package/src/lib/components/data-table/data-table-view-options.tsx +21 -10
  199. package/src/lib/components/data-table/data-table.tsx +146 -94
  200. package/src/lib/components/data-table/filters/data-table-boolean-filter.tsx +4 -4
  201. package/src/lib/components/data-table/global-views-bar.tsx +97 -0
  202. package/src/lib/components/data-table/global-views-sheet.tsx +11 -0
  203. package/src/lib/components/data-table/human-readable-operator.tsx +1 -1
  204. package/src/lib/components/data-table/manage-global-views-button.tsx +26 -0
  205. package/src/lib/components/data-table/my-views-button.tsx +47 -0
  206. package/src/lib/components/data-table/refresh-button.tsx +12 -3
  207. package/src/lib/components/data-table/save-view-button.tsx +41 -0
  208. package/src/lib/components/data-table/save-view-dialog.tsx +113 -0
  209. package/src/lib/components/data-table/use-generated-columns.tsx +13 -8
  210. package/src/lib/components/data-table/user-views-sheet.tsx +11 -0
  211. package/src/lib/components/data-table/views-sheet.tsx +305 -0
  212. package/src/lib/components/date-range-picker.tsx +186 -0
  213. package/src/lib/components/layout/app-sidebar.tsx +3 -1
  214. package/src/lib/components/layout/channel-switcher.tsx +8 -10
  215. package/src/lib/components/layout/dev-mode-indicator.tsx +1 -1
  216. package/src/lib/components/layout/generated-breadcrumbs.tsx +10 -8
  217. package/src/lib/components/layout/language-dialog.tsx +34 -13
  218. package/src/lib/components/layout/manage-languages-dialog.tsx +1 -1
  219. package/src/lib/components/layout/nav-main.tsx +23 -13
  220. package/src/lib/components/layout/nav-user.tsx +19 -23
  221. package/src/lib/components/login/login-form.tsx +1 -1
  222. package/src/lib/components/shared/asset/asset-bulk-actions.tsx +4 -4
  223. package/src/lib/components/shared/asset/asset-focal-point-editor.tsx +1 -1
  224. package/src/lib/components/shared/asset/asset-gallery.tsx +15 -14
  225. package/src/lib/components/shared/assign-to-channel-bulk-action.tsx +11 -11
  226. package/src/lib/components/shared/assign-to-channel-dialog.tsx +6 -5
  227. package/src/lib/components/shared/channel-code-label.tsx +1 -1
  228. package/src/lib/components/shared/channel-selector.tsx +4 -4
  229. package/src/lib/components/shared/configurable-operation-multi-selector.tsx +16 -14
  230. package/src/lib/components/shared/configurable-operation-selector.tsx +1 -1
  231. package/src/lib/components/shared/confirmation-dialog.tsx +8 -8
  232. package/src/lib/components/shared/country-selector.tsx +1 -1
  233. package/src/lib/components/shared/currency-selector.tsx +4 -4
  234. package/src/lib/components/shared/custom-fields-form.tsx +8 -24
  235. package/src/lib/components/shared/customer-address-form.tsx +3 -3
  236. package/src/lib/components/shared/customer-group-selector.tsx +1 -1
  237. package/src/lib/components/shared/customer-selector.tsx +1 -1
  238. package/src/lib/components/shared/error-page.tsx +1 -1
  239. package/src/lib/components/shared/facet-value-selector.tsx +10 -10
  240. package/src/lib/components/shared/history-timeline/history-note-checkbox.tsx +1 -1
  241. package/src/lib/components/shared/history-timeline/history-note-editor.tsx +1 -1
  242. package/src/lib/components/shared/history-timeline/history-note-entry.tsx +1 -1
  243. package/src/lib/components/shared/language-selector.tsx +4 -4
  244. package/src/lib/components/shared/navigation-confirmation.tsx +1 -1
  245. package/src/lib/components/shared/paginated-list-data-table.tsx +64 -34
  246. package/src/lib/components/shared/remove-from-channel-bulk-action.tsx +6 -5
  247. package/src/lib/components/shared/rich-text-editor/image-dialog.tsx +1 -1
  248. package/src/lib/components/shared/rich-text-editor/link-dialog.tsx +1 -1
  249. package/src/lib/components/shared/rich-text-editor/responsive-toolbar.tsx +1 -1
  250. package/src/lib/components/shared/rich-text-editor/table-edit-icons.tsx +1 -1
  251. package/src/lib/components/shared/role-code-label.tsx +1 -1
  252. package/src/lib/components/shared/role-selector.tsx +4 -4
  253. package/src/lib/components/shared/seller-selector.tsx +1 -1
  254. package/src/lib/components/shared/stock-level-label.tsx +3 -5
  255. package/src/lib/components/shared/table-cell/order-table-cell-components.tsx +3 -1
  256. package/src/lib/components/shared/tax-category-selector.tsx +1 -1
  257. package/src/lib/components/shared/translatable-form-field.tsx +15 -15
  258. package/src/lib/components/shared/zone-selector.tsx +1 -1
  259. package/src/lib/components/ui/button.tsx +1 -1
  260. package/src/lib/framework/dashboard-widget/base-widget.tsx +11 -9
  261. package/src/lib/framework/dashboard-widget/latest-orders-widget/index.tsx +35 -6
  262. package/src/lib/framework/dashboard-widget/metrics-widget/index.tsx +18 -12
  263. package/src/lib/framework/dashboard-widget/metrics-widget/metrics-widget.graphql.ts +9 -3
  264. package/src/lib/framework/dashboard-widget/orders-summary/index.tsx +26 -79
  265. package/src/lib/framework/dashboard-widget/widget-filters-context.tsx +35 -0
  266. package/src/lib/framework/defaults.ts +34 -63
  267. package/src/lib/framework/document-introspection/add-custom-fields.spec.ts +319 -9
  268. package/src/lib/framework/document-introspection/add-custom-fields.ts +60 -31
  269. package/src/lib/framework/document-introspection/get-document-structure.spec.ts +1 -159
  270. package/src/lib/framework/document-introspection/include-only-selected-list-fields.spec.ts +1840 -0
  271. package/src/lib/framework/document-introspection/include-only-selected-list-fields.ts +940 -0
  272. package/src/lib/framework/document-introspection/testing-utils.ts +161 -0
  273. package/src/lib/framework/extension-api/display-component-extensions.tsx +2 -0
  274. package/src/lib/framework/extension-api/types/data-table.ts +62 -4
  275. package/src/lib/framework/extension-api/types/navigation.ts +16 -0
  276. package/src/lib/framework/form-engine/utils.ts +34 -0
  277. package/src/lib/framework/layout-engine/page-layout.tsx +36 -36
  278. package/src/lib/framework/page/detail-page.tsx +10 -10
  279. package/src/lib/framework/page/list-page.tsx +289 -4
  280. package/src/lib/framework/page/use-extended-router.tsx +101 -34
  281. package/src/lib/graphql/api.ts +6 -2
  282. package/src/lib/graphql/graphql-env.d.ts +38 -26
  283. package/src/lib/hooks/use-display-locale.ts +40 -0
  284. package/src/lib/hooks/use-dynamic-translations.ts +46 -0
  285. package/src/lib/hooks/use-extended-detail-query.ts +1 -1
  286. package/src/lib/hooks/use-extended-list-query.ts +6 -1
  287. package/src/lib/hooks/use-local-format.ts +15 -1
  288. package/src/lib/hooks/use-saved-views.ts +230 -0
  289. package/src/lib/hooks/use-ui-language-loader.ts +30 -0
  290. package/src/lib/index.ts +15 -0
  291. package/src/lib/lib/load-i18n-messages.ts +17 -0
  292. package/src/lib/lib/trans.tsx +15 -11
  293. package/src/lib/providers/i18n-provider.tsx +7 -14
  294. package/src/lib/types/saved-views.ts +39 -0
  295. package/src/lib/utils/saved-views-utils.ts +40 -0
@@ -0,0 +1,186 @@
1
+ 'use client';
2
+
3
+ import { useDayPickerLocale } from '@/vdb/components/data-input/index.js';
4
+ import { Button } from '@/vdb/components/ui/button.js';
5
+ import { Calendar } from '@/vdb/components/ui/calendar.js';
6
+ import { Popover, PopoverContent, PopoverTrigger } from '@/vdb/components/ui/popover.js';
7
+ import { DefinedDateRange } from '@/vdb/framework/dashboard-widget/widget-filters-context.js';
8
+ import { useLocalFormat } from '@/vdb/hooks/use-local-format.js';
9
+ import { cn } from '@/vdb/lib/utils.js';
10
+ import { Trans, useLingui } from '@lingui/react/macro';
11
+ import { endOfDay, endOfMonth, endOfWeek, startOfDay, startOfMonth, startOfWeek, subDays } from 'date-fns';
12
+ import { CalendarIcon } from 'lucide-react';
13
+ import * as React from 'react';
14
+ import { DateRange } from 'react-day-picker';
15
+
16
+ interface DateRangePickerProps {
17
+ className?: string;
18
+ dateRange: DefinedDateRange;
19
+ onDateRangeChange: (range: DefinedDateRange) => void;
20
+ }
21
+
22
+ const presets = [
23
+ {
24
+ id: 'today',
25
+ label: <Trans context="date-range">Today</Trans>,
26
+ getValue: () => ({
27
+ from: startOfDay(new Date()),
28
+ to: endOfDay(new Date()),
29
+ }),
30
+ },
31
+ {
32
+ id: 'yesterday',
33
+ label: <Trans context="date-range">Yesterday</Trans>,
34
+ getValue: () => ({
35
+ from: startOfDay(subDays(new Date(), 1)),
36
+ to: endOfDay(subDays(new Date(), 1)),
37
+ }),
38
+ },
39
+ {
40
+ id: 'last-7-days',
41
+ label: <Trans context="date-range">Last 7 days</Trans>,
42
+ getValue: () => ({
43
+ from: startOfDay(subDays(new Date(), 6)),
44
+ to: endOfDay(new Date()),
45
+ }),
46
+ },
47
+ {
48
+ id: 'this-week',
49
+ label: <Trans context="date-range">This week</Trans>,
50
+ getValue: () => ({
51
+ from: startOfWeek(new Date(), { weekStartsOn: 1 }),
52
+ to: endOfWeek(new Date(), { weekStartsOn: 1 }),
53
+ }),
54
+ },
55
+ {
56
+ id: 'last-30-days',
57
+ label: <Trans context="date-range">Last 30 days</Trans>,
58
+ getValue: () => ({
59
+ from: startOfDay(subDays(new Date(), 29)),
60
+ to: endOfDay(new Date()),
61
+ }),
62
+ },
63
+ {
64
+ id: 'month-to-date',
65
+ label: <Trans context="date-range">Month to date</Trans>,
66
+ getValue: () => ({
67
+ from: startOfMonth(new Date()),
68
+ to: endOfDay(new Date()),
69
+ }),
70
+ },
71
+ {
72
+ id: 'this-month',
73
+ label: <Trans context="date-range">This month</Trans>,
74
+ getValue: () => ({
75
+ from: startOfMonth(new Date()),
76
+ to: endOfMonth(new Date()),
77
+ }),
78
+ },
79
+ {
80
+ id: 'last-month',
81
+ label: <Trans context="date-range">Last month</Trans>,
82
+ getValue: () => ({
83
+ from: startOfMonth(subDays(new Date(), 30)),
84
+ to: endOfMonth(subDays(new Date(), 30)),
85
+ }),
86
+ },
87
+ ];
88
+
89
+ export function DateRangePicker({ className, dateRange, onDateRangeChange }: DateRangePickerProps) {
90
+ const [open, setOpen] = React.useState(false);
91
+ const { t } = useLingui();
92
+ const { formatDate } = useLocalFormat();
93
+ const locale = useDayPickerLocale();
94
+ // Internal state uses react-day-picker's DateRange type for Calendar compatibility
95
+ const [selectedRange, setSelectedRange] = React.useState<DateRange>({
96
+ from: dateRange.from,
97
+ to: dateRange.to,
98
+ });
99
+
100
+ React.useEffect(() => {
101
+ setSelectedRange({
102
+ from: dateRange.from,
103
+ to: dateRange.to,
104
+ });
105
+ }, [dateRange]);
106
+
107
+ const handleSelect = (range: DateRange | undefined) => {
108
+ if (range?.from) {
109
+ // If no end date is selected, use the from date as the end date
110
+ const to = range.to || range.from;
111
+ const finalRange: DefinedDateRange = {
112
+ from: range.from,
113
+ to: to,
114
+ };
115
+ setSelectedRange({ from: range.from, to });
116
+ onDateRangeChange(finalRange);
117
+ }
118
+ };
119
+
120
+ const handlePresetClick = (preset: (typeof presets)[number]) => {
121
+ const range = preset.getValue();
122
+ handleSelect(range);
123
+ setOpen(false);
124
+ };
125
+
126
+ const formatDateRange = () => {
127
+ if (!selectedRange.from) {
128
+ return t`Select date range`;
129
+ }
130
+ if (!selectedRange.to || selectedRange.from === selectedRange.to) {
131
+ return formatDate(selectedRange.from, { dateStyle: 'medium' });
132
+ }
133
+ return `${formatDate(selectedRange.from, { dateStyle: 'medium' })} - ${formatDate(selectedRange.to, { dateStyle: 'medium' })}`;
134
+ };
135
+
136
+ const isPresetActive = (preset: (typeof presets)[number]) => {
137
+ if (!selectedRange.from || !selectedRange.to) return false;
138
+ const presetRange = preset.getValue();
139
+ return (
140
+ selectedRange.from.getTime() === presetRange.from.getTime() &&
141
+ selectedRange.to.getTime() === presetRange.to.getTime()
142
+ );
143
+ };
144
+
145
+ return (
146
+ <Popover open={open} onOpenChange={setOpen}>
147
+ <PopoverTrigger asChild>
148
+ <Button
149
+ variant="outline"
150
+ className={cn('w-[280px] justify-start text-left font-normal', className)}
151
+ >
152
+ <CalendarIcon className="mr-2 h-4 w-4" />
153
+ {formatDateRange()}
154
+ </Button>
155
+ </PopoverTrigger>
156
+ <PopoverContent className="w-auto p-0" align="start">
157
+ <div className="flex">
158
+ <div className="border-r p-2 space-y-0.5 min-w-0 w-32">
159
+ {presets.map(preset => (
160
+ <Button
161
+ key={preset.id}
162
+ variant={isPresetActive(preset) ? 'default' : 'ghost'}
163
+ size="sm"
164
+ className="w-full justify-start font-normal text-xs h-7 px-2"
165
+ onClick={() => handlePresetClick(preset)}
166
+ >
167
+ {preset.label}
168
+ </Button>
169
+ ))}
170
+ </div>
171
+ <div className="p-3">
172
+ <Calendar
173
+ mode="range"
174
+ defaultMonth={selectedRange?.from}
175
+ selected={selectedRange}
176
+ onSelect={handleSelect}
177
+ numberOfMonths={2}
178
+ showOutsideDays={false}
179
+ locale={locale}
180
+ />
181
+ </div>
182
+ </div>
183
+ </PopoverContent>
184
+ </Popover>
185
+ );
186
+ }
@@ -9,16 +9,18 @@ import {
9
9
  } from '@/vdb/components/ui/sidebar.js';
10
10
  import { useDashboardExtensions } from '@/vdb/framework/extension-api/use-dashboard-extensions.js';
11
11
  import { getNavMenuConfig } from '@/vdb/framework/nav-menu/nav-menu-extensions.js';
12
+ import { useDisplayLocale } from '@/vdb/hooks/use-display-locale.js';
12
13
  import * as React from 'react';
13
14
  import { ChannelSwitcher } from './channel-switcher.js';
14
15
 
15
16
  export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
16
17
  const { extensionsLoaded } = useDashboardExtensions();
18
+ const { isRTL } = useDisplayLocale();
17
19
  const { sections } = getNavMenuConfig();
18
20
 
19
21
  return (
20
22
  extensionsLoaded && (
21
- <Sidebar collapsible="icon" {...props}>
23
+ <Sidebar collapsible="icon" {...props} side={isRTL ? 'right' : 'left'}>
22
24
  <SidebarHeader>
23
25
  <ChannelSwitcher />
24
26
  </SidebarHeader>
@@ -18,8 +18,8 @@ import { useChannel } from '@/vdb/hooks/use-channel.js';
18
18
  import { useLocalFormat } from '@/vdb/hooks/use-local-format.js';
19
19
  import { useServerConfig } from '@/vdb/hooks/use-server-config.js';
20
20
  import { useUserSettings } from '@/vdb/hooks/use-user-settings.js';
21
- import { Trans } from '@/vdb/lib/trans.js';
22
21
  import { cn } from '@/vdb/lib/utils.js';
22
+ import { Trans } from '@lingui/react/macro';
23
23
  import { Link } from '@tanstack/react-router';
24
24
  import { useEffect, useState } from 'react';
25
25
  import { ManageLanguagesDialog } from './manage-languages-dialog.js';
@@ -99,13 +99,9 @@ export function ChannelSwitcher() {
99
99
  <ChannelCodeLabel code={displayChannel?.code} />
100
100
  </span>
101
101
  <span className="truncate text-xs">
102
- {hasMultipleLanguages ? (
103
- <span className="cursor-pointer hover:text-foreground">
104
- Language: {formatLanguageName(contentLanguage)}
105
- </span>
106
- ) : (
107
- <span>Language: {formatLanguageName(contentLanguage)}</span>
108
- )}
102
+ <span>
103
+ <Trans>Language: {formatLanguageName(contentLanguage)}</Trans>
104
+ </span>
109
105
  </span>
110
106
  </div>
111
107
  <ChevronsUpDown className="ml-auto" />
@@ -139,7 +135,7 @@ export function ChannelSwitcher() {
139
135
  <ChannelCodeLabel code={channel.code} />
140
136
  {channel.id === displayChannel?.id && (
141
137
  <span className="ml-auto text-xs text-muted-foreground">
142
- Current
138
+ <Trans context="current channel">Current</Trans>
143
139
  </span>
144
140
  )}
145
141
  </DropdownMenuItem>
@@ -168,7 +164,9 @@ export function ChannelSwitcher() {
168
164
  <span>{formatLanguageName(languageCode)}</span>
169
165
  {contentLanguage === languageCode && (
170
166
  <span className="ml-auto text-xs text-muted-foreground">
171
- Active
167
+ <Trans context="active language">
168
+ Active
169
+ </Trans>
172
170
  </span>
173
171
  )}
174
172
  </DropdownMenuItem>
@@ -1,7 +1,7 @@
1
1
  import { Badge } from '@/vdb/components/ui/badge.js';
2
2
  import { Button } from '@/vdb/components/ui/button.js';
3
3
  import { useUserSettings } from '@/vdb/hooks/use-user-settings.js';
4
- import { Trans } from '@/vdb/lib/trans.js';
4
+ import { Trans } from '@lingui/react/macro';
5
5
  import { CodeXmlIcon, XIcon } from 'lucide-react';
6
6
 
7
7
  export function DevModeIndicator() {
@@ -5,11 +5,13 @@ import {
5
5
  BreadcrumbList,
6
6
  BreadcrumbSeparator,
7
7
  } from '@/vdb/components/ui/breadcrumb.js';
8
+ import type { NavMenuItem, NavMenuSection } from '@/vdb/framework/nav-menu/nav-menu-extensions.js';
9
+ import { getNavMenuConfig } from '@/vdb/framework/nav-menu/nav-menu-extensions.js';
10
+ import { useDisplayLocale } from '@/vdb/hooks/use-display-locale.js';
11
+ import { useLingui } from '@lingui/react';
8
12
  import { Link, useRouter, useRouterState } from '@tanstack/react-router';
9
13
  import * as React from 'react';
10
14
  import { Fragment } from 'react';
11
- import { getNavMenuConfig } from '@/vdb/framework/nav-menu/nav-menu-extensions.js';
12
- import type { NavMenuItem, NavMenuSection } from '@/vdb/framework/nav-menu/nav-menu-extensions.js';
13
15
 
14
16
  export interface BreadcrumbPair {
15
17
  label: string | React.ReactElement;
@@ -24,7 +26,9 @@ export function GeneratedBreadcrumbs() {
24
26
  const matches = useRouterState({ select: s => s.matches });
25
27
  const currentPath = useRouterState({ select: s => s.location.pathname });
26
28
  const router = useRouter();
29
+ const { i18n } = useLingui();
27
30
  const navMenuConfig = getNavMenuConfig();
31
+ const { bcp47Tag } = useDisplayLocale();
28
32
  const basePath = router.basepath || '';
29
33
 
30
34
  const normalizeBreadcrumb = (breadcrumb: any, pathname: string): BreadcrumbPair[] => {
@@ -51,9 +55,7 @@ export function GeneratedBreadcrumbs() {
51
55
  const rawCrumbs: BreadcrumbPair[] = React.useMemo(() => {
52
56
  return matches
53
57
  .filter(match => match.loaderData?.breadcrumb)
54
- .flatMap(({ pathname, loaderData }) =>
55
- normalizeBreadcrumb(loaderData.breadcrumb, pathname)
56
- );
58
+ .flatMap(({ pathname, loaderData }) => normalizeBreadcrumb(loaderData.breadcrumb, pathname));
57
59
  }, [matches]);
58
60
 
59
61
  const isBaseRoute = (p: string) => p === basePath || p === `${basePath}/`;
@@ -119,8 +121,8 @@ export function GeneratedBreadcrumbs() {
119
121
  );
120
122
  const breadcrumbs: BreadcrumbPair[] = React.useMemo(() => {
121
123
  const arr = sectionCrumb ? [sectionCrumb, ...pageCrumbs] : pageCrumbs;
122
- return arr.filter((c, i, self) =>
123
- self.findIndex(x => x.path === c.path && x.label === c.label) === i,
124
+ return arr.filter(
125
+ (c, i, self) => self.findIndex(x => x.path === c.path && x.label === c.label) === i,
124
126
  );
125
127
  }, [sectionCrumb, pageCrumbs]);
126
128
  return (
@@ -130,7 +132,7 @@ export function GeneratedBreadcrumbs() {
130
132
  <Fragment key={`${path}-${index}`}>
131
133
  <BreadcrumbItem className="hidden md:block">
132
134
  <BreadcrumbLink asChild>
133
- <Link to={path}>{label}</Link>
135
+ <Link to={path}>{typeof label === 'string' ? i18n.t(label) : label}</Link>
134
136
  </BreadcrumbLink>
135
137
  </BreadcrumbItem>
136
138
  {index < arr.length - 1 && <BreadcrumbSeparator className="hidden md:block" />}
@@ -1,7 +1,9 @@
1
1
  import { CurrencyCode } from '@/vdb/constants.js';
2
+ import { useDisplayLocale } from '@/vdb/hooks/use-display-locale.js';
2
3
  import { useLocalFormat } from '@/vdb/hooks/use-local-format.js';
4
+ import { useUiLanguageLoader } from '@/vdb/hooks/use-ui-language-loader.js';
3
5
  import { useUserSettings } from '@/vdb/hooks/use-user-settings.js';
4
- import { Trans } from '@/vdb/lib/trans.js';
6
+ import { Trans } from '@lingui/react/macro';
5
7
  import { useState } from 'react';
6
8
  import { uiConfig } from 'virtual:vendure-ui-config';
7
9
  import { Button } from '../ui/button.js';
@@ -11,12 +13,22 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '.
11
13
 
12
14
  export function LanguageDialog() {
13
15
  const { i18n } = uiConfig;
16
+ const { loadAndActivateLocale } = useUiLanguageLoader();
14
17
  const { availableLocales, availableLanguages } = i18n;
15
18
  const { settings, setDisplayLanguage, setDisplayLocale } = useUserSettings();
19
+ const { humanReadableLanguageAndLocale } = useDisplayLocale();
16
20
  const availableCurrencyCodes = Object.values(CurrencyCode);
17
- const { formatCurrency, formatLanguageName, formatCurrencyName, formatDate } = useLocalFormat();
21
+ const { formatCurrency, formatLanguageName, formatRegionName, formatCurrencyName, formatDate } =
22
+ useLocalFormat();
18
23
  const [selectedCurrency, setSelectedCurrency] = useState<string>('USD');
19
24
 
25
+ const orderedAvailableLanguages = availableLanguages.slice().sort((a, b) => a.localeCompare(b));
26
+ const orderedAvailableLocales = availableLocales.slice().sort((a, b) => a.localeCompare(b));
27
+ const handleLanguageChange = async (value: string) => {
28
+ setDisplayLanguage(value);
29
+ void loadAndActivateLocale(value);
30
+ };
31
+
20
32
  return (
21
33
  <DialogContent>
22
34
  <DialogHeader>
@@ -29,14 +41,15 @@ export function LanguageDialog() {
29
41
  <Label>
30
42
  <Trans>Display language</Trans>
31
43
  </Label>
32
- <Select defaultValue={settings.displayLanguage} onValueChange={setDisplayLanguage}>
44
+ <Select defaultValue={settings.displayLanguage} onValueChange={handleLanguageChange}>
33
45
  <SelectTrigger className="w-full">
34
46
  <SelectValue placeholder="Select a language" />
35
47
  </SelectTrigger>
36
48
  <SelectContent>
37
- {availableLanguages.map(language => (
38
- <SelectItem key={language} value={language}>
39
- {formatLanguageName(language)}
49
+ {orderedAvailableLanguages.map(language => (
50
+ <SelectItem key={language} value={language} className="flex gap-1">
51
+ <span className="uppercase text-muted-foreground">{language}</span>
52
+ <span>{formatLanguageName(language)}</span>
40
53
  </SelectItem>
41
54
  ))}
42
55
  </SelectContent>
@@ -51,9 +64,10 @@ export function LanguageDialog() {
51
64
  <SelectValue placeholder="Select a locale" />
52
65
  </SelectTrigger>
53
66
  <SelectContent>
54
- {availableLocales.map(locale => (
55
- <SelectItem key={locale} value={locale}>
56
- {formatLanguageName(locale)}
67
+ {orderedAvailableLocales.map(locale => (
68
+ <SelectItem key={locale} value={locale} className="flex gap-1">
69
+ <span className="uppercase text-muted-foreground">{locale}</span>
70
+ <span>{formatRegionName(locale)}</span>
57
71
  </SelectItem>
58
72
  ))}
59
73
  </SelectContent>
@@ -62,7 +76,8 @@ export function LanguageDialog() {
62
76
  </div>
63
77
  <div className="bg-sidebar border border-border rounded-md px-6 py-4 space-y-4">
64
78
  <span className="font-medium block text-accent-foreground">
65
- <Trans>Sample Formatting</Trans>: {settings.displayLocale} {settings.displayLanguage}
79
+ <Trans>Sample Formatting</Trans>:{' '}
80
+ <span className="text-muted-foreground">{humanReadableLanguageAndLocale}</span>
66
81
  </span>
67
82
  <Select defaultValue={selectedCurrency} onValueChange={setSelectedCurrency}>
68
83
  <SelectTrigger>
@@ -77,15 +92,21 @@ export function LanguageDialog() {
77
92
  </SelectContent>
78
93
  </Select>
79
94
  <div className="flex flex-col">
80
- <span className="text-muted-foreground text-sm font-medium">Medium date</span>
95
+ <span className="text-muted-foreground text-sm font-medium">
96
+ <Trans>Medium date</Trans>
97
+ </span>
81
98
  <span>{formatDate(new Date('2025-03-14'), { dateStyle: 'medium' })}</span>
82
99
  </div>
83
100
  <div className="flex flex-col">
84
- <span className="text-muted-foreground text-sm font-medium">Short date</span>
101
+ <span className="text-muted-foreground text-sm font-medium">
102
+ <Trans>Short date</Trans>
103
+ </span>
85
104
  <span>{formatDate(new Date('2025-03-14'), { dateStyle: 'short' })}</span>
86
105
  </div>
87
106
  <div className="flex flex-col">
88
- <span className="text-muted-foreground text-sm font-medium">Price</span>
107
+ <span className="text-muted-foreground text-sm font-medium">
108
+ <Trans>Price</Trans>
109
+ </span>
89
110
  <span>{formatCurrency(100.0, selectedCurrency)}</span>
90
111
  </div>
91
112
  </div>
@@ -17,7 +17,7 @@ import { graphql } from '@/vdb/graphql/graphql.js';
17
17
  import { useChannel } from '@/vdb/hooks/use-channel.js';
18
18
  import { useLocalFormat } from '@/vdb/hooks/use-local-format.js';
19
19
  import { usePermissions } from '@/vdb/hooks/use-permissions.js';
20
- import { Trans } from '@/vdb/lib/trans.js';
20
+ import { Trans } from '@lingui/react/macro';
21
21
  import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
22
22
  import { AlertCircle, Lock } from 'lucide-react';
23
23
  import { useEffect, useState } from 'react';
@@ -15,6 +15,7 @@ import {
15
15
  NavMenuSectionPlacement,
16
16
  } from '@/vdb/framework/nav-menu/nav-menu-extensions.js';
17
17
  import { usePermissions } from '@/vdb/hooks/use-permissions.js';
18
+ import { useLingui } from '@lingui/react';
18
19
  import { Link, useRouter, useRouterState } from '@tanstack/react-router';
19
20
  import { ChevronRight } from 'lucide-react';
20
21
  import * as React from 'react';
@@ -41,6 +42,7 @@ export function NavMain({ items }: Readonly<{ items: Array<NavMenuSection | NavM
41
42
  const router = useRouter();
42
43
  const routerState = useRouterState();
43
44
  const { hasPermissions } = usePermissions();
45
+ const { i18n } = useLingui();
44
46
  const currentPath = routerState.location.pathname;
45
47
  const basePath = router.basepath || '';
46
48
 
@@ -183,12 +185,16 @@ export function NavMain({ items }: Readonly<{ items: Array<NavMenuSection | NavM
183
185
  const renderTopSection = (item: NavMenuSection | NavMenuItem) => {
184
186
  if ('url' in item) {
185
187
  return (
186
- <NavItemWrapper key={item.title} locationId={item.id} order={item.order} offset={true}>
188
+ <NavItemWrapper key={item.id} locationId={item.id} order={item.order} offset={true}>
187
189
  <SidebarMenuItem>
188
- <SidebarMenuButton tooltip={item.title} asChild isActive={isPathActive(item.url)}>
190
+ <SidebarMenuButton
191
+ tooltip={i18n.t(item.title)}
192
+ asChild
193
+ isActive={isPathActive(item.url)}
194
+ >
189
195
  <Link to={item.url}>
190
196
  {item.icon && <item.icon />}
191
- <span>{item.title}</span>
197
+ <span>{i18n.t(item.title)}</span>
192
198
  </Link>
193
199
  </SidebarMenuButton>
194
200
  </SidebarMenuItem>
@@ -197,7 +203,7 @@ export function NavMain({ items }: Readonly<{ items: Array<NavMenuSection | NavM
197
203
  }
198
204
 
199
205
  return (
200
- <NavItemWrapper key={item.title} locationId={item.id} order={item.order} offset={true}>
206
+ <NavItemWrapper key={item.id} locationId={item.id} order={item.order} offset={true}>
201
207
  <Collapsible
202
208
  asChild
203
209
  open={openTopSectionIds.has(item.id)}
@@ -208,7 +214,7 @@ export function NavMain({ items }: Readonly<{ items: Array<NavMenuSection | NavM
208
214
  <CollapsibleTrigger asChild>
209
215
  <SidebarMenuButton tooltip={item.title}>
210
216
  {item.icon && <item.icon />}
211
- <span>{item.title}</span>
217
+ <span>{i18n.t(item.title)}</span>
212
218
  <ChevronRight className="ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90" />
213
219
  </SidebarMenuButton>
214
220
  </CollapsibleTrigger>
@@ -216,7 +222,7 @@ export function NavMain({ items }: Readonly<{ items: Array<NavMenuSection | NavM
216
222
  <SidebarMenuSub>
217
223
  {item.items?.map(subItem => (
218
224
  <NavItemWrapper
219
- key={subItem.title}
225
+ key={subItem.id}
220
226
  locationId={subItem.id}
221
227
  order={subItem.order}
222
228
  parentLocationId={item.id}
@@ -227,7 +233,7 @@ export function NavMain({ items }: Readonly<{ items: Array<NavMenuSection | NavM
227
233
  isActive={isPathActive(subItem.url)}
228
234
  >
229
235
  <Link to={subItem.url}>
230
- <span>{subItem.title}</span>
236
+ <span>{i18n.t(subItem.title)}</span>
231
237
  </Link>
232
238
  </SidebarMenuSubButton>
233
239
  </SidebarMenuSubItem>
@@ -247,10 +253,14 @@ export function NavMain({ items }: Readonly<{ items: Array<NavMenuSection | NavM
247
253
  return (
248
254
  <NavItemWrapper key={item.title} locationId={item.id} order={item.order} offset={true}>
249
255
  <SidebarMenuItem>
250
- <SidebarMenuButton tooltip={item.title} asChild isActive={isPathActive(item.url)}>
256
+ <SidebarMenuButton
257
+ tooltip={i18n.t(item.title)}
258
+ asChild
259
+ isActive={isPathActive(item.url)}
260
+ >
251
261
  <Link to={item.url}>
252
262
  {item.icon && <item.icon />}
253
- <span>{item.title}</span>
263
+ <span>{i18n.t(item.title)}</span>
254
264
  </Link>
255
265
  </SidebarMenuButton>
256
266
  </SidebarMenuItem>
@@ -267,9 +277,9 @@ export function NavMain({ items }: Readonly<{ items: Array<NavMenuSection | NavM
267
277
  >
268
278
  <SidebarMenuItem>
269
279
  <CollapsibleTrigger asChild>
270
- <SidebarMenuButton tooltip={item.title}>
280
+ <SidebarMenuButton tooltip={i18n.t(item.title)}>
271
281
  {item.icon && <item.icon />}
272
- <span>{item.title}</span>
282
+ <span>{i18n.t(item.title)}</span>
273
283
  <ChevronRight className="ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90" />
274
284
  </SidebarMenuButton>
275
285
  </CollapsibleTrigger>
@@ -277,7 +287,7 @@ export function NavMain({ items }: Readonly<{ items: Array<NavMenuSection | NavM
277
287
  <SidebarMenuSub>
278
288
  {item.items?.map(subItem => (
279
289
  <NavItemWrapper
280
- key={subItem.title}
290
+ key={i18n.t(subItem.title)}
281
291
  locationId={subItem.id}
282
292
  order={subItem.order}
283
293
  parentLocationId={item.id}
@@ -288,7 +298,7 @@ export function NavMain({ items }: Readonly<{ items: Array<NavMenuSection | NavM
288
298
  isActive={isPathActive(subItem.url)}
289
299
  >
290
300
  <Link to={subItem.url}>
291
- <span>{subItem.title}</span>
301
+ <span>{i18n.t(subItem.title)}</span>
292
302
  </Link>
293
303
  </SidebarMenuSubButton>
294
304
  </SidebarMenuSubItem>