@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,103 @@
1
+ import { graphql } from '@/vdb/graphql/graphql.js';
2
+
3
+ export const productOptionGroupDetailDocument = graphql(`
4
+ query ProductOptionGroupDetail($id: ID!) {
5
+ productOptionGroup(id: $id) {
6
+ id
7
+ createdAt
8
+ updatedAt
9
+ name
10
+ code
11
+ languageCode
12
+ translations {
13
+ id
14
+ languageCode
15
+ name
16
+ }
17
+ customFields
18
+ }
19
+ }
20
+ `);
21
+
22
+ export const productIdNameDocument = graphql(`
23
+ query ProductIdName($id: ID!) {
24
+ product(id: $id) {
25
+ id
26
+ name
27
+ }
28
+ }
29
+ `);
30
+
31
+ export const productOptionGroupIdNameDocument = graphql(`
32
+ query ProductOptionGroupIdName($id: ID!) {
33
+ productOptionGroup(id: $id) {
34
+ id
35
+ name
36
+ }
37
+ }
38
+ `);
39
+
40
+ export const createProductOptionGroupDocument = graphql(`
41
+ mutation CreateProductOptionGroup($input: CreateProductOptionGroupInput!) {
42
+ createProductOptionGroup(input: $input) {
43
+ id
44
+ }
45
+ }
46
+ `);
47
+
48
+ export const updateProductOptionGroupDocument = graphql(`
49
+ mutation UpdateProductOptionGroup($input: UpdateProductOptionGroupInput!) {
50
+ updateProductOptionGroup(input: $input) {
51
+ id
52
+ }
53
+ }
54
+ `);
55
+
56
+ export const productOptionDetailDocument = graphql(`
57
+ query ProductOptionDetail($id: ID!) {
58
+ productOption(id: $id) {
59
+ id
60
+ createdAt
61
+ updatedAt
62
+ name
63
+ code
64
+ languageCode
65
+ translations {
66
+ id
67
+ languageCode
68
+ name
69
+ }
70
+ group {
71
+ id
72
+ name
73
+ code
74
+ }
75
+ customFields
76
+ }
77
+ }
78
+ `);
79
+
80
+ export const createProductOptionDocument = graphql(`
81
+ mutation CreateProductOption($input: CreateProductOptionInput!) {
82
+ createProductOption(input: $input) {
83
+ id
84
+ }
85
+ }
86
+ `);
87
+
88
+ export const updateProductOptionDocument = graphql(`
89
+ mutation UpdateProductOption($input: UpdateProductOptionInput!) {
90
+ updateProductOption(input: $input) {
91
+ id
92
+ }
93
+ }
94
+ `);
95
+
96
+ export const deleteProductOptionDocument = graphql(`
97
+ mutation DeleteProductOption($id: ID!) {
98
+ deleteProductOption(id: $id) {
99
+ result
100
+ message
101
+ }
102
+ }
103
+ `);
@@ -15,6 +15,7 @@ export const productListDocument = graphql(`
15
15
  name
16
16
  slug
17
17
  enabled
18
+ description
18
19
  }
19
20
  totalItems
20
21
  }
@@ -44,7 +45,11 @@ export const productDetailFragment = graphql(
44
45
  slug
45
46
  description
46
47
  }
47
-
48
+ optionGroups {
49
+ id
50
+ code
51
+ name
52
+ }
48
53
  facetValues {
49
54
  id
50
55
  name
@@ -55,7 +60,6 @@ export const productDetailFragment = graphql(
55
60
  code
56
61
  }
57
62
  }
58
- customFields
59
63
  }
60
64
  `,
61
65
  [assetFragment],
@@ -104,45 +108,45 @@ export const productDetailDocument = graphql(
104
108
  [productDetailFragment],
105
109
  );
106
110
 
107
- export const productDetailWithVariantsDocument = graphql(
108
- `
109
- query ProductDetailWithVariants($id: ID!) {
110
- product(id: $id) {
111
- ...ProductDetail
112
- variantList {
113
- totalItems
114
- }
115
- optionGroups {
111
+ export const productDetailWithVariantsDocument = graphql(`
112
+ query ProductDetailWithVariants($id: ID!) {
113
+ product(id: $id) {
114
+ id
115
+ createdAt
116
+ updatedAt
117
+ name
118
+ variantList {
119
+ totalItems
120
+ }
121
+ optionGroups {
122
+ id
123
+ code
124
+ name
125
+ options {
116
126
  id
117
127
  code
118
128
  name
119
- options {
120
- id
121
- code
122
- name
123
- }
124
129
  }
125
- variants {
130
+ }
131
+ variants {
132
+ id
133
+ name
134
+ sku
135
+ price
136
+ currencyCode
137
+ priceWithTax
138
+ createdAt
139
+ updatedAt
140
+ options {
126
141
  id
142
+ code
127
143
  name
128
- sku
129
- price
130
- currencyCode
131
- priceWithTax
132
- createdAt
133
- updatedAt
134
- options {
135
- id
136
- code
137
- name
138
- groupId
139
- }
144
+ groupId
140
145
  }
141
146
  }
142
147
  }
143
- `,
144
- [productDetailFragment],
145
- );
148
+ }
149
+ `);
146
150
 
147
151
  export const createProductDocument = graphql(`
148
152
  mutation CreateProduct($input: CreateProductInput!) {
@@ -332,3 +336,11 @@ export const createProductVariantsDocument = graphql(`
332
336
  }
333
337
  }
334
338
  `);
339
+
340
+ export const reindexDocument = graphql(`
341
+ mutation Reindex {
342
+ reindex {
343
+ id
344
+ }
345
+ }
346
+ `);
@@ -3,9 +3,12 @@ import { PermissionGuard } from '@/vdb/components/shared/permission-guard.js';
3
3
  import { Button } from '@/vdb/components/ui/button.js';
4
4
  import { PageActionBarRight } from '@/vdb/framework/layout-engine/page-layout.js';
5
5
  import { ListPage } from '@/vdb/framework/page/list-page.js';
6
- import { Trans } from '@/vdb/lib/trans.js';
6
+ import { api } from '@/vdb/graphql/api.js';
7
+ import { Trans, useLingui } from '@lingui/react/macro';
8
+ import { useMutation } from '@tanstack/react-query';
7
9
  import { createFileRoute, Link } from '@tanstack/react-router';
8
- import { PlusIcon } from 'lucide-react';
10
+ import { PlusIcon, RefreshCwIcon } from 'lucide-react';
11
+ import { toast } from 'sonner';
9
12
  import {
10
13
  AssignFacetValuesToProductsBulkAction,
11
14
  AssignProductsToChannelBulkAction,
@@ -13,7 +16,7 @@ import {
13
16
  DuplicateProductsBulkAction,
14
17
  RemoveProductsFromChannelBulkAction,
15
18
  } from './components/product-bulk-actions.js';
16
- import { productListDocument } from './products.graphql.js';
19
+ import { productListDocument, reindexDocument } from './products.graphql.js';
17
20
 
18
21
  export const Route = createFileRoute('/_authenticated/_products/products')({
19
22
  component: ProductListPage,
@@ -21,14 +24,28 @@ export const Route = createFileRoute('/_authenticated/_products/products')({
21
24
  });
22
25
 
23
26
  function ProductListPage() {
27
+ const { t } = useLingui();
28
+ const reindexMutation = useMutation({
29
+ mutationFn: () => api.mutate(reindexDocument, {}),
30
+ onSuccess: () => {
31
+ toast.success(t`Search index rebuild started`);
32
+ },
33
+ onError: () => {
34
+ toast.error(t`Search index rebuild could not be started`);
35
+ },
36
+ });
37
+
38
+ const handleRebuildSearchIndex = () => {
39
+ reindexMutation.mutate();
40
+ };
41
+
24
42
  return (
25
43
  <ListPage
26
44
  pageId="product-list"
27
45
  listQuery={productListDocument}
28
- title="Products"
46
+ title={<Trans>Products</Trans>}
29
47
  customizeColumns={{
30
48
  name: {
31
- header: 'Product Name',
32
49
  cell: ({ row }) => <DetailPageButton id={row.original.id} label={row.original.name} />,
33
50
  },
34
51
  }}
@@ -37,6 +54,12 @@ function ProductListPage() {
37
54
  name: { contains: searchTerm },
38
55
  };
39
56
  }}
57
+ defaultVisibility={{
58
+ name: true,
59
+ featuredAsset: true,
60
+ slug: true,
61
+ enabled: true,
62
+ }}
40
63
  route={Route}
41
64
  bulkActions={[
42
65
  {
@@ -62,6 +85,12 @@ function ProductListPage() {
62
85
  ]}
63
86
  >
64
87
  <PageActionBarRight>
88
+ <PermissionGuard requires={['UpdateCatalog']}>
89
+ <Button variant="outline" onClick={handleRebuildSearchIndex}>
90
+ <RefreshCwIcon />
91
+ <Trans>Rebuild search index</Trans>
92
+ </Button>
93
+ </PermissionGuard>
65
94
  <PermissionGuard requires={['CreateProduct', 'CreateCatalog']}>
66
95
  <Button asChild>
67
96
  <Link to="./new">
@@ -1,4 +1,5 @@
1
1
  import { RichTextInput } from '@/vdb/components/data-input/rich-text-input.js';
2
+ import { SlugInput } from '@/vdb/components/data-input/slug-input.js';
2
3
  import { AssignedFacetValues } from '@/vdb/components/shared/assigned-facet-values.js';
3
4
  import { EntityAssets } from '@/vdb/components/shared/entity-assets.js';
4
5
  import { ErrorPage } from '@/vdb/components/shared/error-page.js';
@@ -6,7 +7,7 @@ import { FormFieldWrapper } from '@/vdb/components/shared/form-field-wrapper.js'
6
7
  import { PermissionGuard } from '@/vdb/components/shared/permission-guard.js';
7
8
  import { TranslatableFormFieldWrapper } from '@/vdb/components/shared/translatable-form-field.js';
8
9
  import { Button } from '@/vdb/components/ui/button.js';
9
- import { FormControl, FormDescription, FormItem, FormLabel, FormMessage } from '@/vdb/components/ui/form.js';
10
+ import { FormControl, FormDescription, FormItem, FormMessage } from '@/vdb/components/ui/form.js';
10
11
  import { Input } from '@/vdb/components/ui/input.js';
11
12
  import { Switch } from '@/vdb/components/ui/switch.js';
12
13
  import { NEW_ENTITY_PATH } from '@/vdb/constants.js';
@@ -22,12 +23,13 @@ import {
22
23
  } from '@/vdb/framework/layout-engine/page-layout.js';
23
24
  import { detailPageRouteLoader } from '@/vdb/framework/page/detail-page-route-loader.js';
24
25
  import { useDetailPage } from '@/vdb/framework/page/use-detail-page.js';
25
- import { Trans, useLingui } from '@/vdb/lib/trans.js';
26
+ import { Trans, useLingui } from '@lingui/react/macro';
26
27
  import { createFileRoute, Link, useNavigate } from '@tanstack/react-router';
27
28
  import { PlusIcon } from 'lucide-react';
28
29
  import { useRef } from 'react';
29
30
  import { toast } from 'sonner';
30
31
  import { CreateProductVariantsDialog } from './components/create-product-variants-dialog.js';
32
+ import { ProductOptionGroupBadge } from './components/product-option-group-badge.js';
31
33
  import { ProductVariantsTable } from './components/product-variants-table.js';
32
34
  import { createProductDocument, productDetailDocument, updateProductDocument } from './products.graphql.js';
33
35
 
@@ -52,7 +54,7 @@ function ProductDetailPage() {
52
54
  const params = Route.useParams();
53
55
  const navigate = useNavigate();
54
56
  const creatingNewEntity = params.id === NEW_ENTITY_PATH;
55
- const { i18n } = useLingui();
57
+ const { t } = useLingui();
56
58
  const refreshRef = useRef<() => void>(() => {});
57
59
 
58
60
  const { form, submitHandler, entity, isPending, refreshEntity, resetForm } = useDetailPage({
@@ -81,14 +83,16 @@ function ProductDetailPage() {
81
83
  },
82
84
  params: { id: params.id },
83
85
  onSuccess: async data => {
84
- toast.success(i18n.t(creatingNewEntity ? 'Successfully created product' : 'Successfully updated product'));
86
+ toast.success(
87
+ creatingNewEntity ? t`Successfully created product` : t`Successfully updated product`,
88
+ );
85
89
  resetForm();
86
90
  if (creatingNewEntity) {
87
91
  await navigate({ to: `../$id`, params: { id: data.id } });
88
92
  }
89
93
  },
90
94
  onError: err => {
91
- toast.error(i18n.t(creatingNewEntity ? 'Failed to create product' : 'Failed to update product'), {
95
+ toast.error(creatingNewEntity ? t`Failed to create product` : t`Failed to update product`, {
92
96
  description: err instanceof Error ? err.message : 'Unknown error',
93
97
  });
94
98
  },
@@ -133,7 +137,15 @@ function ProductDetailPage() {
133
137
  control={form.control}
134
138
  name="slug"
135
139
  label={<Trans>Slug</Trans>}
136
- render={({ field }) => <Input {...field} />}
140
+ render={({ field }) => (
141
+ <SlugInput
142
+ {...field}
143
+ entityName="Product"
144
+ fieldName="slug"
145
+ watchFieldName="name"
146
+ entityId={entity?.id}
147
+ />
148
+ )}
137
149
  />
138
150
  </DetailFormGrid>
139
151
 
@@ -175,21 +187,26 @@ function ProductDetailPage() {
175
187
  />
176
188
  </PageBlock>
177
189
  )}
178
- <PageBlock column="side" blockId="facet-values">
190
+ {entity?.optionGroups.length ? (
191
+ <PageBlock column="side" blockId="option-groups" title={<Trans>Product Options</Trans>}>
192
+ <div className="flex flex-wrap gap-1.5">
193
+ {entity.optionGroups.map(g => (
194
+ <ProductOptionGroupBadge key={g.id} id={g.id} name={g.name} />
195
+ ))}
196
+ </div>
197
+ </PageBlock>
198
+ ) : null}
199
+ <PageBlock column="side" blockId="facet-values" title={<Trans>Facet Values</Trans>}>
179
200
  <FormFieldWrapper
180
201
  control={form.control}
181
202
  name="facetValueIds"
182
- label={<Trans>Facet values</Trans>}
183
203
  render={({ field }) => (
184
204
  <AssignedFacetValues facetValues={entity?.facetValues ?? []} {...field} />
185
205
  )}
186
206
  />
187
207
  </PageBlock>
188
- <PageBlock column="side" blockId="assets">
208
+ <PageBlock column="side" blockId="assets" title={<Trans>Assets</Trans>}>
189
209
  <FormItem>
190
- <FormLabel>
191
- <Trans>Assets</Trans>
192
- </FormLabel>
193
210
  <FormControl>
194
211
  <EntityAssets
195
212
  assets={entity?.assets}
@@ -17,8 +17,8 @@ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@
17
17
  import { Page, PageBlock, PageLayout, PageTitle } from '@/vdb/framework/layout-engine/page-layout.js';
18
18
  import { api } from '@/vdb/graphql/api.js';
19
19
  import { ResultOf } from '@/vdb/graphql/graphql.js';
20
- import { Trans, useLingui } from '@/vdb/lib/trans.js';
21
20
  import { zodResolver } from '@hookform/resolvers/zod';
21
+ import { Trans, useLingui } from '@lingui/react/macro';
22
22
  import { useMutation, useQuery } from '@tanstack/react-query';
23
23
  import { createFileRoute } from '@tanstack/react-router';
24
24
  import { Plus, Save, Trash2 } from 'lucide-react';
@@ -82,7 +82,7 @@ function AddOptionValueDialog({
82
82
  onSuccess?: () => void;
83
83
  }>) {
84
84
  const [open, setOpen] = useState(false);
85
- const { i18n } = useLingui();
85
+ const { t } = useLingui();
86
86
 
87
87
  const form = useForm<AddOptionValueFormValues>({
88
88
  resolver: zodResolver(addOptionValueSchema),
@@ -94,14 +94,14 @@ function AddOptionValueDialog({
94
94
  const createOptionMutation = useMutation({
95
95
  mutationFn: api.mutate(createProductOptionDocument),
96
96
  onSuccess: () => {
97
- toast.success(i18n.t('Successfully added option value'));
97
+ toast.success(t`Successfully added option value`);
98
98
  setOpen(false);
99
99
  form.reset();
100
100
  onSuccess?.();
101
101
  },
102
102
  onError: error => {
103
- toast.error(i18n.t('Failed to add option value'), {
104
- description: error instanceof Error ? error.message : i18n.t('Unknown error'),
103
+ toast.error(t`Failed to add option value`, {
104
+ description: error instanceof Error ? error.message : t`Unknown error`,
105
105
  });
106
106
  },
107
107
  });
@@ -141,7 +141,7 @@ function AddOptionValueDialog({
141
141
  name="name"
142
142
  label={<Trans>Option value name</Trans>}
143
143
  render={({ field }) => (
144
- <Input {...field} placeholder={i18n.t('e.g., Red, Large, Cotton')} />
144
+ <Input {...field} placeholder={t`e.g., Red, Large, Cotton`} />
145
145
  )}
146
146
  />
147
147
  <DialogFooter>
@@ -158,7 +158,7 @@ function AddOptionValueDialog({
158
158
 
159
159
  function ManageProductVariants() {
160
160
  const { id } = Route.useParams();
161
- const { i18n } = useLingui();
161
+ const { t } = useLingui();
162
162
  const [optionsToAddToVariant, setOptionsToAddToVariant] = useState<
163
163
  Record<string, Record<string, string>>
164
164
  >({});
@@ -171,7 +171,7 @@ function ManageProductVariants() {
171
171
  const updateVariantMutation = useMutation({
172
172
  mutationFn: api.mutate(updateProductVariantDocument),
173
173
  onSuccess: () => {
174
- toast.success(i18n.t('Variant updated successfully'));
174
+ toast.success(t`Variant updated successfully`);
175
175
  refetch();
176
176
  },
177
177
  });
@@ -179,7 +179,7 @@ function ManageProductVariants() {
179
179
  const deleteVariantMutation = useMutation({
180
180
  mutationFn: api.mutate(deleteProductVariantDocument),
181
181
  onSuccess: () => {
182
- toast.success(i18n.t('Variant deleted successfully'));
182
+ toast.success(t`Variant deleted successfully`);
183
183
  refetch();
184
184
  },
185
185
  });
@@ -187,7 +187,7 @@ function ManageProductVariants() {
187
187
  const removeOptionGroupMutation = useMutation({
188
188
  mutationFn: api.mutate(removeOptionGroupFromProductDocument),
189
189
  onSuccess: () => {
190
- toast.success(i18n.t('Option group removed'));
190
+ toast.success(t`Option group removed`);
191
191
  refetch();
192
192
  },
193
193
  });
@@ -233,7 +233,7 @@ function ManageProductVariants() {
233
233
  };
234
234
 
235
235
  const deleteVariant = async (variant: Variant) => {
236
- if (confirm(i18n.t('Are you sure you want to delete this variant?'))) {
236
+ if (confirm(t`Are you sure you want to delete this variant?`)) {
237
237
  await deleteVariantMutation.mutateAsync({ id: variant.id });
238
238
  }
239
239
  };
@@ -0,0 +1,177 @@
1
+ import { SlugInput } from '@/vdb/components/data-input/index.js';
2
+ import { ErrorPage } from '@/vdb/components/shared/error-page.js';
3
+ import { FormFieldWrapper } from '@/vdb/components/shared/form-field-wrapper.js';
4
+ import { PermissionGuard } from '@/vdb/components/shared/permission-guard.js';
5
+ import { TranslatableFormFieldWrapper } from '@/vdb/components/shared/translatable-form-field.js';
6
+ import { Button } from '@/vdb/components/ui/button.js';
7
+ import { Input } from '@/vdb/components/ui/input.js';
8
+ import { NEW_ENTITY_PATH } from '@/vdb/constants.js';
9
+ import { extendDetailFormQuery } from '@/vdb/framework/document-extension/extend-detail-form-query.js';
10
+ import { addCustomFields } from '@/vdb/framework/document-introspection/add-custom-fields.js';
11
+ import {
12
+ CustomFieldsPageBlock,
13
+ DetailFormGrid,
14
+ Page,
15
+ PageActionBar,
16
+ PageActionBarRight,
17
+ PageBlock,
18
+ PageLayout,
19
+ PageTitle,
20
+ } from '@/vdb/framework/layout-engine/page-layout.js';
21
+ import { getDetailQueryOptions, useDetailPage } from '@/vdb/framework/page/use-detail-page.js';
22
+ import { api } from '@/vdb/graphql/api.js';
23
+ import { Trans, useLingui } from '@lingui/react/macro';
24
+ import { createFileRoute, ParsedLocation, useNavigate } from '@tanstack/react-router';
25
+ import { toast } from 'sonner';
26
+ import { ProductOptionsTable } from './components/product-options-table.js';
27
+ import {
28
+ createProductOptionGroupDocument,
29
+ productIdNameDocument,
30
+ productOptionGroupDetailDocument,
31
+ updateProductOptionGroupDocument,
32
+ } from './product-option-groups.graphql.js';
33
+
34
+ const pageId = 'product-option-group-detail';
35
+
36
+ export const Route = createFileRoute('/_authenticated/_products/products_/$productId/option-groups/$id')({
37
+ component: ProductOptionGroupDetailPage,
38
+ loader: async ({ context, params }: { context: any; params: any; location: ParsedLocation }) => {
39
+ if (!params.id) {
40
+ throw new Error('ID param is required');
41
+ }
42
+
43
+ const { extendedQuery: extendedQueryDocument } = extendDetailFormQuery(
44
+ addCustomFields(productOptionGroupDetailDocument),
45
+ pageId,
46
+ );
47
+ const result = await context.queryClient.ensureQueryData(
48
+ getDetailQueryOptions(extendedQueryDocument, { id: params.id }),
49
+ );
50
+ const productResult = await context.queryClient.fetchQuery({
51
+ queryKey: [pageId, 'productIdName', params.productId],
52
+ queryFn: () => api.query(productIdNameDocument, { id: params.productId }),
53
+ });
54
+ const entityName = 'ProductOptionGroup';
55
+
56
+ if (!result.productOptionGroup) {
57
+ throw new Error(`${entityName} with the ID ${params.id} was not found`);
58
+ }
59
+ return {
60
+ breadcrumb: [
61
+ { path: '/products', label: <Trans>Products</Trans> },
62
+ { path: `/products/${productResult.product.id}`, label: productResult.product.name },
63
+ { path: `/products/${productResult.product.id}`, label: <Trans>Option Groups</Trans> },
64
+ result.productOptionGroup?.name,
65
+ ],
66
+ };
67
+ },
68
+ errorComponent: ({ error }) => <ErrorPage message={error.message} />,
69
+ });
70
+
71
+ function ProductOptionGroupDetailPage() {
72
+ const params = Route.useParams();
73
+ const navigate = useNavigate();
74
+ const creatingNewEntity = params.id === NEW_ENTITY_PATH;
75
+ const { t } = useLingui();
76
+
77
+ const { form, submitHandler, entity, isPending, resetForm } = useDetailPage({
78
+ pageId,
79
+ queryDocument: productOptionGroupDetailDocument,
80
+ createDocument: createProductOptionGroupDocument,
81
+ updateDocument: updateProductOptionGroupDocument,
82
+ setValuesForUpdate: entity => {
83
+ return {
84
+ id: entity.id,
85
+ code: entity.code,
86
+ translations: entity.translations.map(translation => ({
87
+ id: translation.id,
88
+ languageCode: translation.languageCode,
89
+ name: translation.name,
90
+ customFields: (translation as any).customFields,
91
+ })),
92
+ options: [],
93
+ customFields: entity.customFields,
94
+ };
95
+ },
96
+ transformCreateInput: values => {
97
+ return {
98
+ ...values,
99
+ options: [],
100
+ };
101
+ },
102
+ params: { id: params.id },
103
+ onSuccess: async data => {
104
+ toast(
105
+ creatingNewEntity
106
+ ? t`Successfully created product option group`
107
+ : t`Successfully updated product option group`,
108
+ );
109
+ resetForm();
110
+ if (creatingNewEntity) {
111
+ await navigate({ to: `../$id`, params: { id: data.id } });
112
+ }
113
+ },
114
+ onError: err => {
115
+ toast(
116
+ creatingNewEntity
117
+ ? t`Failed to create product option group`
118
+ : t`Failed to update product option group`,
119
+ {
120
+ description: err instanceof Error ? err.message : 'Unknown error',
121
+ },
122
+ );
123
+ },
124
+ });
125
+
126
+ return (
127
+ <Page pageId={pageId} form={form} submitHandler={submitHandler} entity={entity}>
128
+ <PageTitle>
129
+ {creatingNewEntity ? <Trans>New product option group</Trans> : (entity?.name ?? '')}
130
+ </PageTitle>
131
+ <PageActionBar>
132
+ <PageActionBarRight>
133
+ <PermissionGuard requires={['UpdateProduct', 'UpdateCatalog']}>
134
+ <Button
135
+ type="submit"
136
+ disabled={!form.formState.isDirty || !form.formState.isValid || isPending}
137
+ >
138
+ {creatingNewEntity ? <Trans>Create</Trans> : <Trans>Update</Trans>}
139
+ </Button>
140
+ </PermissionGuard>
141
+ </PageActionBarRight>
142
+ </PageActionBar>
143
+ <PageLayout>
144
+ <PageBlock column="main" blockId="main-form">
145
+ <DetailFormGrid>
146
+ <TranslatableFormFieldWrapper
147
+ control={form.control}
148
+ name="name"
149
+ label={<Trans>Name</Trans>}
150
+ render={({ field }) => <Input {...field} />}
151
+ />
152
+ <FormFieldWrapper
153
+ control={form.control}
154
+ name="code"
155
+ label={<Trans>Code</Trans>}
156
+ render={({ field }) => (
157
+ <SlugInput
158
+ fieldName="code"
159
+ watchFieldName="name"
160
+ entityName="ProductOptionGroup"
161
+ entityId={entity?.id}
162
+ {...field}
163
+ />
164
+ )}
165
+ />
166
+ </DetailFormGrid>
167
+ </PageBlock>
168
+ <CustomFieldsPageBlock column="main" entityType="ProductOptionGroup" control={form.control} />
169
+ {entity && (
170
+ <PageBlock column="main" blockId="product-options" title={<Trans>Product Options</Trans>}>
171
+ <ProductOptionsTable productOptionGroupId={entity?.id} />
172
+ </PageBlock>
173
+ )}
174
+ </PageLayout>
175
+ </Page>
176
+ );
177
+ }