@vendure/dashboard 3.3.6-master-202507010922 → 3.3.6-master-202507020234

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 (78) hide show
  1. package/package.json +131 -131
  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/detail-page-button.tsx +1 -1
  63. package/src/lib/components/shared/remove-from-channel-bulk-action.tsx +89 -0
  64. package/src/lib/framework/extension-api/use-dashboard-extensions.ts +2 -1
  65. package/src/lib/framework/form-engine/use-generated-form.tsx +5 -8
  66. package/src/lib/framework/page/use-detail-page.ts +4 -4
  67. package/src/lib/framework/page/use-extended-router.tsx +4 -5
  68. package/src/lib/hooks/use-channel.ts +2 -1
  69. package/src/lib/hooks/use-extended-detail-query.ts +2 -1
  70. package/src/lib/hooks/use-extended-list-query.ts +3 -2
  71. package/src/lib/hooks/use-grouped-permissions.ts +4 -2
  72. package/src/lib/hooks/use-page-block.tsx +1 -1
  73. package/src/lib/hooks/use-page.tsx +2 -2
  74. package/src/lib/hooks/use-permissions.ts +3 -2
  75. package/src/lib/hooks/use-server-config.ts +2 -1
  76. package/src/lib/hooks/use-theme.ts +2 -1
  77. package/src/lib/providers/auth.tsx +34 -11
  78. package/src/app/routes/_authenticated/_collections/components/assign-collections-to-channel-dialog.tsx +0 -110
@@ -38,7 +38,10 @@ export const Route = createFileRoute('/_authenticated/_payment-methods/payment-m
38
38
  pageId,
39
39
  queryDocument: paymentMethodDetailDocument,
40
40
  breadcrumb(_isNew, entity) {
41
- return [{ path: '/payment-methods', label: 'Payment methods' }, entity?.name];
41
+ return [
42
+ { path: '/payment-methods', label: 'Payment methods' },
43
+ _isNew ? <Trans>New payment method</Trans> : entity?.name,
44
+ ];
42
45
  },
43
46
  }),
44
47
  errorComponent: ({ error }) => <ErrorPage message={error.message} />,
@@ -1,17 +1,17 @@
1
- import { useMutation } from '@tanstack/react-query';
2
- import { LayersIcon, TagIcon, TrashIcon } from 'lucide-react';
1
+ import { TagIcon } from 'lucide-react';
3
2
  import { useState } from 'react';
4
- import { toast } from 'sonner';
5
3
 
6
4
  import { DataTableBulkActionItem } from '@/components/data-table/data-table-bulk-action-item.js';
5
+ import { AssignToChannelBulkAction } from '@/components/shared/assign-to-channel-bulk-action.js';
6
+ import { usePriceFactor } from '@/components/shared/assign-to-channel-dialog.js';
7
+ import { RemoveFromChannelBulkAction } from '@/components/shared/remove-from-channel-bulk-action.js';
7
8
  import { BulkActionComponent } from '@/framework/data-table/data-table-types.js';
8
9
  import { api } from '@/graphql/api.js';
9
- import { ResultOf } from '@/graphql/graphql.js';
10
10
  import { useChannel, usePaginatedList } from '@/index.js';
11
- import { Trans, useLingui } from '@/lib/trans.js';
11
+ import { Trans } from '@/lib/trans.js';
12
+ import { DeleteBulkAction } from '../../../../common/delete-bulk-action.js';
12
13
 
13
14
  import { AssignFacetValuesDialog } from '../../_products/components/assign-facet-values-dialog.js';
