@vendure/dashboard 3.3.6-master-202507011151 → 3.3.6-master-202507020959

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 (86) hide show
  1. package/package.json +4 -4
  2. package/src/app/common/delete-bulk-action.tsx +147 -0
  3. package/src/app/common/duplicate-bulk-action.tsx +1 -1
  4. package/src/app/routes/_authenticated/_administrators/administrators.graphql.ts +9 -0
  5. package/src/app/routes/_authenticated/_administrators/administrators.tsx +7 -0
  6. package/src/app/routes/_authenticated/_administrators/components/administrator-bulk-actions.tsx +15 -0
  7. package/src/app/routes/_authenticated/_assets/assets.graphql.ts +11 -0
  8. package/src/app/routes/_authenticated/_assets/assets.tsx +10 -2
  9. package/src/app/routes/_authenticated/_assets/components/asset-bulk-actions.tsx +45 -0
  10. package/src/app/routes/_authenticated/_channels/channels.graphql.ts +9 -0
  11. package/src/app/routes/_authenticated/_channels/channels.tsx +7 -0
  12. package/src/app/routes/_authenticated/_channels/components/channel-bulk-actions.tsx +15 -0
  13. package/src/app/routes/_authenticated/_collections/components/collection-bulk-actions.tsx +39 -110
  14. package/src/app/routes/_authenticated/_countries/components/country-bulk-actions.tsx +15 -0
  15. package/src/app/routes/_authenticated/_countries/countries.graphql.ts +9 -0
  16. package/src/app/routes/_authenticated/_countries/countries.tsx +7 -0
  17. package/src/app/routes/_authenticated/_customer-groups/components/customer-group-bulk-actions.tsx +15 -0
  18. package/src/app/routes/_authenticated/_customer-groups/customer-groups.graphql.ts +9 -0
  19. package/src/app/routes/_authenticated/_customer-groups/customer-groups.tsx +7 -0
  20. package/src/app/routes/_authenticated/_customers/components/customer-bulk-actions.tsx +15 -0
  21. package/src/app/routes/_authenticated/_customers/customers.graphql.ts +9 -1
  22. package/src/app/routes/_authenticated/_customers/customers.tsx +7 -0
  23. package/src/app/routes/_authenticated/_facets/components/facet-bulk-actions.tsx +104 -0
  24. package/src/app/routes/_authenticated/_facets/facets.graphql.ts +30 -0
  25. package/src/app/routes/_authenticated/_facets/facets.tsx +24 -0
  26. package/src/app/routes/_authenticated/_payment-methods/components/payment-method-bulk-actions.tsx +58 -0
  27. package/src/app/routes/_authenticated/_payment-methods/payment-methods.graphql.ts +27 -0
  28. package/src/app/routes/_authenticated/_payment-methods/payment-methods.tsx +30 -8
  29. package/src/app/routes/_authenticated/_payment-methods/payment-methods_.$id.tsx +4 -1
  30. package/src/app/routes/_authenticated/_product-variants/components/product-variant-bulk-actions.tsx +36 -110
  31. package/src/app/routes/_authenticated/_products/components/product-bulk-actions.tsx +36 -105
  32. package/src/app/routes/_authenticated/_promotions/components/promotion-bulk-actions.tsx +82 -0
  33. package/src/app/routes/_authenticated/_promotions/promotions.graphql.ts +25 -0
  34. package/src/app/routes/_authenticated/_promotions/promotions.tsx +24 -0
  35. package/src/app/routes/_authenticated/_promotions/promotions_.$id.tsx +1 -1
  36. package/src/app/routes/_authenticated/_roles/components/role-bulk-actions.tsx +15 -0
  37. package/src/app/routes/_authenticated/_roles/roles.graphql.ts +9 -0
  38. package/src/app/routes/_authenticated/_roles/roles.tsx +7 -0
  39. package/src/app/routes/_authenticated/_sellers/components/seller-bulk-actions.tsx +15 -0
  40. package/src/app/routes/_authenticated/_sellers/sellers.graphql.ts +9 -0
  41. package/src/app/routes/_authenticated/_sellers/sellers.tsx +7 -0
  42. package/src/app/routes/_authenticated/_sellers/sellers_.$id.tsx +1 -1
  43. package/src/app/routes/_authenticated/_shipping-methods/components/shipping-method-bulk-actions.tsx +61 -0
  44. package/src/app/routes/_authenticated/_shipping-methods/shipping-methods.graphql.ts +27 -0
  45. package/src/app/routes/_authenticated/_shipping-methods/shipping-methods.tsx +19 -0
  46. package/src/app/routes/_authenticated/_stock-locations/components/stock-location-bulk-actions.tsx +58 -0
  47. package/src/app/routes/_authenticated/_stock-locations/stock-locations.graphql.ts +25 -0
  48. package/src/app/routes/_authenticated/_stock-locations/stock-locations.tsx +19 -0
  49. package/src/app/routes/_authenticated/_tax-categories/components/tax-category-bulk-actions.tsx +15 -0
  50. package/src/app/routes/_authenticated/_tax-categories/tax-categories.graphql.ts +9 -0
  51. package/src/app/routes/_authenticated/_tax-categories/tax-categories.tsx +7 -0
  52. package/src/app/routes/_authenticated/_tax-rates/components/tax-rate-bulk-actions.tsx +15 -0
  53. package/src/app/routes/_authenticated/_tax-rates/tax-rates.graphql.ts +9 -0
  54. package/src/app/routes/_authenticated/_tax-rates/tax-rates.tsx +7 -0
  55. package/src/app/routes/_authenticated/_zones/components/zone-bulk-actions.tsx +15 -0
  56. package/src/app/routes/_authenticated/_zones/zones.graphql.ts +9 -0
  57. package/src/app/routes/_authenticated/_zones/zones.tsx +7 -0
  58. package/src/lib/components/shared/asset/asset-bulk-actions.tsx +90 -0
  59. package/src/lib/components/shared/asset/asset-gallery.tsx +12 -7
  60. package/src/lib/components/shared/assign-to-channel-bulk-action.tsx +70 -0
  61. package/src/{app/routes/_authenticated/_products/components → lib/components/shared}/assign-to-channel-dialog.tsx +48 -30
  62. package/src/lib/components/shared/remove-from-channel-bulk-action.tsx +89 -0
  63. package/src/lib/framework/component-registry/component-registry.tsx +31 -47
  64. package/src/lib/framework/extension-api/define-dashboard-extension.ts +29 -95
  65. package/src/lib/framework/extension-api/display-component-extensions.tsx +69 -0
  66. package/src/lib/framework/extension-api/extension-api-types.ts +18 -160
  67. package/src/lib/framework/extension-api/input-component-extensions.tsx +69 -0
  68. package/src/lib/framework/extension-api/logic/alerts.ts +10 -0
  69. package/src/lib/framework/extension-api/logic/data-table.ts +60 -0
  70. package/src/lib/framework/extension-api/logic/detail-forms.ts +48 -0
  71. package/src/lib/framework/extension-api/logic/form-components.ts +13 -0
  72. package/src/lib/framework/extension-api/logic/index.ts +8 -0
  73. package/src/lib/framework/extension-api/logic/layout.ts +22 -0
  74. package/src/lib/framework/extension-api/logic/navigation.ts +37 -0
  75. package/src/lib/framework/extension-api/logic/widgets.ts +10 -0
  76. package/src/lib/framework/extension-api/types/alerts.ts +54 -0
  77. package/src/lib/framework/extension-api/types/data-table.ts +64 -0
  78. package/src/lib/framework/extension-api/types/detail-forms.ts +81 -0
  79. package/src/lib/framework/extension-api/types/form-components.ts +32 -0
  80. package/src/lib/framework/extension-api/types/index.ts +8 -0
  81. package/src/lib/framework/extension-api/types/layout.ts +78 -0
  82. package/src/lib/framework/extension-api/types/navigation.ts +19 -0
  83. package/src/lib/framework/extension-api/types/widgets.ts +94 -0
  84. package/src/lib/framework/page/detail-page.tsx +48 -3
  85. package/src/lib/framework/registry/registry-types.ts +3 -0
  86. package/src/app/routes/_authenticated/_collections/components/assign-collections-to-channel-dialog.tsx +0 -110
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@vendure/dashboard",
3
3
  "private": false,
