@vendure/dashboard 3.3.6-master-202506290242 → 3.3.6-master-202507010731

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 (54) hide show
  1. package/package.json +4 -4
  2. package/src/app/routes/_authenticated/_administrators/administrators_.$id.tsx +5 -1
  3. package/src/app/routes/_authenticated/_assets/assets_.$id.tsx +7 -2
  4. package/src/app/routes/_authenticated/_channels/channels_.$id.tsx +5 -1
  5. package/src/app/routes/_authenticated/_collections/collections.graphql.ts +16 -0
  6. package/src/app/routes/_authenticated/_collections/collections.tsx +16 -2
  7. package/src/app/routes/_authenticated/_collections/collections_.$id.tsx +5 -1
  8. package/src/app/routes/_authenticated/_collections/components/assign-collections-to-channel-dialog.tsx +110 -0
  9. package/src/app/routes/_authenticated/_collections/components/collection-bulk-actions.tsx +99 -0
  10. package/src/app/routes/_authenticated/_countries/countries.graphql.ts +1 -1
  11. package/src/app/routes/_authenticated/_countries/countries_.$id.tsx +9 -5
  12. package/src/app/routes/_authenticated/_customer-groups/customer-groups.graphql.ts +1 -1
  13. package/src/app/routes/_authenticated/_customer-groups/customer-groups_.$id.tsx +8 -5
  14. package/src/app/routes/_authenticated/_customers/customers_.$id.tsx +5 -1
  15. package/src/app/routes/_authenticated/_facets/facets_.$id.tsx +5 -1
  16. package/src/app/routes/_authenticated/_orders/orders_.$id.tsx +5 -2
  17. package/src/app/routes/_authenticated/_payment-methods/payment-methods_.$id.tsx +5 -1
  18. package/src/app/routes/_authenticated/_product-variants/components/product-variant-bulk-actions.tsx +184 -0
  19. package/src/app/routes/_authenticated/_product-variants/product-variants.graphql.ts +62 -1
  20. package/src/app/routes/_authenticated/_product-variants/product-variants.tsx +33 -3
  21. package/src/app/routes/_authenticated/_product-variants/product-variants_.$id.tsx +14 -3
  22. package/src/app/routes/_authenticated/_products/components/assign-facet-values-dialog.tsx +67 -36
  23. package/src/app/routes/_authenticated/_products/components/assign-to-channel-dialog.tsx +28 -17
  24. package/src/app/routes/_authenticated/_products/components/product-bulk-actions.tsx +12 -2
  25. package/src/app/routes/_authenticated/_products/components/product-variants-table.tsx +74 -55
  26. package/src/app/routes/_authenticated/_products/products_.$id.tsx +6 -1
  27. package/src/app/routes/_authenticated/_promotions/promotions_.$id.tsx +5 -1
  28. package/src/app/routes/_authenticated/_roles/roles_.$id.tsx +5 -1
  29. package/src/app/routes/_authenticated/_sellers/sellers_.$id.tsx +6 -2
  30. package/src/app/routes/_authenticated/_shipping-methods/shipping-methods_.$id.tsx +5 -1
  31. package/src/app/routes/_authenticated/_stock-locations/stock-locations_.$id.tsx +5 -1
  32. package/src/app/routes/_authenticated/_tax-categories/tax-categories.graphql.ts +1 -1
  33. package/src/app/routes/_authenticated/_tax-categories/tax-categories_.$id.tsx +9 -5
  34. package/src/app/routes/_authenticated/_tax-rates/tax-rates.graphql.ts +1 -1
  35. package/src/app/routes/_authenticated/_tax-rates/tax-rates_.$id.tsx +8 -4
  36. package/src/app/routes/_authenticated/_zones/zones.graphql.ts +1 -1
  37. package/src/app/routes/_authenticated/_zones/zones_.$id.tsx +8 -4
  38. package/src/lib/components/shared/detail-page-button.tsx +3 -1
  39. package/src/lib/components/shared/paginated-list-data-table.tsx +6 -4
  40. package/src/lib/framework/data-table/data-table-extensions.ts +14 -0
  41. package/src/lib/framework/document-extension/extend-detail-form-query.ts +50 -0
  42. package/src/lib/framework/document-extension/extend-document.spec.ts +884 -0
  43. package/src/lib/framework/document-extension/extend-document.ts +159 -0
  44. package/src/lib/framework/document-introspection/add-custom-fields.ts +48 -0
  45. package/src/lib/framework/extension-api/define-dashboard-extension.ts +33 -2
  46. package/src/lib/framework/extension-api/extension-api-types.ts +21 -2
  47. package/src/lib/framework/form-engine/custom-form-component-extensions.ts +13 -3
  48. package/src/lib/framework/layout-engine/page-layout.tsx +1 -0
  49. package/src/lib/framework/page/detail-page-route-loader.tsx +22 -4
  50. package/src/lib/framework/page/use-detail-page.ts +11 -2
  51. package/src/lib/framework/registry/registry-types.ts +3 -0
  52. package/src/lib/graphql/graphql-env.d.ts +8 -6
  53. package/src/lib/hooks/use-extended-detail-query.ts +37 -0
  54. package/src/lib/hooks/use-extended-list-query.ts +73 -0
