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

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 (24) hide show
  1. package/package.json +4 -4
  2. package/src/app/routes/_authenticated/_collections/collections.graphql.ts +16 -0
  3. package/src/app/routes/_authenticated/_collections/collections.tsx +16 -2
  4. package/src/app/routes/_authenticated/_collections/components/assign-collections-to-channel-dialog.tsx +110 -0
  5. package/src/app/routes/_authenticated/_collections/components/collection-bulk-actions.tsx +99 -0
  6. package/src/app/routes/_authenticated/_product-variants/components/product-variant-bulk-actions.tsx +184 -0
  7. package/src/app/routes/_authenticated/_product-variants/product-variants.graphql.ts +62 -1
  8. package/src/app/routes/_authenticated/_product-variants/product-variants.tsx +33 -3
  9. package/src/app/routes/_authenticated/_product-variants/product-variants_.$id.tsx +9 -2
  10. package/src/app/routes/_authenticated/_products/components/assign-facet-values-dialog.tsx +67 -36
  11. package/src/app/routes/_authenticated/_products/components/assign-to-channel-dialog.tsx +28 -17
  12. package/src/app/routes/_authenticated/_products/components/product-bulk-actions.tsx +12 -2
  13. package/src/app/routes/_authenticated/_products/components/product-variants-table.tsx +74 -55
  14. package/src/app/routes/_authenticated/_products/products_.$id.tsx +1 -0
  15. package/src/lib/components/shared/detail-page-button.tsx +3 -1
  16. package/src/lib/components/shared/paginated-list-data-table.tsx +6 -4
  17. package/src/lib/framework/data-table/data-table-extensions.ts +14 -0
  18. package/src/lib/framework/document-extension/extend-document.spec.ts +549 -0
  19. package/src/lib/framework/document-extension/extend-document.ts +159 -0
  20. package/src/lib/framework/extension-api/define-dashboard-extension.ts +14 -1
  21. package/src/lib/framework/extension-api/extension-api-types.ts +6 -0
  22. package/src/lib/framework/page/detail-page-route-loader.tsx +9 -3
  23. package/src/lib/framework/registry/registry-types.ts +2 -0
  24. package/src/lib/hooks/use-extended-list-query.ts +73 -0
@@ -13,20 +13,15 @@ import {
13
13
  DialogHeader,
14
14
  DialogTitle,
15
15
  } from '@/components/ui/dialog.js';
16
- import { api } from '@/graphql/api.js';
17
16
  import { ResultOf } from '@/graphql/graphql.js';
18
17
  import { Trans, useLingui } from '@/lib/trans.js';
19
18
 
20
19
  import { getDetailQueryOptions } from '@/framework/page/use-detail-page.js';
21
- import {
22
- getProductsWithFacetValuesByIdsDocument,
23
- productDetailDocument,
24
- updateProductsDocument,
25
- } from '../products.graphql.js';
26
20
 