14
- import { AssignToChannelDialog } from '../../_products/components/assign-to-channel-dialog.js';
15
15
  import {
16
16
  assignProductVariantsToChannelDocument,
17
17
  deleteProductVariantsDocument,
@@ -22,78 +22,34 @@ import {
22
22
  } from '../product-variants.graphql.js';
23
23
 
24
24
  export const DeleteProductVariantsBulkAction: BulkActionComponent<any> = ({ selection, table }) => {
25
- const { refetchPaginatedList } = usePaginatedList();
26
- const { i18n } = useLingui();
27
- const { mutate } = useMutation({
28
- mutationFn: api.mutate(deleteProductVariantsDocument),
29
- onSuccess: (result: ResultOf<typeof deleteProductVariantsDocument>) => {
30
- let deleted = 0;
31
- const errors: string[] = [];
32
- for (const item of result.deleteProductVariants) {
33
- if (item.result === 'DELETED') {
34
- deleted++;
35
- } else if (item.message) {
36
- errors.push(item.message);
37
- }
38
- }
39
- if (0 < deleted) {
40
- toast.success(i18n.t(`Deleted ${deleted} product variants`));
41
- }
42
- if (0 < errors.length) {
43
- toast.error(i18n.t(`Failed to delete ${errors.length} product variants`));
44
- }
45
- refetchPaginatedList();
46
- table.resetRowSelection();
47
- },
48
- onError: () => {
49
- toast.error(`Failed to delete ${selection.length} product variants`);
50
- },
51
- });
52
25
  return (
53
- <DataTableBulkActionItem
54
- requiresPermission={['DeleteCatalog', 'DeleteProduct']}
55
- onClick={() => mutate({ ids: selection.map(s => s.id) })}
56
- label={<Trans>Delete</Trans>}
57
- confirmationText={
58
- <Trans>Are you sure you want to delete {selection.length} product variants?</Trans>
59
- }
60
- icon={TrashIcon}
61
- className="text-destructive"
26
+ <DeleteBulkAction
27
+ mutationDocument={deleteProductVariantsDocument}
28
+ entityName="product variants"
29
+ requiredPermissions={['DeleteCatalog', 'DeleteProduct']}
30
+ selection={selection}
31
+ table={table}
62
32
  />
63
33
  );
64
34
  };
65
35
 
66
36
  export const AssignProductVariantsToChannelBulkAction: BulkActionComponent<any> = ({ selection, table }) => {
67
- const { refetchPaginatedList } = usePaginatedList();
68
- const { channels } = useChannel();
69
- const [dialogOpen, setDialogOpen] = useState(false);
70
-
71
- if (channels.length < 2) {
72
- return null;
73
- }
74
-
75
- const handleSuccess = () => {
76
- refetchPaginatedList();
77
- table.resetRowSelection();
78
- };
37
+ const { priceFactor, priceFactorField } = usePriceFactor();
79
38
 
80
39
  return (
81
- <>
82
- <DataTableBulkActionItem
83
- requiresPermission={['UpdateCatalog', 'UpdateProduct']}
84
- onClick={() => setDialogOpen(true)}
85
- label={<Trans>Assign to channel</Trans>}
86
- icon={LayersIcon}
87
- />
88
- <AssignToChannelDialog
89
- open={dialogOpen}
90
- onOpenChange={setDialogOpen}
91
- entityIds={selection.map(s => s.id)}
92
- entityType="variants"
93
- mutationFn={api.mutate(assignProductVariantsToChannelDocument)}
94
- onSuccess={handleSuccess}
95
- />
96
- </>
40
+ <AssignToChannelBulkAction
41
+ selection={selection}
42
+ table={table}
43
+ entityType="variants"
44
+ mutationFn={api.mutate(assignProductVariantsToChannelDocument)}
45
+ requiredPermissions={['UpdateCatalog', 'UpdateProduct']}
46
+ buildInput={(channelId: string) => ({
47
+ productVariantIds: selection.map(s => s.id),
48
+ channelId,
49
+ priceFactor,
50
+ })}
51
+ additionalFields={priceFactorField}
52
+ />
97
53
  );
98
54
  };
99
55
 
@@ -101,49 +57,19 @@ export const RemoveProductVariantsFromChannelBulkAction: BulkActionComponent<any
101
57
  selection,
102
58
  table,
103
59
  }) => {
104
- const { refetchPaginatedList } = usePaginatedList();
105
60
  const { selectedChannel } = useChannel();
106
- const { i18n } = useLingui();
107
- const { mutate } = useMutation({
108
- mutationFn: api.mutate(removeProductVariantsFromChannelDocument),
109
- onSuccess: () => {
110
- toast.success(i18n.t(`Successfully removed ${selection.length} product variants from channel`));
111
- refetchPaginatedList();
112
- table.resetRowSelection();
113
- },
114
- onError: error => {
115
- toast.error(
116
- `Failed to remove ${selection.length} product variants from channel: ${error.message}`,
117
- );
118
- },
119
- });
120
-
121
- if (!selectedChannel) {
122
- return null;
123
- }
124
-
125
- const handleRemove = () => {
126
- mutate({
127
- input: {
128
- productVariantIds: selection.map(s => s.id),
129
- channelId: selectedChannel.id,
130
- },
131
- });
132
- };
133
61
 
134
62
  return (
135
- <DataTableBulkActionItem
136
- requiresPermission={['UpdateCatalog', 'UpdateProduct']}
137
- onClick={handleRemove}
138
- label={<Trans>Remove from current channel</Trans>}
139
- confirmationText={
140
- <Trans>
141
- Are you sure you want to remove {selection.length} product variants from the current
142
- channel?
143
- </Trans>
144
- }
145
- icon={LayersIcon}
146
- className="text-warning"
63
+ <RemoveFromChannelBulkAction
64
+ selection={selection}
65
+ table={table}
66
+ entityType="product variants"
67
+ mutationFn={api.mutate(removeProductVariantsFromChannelDocument)}
68
+ requiredPermissions={['UpdateCatalog', 'UpdateProduct']}
69
+ buildInput={() => ({
70
+ productVariantIds: selection.map(s => s.id),
71
+ channelId: selectedChannel?.id,
72
+ })}
147
73
  />
148
74
  );
149
75
  };