@@ -12,9 +12,13 @@ import { Trans, useLingui } from '@/lib/trans.js';
12
12
 
13
13
  import { Permission } from '@vendure/common/lib/generated-types';
14
14
  import {
15
+ assignProductsToChannelDocument,
15
16
  deleteProductsDocument,
16
17
  duplicateEntityDocument,
18
+ getProductsWithFacetValuesByIdsDocument,
19
+ productDetailDocument,
17
20
  removeProductsFromChannelDocument,
21
+ updateProductsDocument,
18
22
  } from '../products.graphql.js';
19
23
  import { AssignFacetValuesDialog } from './assign-facet-values-dialog.js';
20
24
  import { AssignToChannelDialog } from './assign-to-channel-dialog.js';
@@ -84,7 +88,9 @@ export const AssignProductsToChannelBulkAction: BulkActionComponent<any> = ({ se
84
88
  <AssignToChannelDialog
85
89
  open={dialogOpen}
86
90
  onOpenChange={setDialogOpen}
87
- productIds={selection.map(s => s.id)}
91
+ entityIds={selection.map(s => s.id)}
92
+ entityType="products"
93
+ mutationFn={api.mutate(assignProductsToChannelDocument)}
88
94
  onSuccess={handleSuccess}
89
95
  />
90
96
  </>
@@ -156,7 +162,11 @@ export const AssignFacetValuesToProductsBulkAction: BulkActionComponent<any> = (
156
162
  <AssignFacetValuesDialog
157
163
  open={dialogOpen}
158
164
  onOpenChange={setDialogOpen}
159
- productIds={selection.map(s => s.id)}
165
+ entityIds={selection.map(s => s.id)}
166
+ entityType="products"
167
+ queryFn={variables => api.query(getProductsWithFacetValuesByIdsDocument, variables)}
168
+ mutationFn={api.mutate(updateProductsDocument)}
169
+ detailDocument={productDetailDocument}
160
170
  onSuccess={handleSuccess}
161
171
  />
162
172
  </>
@@ -1,12 +1,15 @@
1
- import { Money } from "@/components/data-display/money.js";
2
- import { PaginatedListDataTable, PaginatedListRefresherRegisterFn } from "@/components/shared/paginated-list-data-table.js";
3
- import { StockLevelLabel } from "@/components/shared/stock-level-label.js";
4
- import { useLocalFormat } from "@/hooks/use-local-format.js";
5
- import { DetailPageButton } from "@/index.js";
6
- import { ColumnFiltersState, SortingState } from "@tanstack/react-table";
7
- import { useState } from "react";
8
- import { productVariantListDocument } from "../products.graphql.js";
1
+ import { Money } from '@/components/data-display/money.js';
2
+ import {
3
+ PaginatedListDataTable,
4
+ PaginatedListRefresherRegisterFn,
5
+ } from '@/components/shared/paginated-list-data-table.js';
6
+ import { StockLevelLabel } from '@/components/shared/stock-level-label.js';
9
7
  import { graphql } from '@/graphql/graphql.js';
8
+ import { useLocalFormat } from '@/hooks/use-local-format.js';
9
+ import { DetailPageButton } from '@/index.js';
10
+ import { ColumnFiltersState, SortingState } from '@tanstack/react-table';
11
+ import { useState } from 'react';
12
+ import { productVariantListDocument } from '../products.graphql.js';
10
13
 
11
14
  export const deleteProductVariantDocument = graphql(`
12
15
  mutation DeleteProductVariant($id: ID!) {
@@ -17,62 +20,78 @@ export const deleteProductVariantDocument = graphql(`
17
20
  }
18
21
  `);
19
22
 
20
-
21
23
  interface ProductVariantsTableProps {
22
24
  productId: string;
23
25
  registerRefresher?: PaginatedListRefresherRegisterFn;
26
+ fromProductDetailPage?: boolean;
24
27
  }
25
28
 
26
- export function ProductVariantsTable({ productId, registerRefresher }: ProductVariantsTableProps) {
29
+ export function ProductVariantsTable({
30
+ productId,
31
+ registerRefresher,
32
+ fromProductDetailPage,
33
+ }: ProductVariantsTableProps) {
27
34
  const { formatCurrencyName } = useLocalFormat();
28
35
  const [page, setPage] = useState(1);
29
36
  const [pageSize, setPageSize] = useState(10);
30
37
  const [sorting, setSorting] = useState<SortingState>([]);
31
38
  const [filters, setFilters] = useState<ColumnFiltersState>([]);
32
39
 
33
- return <PaginatedListDataTable
34
- registerRefresher={registerRefresher}
35
- listQuery={productVariantListDocument}
36
- deleteMutation={deleteProductVariantDocument}
37
- transformVariables={variables => ({
38
- ...variables,
39
- productId,
40
- })}
41
- defaultVisibility={{
42
- id: false,
43
- currencyCode: false,
44
- }}
45
- customizeColumns={{
46
- name: {
47
- header: 'Variant name',
48
- cell: ({ row: { original } }) => <DetailPageButton href={`../../product-variants/${original.id}`} label={original.name} />,
49
- },
50
- currencyCode: {
51
- cell: ({ row: { original } }) => formatCurrencyName(original.currencyCode, 'full'),
52
- },
53
- price: {
54
- cell: ({ row: { original } }) => <Money value={original.price} currency={original.currencyCode} />,
55
- },
56
- priceWithTax: {
57
- cell: ({ row: { original } }) => <Money value={original.priceWithTax} currency={original.currencyCode} />,
58
- },
59
- stockLevels: {
60
- cell: ({ row: { original } }) => <StockLevelLabel stockLevels={original.stockLevels} />,
61
- },
62
- }}
63
- page={page}
64
- itemsPerPage={pageSize}
65
- sorting={sorting}
66
- columnFilters={filters}
67
- onPageChange={(_, page, perPage) => {
68
- setPage(page);
69
- setPageSize(perPage);
70
- }}
71
- onSortChange={(_, sorting) => {
72
- setSorting(sorting);
73
- }}
74
- onFilterChange={(_, filters) => {
75
- setFilters(filters);
76
- }}
77
- />;
40
+ return (
41
+ <PaginatedListDataTable
42
+ registerRefresher={registerRefresher}
43
+ listQuery={productVariantListDocument}
44
+ deleteMutation={deleteProductVariantDocument}
45
+ transformVariables={variables => ({
46
+ ...variables,
47
+ productId,
48
+ })}
49
+ defaultVisibility={{
50
+ id: false,
51
+ currencyCode: false,
52
+ }}
53
+ customizeColumns={{
54
+ name: {
55
+ header: 'Variant name',
56
+ cell: ({ row: { original } }) => (
57
+ <DetailPageButton
58
+ href={`../../product-variants/${original.id}`}
59
+ label={original.name}
60
+ search={fromProductDetailPage ? { from: 'product' } : undefined}
61
+ />
62
+ ),
63
+ },
64
+ currencyCode: {
65
+ cell: ({ row: { original } }) => formatCurrencyName(original.currencyCode, 'full'),
66
+ },
67
+ price: {
68
+ cell: ({ row: { original } }) => (
69
+ <Money value={original.price} currency={original.currencyCode} />
70
+ ),
71
+ },
72
+ priceWithTax: {
73
+ cell: ({ row: { original } }) => (
74
+ <Money value={original.priceWithTax} currency={original.currencyCode} />
75
+ ),
76
+ },
77
+ stockLevels: {
78
+ cell: ({ row: { original } }) => <StockLevelLabel stockLevels={original.stockLevels} />,
79
+ },
80
+ }}
81
+ page={page}
82
+ itemsPerPage={pageSize}
83
+ sorting={sorting}
84
+ columnFilters={filters}
85
+ onPageChange={(_, page, perPage) => {
86
+ setPage(page);
87
+ setPageSize(perPage);
88
+ }}
89
+ onSortChange={(_, sorting) => {
90
+ setSorting(sorting);
91
+ }}
92
+ onFilterChange={(_, filters) => {
93
+ setFilters(filters);
94
+ }}
95
+ />
96
+ );
78
97
  }
@@ -31,9 +31,12 @@ import { CreateProductVariantsDialog } from './components/create-product-variant
31
31
  import { ProductVariantsTable } from './components/product-variants-table.js';
32
32
  import { createProductDocument, productDetailDocument, updateProductDocument } from './products.graphql.js';
33
33
 
34
+ const pageId = 'product-detail';
35
+
34
36
  export const Route = createFileRoute('/_authenticated/_products/products_/$id')({
35
37
  component: ProductDetailPage,
36
38
  loader: detailPageRouteLoader({
39
+ pageId,
37
40
  queryDocument: productDetailDocument,
38
41
  breadcrumb(isNew, entity) {
39
42
  return [
@@ -53,6 +56,7 @@ function ProductDetailPage() {
53
56
  const refreshRef = useRef<() => void>(() => {});
54
57
 
55
58
  const { form, submitHandler, entity, isPending, refreshEntity, resetForm } = useDetailPage({
59
+ pageId,
56
60
  entityName: 'Product',
57
61
  queryDocument: productDetailDocument,
58
62
  createDocument: createProductDocument,
@@ -91,7 +95,7 @@ function ProductDetailPage() {
91
95
  });
92
96
 
93
97
  return (
94
- <Page pageId="product-detail" form={form} submitHandler={submitHandler} entity={entity}>
98
+ <Page pageId={pageId} form={form} submitHandler={submitHandler} entity={entity}>
95
99
  <PageTitle>{creatingNewEntity ? <Trans>New product</Trans> : (entity?.name ?? '')}</PageTitle>
96
100
  <PageActionBar>
97
101
  <PageActionBarRight>
@@ -148,6 +152,7 @@ function ProductDetailPage() {
148
152
  registerRefresher={refresher => {
149
153
  refreshRef.current = refresher;
150
154
  }}
155
+ fromProductDetailPage={true}
151
156
  />
152
157
  <div className="mt-4">
153
158
  <AddProductVariantDialog
@@ -31,9 +31,12 @@ import {
31
31
  updatePromotionDocument,
32
32
  } from './promotions.graphql.js';
33
33
 
34
+ const pageId = 'promotion-detail';
35
+
34
36
  export const Route = createFileRoute('/_authenticated/_promotions/promotions_/$id')({
35
37
  component: PromotionDetailPage,
36
38
  loader: detailPageRouteLoader({
39
+ pageId,
37
40
  queryDocument: promotionDetailDocument,
38
41
  breadcrumb(isNew, entity) {
39
42
  return [
@@ -52,6 +55,7 @@ function PromotionDetailPage() {
52
55
  const { i18n } = useLingui();
53
56
 
54
57
  const { form, submitHandler, entity, isPending, resetForm } = useDetailPage({
58
+ pageId,
55
59
  queryDocument: promotionDetailDocument,
56
60
  createDocument: createPromotionDocument,
57
61
  transformCreateInput: values => {
@@ -114,7 +118,7 @@ function PromotionDetailPage() {
114
118
  });
115
119
 
116
120
  return (
117
- <Page pageId="promotion-detail" form={form} submitHandler={submitHandler} entity={entity}>
121
+ <Page pageId={pageId} form={form} submitHandler={submitHandler} entity={entity}>
118
122
  <PageTitle>{creatingNewEntity ? <Trans>New promotion</Trans> : (entity?.name ?? '')}</PageTitle>
119
123
  <PageActionBar>
120
124
  <PageActionBarRight>
@@ -22,9 +22,12 @@ import { toast } from 'sonner';
22
22
  import { PermissionsGrid } from './components/permissions-grid.js';
23
23
  import { createRoleDocument, roleDetailDocument, updateRoleDocument } from './roles.graphql.js';
24
24
 
25
+ const pageId = 'role-detail';
26
+
25
27
  export const Route = createFileRoute('/_authenticated/_roles/roles_/$id')({
26
28
  component: RoleDetailPage,
27
29
  loader: detailPageRouteLoader({
30
+ pageId,
28
31
  queryDocument: roleDetailDocument,
29
32
  breadcrumb(isNew, entity) {
30
33
  return [
@@ -43,6 +46,7 @@ function RoleDetailPage() {
43
46
  const { i18n } = useLingui();
44
47
 
45
48
  const { form, submitHandler, entity, isPending, resetForm } = useDetailPage({
49
+ pageId,
46
50
  queryDocument: roleDetailDocument,
47
51
  createDocument: createRoleDocument,
48
52
  updateDocument: updateRoleDocument,
@@ -71,7 +75,7 @@ function RoleDetailPage() {
71
75
  });
72
76
 
73
77
  return (
74
- <Page pageId="role-detail" form={form} submitHandler={submitHandler} entity={entity}>
78
+ <Page pageId={pageId} form={form} submitHandler={submitHandler} entity={entity}>
75
79
  <PageTitle>{creatingNewEntity ? <Trans>New role</Trans> : (entity?.description ?? '')}</PageTitle>
76
80
  <PageActionBar>
77
81
  <PageActionBarRight>
@@ -20,9 +20,12 @@ import { createFileRoute, useNavigate } from '@tanstack/react-router';
20
20
  import { toast } from 'sonner';
21
21
  import { createSellerDocument, sellerDetailDocument, updateSellerDocument } from './sellers.graphql.js';
22
22
 
23
+ const pageId = 'seller-detail';
24
+
23
25
  export const Route = createFileRoute('/_authenticated/_sellers/sellers_/$id')({
24
26
  component: SellerDetailPage,
25
27
  loader: detailPageRouteLoader({
28
+ pageId,
26
29
  queryDocument: sellerDetailDocument,
27
30
  breadcrumb: (isNew, entity) => [
28
31
  { path: '/sellers', label: 'Sellers' },
@@ -38,7 +41,8 @@ function SellerDetailPage() {
38
41
  const creatingNewEntity = params.id === NEW_ENTITY_PATH;
39
42
  const { i18n } = useLingui();
40
43
 
41
- const { form, submitHandler, entity, isPending } = useDetailPage({
44
+ const { form, submitHandler, entity, isPending, resetForm } = useDetailPage({
45
+ pageId,
42
46
  queryDocument: sellerDetailDocument,
43
47
  createDocument: createSellerDocument,
44
48
  updateDocument: updateSellerDocument,
@@ -65,7 +69,7 @@ function SellerDetailPage() {
65
69
  });
66
70
 
67
71
  return (
68
- <Page pageId="seller-detail" form={form} submitHandler={submitHandler} entity={entity}>
72
+ <Page pageId={pageId} form={form} submitHandler={submitHandler} entity={entity}>
69
73
  <PageTitle>{creatingNewEntity ? <Trans>New seller</Trans> : (entity?.name ?? '')}</PageTitle>
70
74
  <PageActionBar>
71
75
  <PageActionBarRight>
@@ -30,9 +30,12 @@ import {
30
30
  updateShippingMethodDocument,
31
31
  } from './shipping-methods.graphql.js';
32
32
 
33
+ const pageId = 'shipping-method-detail';
34
+
33
35
  export const Route = createFileRoute('/_authenticated/_shipping-methods/shipping-methods_/$id')({
34
36
  component: ShippingMethodDetailPage,
35
37
  loader: detailPageRouteLoader({
38
+ pageId,
36
39
  queryDocument: shippingMethodDetailDocument,
37
40
  breadcrumb(isNew, entity) {
38
41
  return [
@@ -51,6 +54,7 @@ function ShippingMethodDetailPage() {
51
54
  const { i18n } = useLingui();
52
55
 
53
56
  const { form, submitHandler, entity, isPending, resetForm } = useDetailPage({
57
+ pageId,
54
58
  queryDocument: shippingMethodDetailDocument,
55
59
  createDocument: createShippingMethodDocument,
56
60
  updateDocument: updateShippingMethodDocument,
@@ -94,7 +98,7 @@ function ShippingMethodDetailPage() {
94
98
  });
95
99
 
96
100
  return (
97
- <Page pageId="shipping-method-detail" form={form} submitHandler={submitHandler} entity={entity}>
101
+ <Page pageId={pageId} form={form} submitHandler={submitHandler} entity={entity}>
98
102
  <PageTitle>
99
103
  {creatingNewEntity ? <Trans>New shipping method</Trans> : (entity?.name ?? '')}
100
104
  </PageTitle>
@@ -26,9 +26,12 @@ import {
26
26
  updateStockLocationDocument,
27
27
  } from './stock-locations.graphql.js';
28
28
 
29
+ const pageId = 'stock-location-detail';
30
+
29
31
  export const Route = createFileRoute('/_authenticated/_stock-locations/stock-locations_/$id')({
30
32
  component: StockLocationDetailPage,
31
33
  loader: detailPageRouteLoader({
34
+ pageId,
32
35
  queryDocument: stockLocationDetailQuery,
33
36
  breadcrumb(isNew, entity) {
34
37
  return [
@@ -47,6 +50,7 @@ function StockLocationDetailPage() {
47
50
  const { i18n } = useLingui();
48
51
 
49
52
  const { form, submitHandler, entity, isPending, resetForm } = useDetailPage({
53
+ pageId,
50
54
  queryDocument: stockLocationDetailQuery,
51
55
  createDocument: createStockLocationDocument,
52
56
  updateDocument: updateStockLocationDocument,
@@ -74,7 +78,7 @@ function StockLocationDetailPage() {
74
78
  });
75
79
 
76
80
  return (
77
- <Page pageId="stock-location-detail" form={form} submitHandler={submitHandler} entity={entity}>
81
+ <Page pageId={pageId} form={form} submitHandler={submitHandler} entity={entity}>
78
82
  <PageTitle>
79
83
  {creatingNewEntity ? <Trans>New stock location</Trans> : (entity?.name ?? '')}
80
84
  </PageTitle>
@@ -24,7 +24,7 @@ export const taxCategoryListQuery = graphql(
24
24
  [taxCategoryItemFragment],
25
25
  );
26
26
 
27
- export const taxCategoryDetailQuery = graphql(`
27
+ export const taxCategoryDetailDocument = graphql(`
28
28
  query TaxCategoryDetail($id: ID!) {
29
29
  taxCategory(id: $id) {
30
30
  id
@@ -22,14 +22,17 @@ import { createFileRoute, useNavigate } from '@tanstack/react-router';
22
22
  import { toast } from 'sonner';
23
23
  import {
24
24
  createTaxCategoryDocument,
25
- taxCategoryDetailQuery,
25
+ taxCategoryDetailDocument,
26
26
  updateTaxCategoryDocument,
27
27
  } from './tax-categories.graphql.js';
28
28
 
29
+ const pageId = 'tax-category-detail';
30
+
29
31
  export const Route = createFileRoute('/_authenticated/_tax-categories/tax-categories_/$id')({
30
32
  component: TaxCategoryDetailPage,
31
33
  loader: detailPageRouteLoader({
32
- queryDocument: taxCategoryDetailQuery,
34
+ pageId,
35
+ queryDocument: taxCategoryDetailDocument,
33
36
  breadcrumb(isNew, entity) {
34
37
  return [
35
38
  { path: '/tax-categories', label: 'Tax categories' },
@@ -46,8 +49,9 @@ function TaxCategoryDetailPage() {
46
49
  const creatingNewEntity = params.id === NEW_ENTITY_PATH;
47
50
  const { i18n } = useLingui();
48
51
 
49
- const { form, submitHandler, entity, isPending } = useDetailPage({
50
- queryDocument: taxCategoryDetailQuery,
52
+ const { form, submitHandler, entity, isPending, resetForm } = useDetailPage({
53
+ pageId,
54
+ queryDocument: taxCategoryDetailDocument,
51
55
  createDocument: createTaxCategoryDocument,
52
56
  updateDocument: updateTaxCategoryDocument,
53
57
  setValuesForUpdate: entity => {
@@ -73,7 +77,7 @@ function TaxCategoryDetailPage() {
73
77
  });
74
78
 
75
79
  return (
76
- <Page pageId="tax-category-detail" form={form} submitHandler={submitHandler} entity={entity}>
80
+ <Page pageId={pageId} form={form} submitHandler={submitHandler} entity={entity}>
77
81
  <PageTitle>
78
82
  {creatingNewEntity ? <Trans>New tax category</Trans> : (entity?.name ?? '')}
79
83
  </PageTitle>
@@ -37,7 +37,7 @@ export const taxRateListQuery = graphql(
37
37
  [taxRateItemFragment],
38
38
  );
39
39
 
40
- export const taxRateDetailQuery = graphql(
40
+ export const taxRateDetailDocument = graphql(
41
41
  `
42
42
  query TaxRateDetail($id: ID!) {
43
43
  taxRate(id: $id) {
@@ -23,12 +23,15 @@ import { useDetailPage } from '@/framework/page/use-detail-page.js';
23
23
  import { Trans, useLingui } from '@/lib/trans.js';
24
24
  import { createFileRoute, useNavigate } from '@tanstack/react-router';
25
25
  import { toast } from 'sonner';
26
- import { createTaxRateDocument, taxRateDetailQuery, updateTaxRateDocument } from './tax-rates.graphql.js';
26
+ import { createTaxRateDocument, taxRateDetailDocument, updateTaxRateDocument } from './tax-rates.graphql.js';
27
+
28
+ const pageId = 'tax-rate-detail';
27
29
 
28
30
  export const Route = createFileRoute('/_authenticated/_tax-rates/tax-rates_/$id')({
29
31
  component: TaxRateDetailPage,
30
32
  loader: detailPageRouteLoader({
31
- queryDocument: taxRateDetailQuery,
33
+ pageId,
34
+ queryDocument: taxRateDetailDocument,
32
35
  breadcrumb(isNew, entity) {
33
36
  return [
34
37
  { path: '/tax-rates', label: 'Tax rates' },
@@ -46,7 +49,8 @@ function TaxRateDetailPage() {
46
49
  const { i18n } = useLingui();
47
50
 
48
51
  const { form, submitHandler, entity, isPending, resetForm } = useDetailPage({
49
- queryDocument: taxRateDetailQuery,
52
+ pageId,
53
+ queryDocument: taxRateDetailDocument,
50
54
  createDocument: createTaxRateDocument,
51
55
  updateDocument: updateTaxRateDocument,
52
56
  setValuesForUpdate: entity => {
@@ -77,7 +81,7 @@ function TaxRateDetailPage() {
77
81
  });
78
82
 
79
83
  return (
80
- <Page pageId="tax-rate-detail" form={form} submitHandler={submitHandler} entity={entity}>
84
+ <Page pageId={pageId} form={form} submitHandler={submitHandler} entity={entity}>
81
85
  <PageTitle>{creatingNewEntity ? <Trans>New tax rate</Trans> : (entity?.name ?? '')}</PageTitle>
82
86
  <PageActionBar>
83
87
  <PageActionBarRight>
@@ -42,7 +42,7 @@ export const zoneMembersQuery = graphql(`
42
42
  }
43
43
  `);
44
44
 
45
- export const zoneDetailQuery = graphql(
45
+ export const zoneDetailDocument = graphql(
46
46
  `
47
47
  query ZoneDetail($id: ID!) {
48
48
  zone(id: $id) {
@@ -20,12 +20,15 @@ import { Trans, useLingui } from '@/lib/trans.js';
20
20
  import { createFileRoute, useNavigate } from '@tanstack/react-router';
21
21
  import { toast } from 'sonner';
22
22
  import { ZoneCountriesTable } from './components/zone-countries-table.js';
23
- import { createZoneDocument, updateZoneDocument, zoneDetailQuery } from './zones.graphql.js';
23
+ import { createZoneDocument, updateZoneDocument, zoneDetailDocument } from './zones.graphql.js';
24
+
25
+ const pageId = 'zone-detail';
24
26
 
25
27
  export const Route = createFileRoute('/_authenticated/_zones/zones_/$id')({
26
28
  component: ZoneDetailPage,
27
29
  loader: detailPageRouteLoader({
28
- queryDocument: zoneDetailQuery,
30
+ pageId,
31
+ queryDocument: zoneDetailDocument,
29
32
  breadcrumb(isNew, entity) {
30
33
  return [{ path: '/zones', label: 'Zones' }, isNew ? <Trans>New zone</Trans> : entity?.name];
31
34
  },
@@ -40,7 +43,8 @@ function ZoneDetailPage() {
40
43
  const { i18n } = useLingui();
41
44
 
42
45
  const { form, submitHandler, entity, isPending, resetForm } = useDetailPage({
43
- queryDocument: zoneDetailQuery,
46
+ pageId,
47
+ queryDocument: zoneDetailDocument,
44
48
  createDocument: createZoneDocument,
45
49
  updateDocument: updateZoneDocument,
46
50
  setValuesForUpdate: entity => {
@@ -66,7 +70,7 @@ function ZoneDetailPage() {
66
70
  });
67
71
 
68
72
  return (
69
- <Page pageId="zone-detail" form={form} submitHandler={submitHandler} entity={entity}>
73
+ <Page pageId={pageId} form={form} submitHandler={submitHandler} entity={entity}>
70
74
  <PageTitle>{creatingNewEntity ? <Trans>New zone</Trans> : (entity?.name ?? '')}</PageTitle>
71
75
  <PageActionBar>
72
76
  <PageActionBarRight>
@@ -7,18 +7,20 @@ export function DetailPageButton({
7
7
  href,
8
8
  label,
9
9
  disabled,
10
+ search,
10
11
  }: {
11
12
  label: string | React.ReactNode;
12
13
  id?: string;
13
14
  href?: string;
14
15
  disabled?: boolean;
16
+ search?: Record<string, string>;
15
17
  }) {
16
18
  if (!id && !href) {
17
19
  return <span>{label}</span>;
18
20
  }
19
21
  return (
20
22
  <Button asChild variant="ghost" disabled={disabled}>
21
- <Link to={href ?? `./${id}`}>
23
+ <Link to={href ?? `./${id}`} search={search ?? {}}>
22
24
  {label}
23
25
  {!disabled && <ChevronRight className="h-3 w-3 text-muted-foreground" />}
24
26
  </Link>
@@ -30,6 +30,7 @@ import {
30
30
  import { DisplayComponent } from '@/framework/component-registry/dynamic-component.js';
31
31
  import { BulkAction } from '@/framework/data-table/data-table-types.js';
32
32
  import { ResultOf } from '@/graphql/graphql.js';
33
+ import { useExtendedListQuery } from '@/hooks/use-extended-list-query.js';
33
34
  import { Trans, useLingui } from '@/lib/trans.js';
34
35
  import { TypedDocumentNode } from '@graphql-typed-document-node/core';
35
36
  import {
@@ -276,6 +277,7 @@ export function PaginatedListDataTable<
276
277
  const [searchTerm, setSearchTerm] = React.useState<string>('');
277
278
  const debouncedSearchTerm = useDebounce(searchTerm, 500);
278
279
  const queryClient = useQueryClient();
280
+ const extendedListQuery = useExtendedListQuery(listQuery);
279
281
 
280
282
  const sort = sorting?.reduce((acc: any, sort: ColumnSort) => {
281
283
  const direction = sort.desc ? 'DESC' : 'ASC';
@@ -300,7 +302,7 @@ export function PaginatedListDataTable<
300
302
 
301
303
  const defaultQueryKey = [
302
304
  PaginatedListDataTableKey,
303
- listQuery,
305
+ extendedListQuery,
304
306
  page,
305
307
  itemsPerPage,
306
308
  sorting,
@@ -329,14 +331,14 @@ export function PaginatedListDataTable<
329
331
  } as V;
330
332
 
331
333
  const transformedVariables = transformVariables ? transformVariables(variables) : variables;
332
- return api.query(listQuery, transformedVariables);
334
+ return api.query(extendedListQuery, transformedVariables);
333
335
  },
334
336
  queryKey,
335
337
  placeholderData: keepPreviousData,
336
338
  });
337
339
 
338
- const fields = useListQueryFields(listQuery);
339
- const paginatedListObjectPath = getObjectPathToPaginatedList(listQuery);
340
+ const fields = useListQueryFields(extendedListQuery);
341
+ const paginatedListObjectPath = getObjectPathToPaginatedList(extendedListQuery);
340
342
 
341
343
  let listData = data as any;
342
344
  for (const path of paginatedListObjectPath) {
@@ -1,8 +1,10 @@
1
1
  import { BulkAction } from '@/framework/data-table/data-table-types.js';
2
+ import { DocumentNode } from 'graphql';
2
3
 
3
4
  import { globalRegistry } from '../registry/global-registry.js';
4
5
 
5
6
  globalRegistry.register('bulkActionsRegistry', new Map<string, BulkAction[]>());
7
+ globalRegistry.register('listQueryDocumentRegistry', new Map<string, DocumentNode[]>());
6
8
 
7
9
  export function getBulkActions(pageId: string, blockId = 'list-table'): BulkAction[] {
8
10
  const key = createKey(pageId, blockId);
@@ -16,6 +18,18 @@ export function addBulkAction(pageId: string, blockId: string | undefined, actio
16
18
  bulkActionsRegistry.set(key, [...existingActions, action]);
17
19
  }
18
20
 
21
+ export function getListQueryDocuments(pageId: string, blockId = 'list-table'): DocumentNode[] {
22
+ const key = createKey(pageId, blockId);
23
+ return globalRegistry.get('listQueryDocumentRegistry').get(key) || [];
24
+ }
25
+
26
+ export function addListQueryDocument(pageId: string, blockId: string | undefined, document: DocumentNode) {
27
+ const listQueryDocumentRegistry = globalRegistry.get('listQueryDocumentRegistry');
28
+ const key = createKey(pageId, blockId);
29
+ const existingDocuments = listQueryDocumentRegistry.get(key) || [];
30
+ listQueryDocumentRegistry.set(key, [...existingDocuments, document]);
31
+ }
32
+
19
33
  function createKey(pageId: string, blockId: string | undefined): string {
20
34
  return `${pageId}__${blockId ?? 'list-table'}`;
21
35
  }