@vendure/dashboard 3.3.6-master-202507040234 → 3.3.6-master-202507041203
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/common/delete-bulk-action.tsx +2 -1
- package/src/app/common/duplicate-bulk-action.tsx +1 -1
- package/src/app/routes/_authenticated/_collections/components/collection-bulk-actions.tsx +4 -1
- package/src/app/routes/_authenticated/_facets/components/facet-bulk-actions.tsx +2 -1
- package/src/app/routes/_authenticated/_payment-methods/components/payment-method-bulk-actions.tsx +2 -1
- package/src/app/routes/_authenticated/_product-variants/components/product-variant-bulk-actions.tsx +3 -1
- package/src/app/routes/_authenticated/_products/components/product-bulk-actions.tsx +3 -1
- package/src/app/routes/_authenticated/_products/components/product-variants-table.tsx +1 -1
- package/src/app/routes/_authenticated/_promotions/components/promotion-bulk-actions.tsx +2 -1
- package/src/app/routes/_authenticated/_shipping-methods/components/shipping-method-bulk-actions.tsx +2 -1
- package/src/app/routes/_authenticated/_stock-locations/components/stock-location-bulk-actions.tsx +2 -1
- package/src/lib/components/data-input/relation-selector.tsx +144 -26
- package/src/lib/components/shared/assign-to-channel-bulk-action.tsx +2 -1
- package/src/lib/components/shared/remove-from-channel-bulk-action.tsx +2 -1
- package/src/lib/framework/dashboard-widget/latest-orders-widget/index.tsx +2 -1
- package/src/lib/framework/dashboard-widget/orders-summary/index.tsx +2 -1
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-202507041203",
|
|
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-202507041203",
|
|
90
|
+
"@vendure/core": "^3.3.6-master-202507041203",
|
|
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": "bf87fca72814e2d3eb0a12b0d6575f234f1be67c"
|
|
134
134
|
}
|
|
@@ -3,8 +3,9 @@ import { TrashIcon } from 'lucide-react';
|
|
|
3
3
|
import { toast } from 'sonner';
|
|
4
4
|
|
|
5
5
|
import { DataTableBulkActionItem } from '@/vdb/components/data-table/data-table-bulk-action-item.js';
|
|
6
|
+
import { usePaginatedList } from '@/vdb/components/shared/paginated-list-data-table.js';
|
|
7
|
+
import { getMutationName } from '@/vdb/framework/document-introspection/get-document-structure.js';
|
|
6
8
|
import { api } from '@/vdb/graphql/api.js';
|
|
7
|
-
import { getMutationName, usePaginatedList } from '@/vdb/index.js';
|
|
8
9
|
import { Trans, useLingui } from '@/vdb/lib/trans.js';
|
|
9
10
|
|
|
10
11
|
interface DeleteBulkActionProps {
|
|
@@ -4,9 +4,9 @@ import { useState } from 'react';
|
|
|
4
4
|
import { toast } from 'sonner';
|
|
5
5
|
|
|
6
6
|
import { DataTableBulkActionItem } from '@/vdb/components/data-table/data-table-bulk-action-item.js';
|
|
7
|
+
import { usePaginatedList } from '@/vdb/components/shared/paginated-list-data-table.js';
|
|
7
8
|
import { api } from '@/vdb/graphql/api.js';
|
|
8
9
|
import { duplicateEntityDocument } from '@/vdb/graphql/common-operations.js';
|
|
9
|
-
import { usePaginatedList } from '@/vdb/index.js';
|
|
10
10
|
import { Trans, useLingui } from '@/vdb/lib/trans.js';
|
|
11
11
|
|
|
12
12
|
interface DuplicateBulkActionProps {
|
|
@@ -6,7 +6,10 @@ import { Trans } from '@/vdb/lib/trans.js';
|
|
|
6
6
|
import { AssignToChannelBulkAction } from '@/vdb/components/shared/assign-to-channel-bulk-action.js';
|
|
7
7
|
import { RemoveFromChannelBulkAction } from '@/vdb/components/shared/remove-from-channel-bulk-action.js';
|
|
8
8
|
import { api } from '@/vdb/graphql/api.js';
|
|
9
|
-
import { BulkActionComponent
|
|
9
|
+
import { BulkActionComponent } from '@/vdb/framework/extension-api/types/data-table.js';
|
|
10
|
+
import { useChannel } from '@/vdb/hooks/use-channel.js';
|
|
11
|
+
import { DataTableBulkActionItem } from '@/vdb/components/data-table/data-table-bulk-action-item.js';
|
|
12
|
+
import { usePaginatedList } from '@/vdb/components/shared/paginated-list-data-table.js';
|
|
10
13
|
import { DeleteBulkAction } from '../../../../common/delete-bulk-action.js';
|
|
11
14
|
import { DuplicateBulkAction } from '../../../../common/duplicate-bulk-action.js';
|
|
12
15
|
import {
|
|
@@ -4,7 +4,8 @@ import { AssignToChannelBulkAction } from '@/vdb/components/shared/assign-to-cha
|
|
|
4
4
|
import { RemoveFromChannelBulkAction } from '@/vdb/components/shared/remove-from-channel-bulk-action.js';
|
|
5
5
|
import { api } from '@/vdb/graphql/api.js';
|
|
6
6
|
import { ResultOf } from '@/vdb/graphql/graphql.js';
|
|
7
|
-
import { BulkActionComponent
|
|
7
|
+
import { BulkActionComponent } from '@/vdb/framework/extension-api/types/data-table.js';
|
|
8
|
+
import { useChannel } from '@/vdb/hooks/use-channel.js';
|
|
8
9
|
import { useLingui } from '@/vdb/lib/trans.js';
|
|
9
10
|
import { DeleteBulkAction } from '../../../../common/delete-bulk-action.js';
|
|
10
11
|
import { DuplicateBulkAction } from '../../../../common/duplicate-bulk-action.js';
|
package/src/app/routes/_authenticated/_payment-methods/components/payment-method-bulk-actions.tsx
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { AssignToChannelBulkAction } from '@/vdb/components/shared/assign-to-channel-bulk-action.js';
|
|
2
2
|
import { RemoveFromChannelBulkAction } from '@/vdb/components/shared/remove-from-channel-bulk-action.js';
|
|
3
|
+
import { BulkActionComponent } from '@/vdb/framework/extension-api/types/data-table.js';
|
|
3
4
|
import { api } from '@/vdb/graphql/api.js';
|
|
4
|
-
import {
|
|
5
|
+
import { useChannel } from '@/vdb/hooks/use-channel.js';
|
|
5
6
|
import { DeleteBulkAction } from '../../../../common/delete-bulk-action.js';
|
|
6
7
|
|
|
7
8
|
import {
|
package/src/app/routes/_authenticated/_product-variants/components/product-variant-bulk-actions.tsx
CHANGED
|
@@ -6,7 +6,9 @@ import { AssignToChannelBulkAction } from '@/vdb/components/shared/assign-to-cha
|
|
|
6
6
|
import { usePriceFactor } from '@/vdb/components/shared/assign-to-channel-dialog.js';
|
|
7
7
|
import { RemoveFromChannelBulkAction } from '@/vdb/components/shared/remove-from-channel-bulk-action.js';
|
|
8
8
|
import { api } from '@/vdb/graphql/api.js';
|
|
9
|
-
import { BulkActionComponent
|
|
9
|
+
import { BulkActionComponent } from '@/vdb/framework/extension-api/types/data-table.js';
|
|
10
|
+
import { useChannel } from '@/vdb/hooks/use-channel.js';
|
|
11
|
+
import { usePaginatedList } from '@/vdb/components/shared/paginated-list-data-table.js';
|
|
10
12
|
import { Trans } from '@/vdb/lib/trans.js';
|
|
11
13
|
import { DeleteBulkAction } from '../../../../common/delete-bulk-action.js';
|
|
12
14
|
|
|
@@ -4,9 +4,11 @@ import { useState } from 'react';
|
|
|
4
4
|
import { DataTableBulkActionItem } from '@/vdb/components/data-table/data-table-bulk-action-item.js';
|
|
5
5
|
import { AssignToChannelBulkAction } from '@/vdb/components/shared/assign-to-channel-bulk-action.js';
|
|
6
6
|
import { usePriceFactor } from '@/vdb/components/shared/assign-to-channel-dialog.js';
|
|
7
|
+
import { usePaginatedList } from '@/vdb/components/shared/paginated-list-data-table.js';
|
|
7
8
|
import { RemoveFromChannelBulkAction } from '@/vdb/components/shared/remove-from-channel-bulk-action.js';
|
|
9
|
+
import { BulkActionComponent } from '@/vdb/framework/extension-api/types/data-table.js';
|
|
8
10
|
import { api } from '@/vdb/graphql/api.js';
|
|
9
|
-
import {
|
|
11
|
+
import { useChannel } from '@/vdb/hooks/use-channel.js';
|
|
10
12
|
import { Trans } from '@/vdb/lib/trans.js';
|
|
11
13
|
import { DeleteBulkAction } from '../../../../common/delete-bulk-action.js';
|
|
12
14
|
import { DuplicateBulkAction } from '../../../../common/duplicate-bulk-action.js';
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Money } from '@/vdb/components/data-display/money.js';
|
|
2
|
+
import { DetailPageButton } from '@/vdb/components/shared/detail-page-button.js';
|
|
2
3
|
import {
|
|
3
4
|
PaginatedListDataTable,
|
|
4
5
|
PaginatedListRefresherRegisterFn,
|
|
@@ -6,7 +7,6 @@ import {
|
|
|
6
7
|
import { StockLevelLabel } from '@/vdb/components/shared/stock-level-label.js';
|
|
7
8
|
import { graphql } from '@/vdb/graphql/graphql.js';
|
|
8
9
|
import { useLocalFormat } from '@/vdb/hooks/use-local-format.js';
|
|
9
|
-
import { DetailPageButton } from '@/vdb/index.js';
|
|
10
10
|
import { ColumnFiltersState, SortingState } from '@tanstack/react-table';
|
|
11
11
|
import { useState } from 'react';
|
|
12
12
|
import { productVariantListDocument } from '../products.graphql.js';
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { AssignToChannelBulkAction } from '@/vdb/components/shared/assign-to-channel-bulk-action.js';
|
|
2
2
|
import { RemoveFromChannelBulkAction } from '@/vdb/components/shared/remove-from-channel-bulk-action.js';
|
|
3
|
+
import { BulkActionComponent } from '@/vdb/framework/extension-api/types/data-table.js';
|
|
3
4
|
import { api } from '@/vdb/graphql/api.js';
|
|
4
|
-
import {
|
|
5
|
+
import { useChannel } from '@/vdb/hooks/use-channel.js';
|
|
5
6
|
import { DeleteBulkAction } from '../../../../common/delete-bulk-action.js';
|
|
6
7
|
import { DuplicateBulkAction } from '../../../../common/duplicate-bulk-action.js';
|
|
7
8
|
|
package/src/app/routes/_authenticated/_shipping-methods/components/shipping-method-bulk-actions.tsx
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { AssignToChannelBulkAction } from '@/vdb/components/shared/assign-to-channel-bulk-action.js';
|
|
2
2
|
import { RemoveFromChannelBulkAction } from '@/vdb/components/shared/remove-from-channel-bulk-action.js';
|
|
3
|
+
import { BulkActionComponent } from '@/vdb/framework/extension-api/types/data-table.js';
|
|
3
4
|
import { api } from '@/vdb/graphql/api.js';
|
|
4
|
-
import {
|
|
5
|
+
import { useChannel } from '@/vdb/hooks/use-channel.js';
|
|
5
6
|
import { DeleteBulkAction } from '../../../../common/delete-bulk-action.js';
|
|
6
7
|
|
|
7
8
|
import {
|
package/src/app/routes/_authenticated/_stock-locations/components/stock-location-bulk-actions.tsx
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { AssignToChannelBulkAction } from '@/vdb/components/shared/assign-to-channel-bulk-action.js';
|
|
2
2
|
import { RemoveFromChannelBulkAction } from '@/vdb/components/shared/remove-from-channel-bulk-action.js';
|
|
3
3
|
import { api } from '@/vdb/graphql/api.js';
|
|
4
|
-
import { BulkActionComponent
|
|
4
|
+
import { BulkActionComponent } from '@/vdb/framework/extension-api/types/data-table.js';
|
|
5
|
+
import { useChannel } from '@/vdb/hooks/use-channel.js';
|
|
5
6
|
import { DeleteBulkAction } from '../../../../common/delete-bulk-action.js';
|
|
6
7
|
|
|
7
8
|
import {
|
|
@@ -32,6 +32,8 @@ export interface RelationSelectorConfig<T = any> {
|
|
|
32
32
|
multiple?: boolean;
|
|
33
33
|
/** Custom filter function for search */
|
|
34
34
|
buildSearchFilter?: (searchTerm: string) => any;
|
|
35
|
+
/** Custom filter function for fetching by IDs */
|
|
36
|
+
buildIdsFilter?: (ids: string[]) => any;
|
|
35
37
|
/** Custom label renderer function for rich display */
|
|
36
38
|
label?: (item: T) => React.ReactNode;
|
|
37
39
|
}
|
|
@@ -96,6 +98,19 @@ export function useRelationSelector<T>(config: RelationSelectorConfig<T>) {
|
|
|
96
98
|
[config.labelKey]: { contains: term },
|
|
97
99
|
}));
|
|
98
100
|
|
|
101
|
+
// Build the default IDs filter if none provided
|
|
102
|
+
const buildIdsFilter = React.useCallback(
|
|
103
|
+
(ids: string[]) => {
|
|
104
|
+
if (config.buildIdsFilter) {
|
|
105
|
+
return config.buildIdsFilter(ids);
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
[config.idKey]: { in: ids },
|
|
109
|
+
};
|
|
110
|
+
},
|
|
111
|
+
[config.idKey, config.buildIdsFilter],
|
|
112
|
+
);
|
|
113
|
+
|
|
99
114
|
const { data, isLoading, fetchNextPage, hasNextPage, isFetchingNextPage, error } = useInfiniteQuery({
|
|
100
115
|
queryKey: ['relationSelector', getQueryName(config.listQuery), debouncedSearch],
|
|
101
116
|
queryFn: async ({ pageParam = 0 }) => {
|
|
@@ -125,6 +140,30 @@ export function useRelationSelector<T>(config: RelationSelectorConfig<T>) {
|
|
|
125
140
|
|
|
126
141
|
const items = data?.pages.flatMap(page => page?.items ?? []) ?? [];
|
|
127
142
|
|
|
143
|
+
// Function to fetch items by IDs
|
|
144
|
+
const fetchItemsByIds = React.useCallback(
|
|
145
|
+
async (ids: string[]): Promise<T[]> => {
|
|
146
|
+
if (ids.length === 0) return [];
|
|
147
|
+
|
|
148
|
+
const variables: any = {
|
|
149
|
+
options: {
|
|
150
|
+
take: ids.length,
|
|
151
|
+
filter: buildIdsFilter(ids),
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
try {
|
|
156
|
+
const response = (await api.query(config.listQuery, variables)) as any;
|
|
157
|
+
const result = response[getQueryName(config.listQuery)];
|
|
158
|
+
return result?.items ?? [];
|
|
159
|
+
} catch (error) {
|
|
160
|
+
console.error('Error fetching items by IDs:', error);
|
|
161
|
+
return [];
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
[buildIdsFilter, config.listQuery],
|
|
165
|
+
);
|
|
166
|
+
|
|
128
167
|
return {
|
|
129
168
|
items,
|
|
130
169
|
isLoading,
|
|
@@ -134,6 +173,7 @@ export function useRelationSelector<T>(config: RelationSelectorConfig<T>) {
|
|
|
134
173
|
error,
|
|
135
174
|
searchTerm,
|
|
136
175
|
setSearchTerm,
|
|
176
|
+
fetchItemsByIds,
|
|
137
177
|
};
|
|
138
178
|
}
|
|
139
179
|
|
|
@@ -149,19 +189,104 @@ export function RelationSelector<T>({
|
|
|
149
189
|
}: Readonly<RelationSelectorProps<T>>) {
|
|
150
190
|
const [open, setOpen] = useState(false);
|
|
151
191
|
const [selectedItemsCache, setSelectedItemsCache] = useState<T[]>([]);
|
|
192
|
+
const fetchedIdsRef = React.useRef<Set<string>>(new Set());
|
|
193
|
+
const fetchingIdsRef = React.useRef<Set<string>>(new Set());
|
|
152
194
|
const isMultiple = config.multiple ?? false;
|
|
153
195
|
|
|
154
|
-
const {
|
|
155
|
-
|
|
196
|
+
const {
|
|
197
|
+
items,
|
|
198
|
+
isLoading,
|
|
199
|
+
fetchNextPage,
|
|
200
|
+
hasNextPage,
|
|
201
|
+
isFetchingNextPage,
|
|
202
|
+
searchTerm,
|
|
203
|
+
setSearchTerm,
|
|
204
|
+
fetchItemsByIds,
|
|
205
|
+
} = useRelationSelector(config);
|
|
206
|
+
|
|
207
|
+
// Store a stable reference to fetchItemsByIds
|
|
208
|
+
const fetchItemsByIdsRef = React.useRef(fetchItemsByIds);
|
|
209
|
+
fetchItemsByIdsRef.current = fetchItemsByIds;
|
|
156
210
|
|
|
157
211
|
// Normalize value to always be an array for easier handling
|
|
158
212
|
const selectedIds = React.useMemo(() => {
|
|
159
213
|
if (isMultiple) {
|
|
160
214
|
return Array.isArray(value) ? value : value ? [value] : [];
|
|
161
215
|
}
|
|
162
|
-
|
|
216
|
+
// For single select, ensure we only have at most one ID
|
|
217
|
+
const singleValue = Array.isArray(value) ? value[0] : value;
|
|
218
|
+
return singleValue ? [String(singleValue)] : [];
|
|
163
219
|
}, [value, isMultiple]);
|
|
164
220
|
|
|
221
|
+
// Fetch selected items by IDs on mount and when selectedIds change
|
|
222
|
+
React.useEffect(() => {
|
|
223
|
+
const fetchSelectedItems = async () => {
|
|
224
|
+
if (selectedIds.length === 0) {
|
|
225
|
+
setSelectedItemsCache([]);
|
|
226
|
+
fetchedIdsRef.current.clear();
|
|
227
|
+
fetchingIdsRef.current.clear();
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Find which selected IDs we haven't fetched yet and aren't currently fetching
|
|
232
|
+
const missingIds = selectedIds.filter(
|
|
233
|
+
id => !fetchedIdsRef.current.has(id) && !fetchingIdsRef.current.has(id),
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
if (missingIds.length > 0) {
|
|
237
|
+
// Mark these IDs as being fetched
|
|
238
|
+
missingIds.forEach(id => fetchingIdsRef.current.add(id));
|
|
239
|
+
|
|
240
|
+
try {
|
|
241
|
+
const fetchedItems = await fetchItemsByIdsRef.current(missingIds);
|
|
242
|
+
|
|
243
|
+
// Mark these IDs as fetched and remove from fetching
|
|
244
|
+
missingIds.forEach(id => {
|
|
245
|
+
fetchedIdsRef.current.add(id);
|
|
246
|
+
fetchingIdsRef.current.delete(id);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
setSelectedItemsCache(prev => {
|
|
250
|
+
if (!isMultiple) {
|
|
251
|
+
// For single select, replace the entire cache
|
|
252
|
+
return fetchedItems;
|
|
253
|
+
}
|
|
254
|
+
// For multi-select, filter and append
|
|
255
|
+
const stillSelected = prev.filter(item =>
|
|
256
|
+
selectedIds.includes(String(item[config.idKey])),
|
|
257
|
+
);
|
|
258
|
+
return [...stillSelected, ...fetchedItems];
|
|
259
|
+
});
|
|
260
|
+
} catch (error) {
|
|
261
|
+
// Remove from fetching set on error
|
|
262
|
+
missingIds.forEach(id => fetchingIdsRef.current.delete(id));
|
|
263
|
+
console.error('Error fetching items by IDs:', error);
|
|
264
|
+
}
|
|
265
|
+
} else {
|
|
266
|
+
// Just filter out items that are no longer selected
|
|
267
|
+
setSelectedItemsCache(prev => {
|
|
268
|
+
if (!isMultiple) {
|
|
269
|
+
// For single select, if no items need fetching but we have a selection,
|
|
270
|
+
// keep only items that are in the current selection
|
|
271
|
+
return prev.filter(item => selectedIds.includes(String(item[config.idKey])));
|
|
272
|
+
}
|
|
273
|
+
// For multi-select, normal filtering
|
|
274
|
+
return prev.filter(item => selectedIds.includes(String(item[config.idKey])));
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Clean up fetched IDs that are no longer selected
|
|
279
|
+
const selectedIdsSet = new Set(selectedIds);
|
|
280
|
+
for (const fetchedId of fetchedIdsRef.current) {
|
|
281
|
+
if (!selectedIdsSet.has(fetchedId)) {
|
|
282
|
+
fetchedIdsRef.current.delete(fetchedId);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
fetchSelectedItems();
|
|
288
|
+
}, [selectedIds, config.idKey, isMultiple]);
|
|
289
|
+
|
|
165
290
|
const handleSelect = (item: T) => {
|
|
166
291
|
const itemId = String(item[config.idKey]);
|
|
167
292
|
|
|
@@ -182,10 +307,20 @@ export function RelationSelector<T>({
|
|
|
182
307
|
}
|
|
183
308
|
});
|
|
184
309
|
|
|
310
|
+
// Mark the item as fetched to prevent duplicate fetching
|
|
311
|
+
if (!isCurrentlySelected) {
|
|
312
|
+
fetchedIdsRef.current.add(itemId);
|
|
313
|
+
} else {
|
|
314
|
+
fetchedIdsRef.current.delete(itemId);
|
|
315
|
+
}
|
|
316
|
+
|
|
185
317
|
onChange(newSelectedIds);
|
|
186
318
|
} else {
|
|
187
319
|
// For single select, update cache with the new item
|
|
188
320
|
setSelectedItemsCache([item]);
|
|
321
|
+
// Mark as fetched for single select too
|
|
322
|
+
fetchedIdsRef.current.clear();
|
|
323
|
+
fetchedIdsRef.current.add(itemId);
|
|
189
324
|
onChange(itemId);
|
|
190
325
|
setOpen(false);
|
|
191
326
|
setSearchTerm('');
|
|
@@ -214,31 +349,14 @@ export function RelationSelector<T>({
|
|
|
214
349
|
}
|
|
215
350
|
};
|
|
216
351
|
|
|
217
|
-
// Clean up cache when selectedIds change externally (e.g., form reset)
|
|
218
|
-
React.useEffect(() => {
|
|
219
|
-
setSelectedItemsCache(prev => prev.filter(item => selectedIds.includes(String(item[config.idKey]))));
|
|
220
|
-
}, [selectedIds, config.idKey]);
|
|
221
|
-
|
|
222
|
-
// Populate cache with items from search results that are selected but not yet cached
|
|
223
|
-
React.useEffect(() => {
|
|
224
|
-
const itemsToAdd = items.filter(item => {
|
|
225
|
-
const itemId = String(item[config.idKey]);
|
|
226
|
-
const isSelected = selectedIds.includes(itemId);
|
|
227
|
-
const isAlreadyCached = selectedItemsCache.some(
|
|
228
|
-
cachedItem => String(cachedItem[config.idKey]) === itemId,
|
|
229
|
-
);
|
|
230
|
-
return isSelected && !isAlreadyCached;
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
if (itemsToAdd.length > 0) {
|
|
234
|
-
setSelectedItemsCache(prev => [...prev, ...itemsToAdd]);
|
|
235
|
-
}
|
|
236
|
-
}, [items, selectedIds, selectedItemsCache, config.idKey]);
|
|
237
|
-
|
|
238
352
|
// Get selected items for display from cache, filtered by current selection
|
|
239
353
|
const selectedItems = React.useMemo(() => {
|
|
240
|
-
|
|
241
|
-
|
|
354
|
+
const filteredItems = selectedItemsCache.filter(item =>
|
|
355
|
+
selectedIds.includes(String(item[config.idKey])),
|
|
356
|
+
);
|
|
357
|
+
// For single select, ensure we only display one item
|
|
358
|
+
return isMultiple ? filteredItems : filteredItems.slice(0, 1);
|
|
359
|
+
}, [selectedItemsCache, selectedIds, config.idKey, isMultiple]);
|
|
242
360
|
|
|
243
361
|
return (
|
|
244
362
|
<div className={className}>
|
|
@@ -3,7 +3,8 @@ import { useState } from 'react';
|
|
|
3
3
|
|
|
4
4
|
import { DataTableBulkActionItem } from '@/vdb/components/data-table/data-table-bulk-action-item.js';
|
|
5
5
|
import { AssignToChannelDialog } from '@/vdb/components/shared/assign-to-channel-dialog.js';
|
|
6
|
-
import {
|
|
6
|
+
import { usePaginatedList } from '@/vdb/components/shared/paginated-list-data-table.js';
|
|
7
|
+
import { useChannel } from '@/vdb/hooks/use-channel.js';
|
|
7
8
|
import { Trans } from '@/vdb/lib/trans.js';
|
|
8
9
|
|
|
9
10
|
interface AssignToChannelBulkActionProps {
|
|
@@ -3,8 +3,9 @@ import { LayersIcon } from 'lucide-react';
|
|
|
3
3
|
import { toast } from 'sonner';
|
|
4
4
|
|
|
5
5
|
import { DataTableBulkActionItem } from '@/vdb/components/data-table/data-table-bulk-action-item.js';
|
|
6
|
+
import { usePaginatedList } from '@/vdb/components/shared/paginated-list-data-table.js';
|
|
6
7
|
import { ResultOf } from '@/vdb/graphql/graphql.js';
|
|
7
|
-
import { useChannel
|
|
8
|
+
import { useChannel } from '@/vdb/hooks/use-channel.js';
|
|
8
9
|
import { Trans, useLingui } from '@/vdb/lib/trans.js';
|
|
9
10
|
|
|
10
11
|
interface RemoveFromChannelBulkActionProps {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { PaginatedListDataTable } from '@/vdb/components/shared/paginated-list-data-table.js';
|
|
1
2
|
import { Button } from '@/vdb/components/ui/button.js';
|
|
2
|
-
import {
|
|
3
|
+
import { useLocalFormat } from '@/vdb/hooks/use-local-format.js';
|
|
3
4
|
import { Link } from '@tanstack/react-router';
|
|
4
5
|
import { ColumnFiltersState, SortingState } from '@tanstack/react-table';
|
|
5
6
|
import { formatRelative } from 'date-fns';
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { AnimatedCurrency, AnimatedNumber } from '@/vdb/components/shared/animated-number.js';
|
|
2
2
|
import { Tabs, TabsList, TabsTrigger } from '@/vdb/components/ui/tabs.js';
|
|
3
3
|
import { api } from '@/vdb/graphql/api.js';
|
|
4
|
-
import { useChannel
|
|
4
|
+
import { useChannel } from '@/vdb/hooks/use-channel.js';
|
|
5
|
+
import { useLocalFormat } from '@/vdb/hooks/use-local-format.js';
|
|
5
6
|
import { useQuery } from '@tanstack/react-query';
|
|
6
7
|
import { endOfDay, endOfMonth, startOfDay, startOfMonth, subDays, subMonths } from 'date-fns';
|
|
7
8
|
import { useMemo, useState } from 'react';
|