27
- interface ProductWithFacetValues {
21
+ interface EntityWithFacetValues {
28
22
  id: string;
29
23
  name: string;
24
+ sku?: string;
30
25
  facetValues: Array<{
31
26
  id: string;
32
27
  name: string;
@@ -42,14 +37,22 @@ interface ProductWithFacetValues {
42
37
  interface AssignFacetValuesDialogProps {
43
38
  open: boolean;
44
39
  onOpenChange: (open: boolean) => void;
45
- productIds: string[];
40
+ entityIds: string[];
41
+ entityType: 'products' | 'variants';
42
+ queryFn: (variables: any) => Promise<ResultOf<any>>;
43
+ mutationFn: (variables: any) => Promise<ResultOf<any>>;
44
+ detailDocument: any;
46
45
  onSuccess?: () => void;
47
46
  }
48
47
 
49
48
  export function AssignFacetValuesDialog({
50
49
  open,
51
50
  onOpenChange,
52
- productIds,
51
+ entityIds,
52
+ entityType,
53
+ queryFn,
54
+ mutationFn,
55
+ detailDocument,
53
56
  onSuccess,
54
57
  }: AssignFacetValuesDialogProps) {
55
58
  const { i18n } = useLingui();
@@ -58,30 +61,30 @@ export function AssignFacetValuesDialog({
58
61
  const [removedFacetValues, setRemovedFacetValues] = useState<Set<string>>(new Set());
59
62
  const queryClient = useQueryClient();
60
63
 
61
- // Fetch existing facet values for the products
62
- const { data: productsData, isLoading } = useQuery({
63
- queryKey: ['productsWithFacetValues', productIds],
64
- queryFn: () => api.query(getProductsWithFacetValuesByIdsDocument, { ids: productIds }),
65
- enabled: open && productIds.length > 0,
64
+ // Fetch existing facet values for the entities
65
+ const { data: entitiesData, isLoading } = useQuery({
66
+ queryKey: [`${entityType}WithFacetValues`, entityIds],
67
+ queryFn: () => queryFn({ ids: entityIds }),
68
+ enabled: open && entityIds.length > 0,
66
69
  });
67
70
 
68
71
  const { mutate, isPending } = useMutation({
69
- mutationFn: api.mutate(updateProductsDocument),
70
- onSuccess: (result: ResultOf<typeof updateProductsDocument>) => {
71
- toast.success(i18n.t(`Successfully updated facet values for ${productIds.length} products`));
72
+ mutationFn,
73
+ onSuccess: () => {
74
+ toast.success(i18n.t(`Successfully updated facet values for ${entityIds.length} ${entityType}`));
72
75
  onSuccess?.();
73
76
  onOpenChange(false);
74
77
  // Reset state
75
78
  setSelectedValues([]);
76
79
  setFacetValuesRemoved(false);
77
80
  setRemovedFacetValues(new Set());
78
- productIds.forEach(id => {
79
- const { queryKey } = getDetailQueryOptions(productDetailDocument, { id });
81
+ entityIds.forEach(id => {
82
+ const { queryKey } = getDetailQueryOptions(detailDocument, { id });
80
83
  queryClient.removeQueries({ queryKey });
81
84
  });
82
85
  },
83
86
  onError: () => {
84
- toast.error(`Failed to update facet values for ${productIds.length} products`);
87
+ toast.error(`Failed to update facet values for ${entityIds.length} ${entityType}`);
85
88
  },
86
89
  });
87
90
 
@@ -91,18 +94,25 @@ export function AssignFacetValuesDialog({
91
94
  return;
92
95
  }
93
96
 
94
- if (!productsData?.products.items) {
97
+ const items =
98
+ entityType === 'products'
99
+ ? (entitiesData as any)?.products?.items
100
+ : (entitiesData as any)?.productVariants?.items;
101
+
102
+ if (!items) {
95
103
  return;
96
104
  }
97
105
 
98
106
  const selectedFacetValueIds = selectedValues.map(sv => sv.id);
99
107
 
100
108
  mutate({
101
- input: productsData.products.items.map(product => ({
102
- id: product.id,
109
+ input: items.map((entity: EntityWithFacetValues) => ({
110
+ id: entity.id,
103
111
  facetValueIds: [
104
112
  ...new Set([
105
- ...product.facetValues.filter(fv => !removedFacetValues.has(fv.id)).map(fv => fv.id),
113
+ ...entity.facetValues
114
+ .filter((fv: any) => !removedFacetValues.has(fv.id))
115
+ .map((fv: any) => fv.id),
106
116
  ...selectedFacetValueIds,
107
117
  ]),
108
118
  ],
@@ -114,7 +124,7 @@ export function AssignFacetValuesDialog({
114
124
  setSelectedValues(prev => [...prev, facetValue]);
115
125
  };
116
126
 
117
- const removeFacetValue = (productId: string, facetValueId: string) => {
127
+ const removeFacetValue = (entityId: string, facetValueId: string) => {
118
128
  setRemovedFacetValues(prev => new Set([...prev, facetValueId]));
119
129
  setFacetValuesRemoved(true);
120
130
  };
@@ -128,10 +138,15 @@ export function AssignFacetValuesDialog({
128
138
  };
129
139
 
130
140
  // Filter out removed facet values for display
131
- const getDisplayFacetValues = (product: ProductWithFacetValues) => {
132
- return product.facetValues.filter(fv => !removedFacetValues.has(fv.id));
141
+ const getDisplayFacetValues = (entity: EntityWithFacetValues) => {
142
+ return entity.facetValues.filter(fv => !removedFacetValues.has(fv.id));
133
143
  };
134
144
 
145
+ const items =
146
+ entityType === 'products'
147
+ ? (entitiesData as any)?.products?.items
148
+ : (entitiesData as any)?.productVariants?.items;
149
+
135
150
  return (
136
151
  <Dialog open={open} onOpenChange={onOpenChange}>
137
152
  <DialogContent className="sm:max-w-[800px] max-h-[80vh] overflow-hidden flex flex-col">
@@ -140,7 +155,9 @@ export function AssignFacetValuesDialog({
140
155
  <Trans>Edit facet values</Trans>
141
156
  </DialogTitle>
142
157
  <DialogDescription>
143
- <Trans>Add or remove facet values for {productIds.length} products</Trans>
158
+ <Trans>
159
+ Add or remove facet values for {entityIds.length} {entityType}
160
+ </Trans>
144
161
  </DialogDescription>
145
162
  </DialogHeader>
146
163
 
@@ -156,33 +173,47 @@ export function AssignFacetValuesDialog({
156
173
  />
157
174
  </div>
158
175
 
159
- {/* Products table */}
176
+ {/* Entities table */}
160
177
  <div className="flex-1 overflow-auto">
161
178
  {isLoading ? (
162
179
  <div className="flex items-center justify-center py-8">
163
180
  <div className="text-sm text-muted-foreground">Loading...</div>
164
181
  </div>
165
- ) : productsData?.products.items ? (
182
+ ) : items ? (
166
183
  <div className="border rounded-md">
167
184
  <table className="w-full">
168
185
  <thead className="bg-muted/50">
169
186
  <tr>
170
187
  <th className="text-left p-3 text-sm font-medium">
171
- <Trans>Product</Trans>
188
+ <Trans>
189
+ {entityType === 'products' ? 'Product' : 'Variant'}
190
+ </Trans>
172
191
  </th>
192
+ {entityType === 'variants' && (
193
+ <th className="text-left p-3 text-sm font-medium">
194
+ <Trans>SKU</Trans>
195
+ </th>
196
+ )}
173
197
  <th className="text-left p-3 text-sm font-medium">
174
198
  <Trans>Current facet values</Trans>
175
199
  </th>
176
200
  </tr>
177
201
  </thead>
178
202
  <tbody>
179
- {productsData.products.items.map(product => {
180
- const displayFacetValues = getDisplayFacetValues(product);
203
+ {items.map((entity: EntityWithFacetValues) => {
204
+ const displayFacetValues = getDisplayFacetValues(entity);
181
205
  return (
182
- <tr key={product.id} className="border-t">
206
+ <tr key={entity.id} className="border-t">
183
207
  <td className="p-3 align-top">
184
- <div className="font-medium">{product.name}</div>
208
+ <div className="font-medium">{entity.name}</div>
185
209
  </td>
210
+ {entityType === 'variants' && (
211
+ <td className="p-3 align-top">
212
+ <div className="text-sm text-muted-foreground">
213
+ {entity.sku}
214
+ </div>
215
+ </td>
216
+ )}
186
217
  <td className="p-3">
187
218
  <div className="flex flex-wrap gap-2">
188
219
  {displayFacetValues.map(facetValue => (
@@ -192,7 +223,7 @@ export function AssignFacetValuesDialog({
192
223
  removable={true}
193
224
  onRemove={() =>
194
225
  removeFacetValue(
195
- product.id,
226
+ entity.id,
196
227
  facetValue.id,
197
228
  )
198
229
  }
@@ -14,24 +14,26 @@ import {
14
14
  } from '@/components/ui/dialog.js';
15
15
  import { Input } from '@/components/ui/input.js';
16
16
  import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select.js';
17
- import { api } from '@/graphql/api.js';
18
17
  import { ResultOf } from '@/graphql/graphql.js';
19
18
  import { Trans, useLingui } from '@/lib/trans.js';
20
19
 
21
20
  import { useChannel } from '@/hooks/use-channel.js';
22
- import { assignProductsToChannelDocument } from '../products.graphql.js';
23
21
 
24
22
  interface AssignToChannelDialogProps {
25
23
  open: boolean;
26
24
  onOpenChange: (open: boolean) => void;
27
- productIds: string[];
25
+ entityIds: string[];
26
+ entityType: 'products' | 'variants';
27
+ mutationFn: (variables: any) => Promise<ResultOf<any>>;
28
28
  onSuccess?: () => void;
29
29
  }
30
30
 
31
31
  export function AssignToChannelDialog({
32
32
  open,
33
33
  onOpenChange,
34
- productIds,
34
+ entityIds,
35
+ entityType,
36
+ mutationFn,
35
37
  onSuccess,
36
38
  }: AssignToChannelDialogProps) {
37
39
  const { i18n } = useLingui();
@@ -43,14 +45,14 @@ export function AssignToChannelDialog({
43
45
  const availableChannels = channels.filter(channel => channel.id !== selectedChannel?.id);
44
46
 
45
47
  const { mutate, isPending } = useMutation({
46
- mutationFn: api.mutate(assignProductsToChannelDocument),
47
- onSuccess: (result: ResultOf<typeof assignProductsToChannelDocument>) => {
48
- toast.success(i18n.t(`Successfully assigned ${productIds.length} products to channel`));
48
+ mutationFn,
49
+ onSuccess: () => {
50
+ toast.success(i18n.t(`Successfully assigned ${entityIds.length} ${entityType} to channel`));
49
51
  onSuccess?.();
50
52
  onOpenChange(false);
51
53
  },
52
54
  onError: () => {
53
- toast.error(`Failed to assign ${productIds.length} products to channel`);
55
+ toast.error(`Failed to assign ${entityIds.length} ${entityType} to channel`);
54
56
  },
55
57
  });
56
58
 
@@ -60,13 +62,20 @@ export function AssignToChannelDialog({
60
62
  return;
61
63
  }
62
64
 
63
- mutate({
64
- input: {
65
- productIds,
66
- channelId: selectedChannelId,
67
- priceFactor,
68
- },
69
- });
65
+ const input =
66
+ entityType === 'products'
67
+ ? {
68
+ productIds: entityIds,
69
+ channelId: selectedChannelId,
70
+ priceFactor,
71
+ }
72
+ : {
73
+ productVariantIds: entityIds,
74
+ channelId: selectedChannelId,
75
+ priceFactor,
76
+ };
77
+
78
+ mutate({ input });
70
79
  };
71
80
 
72
81
  return (
@@ -74,10 +83,12 @@ export function AssignToChannelDialog({
74
83
  <DialogContent className="sm:max-w-[425px]">
75
84
  <DialogHeader>
76
85
  <DialogTitle>
77
- <Trans>Assign products to channel</Trans>
86
+ <Trans>Assign {entityType} to channel</Trans>
78
87
  </DialogTitle>
79
88
  <DialogDescription>
80
- <Trans>Select a channel to assign {productIds.length} products to</Trans>
89
+ <Trans>
90
+ Select a channel to assign {entityIds.length} {entityType} to
91
+ </Trans>
81
92
  </DialogDescription>
82
93
  </DialogHeader>
83
94
  <div className="grid gap-4 py-4">
@@ -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
  }
@@ -148,6 +148,7 @@ function ProductDetailPage() {
148
148
  registerRefresher={refresher => {
149
149
  refreshRef.current = refresher;
150
150
  }}
151
+ fromProductDetailPage={true}
151
152
  />
152
153
  <div className="mt-4">
153
154
  <AddProductVariantDialog
@@ -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
  }