4
- "version": "3.3.6-master-202507011151",
4
+ "version": "3.3.6-master-202507020959",
5
5
  "type": "module",
6
6
  "repository": {
7
7
  "type": "git",
@@ -86,8 +86,8 @@
86
86
  "@types/react-dom": "^19.0.4",
87
87
  "@types/react-grid-layout": "^1.3.5",
88
88
  "@uidotdev/usehooks": "^2.4.1",
89
- "@vendure/common": "^3.3.6-master-202507011151",
90
- "@vendure/core": "^3.3.6-master-202507011151",
89
+ "@vendure/common": "^3.3.6-master-202507020959",
90
+ "@vendure/core": "^3.3.6-master-202507020959",
91
91
  "@vitejs/plugin-react": "^4.3.4",
92
92
  "awesome-graphql-client": "^2.1.0",
93
93
  "class-variance-authority": "^0.7.1",
@@ -130,5 +130,5 @@
130
130
  "lightningcss-linux-arm64-musl": "^1.29.3",
131
131
  "lightningcss-linux-x64-musl": "^1.29.1"
132
132
  },
133
- "gitHead": "a707c76b649dd13d245fec107e4c4c7ccc23866b"
133
+ "gitHead": "5ee07181c993fe6edee6c000fe4cd90ffcba852f"
134
134
  }
