@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.
- package/package.json +4 -4
- package/src/app/routes/_authenticated/_administrators/administrators_.$id.tsx +5 -1
- package/src/app/routes/_authenticated/_assets/assets_.$id.tsx +7 -2
- package/src/app/routes/_authenticated/_channels/channels_.$id.tsx +5 -1
- package/src/app/routes/_authenticated/_collections/collections.graphql.ts +16 -0
- package/src/app/routes/_authenticated/_collections/collections.tsx +16 -2
- package/src/app/routes/_authenticated/_collections/collections_.$id.tsx +5 -1
- package/src/app/routes/_authenticated/_collections/components/assign-collections-to-channel-dialog.tsx +110 -0
- package/src/app/routes/_authenticated/_collections/components/collection-bulk-actions.tsx +99 -0
- package/src/app/routes/_authenticated/_countries/countries.graphql.ts +1 -1
- package/src/app/routes/_authenticated/_countries/countries_.$id.tsx +9 -5
- package/src/app/routes/_authenticated/_customer-groups/customer-groups.graphql.ts +1 -1
- package/src/app/routes/_authenticated/_customer-groups/customer-groups_.$id.tsx +8 -5
- package/src/app/routes/_authenticated/_customers/customers_.$id.tsx +5 -1
- package/src/app/routes/_authenticated/_facets/facets_.$id.tsx +5 -1
- package/src/app/routes/_authenticated/_orders/orders_.$id.tsx +5 -2
- package/src/app/routes/_authenticated/_payment-methods/payment-methods_.$id.tsx +5 -1
- package/src/app/routes/_authenticated/_product-variants/components/product-variant-bulk-actions.tsx +184 -0
- package/src/app/routes/_authenticated/_product-variants/product-variants.graphql.ts +62 -1
- package/src/app/routes/_authenticated/_product-variants/product-variants.tsx +33 -3
- package/src/app/routes/_authenticated/_product-variants/product-variants_.$id.tsx +14 -3
- package/src/app/routes/_authenticated/_products/components/assign-facet-values-dialog.tsx +67 -36
- package/src/app/routes/_authenticated/_products/components/assign-to-channel-dialog.tsx +28 -17
- package/src/app/routes/_authenticated/_products/components/product-bulk-actions.tsx +12 -2
- package/src/app/routes/_authenticated/_products/components/product-variants-table.tsx +74 -55
- package/src/app/routes/_authenticated/_products/products_.$id.tsx +6 -1
- package/src/app/routes/_authenticated/_promotions/promotions_.$id.tsx +5 -1
- package/src/app/routes/_authenticated/_roles/roles_.$id.tsx +5 -1
- package/src/app/routes/_authenticated/_sellers/sellers_.$id.tsx +6 -2
- package/src/app/routes/_authenticated/_shipping-methods/shipping-methods_.$id.tsx +5 -1
- package/src/app/routes/_authenticated/_stock-locations/stock-locations_.$id.tsx +5 -1
- package/src/app/routes/_authenticated/_tax-categories/tax-categories.graphql.ts +1 -1
- package/src/app/routes/_authenticated/_tax-categories/tax-categories_.$id.tsx +9 -5
- package/src/app/routes/_authenticated/_tax-rates/tax-rates.graphql.ts +1 -1
- package/src/app/routes/_authenticated/_tax-rates/tax-rates_.$id.tsx +8 -4
- package/src/app/routes/_authenticated/_zones/zones.graphql.ts +1 -1
- package/src/app/routes/_authenticated/_zones/zones_.$id.tsx +8 -4
- package/src/lib/components/shared/detail-page-button.tsx +3 -1
- package/src/lib/components/shared/paginated-list-data-table.tsx +6 -4
- package/src/lib/framework/data-table/data-table-extensions.ts +14 -0
- package/src/lib/framework/document-extension/extend-detail-form-query.ts +50 -0
- package/src/lib/framework/document-extension/extend-document.spec.ts +884 -0
- package/src/lib/framework/document-extension/extend-document.ts +159 -0
- package/src/lib/framework/document-introspection/add-custom-fields.ts +48 -0
- package/src/lib/framework/extension-api/define-dashboard-extension.ts +33 -2
- package/src/lib/framework/extension-api/extension-api-types.ts +21 -2
- package/src/lib/framework/form-engine/custom-form-component-extensions.ts +13 -3
- package/src/lib/framework/layout-engine/page-layout.tsx +1 -0
- package/src/lib/framework/page/detail-page-route-loader.tsx +22 -4
- package/src/lib/framework/page/use-detail-page.ts +11 -2
- package/src/lib/framework/registry/registry-types.ts +3 -0
- package/src/lib/graphql/graphql-env.d.ts +8 -6
- package/src/lib/hooks/use-extended-detail-query.ts +37 -0
- package/src/lib/hooks/use-extended-list-query.ts +73 -0
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-
|
|
4
|
+
"version": "3.3.6-master-202507010731",
|
|
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-
|
|
90
|
-
"@vendure/core": "^3.3.6-master-
|
|
89
|
+
"@vendure/common": "^3.3.6-master-202507010731",
|
|
90
|
+
"@vendure/core": "^3.3.6-master-202507010731",
|
|
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": "
|
|
133
|
+
"gitHead": "f5da386affaa5dcc54296a51228e2a7e5d3b0f84"
|
|
134
134
|
}
|
|
@@ -26,9 +26,12 @@ import {
|
|
|
26
26
|
} from './administrators.graphql.js';
|
|
27
27
|
import { RolePermissionsDisplay } from './components/role-permissions-display.js';
|
|
28
28
|
|
|
29
|
+
const pageId = 'administrator-detail';
|
|
30
|
+
|
|
29
31
|
export const Route = createFileRoute('/_authenticated/_administrators/administrators_/$id')({
|
|
30
32
|
component: AdministratorDetailPage,
|
|
31
33
|
loader: detailPageRouteLoader({
|
|
34
|
+
pageId,
|
|
32
35
|
queryDocument: administratorDetailDocument,
|
|
33
36
|
breadcrumb: (isNew, entity) => {
|
|
34
37
|
const name = `${entity?.firstName} ${entity?.lastName}`;
|
|
@@ -48,6 +51,7 @@ function AdministratorDetailPage() {
|
|
|
48
51
|
const { i18n } = useLingui();
|
|
49
52
|
|
|
50
53
|
const { form, submitHandler, entity, isPending, resetForm } = useDetailPage({
|
|
54
|
+
pageId,
|
|
51
55
|
queryDocument: administratorDetailDocument,
|
|
52
56
|
createDocument: createAdministratorDocument,
|
|
53
57
|
updateDocument: updateAdministratorDocument,
|
|
@@ -87,7 +91,7 @@ function AdministratorDetailPage() {
|
|
|
87
91
|
const roleIds = form.watch('roleIds');
|
|
88
92
|
|
|
89
93
|
return (
|
|
90
|
-
<Page pageId=
|
|
94
|
+
<Page pageId={pageId} form={form} submitHandler={submitHandler} entity={entity}>
|
|
91
95
|
<PageTitle>{creatingNewEntity ? <Trans>New administrator</Trans> : name}</PageTitle>
|
|
92
96
|
|
|
93
97
|
<PageActionBar>
|
|
@@ -25,9 +25,13 @@ import { FocusIcon } from 'lucide-react';
|
|
|
25
25
|
import { useRef, useState } from 'react';
|
|
26
26
|
import { toast } from 'sonner';
|
|
27
27
|
import { assetDetailDocument, assetUpdateDocument } from './assets.graphql.js';
|
|
28
|
+
|
|
29
|
+
const pageId = 'asset-detail';
|
|
30
|
+
|
|
28
31
|
export const Route = createFileRoute('/_authenticated/_assets/assets_/$id')({
|
|
29
32
|
component: AssetDetailPage,
|
|
30
33
|
loader: detailPageRouteLoader({
|
|
34
|
+
pageId,
|
|
31
35
|
queryDocument: assetDetailDocument,
|
|
32
36
|
breadcrumb(isNew, entity) {
|
|
33
37
|
return [
|
|
@@ -49,7 +53,8 @@ function AssetDetailPage() {
|
|
|
49
53
|
const [height, setHeight] = useState(0);
|
|
50
54
|
const [focalPoint, setFocalPoint] = useState<Point | undefined>(undefined);
|
|
51
55
|
const [settingFocalPoint, setSettingFocalPoint] = useState(false);
|
|
52
|
-
const { form, submitHandler, entity, isPending } = useDetailPage({
|
|
56
|
+
const { form, submitHandler, entity, isPending, resetForm } = useDetailPage({
|
|
57
|
+
pageId,
|
|
53
58
|
queryDocument: assetDetailDocument,
|
|
54
59
|
updateDocument: assetUpdateDocument,
|
|
55
60
|
setValuesForUpdate: entity => {
|
|
@@ -86,7 +91,7 @@ function AssetDetailPage() {
|
|
|
86
91
|
return null;
|
|
87
92
|
}
|
|
88
93
|
return (
|
|
89
|
-
<Page pageId=
|
|
94
|
+
<Page pageId={pageId} form={form} submitHandler={submitHandler} entity={entity}>
|
|
90
95
|
<PageTitle>
|
|
91
96
|
<Trans>Edit asset</Trans>
|
|
92
97
|
</PageTitle>
|
|
@@ -27,9 +27,12 @@ import { createFileRoute, useNavigate } from '@tanstack/react-router';
|
|
|
27
27
|
import { toast } from 'sonner';
|
|
28
28
|
import { channelDetailDocument, createChannelDocument, updateChannelDocument } from './channels.graphql.js';
|
|
29
29
|
|
|
30
|
+
const pageId = 'channel-detail';
|
|
31
|
+
|
|
30
32
|
export const Route = createFileRoute('/_authenticated/_channels/channels_/$id')({
|
|
31
33
|
component: ChannelDetailPage,
|
|
32
34
|
loader: detailPageRouteLoader({
|
|
35
|
+
pageId,
|
|
33
36
|
queryDocument: channelDetailDocument,
|
|
34
37
|
breadcrumb(isNew, entity) {
|
|
35
38
|
return [
|
|
@@ -48,6 +51,7 @@ function ChannelDetailPage() {
|
|
|
48
51
|
const { i18n } = useLingui();
|
|
49
52
|
|
|
50
53
|
const { form, submitHandler, entity, isPending, resetForm } = useDetailPage({
|
|
54
|
+
pageId,
|
|
51
55
|
queryDocument: channelDetailDocument,
|
|
52
56
|
createDocument: createChannelDocument,
|
|
53
57
|
updateDocument: updateChannelDocument,
|
|
@@ -100,7 +104,7 @@ function ChannelDetailPage() {
|
|
|
100
104
|
const codeIsDefault = entity?.code === DEFAULT_CHANNEL_CODE;
|
|
101
105
|
|
|
102
106
|
return (
|
|
103
|
-
<Page pageId=
|
|
107
|
+
<Page pageId={pageId} form={form} submitHandler={submitHandler} entity={entity}>
|
|
104
108
|
<PageTitle>
|
|
105
109
|
{creatingNewEntity ? (
|
|
106
110
|
<Trans>New channel</Trans>
|
|
@@ -131,3 +131,19 @@ export const getCollectionFiltersQueryOptions = queryOptions({
|
|
|
131
131
|
queryKey: ['getCollectionFilters'],
|
|
132
132
|
queryFn: () => api.query(getCollectionFiltersDocument),
|
|
133
133
|
}) as DefinedInitialDataOptions<ResultOf<typeof getCollectionFiltersDocument>>;
|
|
134
|
+
|
|
135
|
+
export const assignCollectionToChannelDocument = graphql(`
|
|
136
|
+
mutation AssignCollectionsToChannel($input: AssignCollectionsToChannelInput!) {
|
|
137
|
+
assignCollectionsToChannel(input: $input) {
|
|
138
|
+
id
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
`);
|
|
142
|
+
|
|
143
|
+
export const removeCollectionFromChannelDocument = graphql(`
|
|
144
|
+
mutation RemoveCollectionsFromChannel($input: RemoveCollectionsFromChannelInput!) {
|
|
145
|
+
removeCollectionsFromChannel(input: $input) {
|
|
146
|
+
id
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
`);
|
|
@@ -5,7 +5,7 @@ import { PageActionBarRight } from '@/framework/layout-engine/page-layout.js';
|
|
|
5
5
|
import { ListPage } from '@/framework/page/list-page.js';
|
|
6
6
|
import { api } from '@/graphql/api.js';
|
|
7
7
|
import { Trans } from '@/lib/trans.js';
|
|
8
|
-
import { useQueries } from '@tanstack/react-query';
|
|
8
|
+
import { FetchQueryOptions, useQueries } from '@tanstack/react-query';
|
|
9
9
|
import { createFileRoute, Link } from '@tanstack/react-router';
|
|
10
10
|
import { ExpandedState, getExpandedRowModel } from '@tanstack/react-table';
|
|
11
11
|
import { TableOptions } from '@tanstack/table-core';
|
|
@@ -14,6 +14,10 @@ import { Folder, FolderOpen, PlusIcon } from 'lucide-react';
|
|
|
14
14
|
import { useState } from 'react';
|
|
15
15
|
|
|
16
16
|
import { collectionListDocument, deleteCollectionDocument } from './collections.graphql.js';
|
|
17
|
+
import {
|
|
18
|
+
AssignCollectionsToChannelBulkAction,
|
|
19
|
+
RemoveCollectionsFromChannelBulkAction,
|
|
20
|
+
} from './components/collection-bulk-actions.js';
|
|
17
21
|
import { CollectionContentsSheet } from './components/collection-contents-sheet.js';
|
|
18
22
|
|
|
19
23
|
export const Route = createFileRoute('/_authenticated/_collections/collections')({
|
|
@@ -38,7 +42,7 @@ function CollectionListPage() {
|
|
|
38
42
|
},
|
|
39
43
|
}),
|
|
40
44
|
staleTime: 1000 * 60 * 5,
|
|
41
|
-
};
|
|
45
|
+
} satisfies FetchQueryOptions;
|
|
42
46
|
}),
|
|
43
47
|
});
|
|
44
48
|
const childCollectionsByParentId = childrenQueries.reduce(
|
|
@@ -179,6 +183,16 @@ function CollectionListPage() {
|
|
|
179
183
|
};
|
|
180
184
|
}}
|
|
181
185
|
route={Route}
|
|
186
|
+
bulkActions={[
|
|
187
|
+
{
|
|
188
|
+
component: AssignCollectionsToChannelBulkAction,
|
|
189
|
+
order: 100,
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
component: RemoveCollectionsFromChannelBulkAction,
|
|
193
|
+
order: 200,
|
|
194
|
+
},
|
|
195
|
+
]}
|
|
182
196
|
>
|
|
183
197
|
<PageActionBarRight>
|
|
184
198
|
<PermissionGuard requires={['CreateCollection', 'CreateCatalog']}>
|
|
@@ -33,9 +33,12 @@ import { CollectionContentsPreviewTable } from './components/collection-contents
|
|
|
33
33
|
import { CollectionContentsTable } from './components/collection-contents-table.js';
|
|
34
34
|
import { CollectionFiltersSelector } from './components/collection-filters-selector.js';
|
|
35
35
|
|
|
36
|
+
const pageId = 'collection-detail';
|
|
37
|
+
|
|
36
38
|
export const Route = createFileRoute('/_authenticated/_collections/collections_/$id')({
|
|
37
39
|
component: CollectionDetailPage,
|
|
38
40
|
loader: detailPageRouteLoader({
|
|
41
|
+
pageId,
|
|
39
42
|
queryDocument: collectionDetailDocument,
|
|
40
43
|
breadcrumb: (isNew, entity) => [
|
|
41
44
|
{ path: '/collections', label: 'Collections' },
|
|
@@ -52,6 +55,7 @@ function CollectionDetailPage() {
|
|
|
52
55
|
const { i18n } = useLingui();
|
|
53
56
|
|
|
54
57
|
const { form, submitHandler, entity, isPending, resetForm } = useDetailPage({
|
|
58
|
+
pageId,
|
|
55
59
|
queryDocument: collectionDetailDocument,
|
|
56
60
|
createDocument: createCollectionDocument,
|
|
57
61
|
transformCreateInput: values => {
|
|
@@ -105,7 +109,7 @@ function CollectionDetailPage() {
|
|
|
105
109
|
const currentInheritFiltersValue = form.watch('inheritFilters');
|
|
106
110
|
|
|
107
111
|
return (
|
|
108
|
-
<Page pageId=
|
|
112
|
+
<Page pageId={pageId} form={form} submitHandler={submitHandler} entity={entity}>
|
|
109
113
|
<PageTitle>{creatingNewEntity ? <Trans>New collection</Trans> : (entity?.name ?? '')}</PageTitle>
|
|
110
114
|
<PageActionBar>
|
|
111
115
|
<PageActionBarRight>
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { useMutation } from '@tanstack/react-query';
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { toast } from 'sonner';
|
|
4
|
+
|
|
5
|
+
import { ChannelCodeLabel } from '@/components/shared/channel-code-label.js';
|
|
6
|
+
import { Button } from '@/components/ui/button.js';
|
|
7
|
+
import {
|
|
8
|
+
Dialog,
|
|
9
|
+
DialogContent,
|
|
10
|
+
DialogDescription,
|
|
11
|
+
DialogFooter,
|
|
12
|
+
DialogHeader,
|
|
13
|
+
DialogTitle,
|
|
14
|
+
} from '@/components/ui/dialog.js';
|
|
15
|
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select.js';
|
|
16
|
+
import { ResultOf } from '@/graphql/graphql.js';
|
|
17
|
+
import { Trans, useLingui } from '@/lib/trans.js';
|
|
18
|
+
|
|
19
|
+
import { useChannel } from '@/hooks/use-channel.js';
|
|
20
|
+
|
|
21
|
+
interface AssignCollectionsToChannelDialogProps {
|
|
22
|
+
open: boolean;
|
|
23
|
+
onOpenChange: (open: boolean) => void;
|
|
24
|
+
entityIds: string[];
|
|
25
|
+
mutationFn: (variables: any) => Promise<ResultOf<any>>;
|
|
26
|
+
onSuccess?: () => void;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function AssignCollectionsToChannelDialog({
|
|
30
|
+
open,
|
|
31
|
+
onOpenChange,
|
|
32
|
+
entityIds,
|
|
33
|
+
mutationFn,
|
|
34
|
+
onSuccess,
|
|
35
|
+
}: AssignCollectionsToChannelDialogProps) {
|
|
36
|
+
const { i18n } = useLingui();
|
|
37
|
+
const [selectedChannelId, setSelectedChannelId] = useState<string>('');
|
|
38
|
+
const { channels, selectedChannel } = useChannel();
|
|
39
|
+
|
|
40
|
+
// Filter out the currently selected channel from available options
|
|
41
|
+
const availableChannels = channels.filter(channel => channel.id !== selectedChannel?.id);
|
|
42
|
+
|
|
43
|
+
const { mutate, isPending } = useMutation({
|
|
44
|
+
mutationFn,
|
|
45
|
+
onSuccess: () => {
|
|
46
|
+
toast.success(i18n.t(`Successfully assigned ${entityIds.length} collections to channel`));
|
|
47
|
+
onSuccess?.();
|
|
48
|
+
onOpenChange(false);
|
|
49
|
+
},
|
|
50
|
+
onError: () => {
|
|
51
|
+
toast.error(`Failed to assign ${entityIds.length} collections to channel`);
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const handleAssign = () => {
|
|
56
|
+
if (!selectedChannelId) {
|
|
57
|
+
toast.error('Please select a channel');
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const input = {
|
|
62
|
+
collectionIds: entityIds,
|
|
63
|
+
channelId: selectedChannelId,
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
mutate({ input });
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
71
|
+
<DialogContent className="sm:max-w-[425px]">
|
|
72
|
+
<DialogHeader>
|
|
73
|
+
<DialogTitle>
|
|
74
|
+
<Trans>Assign collections to channel</Trans>
|
|
75
|
+
</DialogTitle>
|
|
76
|
+
<DialogDescription>
|
|
77
|
+
<Trans>Select a channel to assign {entityIds.length} collections to</Trans>
|
|
78
|
+
</DialogDescription>
|
|
79
|
+
</DialogHeader>
|
|
80
|
+
<div className="grid gap-4 py-4">
|
|
81
|
+
<div className="grid gap-2">
|
|
82
|
+
<label className="text-sm font-medium">
|
|
83
|
+
<Trans>Channel</Trans>
|
|
84
|
+
</label>
|
|
85
|
+
<Select value={selectedChannelId} onValueChange={setSelectedChannelId}>
|
|
86
|
+
<SelectTrigger>
|
|
87
|
+
<SelectValue placeholder={i18n.t('Select a channel')} />
|
|
88
|
+
</SelectTrigger>
|
|
89
|
+
<SelectContent>
|
|
90
|
+
{availableChannels.map(channel => (
|
|
91
|
+
<SelectItem key={channel.id} value={channel.id}>
|
|
92
|
+
<ChannelCodeLabel code={channel.code} />
|
|
93
|
+
</SelectItem>
|
|
94
|
+
))}
|
|
95
|
+
</SelectContent>
|
|
96
|
+
</Select>
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
<DialogFooter>
|
|
100
|
+
<Button variant="outline" onClick={() => onOpenChange(false)}>
|
|
101
|
+
<Trans>Cancel</Trans>
|
|
102
|
+
</Button>
|
|
103
|
+
<Button onClick={handleAssign} disabled={!selectedChannelId || isPending}>
|
|
104
|
+
<Trans>Assign</Trans>
|
|
105
|
+
</Button>
|
|
106
|
+
</DialogFooter>
|
|
107
|
+
</DialogContent>
|
|
108
|
+
</Dialog>
|
|
109
|
+
);
|
|
110
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
|
2
|
+
import { LayersIcon } from 'lucide-react';
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { toast } from 'sonner';
|
|
5
|
+
|
|
6
|
+
import { DataTableBulkActionItem } from '@/components/data-table/data-table-bulk-action-item.js';
|
|
7
|
+
import { BulkActionComponent } from '@/framework/data-table/data-table-types.js';
|
|
8
|
+
import { api } from '@/graphql/api.js';
|
|
9
|
+
import { useChannel, usePaginatedList } from '@/index.js';
|
|
10
|
+
import { Trans, useLingui } from '@/lib/trans.js';
|
|
11
|
+
|
|
12
|
+
import { Permission } from '@vendure/common/lib/generated-types';
|
|
13
|
+
import {
|
|
14
|
+
assignCollectionToChannelDocument,
|
|
15
|
+
removeCollectionFromChannelDocument,
|
|
16
|
+
} from '../collections.graphql.js';
|
|
17
|
+
import { AssignCollectionsToChannelDialog } from './assign-collections-to-channel-dialog.js';
|
|
18
|
+
|
|
19
|
+
export const AssignCollectionsToChannelBulkAction: BulkActionComponent<any> = ({ selection, table }) => {
|
|
20
|
+
const { refetchPaginatedList } = usePaginatedList();
|
|
21
|
+
const { channels } = useChannel();
|
|
22
|
+
const [dialogOpen, setDialogOpen] = useState(false);
|
|
23
|
+
const queryClient = useQueryClient();
|
|
24
|
+
|
|
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
|
+
return (
|
|
36
|
+
<>
|
|
37
|
+
<DataTableBulkActionItem
|
|
38
|
+
requiresPermission={[Permission.UpdateCatalog, Permission.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
|
+
</>
|
|
51
|
+
);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export const RemoveCollectionsFromChannelBulkAction: BulkActionComponent<any> = ({ selection, table }) => {
|
|
55
|
+
const { refetchPaginatedList } = usePaginatedList();
|
|
56
|
+
const { selectedChannel } = useChannel();
|
|
57
|
+
const { i18n } = useLingui();
|
|
58
|
+
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
|
+
|
|
85
|
+
return (
|
|
86
|
+
<DataTableBulkActionItem
|
|
87
|
+
requiresPermission={[Permission.UpdateCatalog, Permission.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"
|
|
97
|
+
/>
|
|
98
|
+
);
|
|
99
|
+
};
|
|
@@ -21,12 +21,15 @@ import { useDetailPage } from '@/framework/page/use-detail-page.js';
|
|
|
21
21
|
import { Trans, useLingui } from '@/lib/trans.js';
|
|
22
22
|
import { createFileRoute, useNavigate } from '@tanstack/react-router';
|
|
23
23
|
import { toast } from 'sonner';
|
|
24
|
-
import {
|
|
24
|
+
import { countryDetailDocument, createCountryDocument, updateCountryDocument } from './countries.graphql.js';
|
|
25
|
+
|
|
26
|
+
const pageId = 'country-detail';
|
|
25
27
|
|
|
26
28
|
export const Route = createFileRoute('/_authenticated/_countries/countries_/$id')({
|
|
27
29
|
component: CountryDetailPage,
|
|
28
30
|
loader: detailPageRouteLoader({
|
|
29
|
-
|
|
31
|
+
pageId,
|
|
32
|
+
queryDocument: countryDetailDocument,
|
|
30
33
|
breadcrumb: (isNew, entity) => [
|
|
31
34
|
{ path: '/countries', label: 'Countries' },
|
|
32
35
|
isNew ? <Trans>New country</Trans> : entity?.name,
|
|
@@ -41,8 +44,9 @@ function CountryDetailPage() {
|
|
|
41
44
|
const creatingNewEntity = params.id === NEW_ENTITY_PATH;
|
|
42
45
|
const { i18n } = useLingui();
|
|
43
46
|
|
|
44
|
-
const { form, submitHandler, entity, isPending } = useDetailPage({
|
|
45
|
-
|
|
47
|
+
const { form, submitHandler, entity, isPending, resetForm } = useDetailPage({
|
|
48
|
+
pageId,
|
|
49
|
+
queryDocument: countryDetailDocument,
|
|
46
50
|
createDocument: createCountryDocument,
|
|
47
51
|
updateDocument: updateCountryDocument,
|
|
48
52
|
setValuesForUpdate: entity => {
|
|
@@ -71,7 +75,7 @@ function CountryDetailPage() {
|
|
|
71
75
|
});
|
|
72
76
|
|
|
73
77
|
return (
|
|
74
|
-
<Page pageId=
|
|
78
|
+
<Page pageId={pageId} form={form} submitHandler={submitHandler} entity={entity}>
|
|
75
79
|
<PageTitle>{creatingNewEntity ? <Trans>New country</Trans> : (entity?.name ?? '')}</PageTitle>
|
|
76
80
|
<PageActionBar>
|
|
77
81
|
<PageActionBarRight>
|
|
@@ -4,7 +4,6 @@ import { PermissionGuard } from '@/components/shared/permission-guard.js';
|
|
|
4
4
|
import { Button } from '@/components/ui/button.js';
|
|
5
5
|
import { Input } from '@/components/ui/input.js';
|
|
6
6
|
import { NEW_ENTITY_PATH } from '@/constants.js';
|
|
7
|
-
import { addCustomFields } from '@/framework/document-introspection/add-custom-fields.js';
|
|
8
7
|
import {
|
|
9
8
|
CustomFieldsPageBlock,
|
|
10
9
|
DetailFormGrid,
|
|
@@ -23,14 +22,17 @@ import { toast } from 'sonner';
|
|
|
23
22
|
import { CustomerGroupMembersTable } from './components/customer-group-members-table.js';
|
|
24
23
|
import {
|
|
25
24
|
createCustomerGroupDocument,
|
|
26
|
-
|
|
25
|
+
customerGroupDetailDocument,
|
|
27
26
|
updateCustomerGroupDocument,
|
|
28
27
|
} from './customer-groups.graphql.js';
|
|
29
28
|
|
|
29
|
+
const pageId = 'customer-group-detail';
|
|
30
|
+
|
|
30
31
|
export const Route = createFileRoute('/_authenticated/_customer-groups/customer-groups_/$id')({
|
|
31
32
|
component: CustomerGroupDetailPage,
|
|
32
33
|
loader: detailPageRouteLoader({
|
|
33
|
-
|
|
34
|
+
pageId,
|
|
35
|
+
queryDocument: customerGroupDetailDocument,
|
|
34
36
|
breadcrumb: (isNew, entity) => [
|
|
35
37
|
{ path: '/customer-groups', label: 'Customer groups' },
|
|
36
38
|
isNew ? <Trans>New customer group</Trans> : entity?.name,
|
|
@@ -46,7 +48,8 @@ function CustomerGroupDetailPage() {
|
|
|
46
48
|
const { i18n } = useLingui();
|
|
47
49
|
|
|
48
50
|
const { form, submitHandler, entity, isPending, resetForm } = useDetailPage({
|
|
49
|
-
|
|
51
|
+
pageId,
|
|
52
|
+
queryDocument: customerGroupDetailDocument,
|
|
50
53
|
createDocument: createCustomerGroupDocument,
|
|
51
54
|
updateDocument: updateCustomerGroupDocument,
|
|
52
55
|
setValuesForUpdate: entity => {
|
|
@@ -72,7 +75,7 @@ function CustomerGroupDetailPage() {
|
|
|
72
75
|
});
|
|
73
76
|
|
|
74
77
|
return (
|
|
75
|
-
<Page pageId=
|
|
78
|
+
<Page pageId={pageId} form={form} submitHandler={submitHandler} entity={entity}>
|
|
76
79
|
<PageTitle>
|
|
77
80
|
{creatingNewEntity ? <Trans>New customer group</Trans> : (entity?.name ?? '')}
|
|
78
81
|
</PageTitle>
|
|
@@ -47,9 +47,12 @@ import {
|
|
|
47
47
|
updateCustomerDocument,
|
|
48
48
|
} from './customers.graphql.js';
|
|
49
49
|
|
|
50
|
+
const pageId = 'customer-detail';
|
|
51
|
+
|
|
50
52
|
export const Route = createFileRoute('/_authenticated/_customers/customers_/$id')({
|
|
51
53
|
component: CustomerDetailPage,
|
|
52
54
|
loader: detailPageRouteLoader({
|
|
55
|
+
pageId,
|
|
53
56
|
queryDocument: customerDetailDocument,
|
|
54
57
|
breadcrumb: (isNew, entity) => [
|
|
55
58
|
{ path: '/customers', label: 'Customers' },
|
|
@@ -67,6 +70,7 @@ function CustomerDetailPage() {
|
|
|
67
70
|
const [newAddressOpen, setNewAddressOpen] = useState(false);
|
|
68
71
|
|
|
69
72
|
const { form, submitHandler, entity, isPending, refreshEntity, resetForm } = useDetailPage({
|
|
73
|
+
pageId,
|
|
70
74
|
queryDocument: customerDetailDocument,
|
|
71
75
|
createDocument: createCustomerDocument,
|
|
72
76
|
updateDocument: updateCustomerDocument,
|
|
@@ -137,7 +141,7 @@ function CustomerDetailPage() {
|
|
|
137
141
|
const customerName = entity ? `${entity.firstName} ${entity.lastName}` : '';
|
|
138
142
|
|
|
139
143
|
return (
|
|
140
|
-
<Page pageId=
|
|
144
|
+
<Page pageId={pageId} form={form} submitHandler={submitHandler} entity={entity}>
|
|
141
145
|
<PageTitle>{creatingNewEntity ? <Trans>New customer</Trans> : customerName}</PageTitle>
|
|
142
146
|
<PageActionBar>
|
|
143
147
|
<PageActionBarRight>
|
|
@@ -24,9 +24,12 @@ import { toast } from 'sonner';
|
|
|
24
24
|
import { FacetValuesTable } from './components/facet-values-table.js';
|
|
25
25
|
import { createFacetDocument, facetDetailDocument, updateFacetDocument } from './facets.graphql.js';
|
|
26
26
|
|
|
27
|
+
const pageId = 'facet-detail';
|
|
28
|
+
|
|
27
29
|
export const Route = createFileRoute('/_authenticated/_facets/facets_/$id')({
|
|
28
30
|
component: FacetDetailPage,
|
|
29
31
|
loader: detailPageRouteLoader({
|
|
32
|
+
pageId,
|
|
30
33
|
queryDocument: facetDetailDocument,
|
|
31
34
|
breadcrumb(isNew, entity) {
|
|
32
35
|
return [{ path: '/facets', label: 'Facets' }, isNew ? <Trans>New facet</Trans> : entity?.name];
|
|
@@ -42,6 +45,7 @@ function FacetDetailPage() {
|
|
|
42
45
|
const { i18n } = useLingui();
|
|
43
46
|
|
|
44
47
|
const { form, submitHandler, entity, isPending, resetForm } = useDetailPage({
|
|
48
|
+
pageId,
|
|
45
49
|
queryDocument: facetDetailDocument,
|
|
46
50
|
createDocument: createFacetDocument,
|
|
47
51
|
updateDocument: updateFacetDocument,
|
|
@@ -82,7 +86,7 @@ function FacetDetailPage() {
|
|
|
82
86
|
});
|
|
83
87
|
|
|
84
88
|
return (
|
|
85
|
-
<Page pageId=
|
|
89
|
+
<Page pageId={pageId} form={form} submitHandler={submitHandler} entity={entity}>
|
|
86
90
|
<PageTitle>{creatingNewEntity ? <Trans>New facet</Trans> : (entity?.name ?? '')}</PageTitle>
|
|
87
91
|
<PageActionBar>
|
|
88
92
|
<PageActionBarRight>
|
|
@@ -25,6 +25,8 @@ import { OrderTaxSummary } from './components/order-tax-summary.js';
|
|
|
25
25
|
import { PaymentDetails } from './components/payment-details.js';
|
|
26
26
|
import { orderDetailDocument } from './orders.graphql.js';
|
|
27
27
|
|
|
28
|
+
const pageId = 'order-detail';
|
|
29
|
+
|
|
28
30
|
export const Route = createFileRoute('/_authenticated/_orders/orders_/$id')({
|
|
29
31
|
component: OrderDetailPage,
|
|
30
32
|
loader: async ({ context, params }) => {
|
|
@@ -58,7 +60,8 @@ function OrderDetailPage() {
|
|
|
58
60
|
const params = Route.useParams();
|
|
59
61
|
const { i18n } = useLingui();
|
|
60
62
|
|
|
61
|
-
const { form, submitHandler, entity, isPending } = useDetailPage({
|
|
63
|
+
const { form, submitHandler, entity, isPending, resetForm } = useDetailPage({
|
|
64
|
+
pageId,
|
|
62
65
|
queryDocument: orderDetailDocument,
|
|
63
66
|
setValuesForUpdate: entity => {
|
|
64
67
|
return {
|
|
@@ -83,7 +86,7 @@ function OrderDetailPage() {
|
|
|
83
86
|
}
|
|
84
87
|
|
|
85
88
|
return (
|
|
86
|
-
<Page pageId=
|
|
89
|
+
<Page pageId={pageId} form={form} submitHandler={submitHandler} entity={entity}>
|
|
87
90
|
<PageTitle>{entity?.code ?? ''}</PageTitle>
|
|
88
91
|
<PageActionBar>
|
|
89
92
|
<PageActionBarRight>
|
|
@@ -30,9 +30,12 @@ import {
|
|
|
30
30
|
updatePaymentMethodDocument,
|
|
31
31
|
} from './payment-methods.graphql.js';
|
|
32
32
|
|
|
33
|
+
const pageId = 'payment-method-detail';
|
|
34
|
+
|
|
33
35
|
export const Route = createFileRoute('/_authenticated/_payment-methods/payment-methods_/$id')({
|
|
34
36
|
component: PaymentMethodDetailPage,
|
|
35
37
|
loader: detailPageRouteLoader({
|
|
38
|
+
pageId,
|
|
36
39
|
queryDocument: paymentMethodDetailDocument,
|
|
37
40
|
breadcrumb(_isNew, entity) {
|
|
38
41
|
return [{ path: '/payment-methods', label: 'Payment methods' }, entity?.name];
|
|
@@ -48,6 +51,7 @@ function PaymentMethodDetailPage() {
|
|
|
48
51
|
const { i18n } = useLingui();
|
|
49
52
|
|
|
50
53
|
const { form, submitHandler, entity, isPending, resetForm } = useDetailPage({
|
|
54
|
+
pageId,
|
|
51
55
|
queryDocument: paymentMethodDetailDocument,
|
|
52
56
|
createDocument: createPaymentMethodDocument,
|
|
53
57
|
updateDocument: updatePaymentMethodDocument,
|
|
@@ -102,7 +106,7 @@ function PaymentMethodDetailPage() {
|
|
|
102
106
|
});
|
|
103
107
|
|
|
104
108
|
return (
|
|
105
|
-
<Page pageId=
|
|
109
|
+
<Page pageId={pageId} form={form} submitHandler={submitHandler} entity={entity}>
|
|
106
110
|
<PageTitle>
|
|
107
111
|
{creatingNewEntity ? <Trans>New payment method</Trans> : (entity?.name ?? '')}
|
|
108
112
|
</PageTitle>
|