@@ -1,14 +1,15 @@
1
- import { useMutation } from '@tanstack/react-query';
2
- import { LayersIcon, TagIcon, TrashIcon } from 'lucide-react';
1
+ import { TagIcon } from 'lucide-react';
3
2
  import { useState } from 'react';
4
- import { toast } from 'sonner';
5
3
 
6
4
  import { DataTableBulkActionItem } from '@/components/data-table/data-table-bulk-action-item.js';
5
+ import { AssignToChannelBulkAction } from '@/components/shared/assign-to-channel-bulk-action.js';
6
+ import { usePriceFactor } from '@/components/shared/assign-to-channel-dialog.js';
7
+ import { RemoveFromChannelBulkAction } from '@/components/shared/remove-from-channel-bulk-action.js';
7
8
  import { BulkActionComponent } from '@/framework/data-table/data-table-types.js';
8
9
  import { api } from '@/graphql/api.js';
9
- import { ResultOf } from '@/graphql/graphql.js';
10
10
  import { useChannel, usePaginatedList } from '@/index.js';
11
- import { Trans, useLingui } from '@/lib/trans.js';
11
+ import { Trans } from '@/lib/trans.js';
12
+ import { DeleteBulkAction } from '../../../../common/delete-bulk-action.js';
12
13
  import { DuplicateBulkAction } from '../../../../common/duplicate-bulk-action.js';