@@ -0,0 +1,147 @@
1
+ import { useMutation, useQueryClient } from '@tanstack/react-query';
2
+ import { TrashIcon } from 'lucide-react';
3
+ import { toast } from 'sonner';
4
+
5
+ import { DataTableBulkActionItem } from '@/components/data-table/data-table-bulk-action-item.js';
6
+ import { api } from '@/graphql/api.js';
7
+ import { getMutationName, usePaginatedList } from '@/index.js';
8
+ import { Trans, useLingui } from '@/lib/trans.js';
9
+
10
+ interface DeleteBulkActionProps {
11
+ /** The GraphQL mutation document to execute */
12
+ mutationDocument: any;
13
+ /** The entity name for localization (e.g., "products", "administrators") */
14
+ entityName: string;
15
+ /** The required permissions for this action */
16
+ requiredPermissions: string[];
17
+ /** Optional callback for additional cleanup after successful deletion */
18
+ onSuccess?: () => void;
19
+ /** Optional query keys to invalidate after successful deletion */
20
+ invalidateQueries?: string[];
21
+ /** The selected items to delete */
22
+ selection: any[];
23
+ /** The table instance */
24
+ table: any;
25
+ }
26
+
27
+ /**
28
+ * A reusable delete bulk action component that can be used across all bulk action files.
29
+ *
30
+ * This component handles the common pattern of deleting multiple entities:
31
+ * - Executes the provided GraphQL mutation
32
+ * - Shows success/error toasts with localized messages
33
+ * - Refetches the paginated list
34
+ * - Resets table selection
35
+ * - Optionally invalidates additional queries
36
+ * - Optionally calls additional success callbacks
37
+ *
38
+ * @example
39
+ * ```tsx
40
+ * // Basic usage
41
+ * export const DeleteProductsBulkAction: BulkActionComponent<any> = ({ selection, table }) => {
42
+ * return (
43
+ * <DeleteBulkAction
44
+ * mutationDocument={deleteProductsDocument}
45
+ * entityName="products"
46
+ * requiredPermissions={['DeleteCatalog', 'DeleteProduct']}
47
+ * selection={selection}
48
+ * table={table}
49
+ * />
50
+ * );
51
+ * };
52
+ *
53
+ * // With additional cleanup
54
+ * export const DeleteCollectionsBulkAction: BulkActionComponent<any> = ({ selection, table }) => {
55
+ * return (
56
+ * <DeleteBulkAction
57
+ * mutationDocument={deleteCollectionsDocument}
58
+ * entityName="collections"
59
+ * requiredPermissions={['DeleteCatalog', 'DeleteCollection']}
60
+ * invalidateQueries={['childCollections']}
61
+ * onSuccess={() => {
62
+ * // Additional cleanup logic
63
+ * }}
64
+ * selection={selection}
65
+ * table={table}
66
+ * />
67
+ * );
68
+ * };
69
+ * ```
70
+ */
71
+ export function DeleteBulkAction({
72
+ mutationDocument,
73
+ entityName,
74
+ requiredPermissions,
75
+ onSuccess,
76
+ invalidateQueries = [],
77
+ selection,
78
+ table,
79
+ }: Readonly<DeleteBulkActionProps>) {
80
+ const { refetchPaginatedList } = usePaginatedList();
81
+ const { i18n } = useLingui();
82
+ const queryClient = useQueryClient();
83
+
84
+ const { mutate } = useMutation({
85
+ mutationFn: api.mutate(mutationDocument),
86
+ onSuccess: (result: any) => {
87
+ let deleted = 0;
88
+ let failed = 0;
89
+ const errors: string[] = [];
90
+
91
+ // Get the mutation result field name from the document
92
+ const mutationResultFieldName = getMutationName(mutationDocument);
93
+ const deleteResults = result[mutationResultFieldName];
94
+
95
+ for (const item of deleteResults) {
96
+ if (item.result === 'DELETED') {
97
+ deleted++;
98
+ } else {
99
+ failed++;
100
+ if (item.message) {
101
+ errors.push(item.message);
102
+ }
103
+ }
104
+ }
105
+
106
+ if (0 < deleted) {
107
+ toast.success(i18n.t(`Deleted ${deleted} ${entityName}`));
108
+ }
109
+ if (0 < failed) {
110
+ const errorMessage =
111
+ errors.length > 0
112
+ ? i18n.t(`Failed to delete ${failed} ${entityName}: ${errors.join(', ')}`)
113
+ : i18n.t(`Failed to delete ${failed} ${entityName}`);
114
+ toast.error(errorMessage);
115
+ }
116
+
117
+ refetchPaginatedList();
118
+ table.resetRowSelection();
119
+
120
+ // Invalidate additional queries if specified
121
+ invalidateQueries.forEach(queryKey => {
122
+ queryClient.invalidateQueries({ queryKey: [queryKey] });
123
+ });
124
+
125
+ // Call additional success callback if provided
126
+ onSuccess?.();
127
+ },
128
+ onError: () => {
129
+ toast.error(`Failed to delete ${selection.length} ${entityName}`);
130
+ },
131
+ });
132
+
133
+ return (
134
+ <DataTableBulkActionItem
135
+ requiresPermission={requiredPermissions}
136
+ onClick={() => mutate({ ids: selection.map(s => s.id) })}
137
+ label={<Trans>Delete</Trans>}
138
+ confirmationText={
139
+ <Trans>
140
+ Are you sure you want to delete {selection.length} {entityName}?
141
+ </Trans>
142
+ }
143
+ icon={TrashIcon}
144
+ className="text-destructive"
145
+ />
146
+ );
147
+ }
@@ -10,7 +10,7 @@ import { usePaginatedList } from '@/index.js';
10
10
  import { Trans, useLingui } from '@/lib/trans.js';
