@vendure/dashboard 3.5.2-master-202512040233 → 3.5.2-master-202512180239
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/constants.js +2 -2
- package/dist/plugin/dashboard.plugin.js +1 -1
- package/dist/vite/constants.js +1 -0
- package/lingui.config.js +1 -0
- package/package.json +7 -7
- package/src/app/routeTree.gen.ts +1221 -0
- package/src/app/routes/_authenticated/_collections/collections.graphql.ts +1 -0
- package/src/app/routes/_authenticated/_collections/collections.tsx +249 -167
- package/src/app/routes/_authenticated/_collections/components/collection-bulk-actions.tsx +8 -0
- package/src/app/routes/_authenticated/_collections/components/move-collections-dialog.tsx +4 -0
- package/src/app/routes/_authenticated/_customers/components/customer-history/index.ts +0 -1
- package/src/app/routes/_authenticated/_global-settings/global-settings.tsx +1 -1
- package/src/app/routes/_authenticated/_orders/components/add-surcharge-form.tsx +139 -0
- package/src/app/routes/_authenticated/_orders/components/edit-order-table.tsx +3 -0
- package/src/app/routes/_authenticated/_orders/components/order-address.tsx +3 -3
- package/src/app/routes/_authenticated/_orders/components/order-modification-summary.tsx +49 -11
- package/src/app/routes/_authenticated/_orders/orders_.$id_.modify.tsx +9 -0
- package/src/app/routes/_authenticated/_orders/utils/use-modify-order.ts +23 -0
- package/src/app/routes/_authenticated/_product-variants/components/add-currency-dropdown.tsx +3 -3
- package/src/app/routes/_authenticated/_product-variants/components/add-stock-location-dropdown.tsx +2 -2
- package/src/app/routes/_authenticated/_products/products.graphql.ts +1 -0
- package/src/app/routes/_authenticated/_tax-rates/tax-rates_.$id.tsx +2 -9
- package/src/i18n/locales/bg.po +3436 -0
- package/src/lib/components/data-input/datetime-input.tsx +1 -1
- package/src/lib/components/data-input/number-input.tsx +24 -5
- package/src/lib/components/data-input/relation-selector.tsx +1 -1
- package/src/lib/components/data-input/struct-form-input.tsx +175 -174
- package/src/lib/components/data-table/data-table-utils.ts +241 -1
- package/src/lib/components/data-table/data-table.tsx +190 -60
- package/src/lib/components/layout/manage-languages-dialog.tsx +2 -25
- package/src/lib/components/shared/custom-fields-form.tsx +13 -8
- package/src/lib/components/shared/paginated-list-data-table.tsx +19 -0
- package/src/lib/components/ui/alert.tsx +1 -1
- package/src/lib/components/ui/carousel.tsx +2 -2
- package/src/lib/components/ui/chart.tsx +1 -1
- package/src/lib/components/ui/context-menu.tsx +1 -1
- package/src/lib/components/ui/drawer.tsx +1 -1
- package/src/lib/components/ui/grid-layout.tsx +1 -1
- package/src/lib/components/ui/input-group.tsx +1 -0
- package/src/lib/components/ui/input-otp.tsx +1 -1
- package/src/lib/components/ui/menubar.tsx +1 -1
- package/src/lib/components/ui/navigation-menu.tsx +1 -1
- package/src/lib/components/ui/progress.tsx +1 -1
- package/src/lib/components/ui/radio-group.tsx +1 -1
- package/src/lib/components/ui/resizable.tsx +1 -1
- package/src/lib/components/ui/select.tsx +1 -1
- package/src/lib/components/ui/slider.tsx +1 -1
- package/src/lib/components/ui/toggle-group.tsx +2 -2
- package/src/lib/components/ui/toggle.tsx +1 -1
- package/src/lib/framework/component-registry/component-registry.tsx +2 -6
- package/src/lib/framework/extension-api/display-component-extensions.tsx +4 -3
- package/src/lib/framework/extension-api/logic/detail-forms.ts +0 -13
- package/src/lib/framework/extension-api/types/data-table.ts +4 -2
- package/src/lib/framework/extension-api/types/navigation.ts +2 -2
- package/src/lib/framework/form-engine/use-generated-form.tsx +7 -1
- package/src/lib/framework/layout-engine/page-layout.tsx +1 -1
- package/src/lib/framework/nav-menu/nav-menu-extensions.ts +1 -1
- package/src/lib/framework/page/detail-page-route-loader.tsx +1 -1
- package/src/lib/framework/page/list-page.tsx +62 -38
- package/src/lib/framework/page/page-api.ts +1 -1
- package/src/lib/framework/page/use-detail-page.ts +4 -2
- package/src/lib/framework/page/use-extended-router.tsx +20 -16
- package/src/lib/framework/registry/registry-types.ts +2 -1
- package/src/lib/graphql/graphql-env.d.ts +8 -12
- package/src/lib/hooks/use-drag-and-drop.ts +86 -0
- package/src/lib/providers/channel-provider.tsx +11 -7
- package/LICENSE.md +0 -42
- package/src/app/routes/_authenticated/_facets/components/edit-facet-value.tsx +0 -129
- /package/src/{app/routes/_authenticated/_global-settings → lib}/utils/global-languages.ts +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AccessorFnColumnDef } from '@tanstack/react-table';
|
|
1
|
+
import { AccessorFnColumnDef, ExpandedState } from '@tanstack/react-table';
|
|
2
2
|
import { AccessorKeyColumnDef } from '@tanstack/table-core';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -49,3 +49,243 @@ export function getStandardizedDefaultColumnOrder<T extends string | number | sy
|
|
|
49
49
|
const rest = defaultColumnOrder.filter(c => !standardFirstColumns.has(c as string));
|
|
50
50
|
return [...standardFirstColumns, ...rest] as T[];
|
|
51
51
|
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Hierarchical item type with parent-child relationships
|
|
55
|
+
*/
|
|
56
|
+
export interface HierarchicalItem {
|
|
57
|
+
id: string;
|
|
58
|
+
parentId?: string | null;
|
|
59
|
+
breadcrumbs?: Array<{ id: string }>;
|
|
60
|
+
children?: Array<{ id: string }> | null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Gets the parent ID of a hierarchical item
|
|
65
|
+
*/
|
|
66
|
+
export function getItemParentId<T extends HierarchicalItem>(
|
|
67
|
+
item: T | null | undefined,
|
|
68
|
+
): string | null | undefined {
|
|
69
|
+
return item?.parentId || item?.breadcrumbs?.[0]?.id;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Gets all siblings (items with the same parent) for a given parent ID
|
|
74
|
+
*/
|
|
75
|
+
export function getItemSiblings<T extends HierarchicalItem>(
|
|
76
|
+
items: T[],
|
|
77
|
+
parentId: string | null | undefined,
|
|
78
|
+
): T[] {
|
|
79
|
+
return items.filter(item => getItemParentId(item) === parentId);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Checks if moving an item to a new parent would create a circular reference
|
|
84
|
+
*/
|
|
85
|
+
export function isCircularReference<T extends HierarchicalItem>(
|
|
86
|
+
item: T,
|
|
87
|
+
targetParentId: string,
|
|
88
|
+
items: T[],
|
|
89
|
+
): boolean {
|
|
90
|
+
const targetParentItem = items.find(i => i.id === targetParentId);
|
|
91
|
+
return (
|
|
92
|
+
item.children?.some(child => {
|
|
93
|
+
if (child.id === targetParentId) return true;
|
|
94
|
+
const targetBreadcrumbIds = targetParentItem?.breadcrumbs?.map(b => b.id) || [];
|
|
95
|
+
return targetBreadcrumbIds.includes(item.id);
|
|
96
|
+
}) ?? false
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Result of calculating the target position for a drag and drop operation
|
|
102
|
+
*/
|
|
103
|
+
export interface TargetPosition {
|
|
104
|
+
targetParentId: string;
|
|
105
|
+
adjustedIndex: number;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Context for drag and drop position calculation
|
|
110
|
+
*/
|
|
111
|
+
interface DragContext<T extends HierarchicalItem> {
|
|
112
|
+
item: T;
|
|
113
|
+
targetItem: T | undefined;
|
|
114
|
+
previousItem: T | null;
|
|
115
|
+
isDraggingDown: boolean;
|
|
116
|
+
isTargetExpanded: boolean;
|
|
117
|
+
isPreviousExpanded: boolean;
|
|
118
|
+
sourceParentId: string;
|
|
119
|
+
items: T[];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Checks if dragging down directly onto an expanded item
|
|
124
|
+
*/
|
|
125
|
+
function isDroppingIntoExpandedTarget<T extends HierarchicalItem>(context: DragContext<T>): boolean {
|
|
126
|
+
const { isDraggingDown, targetItem, item, isTargetExpanded } = context;
|
|
127
|
+
return isDraggingDown && targetItem?.id !== item.id && isTargetExpanded;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Checks if dragging down into an expanded item's children area
|
|
132
|
+
*/
|
|
133
|
+
function isDroppingIntoExpandedPreviousChildren<T extends HierarchicalItem>(
|
|
134
|
+
context: DragContext<T>,
|
|
135
|
+
): boolean {
|
|
136
|
+
const { isDraggingDown, targetItem, previousItem, item, isPreviousExpanded } = context;
|
|
137
|
+
return (
|
|
138
|
+
isDraggingDown &&
|
|
139
|
+
previousItem !== null &&
|
|
140
|
+
targetItem?.id !== item.id &&
|
|
141
|
+
isPreviousExpanded &&
|
|
142
|
+
targetItem?.parentId === previousItem.id
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Checks if dragging up into an expanded item's children area
|
|
148
|
+
*/
|
|
149
|
+
function isDroppingIntoExpandedPreviousWhenDraggingUp<T extends HierarchicalItem>(
|
|
150
|
+
context: DragContext<T>,
|
|
151
|
+
): boolean {
|
|
152
|
+
const { isDraggingDown, previousItem, isPreviousExpanded } = context;
|
|
153
|
+
return !isDraggingDown && previousItem !== null && isPreviousExpanded;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Creates a position for dropping into an expanded item as first child
|
|
158
|
+
*/
|
|
159
|
+
function createFirstChildPosition(parentId: string): TargetPosition {
|
|
160
|
+
return { targetParentId: parentId, adjustedIndex: 0 };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Calculates position for cross-parent drag operations
|
|
165
|
+
*/
|
|
166
|
+
function calculateCrossParentPosition<T extends HierarchicalItem>(
|
|
167
|
+
targetItem: T,
|
|
168
|
+
sourceParentId: string,
|
|
169
|
+
items: T[],
|
|
170
|
+
): TargetPosition | null {
|
|
171
|
+
const targetItemParentId = getItemParentId(targetItem);
|
|
172
|
+
|
|
173
|
+
if (!targetItemParentId || targetItemParentId === sourceParentId) {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const targetSiblings = getItemSiblings(items, targetItemParentId);
|
|
178
|
+
const adjustedIndex = targetSiblings.findIndex(i => i.id === targetItem.id);
|
|
179
|
+
|
|
180
|
+
return { targetParentId: targetItemParentId, adjustedIndex };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Calculates position when dropping at the end of the list
|
|
185
|
+
*/
|
|
186
|
+
function calculateDropAtEndPosition<T extends HierarchicalItem>(
|
|
187
|
+
previousItem: T | null,
|
|
188
|
+
sourceParentId: string,
|
|
189
|
+
items: T[],
|
|
190
|
+
): TargetPosition | null {
|
|
191
|
+
if (!previousItem) {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const previousItemParentId = getItemParentId(previousItem);
|
|
196
|
+
|
|
197
|
+
if (!previousItemParentId || previousItemParentId === sourceParentId) {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const targetSiblings = getItemSiblings(items, previousItemParentId);
|
|
202
|
+
return { targetParentId: previousItemParentId, adjustedIndex: targetSiblings.length };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Determines the target parent and index for a hierarchical drag and drop operation
|
|
207
|
+
*/
|
|
208
|
+
export function calculateDragTargetPosition<T extends HierarchicalItem>(params: {
|
|
209
|
+
item: T;
|
|
210
|
+
oldIndex: number;
|
|
211
|
+
newIndex: number;
|
|
212
|
+
items: T[];
|
|
213
|
+
sourceParentId: string;
|
|
214
|
+
expanded: ExpandedState;
|
|
215
|
+
}): TargetPosition {
|
|
216
|
+
const { item, oldIndex, newIndex, items, sourceParentId, expanded } = params;
|
|
217
|
+
|
|
218
|
+
const targetItem = items[newIndex];
|
|
219
|
+
const previousItem = newIndex > 0 ? items[newIndex - 1] : null;
|
|
220
|
+
|
|
221
|
+
const context: DragContext<T> = {
|
|
222
|
+
item,
|
|
223
|
+
targetItem,
|
|
224
|
+
previousItem,
|
|
225
|
+
isDraggingDown: oldIndex < newIndex,
|
|
226
|
+
isTargetExpanded: targetItem ? !!expanded[targetItem.id as keyof ExpandedState] : false,
|
|
227
|
+
isPreviousExpanded: previousItem ? !!expanded[previousItem.id as keyof ExpandedState] : false,
|
|
228
|
+
sourceParentId,
|
|
229
|
+
items,
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
// Handle dropping into expanded items (becomes first child)
|
|
233
|
+
if (isDroppingIntoExpandedTarget(context)) {
|
|
234
|
+
return createFirstChildPosition(targetItem.id);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (previousItem && isDroppingIntoExpandedPreviousChildren(context)) {
|
|
238
|
+
return createFirstChildPosition(previousItem.id);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (previousItem && isDroppingIntoExpandedPreviousWhenDraggingUp(context)) {
|
|
242
|
+
return createFirstChildPosition(previousItem.id);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Handle cross-parent drag operations
|
|
246
|
+
if (targetItem?.id !== item.id) {
|
|
247
|
+
const crossParentPosition = calculateCrossParentPosition(targetItem, sourceParentId, items);
|
|
248
|
+
if (crossParentPosition) {
|
|
249
|
+
return crossParentPosition;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Handle dropping at the end of the list
|
|
254
|
+
if (!targetItem && previousItem) {
|
|
255
|
+
const dropAtEndPosition = calculateDropAtEndPosition(previousItem, sourceParentId, items);
|
|
256
|
+
if (dropAtEndPosition) {
|
|
257
|
+
return dropAtEndPosition;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Default: stay in the same parent at the beginning
|
|
262
|
+
return { targetParentId: sourceParentId, adjustedIndex: 0 };
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Calculates the adjusted sibling index when reordering within the same parent
|
|
267
|
+
*/
|
|
268
|
+
export function calculateSiblingIndex<T extends HierarchicalItem>(params: {
|
|
269
|
+
item: T;
|
|
270
|
+
oldIndex: number;
|
|
271
|
+
newIndex: number;
|
|
272
|
+
items: T[];
|
|
273
|
+
parentId: string;
|
|
274
|
+
}): number {
|
|
275
|
+
const { item, oldIndex, newIndex, items, parentId } = params;
|
|
276
|
+
|
|
277
|
+
const siblings = getItemSiblings(items, parentId);
|
|
278
|
+
const oldSiblingIndex = siblings.findIndex(i => i.id === item.id);
|
|
279
|
+
const isDraggingDown = oldIndex < newIndex;
|
|
280
|
+
|
|
281
|
+
let newSiblingIndex = oldSiblingIndex;
|
|
282
|
+
const [start, end] = isDraggingDown ? [oldIndex + 1, newIndex] : [newIndex, oldIndex - 1];
|
|
283
|
+
|
|
284
|
+
for (let i = start; i <= end; i++) {
|
|
285
|
+
if (getItemParentId(items[i]) === parentId) {
|
|
286
|
+
newSiblingIndex += isDraggingDown ? 1 : -1;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return newSiblingIndex;
|
|
291
|
+
}
|
|
@@ -15,6 +15,17 @@ import { useChannel } from '@/vdb/hooks/use-channel.js';
|
|
|
15
15
|
import { usePage } from '@/vdb/hooks/use-page.js';
|
|
16
16
|
import { useSavedViews } from '@/vdb/hooks/use-saved-views.js';
|
|
17
17
|
import { Trans, useLingui } from '@lingui/react/macro';
|
|
18
|
+
import {
|
|
19
|
+
closestCenter,
|
|
20
|
+
DndContext,
|
|
21
|
+
} from '@dnd-kit/core';
|
|
22
|
+
import { restrictToVerticalAxis } from '@dnd-kit/modifiers';
|
|
23
|
+
import {
|
|
24
|
+
SortableContext,
|
|
25
|
+
useSortable,
|
|
26
|
+
verticalListSortingStrategy,
|
|
27
|
+
} from '@dnd-kit/sortable';
|
|
28
|
+
import { CSS } from '@dnd-kit/utilities';
|
|
18
29
|
import {
|
|
19
30
|
ColumnDef,
|
|
20
31
|
ColumnFilter,
|
|
@@ -23,18 +34,66 @@ import {
|
|
|
23
34
|
getCoreRowModel,
|
|
24
35
|
getPaginationRowModel,
|
|
25
36
|
PaginationState,
|
|
37
|
+
Row,
|
|
26
38
|
SortingState,
|
|
27
39
|
Table as TableType,
|
|
28
40
|
useReactTable,
|
|
29
41
|
VisibilityState,
|
|
30
42
|
} from '@tanstack/react-table';
|
|
31
43
|
import { RowSelectionState, TableOptions } from '@tanstack/table-core';
|
|
32
|
-
import
|
|
44
|
+
import { GripVertical } from 'lucide-react';
|
|
45
|
+
import React, { Suspense, useEffect, useId, useMemo, useRef } from 'react';
|
|
33
46
|
import { AddFilterMenu } from './add-filter-menu.js';
|
|
34
47
|
import { DataTableBulkActions } from './data-table-bulk-actions.js';
|
|
35
48
|
import { DataTableProvider } from './data-table-context.js';
|
|
36
49
|
import { DataTableFacetedFilter, DataTableFacetedFilterOption } from './data-table-faceted-filter.js';
|
|
37
50
|
import { DataTableFilterBadgeEditable } from './data-table-filter-badge-editable.js';
|
|
51
|
+
import { useDragAndDrop } from '@/vdb/hooks/use-drag-and-drop.js';
|
|
52
|
+
import { toast } from 'sonner';
|
|
53
|
+
|
|
54
|
+
interface DraggableRowProps<TData> {
|
|
55
|
+
row: Row<TData>;
|
|
56
|
+
isDragDisabled: boolean;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function DraggableRow<TData>({ row, isDragDisabled }: Readonly<DraggableRowProps<TData>>) {
|
|
60
|
+
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
|
|
61
|
+
id: row.id,
|
|
62
|
+
disabled: isDragDisabled,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const style = {
|
|
66
|
+
transform: CSS.Transform.toString(transform),
|
|
67
|
+
transition,
|
|
68
|
+
opacity: isDragging ? 0.5 : 1,
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<TableRow
|
|
73
|
+
ref={setNodeRef}
|
|
74
|
+
style={style}
|
|
75
|
+
data-state={row.getIsSelected() && 'selected'}
|
|
76
|
+
className="animate-in fade-in duration-100"
|
|
77
|
+
>
|
|
78
|
+
{!isDragDisabled && (
|
|
79
|
+
<TableCell className="w-[40px] h-12">
|
|
80
|
+
<div
|
|
81
|
+
{...attributes}
|
|
82
|
+
{...listeners}
|
|
83
|
+
className="cursor-move text-muted-foreground hover:text-foreground transition-colors"
|
|
84
|
+
>
|
|
85
|
+
<GripVertical className="h-4 w-4" />
|
|
86
|
+
</div>
|
|
87
|
+
</TableCell>
|
|
88
|
+
)}
|
|
89
|
+
{row.getVisibleCells().filter(cell => cell.column.id !== '__drag_handle__').map(cell => (
|
|
90
|
+
<TableCell key={cell.id} className="h-12">
|
|
91
|
+
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
|
92
|
+
</TableCell>
|
|
93
|
+
))}
|
|
94
|
+
</TableRow>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
38
97
|
|
|
39
98
|
export interface FacetedFilter {
|
|
40
99
|
title: string;
|
|
@@ -77,6 +136,18 @@ interface DataTableProps<TData> {
|
|
|
77
136
|
*/
|
|
78
137
|
setTableOptions?: (table: TableOptions<TData>) => TableOptions<TData>;
|
|
79
138
|
onRefresh?: () => void;
|
|
139
|
+
/**
|
|
140
|
+
* @description
|
|
141
|
+
* Callback when items are reordered via drag and drop.
|
|
142
|
+
* When provided, enables drag-and-drop functionality.
|
|
143
|
+
* The fourth parameter provides all items for context-aware reordering.
|
|
144
|
+
*/
|
|
145
|
+
onReorder?: (oldIndex: number, newIndex: number, item: TData, allItems?: TData[]) => void | Promise<void>;
|
|
146
|
+
/**
|
|
147
|
+
* @description
|
|
148
|
+
* When true, drag and drop will be disabled. This will only have an effect if the onReorder prop is also set
|
|
149
|
+
*/
|
|
150
|
+
disableDragAndDrop?: boolean;
|
|
80
151
|
}
|
|
81
152
|
|
|
82
153
|
/**
|
|
@@ -111,6 +182,8 @@ export function DataTable<TData>({
|
|
|
111
182
|
bulkActions,
|
|
112
183
|
setTableOptions,
|
|
113
184
|
onRefresh,
|
|
185
|
+
onReorder,
|
|
186
|
+
disableDragAndDrop = false,
|
|
114
187
|
}: Readonly<DataTableProps<TData>>) {
|
|
115
188
|
const [sorting, setSorting] = React.useState<SortingState>(sortingInitialState || []);
|
|
116
189
|
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(filtersInitialState || []);
|
|
@@ -131,6 +204,16 @@ export function DataTable<TData>({
|
|
|
131
204
|
const prevSearchTermRef = useRef(searchTerm);
|
|
132
205
|
const prevColumnFiltersRef = useRef(columnFilters);
|
|
133
206
|
|
|
207
|
+
const componentId = useId();
|
|
208
|
+
const { sensors, localData, handleDragEnd, itemIds } = useDragAndDrop({
|
|
209
|
+
data,
|
|
210
|
+
onReorder,
|
|
211
|
+
disabled: disableDragAndDrop,
|
|
212
|
+
onError: error => {
|
|
213
|
+
toast.error(t`Failed to reorder items`);
|
|
214
|
+
},
|
|
215
|
+
});
|
|
216
|
+
|
|
134
217
|
useEffect(() => {
|
|
135
218
|
// If the defaultColumnVisibility changes externally (e.g. the user reset the table settings),
|
|
136
219
|
// we want to reset the column visibility to the default.
|
|
@@ -143,9 +226,25 @@ export function DataTable<TData>({
|
|
|
143
226
|
// We intentionally do not include `columnVisibility` in the dependency array
|
|
144
227
|
}, [defaultColumnVisibility]);
|
|
145
228
|
|
|
229
|
+
// Add drag handle column if drag and drop is enabled
|
|
230
|
+
const columnsWithOptionalDragHandle = useMemo(() => {
|
|
231
|
+
if (!disableDragAndDrop && onReorder) {
|
|
232
|
+
const dragHandleColumn: ColumnDef<TData, any> = {
|
|
233
|
+
id: '__drag_handle__',
|
|
234
|
+
header: '',
|
|
235
|
+
cell: () => null, // Rendered by DraggableRow
|
|
236
|
+
size: 40,
|
|
237
|
+
enableSorting: false,
|
|
238
|
+
enableHiding: false,
|
|
239
|
+
};
|
|
240
|
+
return [dragHandleColumn, ...columns];
|
|
241
|
+
}
|
|
242
|
+
return columns;
|
|
243
|
+
}, [columns, disableDragAndDrop, onReorder]);
|
|
244
|
+
|
|
146
245
|
let tableOptions: TableOptions<TData> = {
|
|
147
|
-
data,
|
|
148
|
-
columns,
|
|
246
|
+
data: localData,
|
|
247
|
+
columns: columnsWithOptionalDragHandle,
|
|
149
248
|
getRowId: row => (row as { id: string }).id,
|
|
150
249
|
getCoreRowModel: getCoreRowModel(),
|
|
151
250
|
getPaginationRowModel: getPaginationRowModel(),
|
|
@@ -220,6 +319,8 @@ export function DataTable<TData>({
|
|
|
220
319
|
|
|
221
320
|
const visibleColumnCount = Object.values(columnVisibility).filter(Boolean).length;
|
|
222
321
|
|
|
322
|
+
const isDragDisabled = disableDragAndDrop || !onReorder;
|
|
323
|
+
|
|
223
324
|
return (
|
|
224
325
|
<DataTableProvider
|
|
225
326
|
columnFilters={columnFilters}
|
|
@@ -254,6 +355,7 @@ export function DataTable<TData>({
|
|
|
254
355
|
title={filter?.title}
|
|
255
356
|
options={filter?.options}
|
|
256
357
|
optionsFn={filter?.optionsFn}
|
|
358
|
+
icon={filter?.icon}
|
|
257
359
|
/>
|
|
258
360
|
))}
|
|
259
361
|
</Suspense>
|
|
@@ -309,66 +411,94 @@ export function DataTable<TData>({
|
|
|
309
411
|
) : null}
|
|
310
412
|
|
|
311
413
|
<div className="rounded-md border my-2 relative shadow-sm">
|
|
312
|
-
<
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
414
|
+
<DndContext
|
|
415
|
+
sensors={sensors}
|
|
416
|
+
collisionDetection={closestCenter}
|
|
417
|
+
onDragEnd={handleDragEnd}
|
|
418
|
+
modifiers={[restrictToVerticalAxis]}
|
|
419
|
+
>
|
|
420
|
+
<Table>
|
|
421
|
+
<TableHeader className="bg-muted/50">
|
|
422
|
+
{table.getHeaderGroups().map(headerGroup => (
|
|
423
|
+
<TableRow key={headerGroup.id}>
|
|
424
|
+
{headerGroup.headers.map(header => {
|
|
425
|
+
return (
|
|
426
|
+
<TableHead key={header.id}>
|
|
427
|
+
{header.isPlaceholder
|
|
428
|
+
? null
|
|
429
|
+
: flexRender(
|
|
430
|
+
header.column.columnDef.header,
|
|
431
|
+
header.getContext(),
|
|
432
|
+
)}
|
|
433
|
+
</TableHead>
|
|
434
|
+
);
|
|
435
|
+
})}
|
|
436
|
+
</TableRow>
|
|
437
|
+
))}
|
|
438
|
+
</TableHeader>
|
|
439
|
+
<SortableContext items={itemIds} strategy={verticalListSortingStrategy}>
|
|
440
|
+
<TableBody>
|
|
441
|
+
{isLoading && !localData?.length ? (
|
|
442
|
+
Array.from({ length: Math.min(pagination.pageSize, 100) }).map((_, index) => (
|
|
443
|
+
<TableRow
|
|
444
|
+
key={`skeleton-${index}`}
|
|
445
|
+
className="animate-in fade-in duration-100"
|
|
446
|
+
>
|
|
447
|
+
{!isDragDisabled && (
|
|
448
|
+
<TableCell className="w-[40px] h-12">
|
|
449
|
+
<Skeleton className="h-4 w-4" />
|
|
450
|
+
</TableCell>
|
|
451
|
+
)}
|
|
452
|
+
{Array.from({ length: visibleColumnCount }).map((_, cellIndex) => (
|
|
453
|
+
<TableCell
|
|
454
|
+
key={`skeleton-cell-${index}-${cellIndex}`}
|
|
455
|
+
className="h-12"
|
|
456
|
+
>
|
|
457
|
+
<Skeleton className="h-4 my-2 w-full" />
|
|
458
|
+
</TableCell>
|
|
459
|
+
))}
|
|
460
|
+
</TableRow>
|
|
461
|
+
))
|
|
462
|
+
) : table.getRowModel().rows?.length ? (
|
|
463
|
+
(() => {
|
|
464
|
+
const isDraggableEnabled = onReorder && !isDragDisabled;
|
|
465
|
+
const rows = table.getRowModel().rows;
|
|
466
|
+
|
|
467
|
+
if (isDraggableEnabled) {
|
|
468
|
+
return rows.map(row => (
|
|
469
|
+
<DraggableRow key={`${row.id}-${componentId}`} row={row} isDragDisabled={isDragDisabled} />
|
|
470
|
+
));
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
return rows.map(row => (
|
|
474
|
+
<TableRow
|
|
475
|
+
key={row.id}
|
|
476
|
+
data-state={row.getIsSelected() && 'selected'}
|
|
477
|
+
className="animate-in fade-in duration-100"
|
|
478
|
+
>
|
|
479
|
+
{row.getVisibleCells().map(cell => (
|
|
480
|
+
<TableCell key={cell.id} className="h-12">
|
|
481
|
+
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
|
482
|
+
</TableCell>
|
|
483
|
+
))}
|
|
484
|
+
</TableRow>
|
|
485
|
+
));
|
|
486
|
+
})()
|
|
487
|
+
) : (
|
|
488
|
+
<TableRow className="animate-in fade-in duration-100">
|
|
339
489
|
<TableCell
|
|
340
|
-
|
|
341
|
-
className="h-
|
|
490
|
+
colSpan={columnsWithOptionalDragHandle.length + (isDragDisabled ? 0 : 1)}
|
|
491
|
+
className="h-24 text-center"
|
|
342
492
|
>
|
|
343
|
-
<
|
|
493
|
+
<Trans>No results</Trans>
|
|
344
494
|
</TableCell>
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
data-state={row.getIsSelected() && 'selected'}
|
|
353
|
-
className="animate-in fade-in duration-100"
|
|
354
|
-
>
|
|
355
|
-
{row.getVisibleCells().map(cell => (
|
|
356
|
-
<TableCell key={cell.id} className="h-12">
|
|
357
|
-
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
|
358
|
-
</TableCell>
|
|
359
|
-
))}
|
|
360
|
-
</TableRow>
|
|
361
|
-
))
|
|
362
|
-
) : (
|
|
363
|
-
<TableRow className="animate-in fade-in duration-100">
|
|
364
|
-
<TableCell colSpan={columns.length} className="h-24 text-center">
|
|
365
|
-
<Trans>No results</Trans>
|
|
366
|
-
</TableCell>
|
|
367
|
-
</TableRow>
|
|
368
|
-
)}
|
|
369
|
-
{children}
|
|
370
|
-
</TableBody>
|
|
371
|
-
</Table>
|
|
495
|
+
</TableRow>
|
|
496
|
+
)}
|
|
497
|
+
{children}
|
|
498
|
+
</TableBody>
|
|
499
|
+
</SortableContext>
|
|
500
|
+
</Table>
|
|
501
|
+
</DndContext>
|
|
372
502
|
<DataTableBulkActions bulkActions={bulkActions ?? []} table={table} />
|
|
373
503
|
</div>
|
|
374
504
|
{onPageChange && totalItems != null && <DataTablePagination table={table} />}
|
|
@@ -18,6 +18,7 @@ import { useChannel } from '@/vdb/hooks/use-channel.js';
|
|
|
18
18
|
import { useLocalFormat } from '@/vdb/hooks/use-local-format.js';
|
|
19
19
|
import { usePermissions } from '@/vdb/hooks/use-permissions.js';
|
|
20
20
|
import { useSortedLanguages } from '@/vdb/hooks/use-sorted-languages.js';
|
|
21
|
+
import { globalLanguageCodes } from '@/vdb/utils/global-languages.js';
|
|
21
22
|
import { Trans } from '@lingui/react/macro';
|
|
22
23
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
|
23
24
|
import { AlertCircle, Lock } from 'lucide-react';
|
|
@@ -68,36 +69,12 @@ const updateChannelDocument = graphql(`
|
|
|
68
69
|
}
|
|
69
70
|
`);
|
|
70
71
|
|
|
71
|
-
// All possible language codes for global settings - includes more than what might be globally enabled
|
|
72
|
-
const ALL_LANGUAGE_CODES = [
|
|
73
|
-
'en',
|
|
74
|
-
'es',
|
|
75
|
-
'fr',
|
|
76
|
-
'de',
|
|
77
|
-
'it',
|
|
78
|
-
'pt',
|
|
79
|
-
'nl',
|
|
80
|
-
'pl',
|
|
81
|
-
'ru',
|
|
82
|
-
'ja',
|
|
83
|
-
'zh',
|
|
84
|
-
'ko',
|
|
85
|
-
'ar',
|
|
86
|
-
'hi',
|
|
87
|
-
'sv',
|
|
88
|
-
'da',
|
|
89
|
-
'nb',
|
|
90
|
-
'nn',
|
|
91
|
-
'fi',
|
|
92
|
-
];
|
|
93
|
-
|
|
94
72
|
interface ManageLanguagesDialogProps {
|
|
95
73
|
open: boolean;
|
|
96
74
|
onClose: () => void;
|
|
97
75
|
}
|
|
98
76
|
|
|
99
77
|
export function ManageLanguagesDialog({ open, onClose }: ManageLanguagesDialogProps) {
|
|
100
|
-
const { formatLanguageName } = useLocalFormat();
|
|
101
78
|
const { activeChannel } = useChannel();
|
|
102
79
|
const { hasPermissions } = usePermissions();
|
|
103
80
|
const queryClient = useQueryClient();
|
|
@@ -309,7 +286,7 @@ export function ManageLanguagesDialog({ open, onClose }: ManageLanguagesDialogPr
|
|
|
309
286
|
value={globalLanguages}
|
|
310
287
|
onChange={handleGlobalLanguagesChange}
|
|
311
288
|
multiple={true}
|
|
312
|
-
availableLanguageCodes={
|
|
289
|
+
availableLanguageCodes={globalLanguageCodes}
|
|
313
290
|
/>
|
|
314
291
|
</div>
|
|
315
292
|
<p className="text-xs text-muted-foreground">
|