13
14
  import {
14
15
  assignProductsToChannelDocument,
@@ -19,123 +20,53 @@ import {
19
20
  updateProductsDocument,
20
21
  } from '../products.graphql.js';
21
22
  import { AssignFacetValuesDialog } from './assign-facet-values-dialog.js';
22
- import { AssignToChannelDialog } from './assign-to-channel-dialog.js';
23
23
 
24
24
  export const DeleteProductsBulkAction: BulkActionComponent<any> = ({ selection, table }) => {
25
- const { refetchPaginatedList } = usePaginatedList();
26
- const { i18n } = useLingui();
27
- const { mutate } = useMutation({
28
- mutationFn: api.mutate(deleteProductsDocument),
29
- onSuccess: (result: ResultOf<typeof deleteProductsDocument>) => {
30
- let deleted = 0;
31
- const errors: string[] = [];
32
- for (const item of result.deleteProducts) {
33
- if (item.result === 'DELETED') {
34
- deleted++;
35
- } else if (item.message) {
36
- errors.push(item.message);
37
- }
38
- }
39
- if (0 < deleted) {
40
- toast.success(i18n.t(`Deleted ${deleted} products`));
41
- }
42
- if (0 < errors.length) {
43
- toast.error(i18n.t(`Failed to delete ${errors.length} products`));
44
- }
45
- refetchPaginatedList();
46
- table.resetRowSelection();
47
- },
48
- onError: () => {
49
- toast.error(`Failed to delete ${selection.length} products`);
50
- },
51
- });
52
25
  return (
53
- <DataTableBulkActionItem
54
- requiresPermission={['DeleteCatalog', 'DeleteProduct']}
55
- onClick={() => mutate({ ids: selection.map(s => s.id) })}
56
- label={<Trans>Delete</Trans>}
57
- confirmationText={<Trans>Are you sure you want to delete {selection.length} products?</Trans>}
58
- icon={TrashIcon}
59
- className="text-destructive"
26
+ <DeleteBulkAction
27
+ mutationDocument={deleteProductsDocument}
28
+ entityName="products"
29
+ requiredPermissions={['DeleteCatalog', 'DeleteProduct']}
30
+ selection={selection}
31
+ table={table}
60
32
  />
61
33
  );
62
34
  };
63
35
 
64
36
  export const AssignProductsToChannelBulkAction: BulkActionComponent<any> = ({ selection, table }) => {
65
- const { refetchPaginatedList } = usePaginatedList();
66
- const { channels } = useChannel();
67
- const [dialogOpen, setDialogOpen] = useState(false);
68
-
69
- if (channels.length < 2) {
70
- return null;
71
- }
72
-
73
- const handleSuccess = () => {
74
- refetchPaginatedList();
75
- table.resetRowSelection();
76
- };
37
+ const { priceFactor, priceFactorField } = usePriceFactor();
77
38
 
78
39
  return (
79
- <>
80
- <DataTableBulkActionItem
81
- requiresPermission={['UpdateCatalog', 'UpdateProduct']}
82
- onClick={() => setDialogOpen(true)}
83
- label={<Trans>Assign to channel</Trans>}
84
- icon={LayersIcon}
85
- />
86
- <AssignToChannelDialog
87
- open={dialogOpen}
88
- onOpenChange={setDialogOpen}
89
- entityIds={selection.map(s => s.id)}
90
- entityType="products"
91
- mutationFn={api.mutate(assignProductsToChannelDocument)}
92
- onSuccess={handleSuccess}
93
- />
94
- </>
40
+ <AssignToChannelBulkAction
41
+ selection={selection}
42
+ table={table}
43
+ entityType="products"
44
+ mutationFn={api.mutate(assignProductsToChannelDocument)}
45
+ requiredPermissions={['UpdateCatalog', 'UpdateProduct']}
46
+ buildInput={(channelId: string) => ({
47
+ productIds: selection.map(s => s.id),
48
+ channelId,
49
+ priceFactor,
50
+ })}
51
+ additionalFields={priceFactorField}
52
+ />
95
53
  );
96
54
  };
97
55
 
98
56
  export const RemoveProductsFromChannelBulkAction: BulkActionComponent<any> = ({ selection, table }) => {
99
- const { refetchPaginatedList } = usePaginatedList();
100
57
  const { selectedChannel } = useChannel();
101
- const { i18n } = useLingui();
102
- const { mutate } = useMutation({
103
- mutationFn: api.mutate(removeProductsFromChannelDocument),
104
- onSuccess: () => {
105
- toast.success(i18n.t(`Successfully removed ${selection.length} products from channel`));
106
- refetchPaginatedList();
107
- table.resetRowSelection();
108
- },
109
- onError: error => {
110
- toast.error(`Failed to remove ${selection.length} products from channel: ${error.message}`);
111
- },
112
- });
113
-
114
- if (!selectedChannel) {
115
- return null;
116
- }
117
-
118
- const handleRemove = () => {
119
- mutate({
120
- input: {
121
- productIds: selection.map(s => s.id),
122
- channelId: selectedChannel.id,
123
- },
124
- });
125
- };
126
58
 
127
59
  return (
128
- <DataTableBulkActionItem
129
- requiresPermission={['UpdateCatalog', 'UpdateProduct']}
130
- onClick={handleRemove}
131
- label={<Trans>Remove from current channel</Trans>}
132
- confirmationText={
133
- <Trans>
134
- Are you sure you want to remove {selection.length} products from the current channel?
135
- </Trans>
136
- }
137
- icon={LayersIcon}
138
- className="text-warning"
60
+ <RemoveFromChannelBulkAction
61
+ selection={selection}
62
+ table={table}
63
+ entityType="products"
64
+ mutationFn={api.mutate(removeProductsFromChannelDocument)}
65
+ requiredPermissions={['UpdateCatalog', 'UpdateProduct']}
66
+ buildInput={() => ({
67
+ productIds: selection.map(s => s.id),
68
+ channelId: selectedChannel?.id,
69
+ })}
139
70
  />
140
71
  );
141
72
  };
@@ -0,0 +1,82 @@
1
+ import { AssignToChannelBulkAction } from '@/components/shared/assign-to-channel-bulk-action.js';
2
+ import { RemoveFromChannelBulkAction } from '@/components/shared/remove-from-channel-bulk-action.js';
3
+ import { BulkActionComponent } from '@/framework/data-table/data-table-types.js';
4
+ import { api } from '@/graphql/api.js';
5
+ import { useChannel } from '@/index.js';
6
+ import { DeleteBulkAction } from '../../../../common/delete-bulk-action.js';
7
+ import { DuplicateBulkAction } from '../../../../common/duplicate-bulk-action.js';
8
+
9
+ import {
10
+ assignPromotionsToChannelDocument,
11
+ deletePromotionsDocument,
12
+ removePromotionsFromChannelDocument,
13
+ } from '../promotions.graphql.js';
14
+
15
+ export const DeletePromotionsBulkAction: BulkActionComponent<any> = ({ selection, table }) => {
16
+ return (
17
+ <DeleteBulkAction
18
+ mutationDocument={deletePromotionsDocument}
19
+ entityName="promotions"
20
+ requiredPermissions={['DeletePromotion']}
21
+ selection={selection}
22
+ table={table}
23
+ />
24
+ );
25
+ };
26
+
27
+ export const AssignPromotionsToChannelBulkAction: BulkActionComponent<any> = ({ selection, table }) => {
28
+ return (
29
+ <AssignToChannelBulkAction
30
+ selection={selection}
31
+ table={table}
32
+ entityType="promotions"
33
+ mutationFn={api.mutate(assignPromotionsToChannelDocument)}
34
+ requiredPermissions={['UpdatePromotion']}
35
+ buildInput={(channelId: string) => ({
36
+ promotionIds: selection.map(s => s.id),
37
+ channelId,
38
+ })}
39
+ />
40
+ );
41
+ };
42
+
43
+ export const RemovePromotionsFromChannelBulkAction: BulkActionComponent<any> = ({ selection, table }) => {
44
+ const { selectedChannel } = useChannel();
45
+
46
+ return (
47
+ <RemoveFromChannelBulkAction
48
+ selection={selection}
49
+ table={table}
50
+ entityType="promotions"
51
+ mutationFn={api.mutate(removePromotionsFromChannelDocument)}
52
+ requiredPermissions={['UpdatePromotion']}
53
+ buildInput={() => ({
54
+ promotionIds: selection.map(s => s.id),
55
+ channelId: selectedChannel?.id,
56
+ })}
57
+ />
58
+ );
59
+ };
60
+
61
+ export const DuplicatePromotionsBulkAction: BulkActionComponent<any> = ({ selection, table }) => {
62
+ return (
63
+ <DuplicateBulkAction
64
+ entityType="Promotion"
65
+ duplicatorCode="promotion-duplicator"
66
+ duplicatorArguments={[
67
+ {
68
+ name: 'includeConditions',
69
+ value: 'true',
70
+ },
71
+ {
72
+ name: 'includeActions',
73
+ value: 'true',
74
+ },
75
+ ]}
76
+ requiredPermissions={['CreatePromotion']}
77
+ entityName="Promotion"
78
+ selection={selection}
79
+ table={table}
80
+ />
81
+ );
82
+ };
@@ -94,3 +94,28 @@ export const deletePromotionDocument = graphql(`
94
94
  }
95
95
  }
96
96
  `);
97
+
98
+ export const assignPromotionsToChannelDocument = graphql(`
99
+ mutation AssignPromotionsToChannel($input: AssignPromotionsToChannelInput!) {
100
+ assignPromotionsToChannel(input: $input) {
101
+ id
102
+ }
103
+ }
104
+ `);
105
+
106
+ export const removePromotionsFromChannelDocument = graphql(`
107
+ mutation RemovePromotionsFromChannel($input: RemovePromotionsFromChannelInput!) {
108
+ removePromotionsFromChannel(input: $input) {
109
+ id
110
+ }
111
+ }
112
+ `);
113
+
114
+ export const deletePromotionsDocument = graphql(`
115
+ mutation DeletePromotions($ids: [ID!]!) {
116
+ deletePromotions(ids: $ids) {
117
+ result
118
+ message
119
+ }
120
+ }
121
+ `);
@@ -7,6 +7,12 @@ import { ListPage } from '@/framework/page/list-page.js';
7
7
  import { Trans } from '@/lib/trans.js';
8
8
  import { createFileRoute, Link } from '@tanstack/react-router';
9
9
  import { PlusIcon } from 'lucide-react';
10
+ import {
11
+ AssignPromotionsToChannelBulkAction,
12
+ DeletePromotionsBulkAction,
13
+ DuplicatePromotionsBulkAction,
14
+ RemovePromotionsFromChannelBulkAction,
15
+ } from './components/promotion-bulk-actions.js';
10
16
  import { deletePromotionDocument, promotionListDocument } from './promotions.graphql.js';
11
17
 
12
18
  export const Route = createFileRoute('/_authenticated/_promotions/promotions')({
@@ -45,6 +51,24 @@ function PromotionListPage() {
45
51
  cell: ({ row }) => <BooleanDisplayBadge value={row.original.enabled} />,
46
52
  },
47
53
  }}
54
+ bulkActions={[
55
+ {
56
+ order: 100,
57
+ component: AssignPromotionsToChannelBulkAction,
58
+ },
59
+ {
60
+ order: 200,
61
+ component: RemovePromotionsFromChannelBulkAction,
62
+ },
63
+ {
64
+ order: 300,
65
+ component: DuplicatePromotionsBulkAction,
66
+ },
67
+ {
68
+ order: 400,
69
+ component: DeletePromotionsBulkAction,
70
+ },
71
+ ]}
48
72
  >
49
73
  <PageActionBarRight>
50
74
  <PermissionGuard requires={['CreatePromotion']}>
@@ -102,7 +102,7 @@ function PromotionDetailPage() {
102
102
  toast.success(i18n.t('Successfully updated promotion'));
103
103
  resetForm();
104
104
  if (creatingNewEntity) {
105
- await navigate({ to: `../${data.id}`, from: Route.id });
105
+ await navigate({ to: `../$id`, params: { id: data.id } });
106
106
  }
107
107
  } else {
108
108
  toast.error(i18n.t('Failed to update promotion'), {
@@ -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 { deleteRolesDocument } from '../roles.graphql.js';
4
+
5
+ export const DeleteRolesBulkAction: BulkActionComponent<any> = ({ selection, table }) => {
6
+ return (
7
+ <DeleteBulkAction
8
+ mutationDocument={deleteRolesDocument}
9
+ entityName="roles"
10
+ requiredPermissions={['DeleteAdministrator']}
11
+ selection={selection}
12
+ table={table}
13
+ />
14
+ );
15
+ };
@@ -65,3 +65,12 @@ export const deleteRoleDocument = graphql(`
65
65
  }
66
66
  }
67
67
  `);
68
+
69
+ export const deleteRolesDocument = graphql(`
70
+ mutation DeleteRoles($ids: [ID!]!) {
71
+ deleteRoles(ids: $ids) {
72
+ result
73
+ message
74
+ }
75
+ }
76
+ `);
@@ -11,6 +11,7 @@ import { Trans } from '@/lib/trans.js';
11
11
  import { createFileRoute, Link } from '@tanstack/react-router';
12
12
  import { LayersIcon, PlusIcon } from 'lucide-react';
13
13
  import { ExpandablePermissions } from './components/expandable-permissions.js';
14
+ import { DeleteRolesBulkAction } from './components/role-bulk-actions.js';
14
15
  import { deleteRoleDocument, roleListQuery } from './roles.graphql.js';
15
16
 
16
17
  export const Route = createFileRoute('/_authenticated/_roles/roles')({
@@ -80,6 +81,12 @@ function RoleListPage() {
80
81
  },
81
82
  },
82
83
  }}
84
+ bulkActions={[
85
+ {
86
+ component: DeleteRolesBulkAction,
87
+ order: 500,
88
+ },
89
+ ]}
83
90
  >
84
91
  <PageActionBarRight>
85
92
  <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 { deleteSellersDocument } from '../sellers.graphql.js';
4
+
5
+ export const DeleteSellersBulkAction: BulkActionComponent<any> = ({ selection, table }) => {
6
+ return (
7
+ <DeleteBulkAction
8
+ mutationDocument={deleteSellersDocument}
9
+ entityName="sellers"
10
+ requiredPermissions={['DeleteSeller']}
11
+ selection={selection}
12
+ table={table}
13
+ />
14
+ );
15
+ };
@@ -59,3 +59,12 @@ export const deleteSellerDocument = graphql(`
59
59
  }
60
60
  }
61
61
  `);
62
+
63
+ export const deleteSellersDocument = graphql(`
64
+ mutation DeleteSellers($ids: [ID!]!) {
65
+ deleteSellers(ids: $ids) {
66
+ result
67
+ message
68
+ }
69
+ }
70
+ `);
@@ -7,6 +7,7 @@ import { Trans } from '@/lib/trans.js';
7
7
  import { createFileRoute, Link } from '@tanstack/react-router';
8
8
  import { PlusIcon } from 'lucide-react';
9
9
  import { deleteSellerDocument, sellerListQuery } from './sellers.graphql.js';
10
+ import { DeleteSellersBulkAction } from './components/seller-bulk-actions.js';
10
11
 
11
12
  export const Route = createFileRoute('/_authenticated/_sellers/sellers')({
12
13
  component: SellerListPage,
@@ -35,6 +36,12 @@ function SellerListPage() {
35
36
  cell: ({ row }) => <DetailPageButton id={row.original.id} label={row.original.name} />,
36
37
  },
37
38
  }}
39
+ bulkActions={[
40
+ {
41
+ component: DeleteSellersBulkAction,
42
+ order: 500,
43
+ },
44
+ ]}
38
45
  >
39
46
  <PageActionBarRight>
40
47
  <PermissionGuard requires={['CreateSeller']}>
@@ -58,7 +58,7 @@ function SellerDetailPage() {
58
58
  toast(i18n.t('Successfully updated seller'));
59
59
  form.reset(form.getValues());
60
60
  if (creatingNewEntity) {
61
- await navigate({ to: `../${data?.id}`, from: Route.id });
61
+ await navigate({ to: `../$id`, params: { id: data.id } });
62
62
  }
63
63
  },
64
64
  onError: err => {