@vendure/dashboard 3.3.6-master-202507030732 → 3.3.6-master-202507031127
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/dist/plugin/vite-plugin-vendure-dashboard.js +1 -1
- package/package.json +4 -4
- package/src/app/routes/_authenticated/_collections/collections.graphql.ts +32 -0
- package/src/app/routes/_authenticated/_collections/collections.tsx +153 -133
- package/src/app/routes/_authenticated/_collections/components/collection-bulk-actions.tsx +34 -1
- package/src/app/routes/_authenticated/_collections/components/move-collections-dialog.tsx +430 -0
- package/src/app/routes/_authenticated/_collections/components/move-single-collection.tsx +33 -0
- package/src/lib/components/data-table/data-table-bulk-actions.tsx +9 -3
- package/src/lib/components/data-table/data-table.tsx +4 -3
- package/src/lib/components/shared/paginated-list-data-table.tsx +1 -1
- package/src/lib/framework/page/list-page.tsx +1 -1
- package/vite/vite-plugin-vendure-dashboard.ts +1 -1
|
@@ -32,7 +32,7 @@ export function vendureDashboardPlugin(options) {
|
|
|
32
32
|
: [
|
|
33
33
|
TanStackRouterVite({
|
|
34
34
|
autoCodeSplitting: true,
|
|
35
|
-
routeFileIgnorePattern: '.graphql.ts|components',
|
|
35
|
+
routeFileIgnorePattern: '.graphql.ts|components|hooks',
|
|
36
36
|
routesDirectory: path.join(packageRoot, 'src/app/routes'),
|
|
37
37
|
generatedRouteTree: path.join(packageRoot, 'src/app/routeTree.gen.ts'),
|
|
38
38
|
}),
|
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-202507031127",
|
|
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-202507031127",
|
|
90
|
+
"@vendure/core": "^3.3.6-master-202507031127",
|
|
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": "82c67a6665c77f59b6bb2e652e73d4580ca4f291"
|
|
134
134
|
}
|
|
@@ -156,3 +156,35 @@ export const deleteCollectionsDocument = graphql(`
|
|
|
156
156
|
}
|
|
157
157
|
}
|
|
158
158
|
`);
|
|
159
|
+
|
|
160
|
+
export const moveCollectionDocument = graphql(`
|
|
161
|
+
mutation MoveCollection($input: MoveCollectionInput!) {
|
|
162
|
+
moveCollection(input: $input) {
|
|
163
|
+
id
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
`);
|
|
167
|
+
|
|
168
|
+
export const collectionListForMoveDocument = graphql(`
|
|
169
|
+
query CollectionListForMove($options: CollectionListOptions) {
|
|
170
|
+
collections(options: $options) {
|
|
171
|
+
items {
|
|
172
|
+
id
|
|
173
|
+
name
|
|
174
|
+
slug
|
|
175
|
+
breadcrumbs {
|
|
176
|
+
id
|
|
177
|
+
name
|
|
178
|
+
slug
|
|
179
|
+
}
|
|
180
|
+
children {
|
|
181
|
+
id
|
|
182
|
+
}
|
|
183
|
+
position
|
|
184
|
+
isPrivate
|
|
185
|
+
parentId
|
|
186
|
+
}
|
|
187
|
+
totalItems
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
`);
|
|
@@ -10,7 +10,7 @@ import { createFileRoute, Link } from '@tanstack/react-router';
|
|
|
10
10
|
import { ExpandedState, getExpandedRowModel } from '@tanstack/react-table';
|
|
11
11
|
import { TableOptions } from '@tanstack/table-core';
|
|
12
12
|
import { ResultOf } from 'gql.tada';
|
|
13
|
-
import { Folder, FolderOpen, PlusIcon } from 'lucide-react';
|
|
13
|
+
import { Folder, FolderOpen, FolderTreeIcon, PlusIcon } from 'lucide-react';
|
|
14
14
|
import { useState } from 'react';
|
|
15
15
|
|
|
16
16
|
import { collectionListDocument, deleteCollectionDocument } from './collections.graphql.js';
|
|
@@ -18,9 +18,11 @@ import {
|
|
|
18
18
|
AssignCollectionsToChannelBulkAction,
|
|
19
19
|
DeleteCollectionsBulkAction,
|
|
20
20
|
DuplicateCollectionsBulkAction,
|
|
21
|
+
MoveCollectionsBulkAction,
|
|
21
22
|
RemoveCollectionsFromChannelBulkAction,
|
|
22
23
|
} from './components/collection-bulk-actions.js';
|
|
23
24
|
import { CollectionContentsSheet } from './components/collection-contents-sheet.js';
|
|
25
|
+
import { useMoveSingleCollection } from './components/move-single-collection.js';
|
|
24
26
|
|
|
25
27
|
export const Route = createFileRoute('/_authenticated/_collections/collections')({
|
|
26
28
|
component: CollectionListPage,
|
|
@@ -31,6 +33,7 @@ type Collection = ResultOf<typeof collectionListDocument>['collections']['items'
|
|
|
31
33
|
|
|
32
34
|
function CollectionListPage() {
|
|
33
35
|
const [expanded, setExpanded] = useState<ExpandedState>({});
|
|
36
|
+
const { handleMoveClick, MoveDialog } = useMoveSingleCollection();
|
|
34
37
|
const childrenQueries = useQueries({
|
|
35
38
|
queries: Object.entries(expanded).map(([collectionId, isExpanded]) => {
|
|
36
39
|
return {
|
|
@@ -77,143 +80,160 @@ function CollectionListPage() {
|
|
|
77
80
|
};
|
|
78
81
|
|
|
79
82
|
return (
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
83
|
+
<>
|
|
84
|
+
<ListPage
|
|
85
|
+
pageId="collection-list"
|
|
86
|
+
title="Collections"
|
|
87
|
+
listQuery={collectionListDocument}
|
|
88
|
+
transformVariables={input => {
|
|
89
|
+
const filterTerm = input.options?.filter?.name?.contains;
|
|
90
|
+
const isFiltering = !!filterTerm;
|
|
91
|
+
return {
|
|
92
|
+
options: {
|
|
93
|
+
...input.options,
|
|
94
|
+
topLevelOnly: !isFiltering,
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}}
|
|
98
|
+
deleteMutation={deleteCollectionDocument}
|
|
99
|
+
customizeColumns={{
|
|
100
|
+
name: {
|
|
101
|
+
header: 'Collection Name',
|
|
102
|
+
cell: ({ row }) => {
|
|
103
|
+
const isExpanded = row.getIsExpanded();
|
|
104
|
+
const hasChildren = !!row.original.children?.length;
|
|
105
|
+
return (
|
|
106
|
+
<div
|
|
107
|
+
style={{ marginLeft: (row.original.breadcrumbs.length - 2) * 20 + 'px' }}
|
|
108
|
+
className="flex gap-2 items-center"
|
|
109
|
+
>
|
|
110
|
+
<Button
|
|
111
|
+
size="icon"
|
|
112
|
+
variant="secondary"
|
|
113
|
+
onClick={row.getToggleExpandedHandler()}
|
|
114
|
+
disabled={!hasChildren}
|
|
115
|
+
className={!hasChildren ? 'opacity-20' : ''}
|
|
116
|
+
>
|
|
117
|
+
{isExpanded ? <FolderOpen /> : <Folder />}
|
|
118
|
+
</Button>
|
|
119
|
+
<DetailPageButton id={row.original.id} label={row.original.name} />
|
|
120
|
+
</div>
|
|
121
|
+
);
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
breadcrumbs: {
|
|
125
|
+
cell: ({ cell }) => {
|
|
126
|
+
const value = cell.getValue();
|
|
127
|
+
if (!Array.isArray(value)) {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
return (
|
|
131
|
+
<div>
|
|
132
|
+
{value
|
|
133
|
+
.slice(1)
|
|
134
|
+
.map(breadcrumb => breadcrumb.name)
|
|
135
|
+
.join(' / ')}
|
|
136
|
+
</div>
|
|
137
|
+
);
|
|
138
|
+
},
|
|
91
139
|
},
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const isExpanded = row.getIsExpanded();
|
|
100
|
-
const hasChildren = !!row.original.children?.length;
|
|
101
|
-
return (
|
|
102
|
-
<div
|
|
103
|
-
style={{ marginLeft: (row.original.breadcrumbs.length - 2) * 20 + 'px' }}
|
|
104
|
-
className="flex gap-2 items-center"
|
|
105
|
-
>
|
|
106
|
-
<Button
|
|
107
|
-
size="icon"
|
|
108
|
-
variant="secondary"
|
|
109
|
-
onClick={row.getToggleExpandedHandler()}
|
|
110
|
-
disabled={!hasChildren}
|
|
111
|
-
className={!hasChildren ? 'opacity-20' : ''}
|
|
140
|
+
productVariants: {
|
|
141
|
+
header: 'Contents',
|
|
142
|
+
cell: ({ row }) => {
|
|
143
|
+
return (
|
|
144
|
+
<CollectionContentsSheet
|
|
145
|
+
collectionId={row.original.id}
|
|
146
|
+
collectionName={row.original.name}
|
|
112
147
|
>
|
|
113
|
-
{
|
|
114
|
-
</
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
);
|
|
148
|
+
<Trans>{row.original.productVariants.totalItems} variants</Trans>
|
|
149
|
+
</CollectionContentsSheet>
|
|
150
|
+
);
|
|
151
|
+
},
|
|
118
152
|
},
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
153
|
+
}}
|
|
154
|
+
defaultColumnOrder={[
|
|
155
|
+
'featuredAsset',
|
|
156
|
+
'children',
|
|
157
|
+
'name',
|
|
158
|
+
'slug',
|
|
159
|
+
'breadcrumbs',
|
|
160
|
+
'productVariants',
|
|
161
|
+
]}
|
|
162
|
+
transformData={data => {
|
|
163
|
+
return addSubCollections(data);
|
|
164
|
+
}}
|
|
165
|
+
setTableOptions={(options: TableOptions<any>) => {
|
|
166
|
+
options.state = {
|
|
167
|
+
...options.state,
|
|
168
|
+
expanded: expanded,
|
|
169
|
+
};
|
|
170
|
+
options.onExpandedChange = setExpanded;
|
|
171
|
+
options.getExpandedRowModel = getExpandedRowModel();
|
|
172
|
+
options.getRowCanExpand = () => true;
|
|
173
|
+
options.getRowId = row => {
|
|
174
|
+
return row.id;
|
|
175
|
+
};
|
|
176
|
+
return options;
|
|
177
|
+
}}
|
|
178
|
+
defaultVisibility={{
|
|
179
|
+
id: false,
|
|
180
|
+
createdAt: false,
|
|
181
|
+
updatedAt: false,
|
|
182
|
+
position: false,
|
|
183
|
+
parentId: false,
|
|
184
|
+
children: false,
|
|
185
|
+
}}
|
|
186
|
+
onSearchTermChange={searchTerm => {
|
|
187
|
+
return {
|
|
188
|
+
name: { contains: searchTerm },
|
|
189
|
+
};
|
|
190
|
+
}}
|
|
191
|
+
route={Route}
|
|
192
|
+
rowActions={[
|
|
193
|
+
{
|
|
194
|
+
label: (
|
|
195
|
+
<div className="flex items-center gap-2">
|
|
196
|
+
<FolderTreeIcon className="w-4 h-4" /> <Trans>Move</Trans>
|
|
132
197
|
</div>
|
|
133
|
-
)
|
|
198
|
+
),
|
|
199
|
+
onClick: row => handleMoveClick(row.original),
|
|
200
|
+
},
|
|
201
|
+
]}
|
|
202
|
+
bulkActions={[
|
|
203
|
+
{
|
|
204
|
+
component: AssignCollectionsToChannelBulkAction,
|
|
205
|
+
order: 100,
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
component: RemoveCollectionsFromChannelBulkAction,
|
|
209
|
+
order: 200,
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
component: DuplicateCollectionsBulkAction,
|
|
213
|
+
order: 300,
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
component: MoveCollectionsBulkAction,
|
|
217
|
+
order: 400,
|
|
134
218
|
},
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
cell: ({ row }) => {
|
|
139
|
-
return (
|
|
140
|
-
<CollectionContentsSheet
|
|
141
|
-
collectionId={row.original.id}
|
|
142
|
-
collectionName={row.original.name}
|
|
143
|
-
>
|
|
144
|
-
<Trans>{row.original.productVariants.totalItems} variants</Trans>
|
|
145
|
-
</CollectionContentsSheet>
|
|
146
|
-
);
|
|
219
|
+
{
|
|
220
|
+
component: DeleteCollectionsBulkAction,
|
|
221
|
+
order: 500,
|
|
147
222
|
},
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
...options.state,
|
|
164
|
-
expanded: expanded,
|
|
165
|
-
};
|
|
166
|
-
options.onExpandedChange = setExpanded;
|
|
167
|
-
options.getExpandedRowModel = getExpandedRowModel();
|
|
168
|
-
options.getRowCanExpand = () => true;
|
|
169
|
-
options.getRowId = row => {
|
|
170
|
-
return row.id;
|
|
171
|
-
};
|
|
172
|
-
return options;
|
|
173
|
-
}}
|
|
174
|
-
defaultVisibility={{
|
|
175
|
-
id: false,
|
|
176
|
-
createdAt: false,
|
|
177
|
-
updatedAt: false,
|
|
178
|
-
position: false,
|
|
179
|
-
parentId: false,
|
|
180
|
-
children: false,
|
|
181
|
-
}}
|
|
182
|
-
onSearchTermChange={searchTerm => {
|
|
183
|
-
return {
|
|
184
|
-
name: { contains: searchTerm },
|
|
185
|
-
};
|
|
186
|
-
}}
|
|
187
|
-
route={Route}
|
|
188
|
-
bulkActions={[
|
|
189
|
-
{
|
|
190
|
-
component: AssignCollectionsToChannelBulkAction,
|
|
191
|
-
order: 100,
|
|
192
|
-
},
|
|
193
|
-
{
|
|
194
|
-
component: RemoveCollectionsFromChannelBulkAction,
|
|
195
|
-
order: 200,
|
|
196
|
-
},
|
|
197
|
-
{
|
|
198
|
-
component: DuplicateCollectionsBulkAction,
|
|
199
|
-
order: 300,
|
|
200
|
-
},
|
|
201
|
-
{
|
|
202
|
-
component: DeleteCollectionsBulkAction,
|
|
203
|
-
order: 400,
|
|
204
|
-
},
|
|
205
|
-
]}
|
|
206
|
-
>
|
|
207
|
-
<PageActionBarRight>
|
|
208
|
-
<PermissionGuard requires={['CreateCollection', 'CreateCatalog']}>
|
|
209
|
-
<Button asChild>
|
|
210
|
-
<Link to="./new">
|
|
211
|
-
<PlusIcon className="mr-2 h-4 w-4" />
|
|
212
|
-
<Trans>New Collection</Trans>
|
|
213
|
-
</Link>
|
|
214
|
-
</Button>
|
|
215
|
-
</PermissionGuard>
|
|
216
|
-
</PageActionBarRight>
|
|
217
|
-
</ListPage>
|
|
223
|
+
]}
|
|
224
|
+
>
|
|
225
|
+
<PageActionBarRight>
|
|
226
|
+
<PermissionGuard requires={['CreateCollection', 'CreateCatalog']}>
|
|
227
|
+
<Button asChild>
|
|
228
|
+
<Link to="./new">
|
|
229
|
+
<PlusIcon className="mr-2 h-4 w-4" />
|
|
230
|
+
<Trans>New Collection</Trans>
|
|
231
|
+
</Link>
|
|
232
|
+
</Button>
|
|
233
|
+
</PermissionGuard>
|
|
234
|
+
</PageActionBarRight>
|
|
235
|
+
</ListPage>
|
|
236
|
+
<MoveDialog />
|
|
237
|
+
</>
|
|
218
238
|
);
|
|
219
239
|
}
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { useQueryClient } from '@tanstack/react-query';
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { FolderTree } from 'lucide-react';
|
|
2
4
|
|
|
5
|
+
import { Trans } from '@/vdb/lib/trans.js';
|
|
3
6
|
import { AssignToChannelBulkAction } from '@/vdb/components/shared/assign-to-channel-bulk-action.js';
|
|
4
7
|
import { RemoveFromChannelBulkAction } from '@/vdb/components/shared/remove-from-channel-bulk-action.js';
|
|
5
8
|
import { api } from '@/vdb/graphql/api.js';
|
|
6
|
-
import { BulkActionComponent, useChannel } from '@/vdb/index.js';
|
|
9
|
+
import { BulkActionComponent, useChannel, DataTableBulkActionItem, usePaginatedList } from '@/vdb/index.js';
|
|
7
10
|
import { DeleteBulkAction } from '../../../../common/delete-bulk-action.js';
|
|
8
11
|
import { DuplicateBulkAction } from '../../../../common/duplicate-bulk-action.js';
|
|
9
12
|
import {
|
|
@@ -11,6 +14,7 @@ import {
|
|
|
11
14
|
deleteCollectionsDocument,
|
|
12
15
|
removeCollectionFromChannelDocument,
|
|
13
16
|
} from '../collections.graphql.js';
|
|
17
|
+
import { MoveCollectionsDialog } from './move-collections-dialog.js';
|
|
14
18
|
|
|
15
19
|
export const AssignCollectionsToChannelBulkAction: BulkActionComponent<any> = ({ selection, table }) => {
|
|
16
20
|
const queryClient = useQueryClient();
|
|
@@ -85,3 +89,32 @@ export const DeleteCollectionsBulkAction: BulkActionComponent<any> = ({ selectio
|
|
|
85
89
|
/>
|
|
86
90
|
);
|
|
87
91
|
};
|
|
92
|
+
|
|
93
|
+
export const MoveCollectionsBulkAction: BulkActionComponent<any> = ({ selection, table }) => {
|
|
94
|
+
const [dialogOpen, setDialogOpen] = useState(false);
|
|
95
|
+
const queryClient = useQueryClient();
|
|
96
|
+
const { refetchPaginatedList } = usePaginatedList();
|
|
97
|
+
|
|
98
|
+
const handleSuccess = () => {
|
|
99
|
+
queryClient.invalidateQueries({ queryKey: ['childCollections'] });
|
|
100
|
+
refetchPaginatedList();
|
|
101
|
+
table.resetRowSelection();
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<>
|
|
106
|
+
<DataTableBulkActionItem
|
|
107
|
+
requiresPermission={['UpdateCatalog', 'UpdateCollection']}
|
|
108
|
+
onClick={() => setDialogOpen(true)}
|
|
109
|
+
label={<Trans>Move</Trans>}
|
|
110
|
+
icon={FolderTree}
|
|
111
|
+
/>
|
|
112
|
+
<MoveCollectionsDialog
|
|
113
|
+
open={dialogOpen}
|
|
114
|
+
onOpenChange={setDialogOpen}
|
|
115
|
+
collectionsToMove={selection}
|
|
116
|
+
onSuccess={handleSuccess}
|
|
117
|
+
/>
|
|
118
|
+
</>
|
|
119
|
+
);
|
|
120
|
+
};
|
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
import { useMutation, useQueries, useQuery, useQueryClient } from '@tanstack/react-query';
|
|
2
|
+
import { useDebounce } from '@uidotdev/usehooks';
|
|
3
|
+
import { useRef, useState } from 'react';
|
|
4
|
+
import { toast } from 'sonner';
|
|
5
|
+
|
|
6
|
+
import { Alert, AlertDescription } from '@/vdb/components/ui/alert.js';
|
|
7
|
+
import { Button } from '@/vdb/components/ui/button.js';
|
|
8
|
+
import {
|
|
9
|
+
Dialog,
|
|
10
|
+
DialogContent,
|
|
11
|
+
DialogDescription,
|
|
12
|
+
DialogFooter,
|
|
13
|
+
DialogHeader,
|
|
14
|
+
DialogTitle,
|
|
15
|
+
} from '@/vdb/components/ui/dialog.js';
|
|
16
|
+
import { Input } from '@/vdb/components/ui/input.js';
|
|
17
|
+
import { ScrollArea } from '@/vdb/components/ui/scroll-area.js';
|
|
18
|
+
import { api } from '@/vdb/graphql/api.js';
|
|
19
|
+
import { Trans, useLingui } from '@/vdb/lib/trans.js';
|
|
20
|
+
import { ChevronRight, Folder, FolderOpen, Search } from 'lucide-react';
|
|
21
|
+
|
|
22
|
+
import { collectionListForMoveDocument, moveCollectionDocument } from '../collections.graphql.js';
|
|
23
|
+
|
|
24
|
+
type Collection = {
|
|
25
|
+
id: string;
|
|
26
|
+
name: string;
|
|
27
|
+
slug: string;
|
|
28
|
+
children?: { id: string }[] | null;
|
|
29
|
+
breadcrumbs: Array<{ id: string; name: string; slug: string }>;
|
|
30
|
+
parentId?: string;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
interface MoveCollectionsDialogProps {
|
|
34
|
+
open: boolean;
|
|
35
|
+
onOpenChange: (open: boolean) => void;
|
|
36
|
+
collectionsToMove: Collection[];
|
|
37
|
+
onSuccess?: () => void;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface CollectionTreeNodeProps {
|
|
41
|
+
collection: Collection;
|
|
42
|
+
depth: number;
|
|
43
|
+
expanded: Record<string, boolean>;
|
|
44
|
+
onToggleExpanded: (id: string) => void;
|
|
45
|
+
onSelect: (collection: Collection) => void;
|
|
46
|
+
selectedCollectionId?: string;
|
|
47
|
+
collectionsToMove: Collection[];
|
|
48
|
+
childCollectionsByParentId: Record<string, Collection[]>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
interface TargetAlertProps {
|
|
52
|
+
selectedCollectionId?: string;
|
|
53
|
+
collectionsToMove: Collection[];
|
|
54
|
+
topLevelCollectionId?: string;
|
|
55
|
+
collectionNameCache: React.MutableRefObject<Map<string, string>>;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
interface MoveToTopLevelProps {
|
|
59
|
+
selectedCollectionId?: string;
|
|
60
|
+
topLevelCollectionId?: string;
|
|
61
|
+
onSelect: (id?: string) => void;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function TargetAlert({
|
|
65
|
+
selectedCollectionId,
|
|
66
|
+
collectionsToMove,
|
|
67
|
+
topLevelCollectionId,
|
|
68
|
+
collectionNameCache,
|
|
69
|
+
}: Readonly<TargetAlertProps>) {
|
|
70
|
+
return (
|
|
71
|
+
<Alert className={selectedCollectionId ? 'border-blue-200 bg-blue-50' : ''}>
|
|
72
|
+
<Folder className="h-4 w-4" />
|
|
73
|
+
<AlertDescription>
|
|
74
|
+
{selectedCollectionId ? (
|
|
75
|
+
<Trans>
|
|
76
|
+
Moving {collectionsToMove.length} collection
|
|
77
|
+
{collectionsToMove.length === 1 ? '' : 's'} into{' '}
|
|
78
|
+
{selectedCollectionId === topLevelCollectionId
|
|
79
|
+
? 'top level'
|
|
80
|
+
: collectionNameCache.current.get(selectedCollectionId) || 'selected collection'}
|
|
81
|
+
</Trans>
|
|
82
|
+
) : (
|
|
83
|
+
<Trans>Select a destination collection</Trans>
|
|
84
|
+
)}
|
|
85
|
+
</AlertDescription>
|
|
86
|
+
</Alert>
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function MoveToTopLevel({
|
|
91
|
+
selectedCollectionId,
|
|
92
|
+
topLevelCollectionId,
|
|
93
|
+
onSelect,
|
|
94
|
+
}: Readonly<MoveToTopLevelProps>) {
|
|
95
|
+
return (
|
|
96
|
+
<button
|
|
97
|
+
type="button"
|
|
98
|
+
className={`flex items-center gap-2 py-2 px-3 hover:bg-accent rounded-sm cursor-pointer w-full text-left ${
|
|
99
|
+
selectedCollectionId === topLevelCollectionId ? 'bg-accent' : ''
|
|
100
|
+
}`}
|
|
101
|
+
onClick={() => onSelect(topLevelCollectionId)}
|
|
102
|
+
>
|
|
103
|
+
<div className="w-3 h-3" />
|
|
104
|
+
<div className="flex items-center gap-2">
|
|
105
|
+
<Folder className="h-4 w-4 text-muted-foreground" />
|
|
106
|
+
<span className="text-sm font-medium">
|
|
107
|
+
<Trans>Move to the top level</Trans>
|
|
108
|
+
</span>
|
|
109
|
+
</div>
|
|
110
|
+
</button>
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function CollectionTreeNode({
|
|
115
|
+
collection,
|
|
116
|
+
depth,
|
|
117
|
+
expanded,
|
|
118
|
+
onToggleExpanded,
|
|
119
|
+
onSelect,
|
|
120
|
+
selectedCollectionId,
|
|
121
|
+
collectionsToMove,
|
|
122
|
+
childCollectionsByParentId,
|
|
123
|
+
}: Readonly<CollectionTreeNodeProps>) {
|
|
124
|
+
const hasChildren = collection.children && collection.children.length > 0;
|
|
125
|
+
const isExpanded = expanded[collection.id];
|
|
126
|
+
const isSelected = selectedCollectionId === collection.id;
|
|
127
|
+
const isBeingMoved = collectionsToMove.some(c => c.id === collection.id);
|
|
128
|
+
const isChildOfBeingMoved = collectionsToMove.some(c => collection.breadcrumbs.some(b => b.id === c.id));
|
|
129
|
+
|
|
130
|
+
// Don't allow selecting collections that are being moved or are children of collections being moved
|
|
131
|
+
const isSelectable = !isBeingMoved && !isChildOfBeingMoved;
|
|
132
|
+
|
|
133
|
+
const childCollections = childCollectionsByParentId[collection.id] || [];
|
|
134
|
+
|
|
135
|
+
return (
|
|
136
|
+
<div className="my-0.5">
|
|
137
|
+
<div className="flex items-center" style={{ marginLeft: depth * 20 }}>
|
|
138
|
+
{hasChildren && (
|
|
139
|
+
<Button
|
|
140
|
+
size="icon"
|
|
141
|
+
variant="ghost"
|
|
142
|
+
className="h-4 w-4 p-0 mr-1"
|
|
143
|
+
onClick={() => onToggleExpanded(collection.id)}
|
|
144
|
+
>
|
|
145
|
+
{isExpanded ? (
|
|
146
|
+
<ChevronRight className="h-3 w-3 rotate-90" />
|
|
147
|
+
) : (
|
|
148
|
+
<ChevronRight className="h-3 w-3" />
|
|
149
|
+
)}
|
|
150
|
+
</Button>
|
|
151
|
+
)}
|
|
152
|
+
{!hasChildren && <div className="w-5 h-4 mr-1" />}
|
|
153
|
+
<button
|
|
154
|
+
type="button"
|
|
155
|
+
className={`flex items-center gap-2 py-2 px-3 hover:bg-accent rounded-sm cursor-pointer w-full text-left ${
|
|
156
|
+
isSelected ? 'bg-accent' : ''
|
|
157
|
+
} ${!isSelectable ? 'opacity-50 cursor-not-allowed' : ''}`}
|
|
158
|
+
onClick={() => {
|
|
159
|
+
if (isSelectable) {
|
|
160
|
+
onSelect(collection);
|
|
161
|
+
}
|
|
162
|
+
}}
|
|
163
|
+
disabled={!isSelectable}
|
|
164
|
+
>
|
|
165
|
+
<div className="flex items-center gap-2">
|
|
166
|
+
{hasChildren &&
|
|
167
|
+
(isExpanded ? (
|
|
168
|
+
<FolderOpen className="h-4 w-4 text-muted-foreground" />
|
|
169
|
+
) : (
|
|
170
|
+
<Folder className="h-4 w-4 text-muted-foreground" />
|
|
171
|
+
))}
|
|
172
|
+
{!hasChildren && <div className="w-4 h-4" />}
|
|
173
|
+
<div className="flex flex-col">
|
|
174
|
+
<span className="text-sm">{collection.name}</span>
|
|
175
|
+
{collection.breadcrumbs.length > 1 && (
|
|
176
|
+
<span className="text-xs text-muted-foreground">
|
|
177
|
+
{collection.breadcrumbs
|
|
178
|
+
.slice(1)
|
|
179
|
+
.map(b => b.name)
|
|
180
|
+
.join(' / ')}
|
|
181
|
+
</span>
|
|
182
|
+
)}
|
|
183
|
+
</div>
|
|
184
|
+
</div>
|
|
185
|
+
</button>
|
|
186
|
+
</div>
|
|
187
|
+
{hasChildren && isExpanded && (
|
|
188
|
+
<div>
|
|
189
|
+
{childCollections.map((childCollection: Collection) => (
|
|
190
|
+
<CollectionTreeNode
|
|
191
|
+
key={childCollection.id}
|
|
192
|
+
collection={childCollection}
|
|
193
|
+
depth={depth + 1}
|
|
194
|
+
expanded={expanded}
|
|
195
|
+
onToggleExpanded={onToggleExpanded}
|
|
196
|
+
onSelect={onSelect}
|
|
197
|
+
selectedCollectionId={selectedCollectionId}
|
|
198
|
+
collectionsToMove={collectionsToMove}
|
|
199
|
+
childCollectionsByParentId={childCollectionsByParentId}
|
|
200
|
+
/>
|
|
201
|
+
))}
|
|
202
|
+
</div>
|
|
203
|
+
)}
|
|
204
|
+
</div>
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function MoveCollectionsDialog({
|
|
209
|
+
open,
|
|
210
|
+
onOpenChange,
|
|
211
|
+
collectionsToMove,
|
|
212
|
+
onSuccess,
|
|
213
|
+
}: Readonly<MoveCollectionsDialogProps>) {
|
|
214
|
+
const [expanded, setExpanded] = useState<Record<string, boolean>>({});
|
|
215
|
+
const [selectedCollectionId, setSelectedCollectionId] = useState<string>();
|
|
216
|
+
const [searchTerm, setSearchTerm] = useState('');
|
|
217
|
+
const debouncedSearchTerm = useDebounce(searchTerm, 300);
|
|
218
|
+
const collectionNameCache = useRef<Map<string, string>>(new Map());
|
|
219
|
+
const queryClient = useQueryClient();
|
|
220
|
+
const { i18n } = useLingui();
|
|
221
|
+
const collectionForMoveKey = ['collectionsForMove', debouncedSearchTerm];
|
|
222
|
+
const childCollectionsForMoveKey = (collectionId?: string) =>
|
|
223
|
+
collectionId ? ['childCollectionsForMove', collectionId] : ['childCollectionsForMove'];
|
|
224
|
+
|
|
225
|
+
const { data: collectionsData, isLoading } = useQuery({
|
|
226
|
+
queryKey: collectionForMoveKey,
|
|
227
|
+
queryFn: () =>
|
|
228
|
+
api.query(collectionListForMoveDocument, {
|
|
229
|
+
options: {
|
|
230
|
+
take: 100,
|
|
231
|
+
topLevelOnly: !debouncedSearchTerm,
|
|
232
|
+
...(debouncedSearchTerm && {
|
|
233
|
+
filter: {
|
|
234
|
+
name: { contains: debouncedSearchTerm },
|
|
235
|
+
},
|
|
236
|
+
}),
|
|
237
|
+
},
|
|
238
|
+
}),
|
|
239
|
+
staleTime: 1000 * 60 * 5,
|
|
240
|
+
enabled: open,
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
const topLevelCollectionId = collectionsData?.collections.items[0]?.parentId;
|
|
244
|
+
const selectionHasTopLevelParent = collectionsToMove.some(c => c.parentId === topLevelCollectionId);
|
|
245
|
+
|
|
246
|
+
// Load child collections for expanded nodes
|
|
247
|
+
const childrenQueries = useQueries({
|
|
248
|
+
queries: Object.entries(expanded).map(([collectionId, isExpanded]) => {
|
|
249
|
+
return {
|
|
250
|
+
queryKey: childCollectionsForMoveKey(collectionId),
|
|
251
|
+
queryFn: () =>
|
|
252
|
+
api.query(collectionListForMoveDocument, {
|
|
253
|
+
options: {
|
|
254
|
+
filter: {
|
|
255
|
+
parentId: { eq: collectionId },
|
|
256
|
+
},
|
|
257
|
+
},
|
|
258
|
+
}),
|
|
259
|
+
staleTime: 1000 * 60 * 5,
|
|
260
|
+
};
|
|
261
|
+
}),
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
const childCollectionsByParentId = childrenQueries.reduce(
|
|
265
|
+
(acc, query, index) => {
|
|
266
|
+
const collectionId = Object.keys(expanded)[index];
|
|
267
|
+
if (query.data) {
|
|
268
|
+
const collections = query.data.collections.items as Collection[];
|
|
269
|
+
// Populate the name cache with these collections
|
|
270
|
+
collections.forEach(collection => {
|
|
271
|
+
collectionNameCache.current.set(collection.id, collection.name);
|
|
272
|
+
});
|
|
273
|
+
acc[collectionId] = collections;
|
|
274
|
+
}
|
|
275
|
+
return acc;
|
|
276
|
+
},
|
|
277
|
+
{} as Record<string, Collection[]>,
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
const moveCollectionsMutation = useMutation({
|
|
281
|
+
mutationFn: api.mutate(moveCollectionDocument),
|
|
282
|
+
onSuccess: () => {
|
|
283
|
+
toast.success(i18n.t('Collections moved successfully'));
|
|
284
|
+
queryClient.invalidateQueries({ queryKey: collectionForMoveKey });
|
|
285
|
+
queryClient.invalidateQueries({ queryKey: childCollectionsForMoveKey() });
|
|
286
|
+
onSuccess?.();
|
|
287
|
+
onOpenChange(false);
|
|
288
|
+
},
|
|
289
|
+
onError: error => {
|
|
290
|
+
toast.error(i18n.t('Failed to move collections'));
|
|
291
|
+
console.error('Move collections error:', error);
|
|
292
|
+
},
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
const handleToggleExpanded = (id: string) => {
|
|
296
|
+
setExpanded(prev => ({
|
|
297
|
+
...prev,
|
|
298
|
+
[id]: !prev[id],
|
|
299
|
+
}));
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
const handleSelect = (collection: Collection) => {
|
|
303
|
+
setSelectedCollectionId(collection.id);
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
const handleMove = () => {
|
|
307
|
+
if (!selectedCollectionId) {
|
|
308
|
+
toast.error(i18n.t('Please select a target collection'));
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
// Move to a specific parent using moveCollection
|
|
312
|
+
const movePromises = collectionsToMove.map((collection: Collection) =>
|
|
313
|
+
moveCollectionsMutation.mutateAsync({
|
|
314
|
+
input: {
|
|
315
|
+
collectionId: collection.id,
|
|
316
|
+
parentId: selectedCollectionId,
|
|
317
|
+
index: 0, // Move to the beginning of the target collection
|
|
318
|
+
},
|
|
319
|
+
}),
|
|
320
|
+
);
|
|
321
|
+
Promise.all(movePromises);
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
const collections = (collectionsData?.collections.items as Collection[]) || [];
|
|
325
|
+
|
|
326
|
+
// Populate the name cache with top-level collections
|
|
327
|
+
collections.forEach(collection => {
|
|
328
|
+
collectionNameCache.current.set(collection.id, collection.name);
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
return (
|
|
332
|
+
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
333
|
+
<DialogContent className="sm:max-w-[600px] max-h-[80vh]">
|
|
334
|
+
<DialogHeader>
|
|
335
|
+
<DialogTitle>
|
|
336
|
+
<Trans>Move Collections</Trans>
|
|
337
|
+
</DialogTitle>
|
|
338
|
+
<DialogDescription>
|
|
339
|
+
<Trans>
|
|
340
|
+
Select a target collection to move{' '}
|
|
341
|
+
{collectionsToMove.length === 1
|
|
342
|
+
? 'this collection'
|
|
343
|
+
: `${collectionsToMove.length} collections`}{' '}
|
|
344
|
+
to.
|
|
345
|
+
</Trans>
|
|
346
|
+
</DialogDescription>
|
|
347
|
+
</DialogHeader>
|
|
348
|
+
<div className="px-6 py-3 bg-muted/50 border-b">
|
|
349
|
+
<div className="flex flex-wrap gap-2">
|
|
350
|
+
{collectionsToMove.map(collection => (
|
|
351
|
+
<div
|
|
352
|
+
key={collection.id}
|
|
353
|
+
className="flex items-center gap-2 px-3 py-1 bg-background border rounded-md text-sm"
|
|
354
|
+
>
|
|
355
|
+
<Folder className="h-3 w-3 text-muted-foreground" />
|
|
356
|
+
<span>{collection.name}</span>
|
|
357
|
+
</div>
|
|
358
|
+
))}
|
|
359
|
+
</div>
|
|
360
|
+
</div>
|
|
361
|
+
<div className="py-4">
|
|
362
|
+
<div className="px-6 pb-3">
|
|
363
|
+
<div className="relative mb-3">
|
|
364
|
+
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
|
365
|
+
<Input
|
|
366
|
+
placeholder={i18n.t('Filter by collection name')}
|
|
367
|
+
value={searchTerm}
|
|
368
|
+
onChange={e => setSearchTerm(e.target.value)}
|
|
369
|
+
className="pl-10"
|
|
370
|
+
/>
|
|
371
|
+
</div>
|
|
372
|
+
<ScrollArea className="h-[400px]">
|
|
373
|
+
<div className="space-y-1">
|
|
374
|
+
{isLoading ? (
|
|
375
|
+
<div className="flex items-center justify-center py-8">
|
|
376
|
+
<Trans>Loading collections...</Trans>
|
|
377
|
+
</div>
|
|
378
|
+
) : (
|
|
379
|
+
<>
|
|
380
|
+
{!debouncedSearchTerm && !selectionHasTopLevelParent && (
|
|
381
|
+
<MoveToTopLevel
|
|
382
|
+
selectedCollectionId={selectedCollectionId}
|
|
383
|
+
topLevelCollectionId={topLevelCollectionId}
|
|
384
|
+
onSelect={setSelectedCollectionId}
|
|
385
|
+
/>
|
|
386
|
+
)}
|
|
387
|
+
{collections.map((collection: Collection) => (
|
|
388
|
+
<CollectionTreeNode
|
|
389
|
+
key={collection.id}
|
|
390
|
+
collection={collection}
|
|
391
|
+
depth={0}
|
|
392
|
+
expanded={expanded}
|
|
393
|
+
onToggleExpanded={handleToggleExpanded}
|
|
394
|
+
onSelect={handleSelect}
|
|
395
|
+
selectedCollectionId={selectedCollectionId}
|
|
396
|
+
collectionsToMove={collectionsToMove}
|
|
397
|
+
childCollectionsByParentId={childCollectionsByParentId}
|
|
398
|
+
/>
|
|
399
|
+
))}
|
|
400
|
+
</>
|
|
401
|
+
)}
|
|
402
|
+
</div>
|
|
403
|
+
</ScrollArea>
|
|
404
|
+
<TargetAlert
|
|
405
|
+
selectedCollectionId={selectedCollectionId}
|
|
406
|
+
collectionsToMove={collectionsToMove}
|
|
407
|
+
topLevelCollectionId={topLevelCollectionId}
|
|
408
|
+
collectionNameCache={collectionNameCache}
|
|
409
|
+
/>
|
|
410
|
+
</div>
|
|
411
|
+
</div>
|
|
412
|
+
<DialogFooter>
|
|
413
|
+
<Button variant="outline" onClick={() => onOpenChange(false)}>
|
|
414
|
+
<Trans>Cancel</Trans>
|
|
415
|
+
</Button>
|
|
416
|
+
<Button
|
|
417
|
+
onClick={handleMove}
|
|
418
|
+
disabled={!selectedCollectionId || moveCollectionsMutation.isPending}
|
|
419
|
+
>
|
|
420
|
+
{moveCollectionsMutation.isPending ? (
|
|
421
|
+
<Trans>Moving...</Trans>
|
|
422
|
+
) : (
|
|
423
|
+
<Trans>Move Collections</Trans>
|
|
424
|
+
)}
|
|
425
|
+
</Button>
|
|
426
|
+
</DialogFooter>
|
|
427
|
+
</DialogContent>
|
|
428
|
+
</Dialog>
|
|
429
|
+
);
|
|
430
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { ResultOf } from 'gql.tada';
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
|
|
4
|
+
import { collectionListDocument } from '../collections.graphql.js';
|
|
5
|
+
import { MoveCollectionsDialog } from './move-collections-dialog.js';
|
|
6
|
+
|
|
7
|
+
type Collection = ResultOf<typeof collectionListDocument>['collections']['items'][number];
|
|
8
|
+
|
|
9
|
+
export function useMoveSingleCollection() {
|
|
10
|
+
const [moveDialogOpen, setMoveDialogOpen] = useState(false);
|
|
11
|
+
const [collectionsToMove, setCollectionsToMove] = useState<Collection[]>([]);
|
|
12
|
+
|
|
13
|
+
const handleMoveClick = (collection: Collection) => {
|
|
14
|
+
setCollectionsToMove([collection]);
|
|
15
|
+
setMoveDialogOpen(true);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const MoveDialog = () => (
|
|
19
|
+
<MoveCollectionsDialog
|
|
20
|
+
open={moveDialogOpen}
|
|
21
|
+
onOpenChange={setMoveDialogOpen}
|
|
22
|
+
collectionsToMove={collectionsToMove}
|
|
23
|
+
onSuccess={() => {
|
|
24
|
+
// The dialog will handle invalidating queries internally
|
|
25
|
+
}}
|
|
26
|
+
/>
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
handleMoveClick,
|
|
31
|
+
MoveDialog,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
@@ -21,7 +21,10 @@ interface DataTableBulkActionsProps<TData> {
|
|
|
21
21
|
bulkActions: BulkAction[];
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
export function DataTableBulkActions<TData>({
|
|
24
|
+
export function DataTableBulkActions<TData>({
|
|
25
|
+
table,
|
|
26
|
+
bulkActions,
|
|
27
|
+
}: Readonly<DataTableBulkActionsProps<TData>>) {
|
|
25
28
|
const { pageId } = usePage();
|
|
26
29
|
const { blockId } = usePageBlock();
|
|
27
30
|
|
|
@@ -57,13 +60,16 @@ export function DataTableBulkActions<TData>({ table, bulkActions }: DataTableBul
|
|
|
57
60
|
allBulkActions.sort((a, b) => (a.order ?? 10_000) - (b.order ?? 10_000));
|
|
58
61
|
|
|
59
62
|
return (
|
|
60
|
-
<div
|
|
63
|
+
<div
|
|
64
|
+
className="flex items-center gap-4 px-8 py-2 animate-in fade-in duration-200 absolute bottom-10 left-1/2 transform -translate-x-1/2 bg-white shadow-2xl rounded-md border"
|
|
65
|
+
style={{ height: 'auto', maxHeight: '60px' }}
|
|
66
|
+
>
|
|
61
67
|
<span className="text-sm text-muted-foreground">
|
|
62
68
|
<Trans>{selection.length} selected</Trans>
|
|
63
69
|
</span>
|
|
64
70
|
<DropdownMenu>
|
|
65
71
|
<DropdownMenuTrigger asChild>
|
|
66
|
-
<Button variant="outline" size="sm" className="h-8">
|
|
72
|
+
<Button variant="outline" size="sm" className="h-8 shadow-none">
|
|
67
73
|
<Trans>With selected...</Trans>
|
|
68
74
|
<ChevronDown className="ml-2 h-4 w-4" />
|
|
69
75
|
</Button>
|
|
@@ -81,7 +81,7 @@ export function DataTable<TData>({
|
|
|
81
81
|
bulkActions,
|
|
82
82
|
setTableOptions,
|
|
83
83
|
onRefresh,
|
|
84
|
-
}: DataTableProps<TData
|
|
84
|
+
}: Readonly<DataTableProps<TData>>) {
|
|
85
85
|
const [sorting, setSorting] = React.useState<SortingState>(sortingInitialState || []);
|
|
86
86
|
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(filtersInitialState || []);
|
|
87
87
|
const { activeChannel } = useChannel();
|
|
@@ -207,8 +207,8 @@ export function DataTable<TData>({
|
|
|
207
207
|
{onRefresh && <RefreshButton onRefresh={onRefresh} isLoading={isLoading ?? false} />}
|
|
208
208
|
</div>
|
|
209
209
|
</div>
|
|
210
|
-
|
|
211
|
-
<div className="rounded-md border my-2">
|
|
210
|
+
|
|
211
|
+
<div className="rounded-md border my-2 relative">
|
|
212
212
|
<Table>
|
|
213
213
|
<TableHeader>
|
|
214
214
|
{table.getHeaderGroups().map(headerGroup => (
|
|
@@ -268,6 +268,7 @@ export function DataTable<TData>({
|
|
|
268
268
|
)}
|
|
269
269
|
</TableBody>
|
|
270
270
|
</Table>
|
|
271
|
+
<DataTableBulkActions bulkActions={bulkActions ?? []} table={table} />
|
|
271
272
|
</div>
|
|
272
273
|
<DataTablePagination table={table} />
|
|
273
274
|
</>
|
|
@@ -273,7 +273,7 @@ export function PaginatedListDataTable<
|
|
|
273
273
|
setTableOptions,
|
|
274
274
|
transformData,
|
|
275
275
|
registerRefresher,
|
|
276
|
-
}: PaginatedListDataTableProps<T, U, V, AC
|
|
276
|
+
}: Readonly<PaginatedListDataTableProps<T, U, V, AC>>) {
|
|
277
277
|
const [searchTerm, setSearchTerm] = React.useState<string>('');
|
|
278
278
|
const debouncedSearchTerm = useDebounce(searchTerm, 500);
|
|
279
279
|
const queryClient = useQueryClient();
|
|
@@ -90,7 +90,7 @@ export function ListPage<
|
|
|
90
90
|
transformData,
|
|
91
91
|
setTableOptions,
|
|
92
92
|
bulkActions,
|
|
93
|
-
}: ListPageProps<T, U, V, AC
|
|
93
|
+
}: Readonly<ListPageProps<T, U, V, AC>>) {
|
|
94
94
|
const route = typeof routeOrFn === 'function' ? routeOrFn() : routeOrFn;
|
|
95
95
|
const routeSearch = route.useSearch();
|
|
96
96
|
const navigate = useNavigate<AnyRouter>({ from: route.fullPath });
|
|
@@ -111,7 +111,7 @@ export function vendureDashboardPlugin(options: VitePluginVendureDashboardOption
|
|
|
111
111
|
: [
|
|
112
112
|
TanStackRouterVite({
|
|
113
113
|
autoCodeSplitting: true,
|
|
114
|
-
routeFileIgnorePattern: '.graphql.ts|components',
|
|
114
|
+
routeFileIgnorePattern: '.graphql.ts|components|hooks',
|
|
115
115
|
routesDirectory: path.join(packageRoot, 'src/app/routes'),
|
|
116
116
|
generatedRouteTree: path.join(packageRoot, 'src/app/routeTree.gen.ts'),
|
|
117
117
|
}),
|