11
11
 
12
12
  interface DuplicateBulkActionProps {
13
- entityType: 'Product' | 'Collection';
13
+ entityType: 'Product' | 'Collection' | 'Facet' | 'Promotion';
14
14
  duplicatorCode: string;
15
15
  duplicatorArguments?: Array<{ name: string; value: string }>;
16
16
  requiredPermissions: string[];
@@ -77,3 +77,12 @@ export const deleteAdministratorDocument = graphql(`
77
77
  }
78
78
  }
79
79
  `);
80
+
81
+ export const deleteAdministratorsDocument = graphql(`
82
+ mutation DeleteAdministrators($ids: [ID!]!) {
83
+ deleteAdministrators(ids: $ids) {
84
+ result
85
+ message
86
+ }
87
+ }
88
+ `);
@@ -9,6 +9,7 @@ import { Trans } from '@/lib/trans.js';
9
9
  import { createFileRoute, Link } from '@tanstack/react-router';
10
10
  import { PlusIcon } from 'lucide-react';
11
11
  import { administratorListDocument, deleteAdministratorDocument } from './administrators.graphql.js';
12
+ import { DeleteAdministratorsBulkAction } from './components/administrator-bulk-actions.js';
12
13
 
13
14
  export const Route = createFileRoute('/_authenticated/_administrators/administrators')({
14
15
  component: AdministratorListPage,
@@ -70,6 +71,12 @@ function AdministratorListPage() {
70
71
  emailAddress: true,
71
72
  }}
72
73
  defaultColumnOrder={['name', 'emailAddress', 'roles']}
74
+ bulkActions={[
75
+ {
76
+ component: DeleteAdministratorsBulkAction,
77
+ order: 500,
78
+ },
79
+ ]}
73
80
  >
74
81
  <PageActionBarRight>
75
82
  <PermissionGuard requires={['CreateAdministrator']}>
@@ -0,0 +1,15 @@
1
+ import { BulkActionComponent } from '@/framework/data-table/data-table-types.js';
2
+ import { DeleteBulkAction } from '../../../../common/delete-bulk-action.js';
3
+ import { deleteAdministratorsDocument } from '../administrators.graphql.js';
4
+
5
+ export const DeleteAdministratorsBulkAction: BulkActionComponent<any> = ({ selection, table }) => {
6
+ return (
7
+ <DeleteBulkAction
8
+ mutationDocument={deleteAdministratorsDocument}
9
+ entityName="administrators"
10
+ requiredPermissions={['DeleteAdministrator']}
11
+ selection={selection}
12
+ table={table}
13
+ />
14
+ );
15
+ };
@@ -24,3 +24,14 @@ export const assetUpdateDocument = graphql(`
24
24
  }
25
25
  }
26
26
  `);
27
+
28
+ export const deleteAssetsDocument = graphql(`
29
+ mutation DeleteAssets($input: DeleteAssetsInput!) {
30
+ deleteAssets(input: $input) {
31
+ ... on DeletionResponse {
32
+ result
33
+ message
34
+ }
35
+ }
36
+ }
37
+ `);
@@ -1,7 +1,8 @@
1
1
  import { AssetGallery } from '@/components/shared/asset/asset-gallery.js';
2
- import { Page, PageTitle, PageActionBar } from '@/framework/layout-engine/page-layout.js';
2
+ import { Page, PageBlock, PageTitle } from '@/framework/layout-engine/page-layout.js';
3
3
  import { Trans } from '@/lib/trans.js';
4
4
  import { createFileRoute } from '@tanstack/react-router';
5
+ import { DeleteAssetsBulkAction } from './components/asset-bulk-actions.js';
5
6
 
6
7
  export const Route = createFileRoute('/_authenticated/_assets/assets')({
7
8
  component: RouteComponent,
@@ -13,7 +14,14 @@ function RouteComponent() {
13
14
  <PageTitle>
14
15
  <Trans>Assets</Trans>
15
16
  </PageTitle>
16
- <AssetGallery selectable={true} multiSelect='manual' />
17
+ <PageBlock blockId="asset-gallery" column="main">
18
+ <AssetGallery selectable={true} multiSelect="manual" bulkActions={[{
19
+ order: 10,
20
+ component: DeleteAssetsBulkAction,
21
+ },
22
+ ]}
23
+ />
24
+ </PageBlock>
17
25
  </Page>
18
26
  );
19
27
  }
@@ -0,0 +1,45 @@
1
+ import { useMutation } from '@tanstack/react-query';
2
+ import { TrashIcon } from 'lucide-react';
3
+ import { toast } from 'sonner';
4
+
5
+ import { DataTableBulkActionItem } from '@/components/data-table/data-table-bulk-action-item.js';
6
+ import { api } from '@/graphql/api.js';
7
+ import { AssetFragment } from '@/graphql/fragments.js';
8
+ import { ResultOf } from '@/graphql/graphql.js';
9
+ import { Trans, useLingui } from '@/lib/trans.js';
10
+ import { deleteAssetsDocument } from '../assets.graphql.js';
11
+
12
+ export const DeleteAssetsBulkAction = ({
13
+ selection,
14
+ refetch,
15
+ }: {
16
+ selection: AssetFragment[];
17
+ refetch: () => void;
18
+ }) => {
19
+ const { i18n } = useLingui();
20
+ const { mutate } = useMutation({
21
+ mutationFn: api.mutate(deleteAssetsDocument),
22
+ onSuccess: (result: ResultOf<typeof deleteAssetsDocument>) => {
23
+ if (result.deleteAssets.result === 'DELETED') {
24
+ toast.success(i18n.t(`Deleted ${selection.length} assets`));
25
+ } else {
26
+ toast.error(i18n.t(`Failed to delete assets: ${result.deleteAssets.message}`));
27
+ }
28
+ refetch();
29
+ },
30
+ onError: () => {
31
+ toast.error(`Failed to delete ${selection.length} assets`);
32
+ },
33
+ });
34
+
35
+ return (
36
+ <DataTableBulkActionItem
37
+ requiresPermission={['DeleteCatalog', 'DeleteAsset']}
38
+ onClick={() => mutate({ input: { assetIds: selection.map(s => s.id) } })}
39
+ label={<Trans>Delete</Trans>}
40
+ confirmationText={<Trans>Are you sure you want to delete {selection.length} assets?</Trans>}
41
+ icon={TrashIcon}
42
+ className="text-destructive"
43
+ />
44
+ );
45
+ };
@@ -91,3 +91,12 @@ export const deleteChannelDocument = graphql(`
91
91
  }
92
92
  }
93
93
  `);
94
+
95
+ export const deleteChannelsDocument = graphql(`
96
+ mutation DeleteChannels($ids: [ID!]!) {
97
+ deleteChannels(ids: $ids) {
98
+ result
99
+ message
100
+ }
101
+ }
102
+ `);
@@ -9,6 +9,7 @@ import { createFileRoute, Link } from '@tanstack/react-router';
9
9
  import { PlusIcon } from 'lucide-react';
10
10
  import { channelListQuery, deleteChannelDocument } from './channels.graphql.js';
11
11
  import { useLocalFormat } from '@/hooks/use-local-format.js';
12
+ import { DeleteChannelsBulkAction } from './components/channel-bulk-actions.js';
12
13
 
13
14
  export const Route = createFileRoute('/_authenticated/_channels/channels')({
14
15
  component: ChannelListPage,
@@ -62,6 +63,12 @@ function ChannelListPage() {
62
63
  }
63
64
  },
64
65
  }}
66
+ bulkActions={[
67
+ {
68
+ component: DeleteChannelsBulkAction,
69
+ order: 500,
70
+ },
71
+ ]}
65
72
  >
66
73
  <PageActionBarRight>
67
74
  <PermissionGuard requires={['CreateChannel']}>
@@ -0,0 +1,15 @@
1
+ import { BulkActionComponent } from '@/framework/data-table/data-table-types.js';
2
+ import { DeleteBulkAction } from '../../../../common/delete-bulk-action.js';
3
+ import { deleteChannelsDocument } from '../channels.graphql.js';
4
+
5
+ export const DeleteChannelsBulkAction: BulkActionComponent<any> = ({ selection, table }) => {
6
+ return (
7
+ <DeleteBulkAction
8
+ mutationDocument={deleteChannelsDocument}
9
+ entityName="channels"
10
+ requiredPermissions={['DeleteChannel']}
11
+ selection={selection}
12
+ table={table}
13
+ />
14
+ );
15
+ };
@@ -1,99 +1,57 @@
1
- import { useMutation, useQueryClient } from '@tanstack/react-query';
2
- import { LayersIcon, TrashIcon } from 'lucide-react';
3
- import { useState } from 'react';
4
- import { toast } from 'sonner';
1
+ import { useQueryClient } from '@tanstack/react-query';
5
2
 
6
- import { DataTableBulkActionItem } from '@/components/data-table/data-table-bulk-action-item.js';
3
+ import { AssignToChannelBulkAction } from '@/components/shared/assign-to-channel-bulk-action.js';
4
+ import { RemoveFromChannelBulkAction } from '@/components/shared/remove-from-channel-bulk-action.js';
7
5
  import { BulkActionComponent } from '@/framework/data-table/data-table-types.js';
8
6
  import { api } from '@/graphql/api.js';
9
- import { ResultOf, useChannel, usePaginatedList } from '@/index.js';
10
- import { Trans, useLingui } from '@/lib/trans.js';
7
+ import { useChannel } from '@/index.js';
8
+ import { DeleteBulkAction } from '../../../../common/delete-bulk-action.js';
11
9
  import { DuplicateBulkAction } from '../../../../common/duplicate-bulk-action.js';
12
10
  import {
13
11
  assignCollectionToChannelDocument,
14
12
  deleteCollectionsDocument,
15
13
  removeCollectionFromChannelDocument,
16
14
  } from '../collections.graphql.js';
17
- import { AssignCollectionsToChannelDialog } from './assign-collections-to-channel-dialog.js';
18
15
 
19
16
  export const AssignCollectionsToChannelBulkAction: BulkActionComponent<any> = ({ selection, table }) => {
20
- const { refetchPaginatedList } = usePaginatedList();
21
- const { channels } = useChannel();
22
- const [dialogOpen, setDialogOpen] = useState(false);
23
17
  const queryClient = useQueryClient();
24
18
 
25
- if (channels.length < 2) {
26
- return null;
27
- }
28
-
29
- const handleSuccess = () => {
30
- refetchPaginatedList();
31
- table.resetRowSelection();
32
- queryClient.invalidateQueries({ queryKey: ['childCollections'] });
33
- };
34
-
35
19
  return (
36
- <>
37
- <DataTableBulkActionItem
38
- requiresPermission={['UpdateCatalog', 'UpdateCollection']}
39
- onClick={() => setDialogOpen(true)}
40
- label={<Trans>Assign to channel</Trans>}
41
- icon={LayersIcon}
42
- />
43
- <AssignCollectionsToChannelDialog
44
- open={dialogOpen}
45
- onOpenChange={setDialogOpen}
46
- entityIds={selection.map(s => s.id)}
47
- mutationFn={api.mutate(assignCollectionToChannelDocument)}
48
- onSuccess={handleSuccess}
49
- />
50
- </>
20
+ <AssignToChannelBulkAction
21
+ selection={selection}
22
+ table={table}
23
+ entityType="collections"
24
+ mutationFn={api.mutate(assignCollectionToChannelDocument)}
25
+ requiredPermissions={['UpdateCatalog', 'UpdateCollection']}
26
+ buildInput={(channelId: string) => ({
27
+ collectionIds: selection.map(s => s.id),
28
+ channelId,
29
+ })}
30
+ onSuccess={() => {
31
+ queryClient.invalidateQueries({ queryKey: ['childCollections'] });
32
+ }}
33
+ />
51
34
  );
52
35
  };
53
36
 
54
37
  export const RemoveCollectionsFromChannelBulkAction: BulkActionComponent<any> = ({ selection, table }) => {
55
- const { refetchPaginatedList } = usePaginatedList();
56
38
  const { selectedChannel } = useChannel();
57
- const { i18n } = useLingui();
58
39
  const queryClient = useQueryClient();
59
- const { mutate } = useMutation({
60
- mutationFn: api.mutate(removeCollectionFromChannelDocument),
61
- onSuccess: () => {
62
- toast.success(i18n.t(`Successfully removed ${selection.length} collections from channel`));
63
- refetchPaginatedList();
64
- table.resetRowSelection();
65
- queryClient.invalidateQueries({ queryKey: ['childCollections'] });
66
- },
67
- onError: error => {
68
- toast.error(`Failed to remove ${selection.length} collections from channel: ${error.message}`);
69
- },
70
- });
71
-
72
- if (!selectedChannel) {
73
- return null;
74
- }
75
-
76
- const handleRemove = () => {
77
- mutate({
78
- input: {
79
- collectionIds: selection.map(s => s.id),
80
- channelId: selectedChannel.id,
81
- },
82
- });
83
- };
84
40
 
85
41
  return (
86
- <DataTableBulkActionItem
87
- requiresPermission={['UpdateCatalog', 'UpdateCollection']}
88
- onClick={handleRemove}
89
- label={<Trans>Remove from current channel</Trans>}
90
- confirmationText={
91
- <Trans>
92
- Are you sure you want to remove {selection.length} collections from the current channel?
93
- </Trans>
94
- }
95
- icon={LayersIcon}
96
- className="text-warning"
42
+ <RemoveFromChannelBulkAction
43
+ selection={selection}
44
+ table={table}
45
+ entityType="collections"
46
+ mutationFn={api.mutate(removeCollectionFromChannelDocument)}
47
+ requiredPermissions={['UpdateCatalog', 'UpdateCollection']}
48
+ buildInput={() => ({
49
+ collectionIds: selection.map(s => s.id),
50
+ channelId: selectedChannel?.id,
51
+ })}
52
+ onSuccess={() => {
53
+ queryClient.invalidateQueries({ queryKey: ['childCollections'] });
54
+ }}
97
55
  />
98
56
  );
99
57
  };
@@ -117,43 +75,14 @@ export const DuplicateCollectionsBulkAction: BulkActionComponent<any> = ({ selec
117
75
  };
118
76
 
119
77
  export const DeleteCollectionsBulkAction: BulkActionComponent<any> = ({ selection, table }) => {
120
- const { refetchPaginatedList } = usePaginatedList();
121
- const { i18n } = useLingui();
122
- const queryClient = useQueryClient();
123
- const { mutate } = useMutation({
124
- mutationFn: api.mutate(deleteCollectionsDocument),
125
- onSuccess: (result: ResultOf<typeof deleteCollectionsDocument>) => {
126
- let deleted = 0;
127
- const errors: string[] = [];
128
- for (const item of result.deleteCollections) {
129
- if (item.result === 'DELETED') {
130
- deleted++;
131
- } else if (item.message) {
132
- errors.push(item.message);
133
- }
134
- }
135
- if (0 < deleted) {
136
- toast.success(i18n.t(`Deleted ${deleted} collections`));
137
- }
138
- if (0 < errors.length) {
139
- toast.error(i18n.t(`Failed to delete ${errors.length} collections`));
140
- }
141
- refetchPaginatedList();
142
- table.resetRowSelection();
143
- queryClient.invalidateQueries({ queryKey: ['childCollections'] });
144
- },
145
- onError: () => {
146
- toast.error(`Failed to delete ${selection.length} collections`);
147
- },
148
- });
149
78
  return (
150
- <DataTableBulkActionItem
151
- requiresPermission={['DeleteCatalog', 'DeleteCollection']}
152
- onClick={() => mutate({ ids: selection.map(s => s.id) })}
153
- label={<Trans>Delete</Trans>}
154
- confirmationText={<Trans>Are you sure you want to delete {selection.length} collections?</Trans>}
155
- icon={TrashIcon}
156
- className="text-destructive"
79
+ <DeleteBulkAction
80
+ mutationDocument={deleteCollectionsDocument}
81
+ entityName="collections"
82
+ requiredPermissions={['DeleteCatalog', 'DeleteCollection']}
83
+ invalidateQueries={['childCollections']}
84
+ selection={selection}
85
+ table={table}
157
86
  />
158
87
  );
159
88
  };
@@ -0,0 +1,15 @@
1
+ import { BulkActionComponent } from '@/framework/data-table/data-table-types.js';
2
+ import { DeleteBulkAction } from '../../../../common/delete-bulk-action.js';
3
+ import { deleteCountriesDocument } from '../countries.graphql.js';
4
+
5
+ export const DeleteCountriesBulkAction: BulkActionComponent<any> = ({ selection, table }) => {
6
+ return (
7
+ <DeleteBulkAction
8
+ mutationDocument={deleteCountriesDocument}
9
+ entityName="countries"
10
+ requiredPermissions={['DeleteCountry']}
11
+ selection={selection}
12
+ table={table}
13
+ />
14
+ );
15
+ };
@@ -67,3 +67,12 @@ export const deleteCountryDocument = graphql(`
67
67
  }
68
68
  }
69
69
  `);
70
+
71
+ export const deleteCountriesDocument = graphql(`
72
+ mutation DeleteCountries($ids: [ID!]!) {
73
+ deleteCountries(ids: $ids) {
74
+ result
75
+ message
76
+ }
77
+ }
78
+ `);
@@ -6,6 +6,7 @@ import { ListPage } from '@/framework/page/list-page.js';
6
6
  import { Trans } from '@/lib/trans.js';
7
7
  import { createFileRoute, Link } from '@tanstack/react-router';
8
8
  import { PlusIcon } from 'lucide-react';
9
+ import { DeleteCountriesBulkAction } from './components/country-bulk-actions.js';
9
10
  import { countriesListQuery, deleteCountryDocument } from './countries.graphql.js';
10
11
 
11
12
  export const Route = createFileRoute('/_authenticated/_countries/countries')({
@@ -51,6 +52,12 @@ function CountryListPage() {
51
52
  cell: ({ row }) => <DetailPageButton id={row.original.id} label={row.original.name} />,
52
53
  },
53
54
  }}
55
+ bulkActions={[
56
+ {
57
+ component: DeleteCountriesBulkAction,
58
+ order: 500,
59
+ },
60
+ ]}
54
61
  >
55
62
  <PageActionBarRight>
56
63
  <PermissionGuard requires={['CreateCountry']}>
@@ -0,0 +1,15 @@
1
+ import { BulkActionComponent } from '@/framework/data-table/data-table-types.js';
2
+ import { DeleteBulkAction } from '../../../../common/delete-bulk-action.js';
3
+ import { deleteCustomerGroupsDocument } from '../customer-groups.graphql.js';
4
+
5
+ export const DeleteCustomerGroupsBulkAction: BulkActionComponent<any> = ({ selection, table }) => {
6
+ return (
7
+ <DeleteBulkAction
8
+ mutationDocument={deleteCustomerGroupsDocument}
9
+ entityName="customer groups"
10
+ requiredPermissions={['DeleteCustomerGroup']}
11
+ selection={selection}
12
+ table={table}
13
+ />
14
+ );
15
+ };
@@ -69,3 +69,12 @@ export const deleteCustomerGroupDocument = graphql(`
69
69
  }
70
70
  }
71
71
  `);
72
+
73
+ export const deleteCustomerGroupsDocument = graphql(`
74
+ mutation DeleteCustomerGroups($ids: [ID!]!) {
75
+ deleteCustomerGroups(ids: $ids) {
76
+ result
77
+ message
78
+ }
79
+ }
80
+ `);
@@ -6,6 +6,7 @@ import { ListPage } from '@/framework/page/list-page.js';
6
6
  import { Trans } from '@/lib/trans.js';
7
7
  import { createFileRoute, Link } from '@tanstack/react-router';
8
8
  import { PlusIcon } from 'lucide-react';
9
+ import { DeleteCustomerGroupsBulkAction } from './components/customer-group-bulk-actions.js';
9
10
  import { CustomerGroupMembersSheet } from './components/customer-group-members-sheet.js';
10
11
  import { customerGroupListDocument, deleteCustomerGroupDocument } from './customer-groups.graphql.js';
11
12
 
@@ -52,6 +53,12 @@ function CustomerGroupListPage() {
52
53
  name: { contains: searchTerm },
53
54
  };
54
55
  }}
56
+ bulkActions={[
57
+ {
58
+ component: DeleteCustomerGroupsBulkAction,
59
+ order: 500,
60
+ },
61
+ ]}
55
62
  >
56
63
  <PageActionBarRight>
57
64
  <PermissionGuard requires={['CreateCustomerGroup']}>