@vendure/dashboard 3.5.2-master-202512170238 → 3.5.2-master-202512190240
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 +21 -2
- package/dist/plugin/dashboard.plugin.js +1 -1
- package/package.json +3 -3
- package/src/app/routeTree.gen.ts +1135 -1072
- 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/_facets/components/facet-values-sheet.tsx +4 -1
- package/src/app/routes/_authenticated/_facets/components/facet-values-table.tsx +1 -1
- package/src/app/routes/_authenticated/_facets/facets.tsx +22 -38
- package/src/app/routes/_authenticated/_orders/components/order-table-totals.tsx +16 -1
- package/src/app/routes/_authenticated/_product-variants/product-variants.graphql.ts +0 -1
- package/src/app/routes/_authenticated/_product-variants/product-variants_.$id.tsx +2 -2
- package/src/app/routes/_authenticated/_products/products.graphql.ts +5 -0
- package/src/app/routes/_authenticated/_products/products_.$id.tsx +24 -1
- package/src/app/routes/_authenticated/_system/components/payload-dialog.tsx +9 -2
- package/src/app/routes/_authenticated/_system/job-queue.tsx +11 -2
- package/src/app/routes/_authenticated/_tax-rates/tax-rates_.$id.tsx +2 -9
- package/src/app/routes/_authenticated/_zones/zones.tsx +1 -0
- package/src/i18n/locales/ar.po +177 -141
- package/src/i18n/locales/cs.po +177 -141
- package/src/i18n/locales/de.po +177 -141
- package/src/i18n/locales/en.po +177 -141
- package/src/i18n/locales/es.po +177 -141
- package/src/i18n/locales/fa.po +177 -141
- package/src/i18n/locales/fr.po +177 -141
- package/src/i18n/locales/he.po +177 -141
- package/src/i18n/locales/hr.po +177 -141
- package/src/i18n/locales/it.po +177 -141
- package/src/i18n/locales/ja.po +177 -141
- package/src/i18n/locales/nb.po +177 -141
- package/src/i18n/locales/ne.po +177 -141
- package/src/i18n/locales/pl.po +177 -141
- package/src/i18n/locales/pt_BR.po +177 -141
- package/src/i18n/locales/pt_PT.po +177 -141
- package/src/i18n/locales/ru.po +177 -141
- package/src/i18n/locales/sv.po +177 -141
- package/src/i18n/locales/tr.po +177 -141
- package/src/i18n/locales/uk.po +177 -141
- package/src/i18n/locales/zh_Hans.po +177 -141
- package/src/i18n/locales/zh_Hant.po +177 -141
- package/src/lib/components/data-input/number-input.tsx +24 -5
- package/src/lib/components/data-table/data-table-context.tsx +18 -3
- package/src/lib/components/data-table/data-table-utils.ts +241 -1
- package/src/lib/components/data-table/data-table.tsx +189 -60
- package/src/lib/components/data-table/global-views-bar.tsx +1 -1
- package/src/lib/components/data-table/save-view-dialog.tsx +21 -0
- package/src/lib/components/data-table/use-generated-columns.tsx +56 -24
- package/src/lib/components/data-table/views-sheet.tsx +1 -1
- package/src/lib/components/layout/channel-switcher.tsx +7 -5
- package/src/lib/components/layout/nav-main.tsx +2 -2
- package/src/lib/components/shared/alerts.tsx +3 -1
- package/src/lib/components/shared/assign-to-channel-bulk-action.tsx +10 -10
- package/src/lib/components/shared/assign-to-channel-dialog.tsx +1 -1
- package/src/lib/components/shared/assigned-channels.tsx +108 -0
- package/src/lib/components/shared/assigned-facet-values.tsx +5 -7
- package/src/lib/components/shared/channel-chip.tsx +43 -0
- 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/dropdown-menu.tsx +4 -1
- package/src/lib/components/ui/sidebar.tsx +2 -1
- package/src/lib/framework/page/list-page.tsx +62 -38
- package/src/lib/hooks/use-drag-and-drop.ts +86 -0
- package/src/lib/hooks/use-saved-views.ts +1 -0
- package/src/lib/providers/channel-provider.tsx +7 -1
- package/src/lib/types/saved-views.ts +3 -0
|
@@ -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}
|
|
@@ -310,66 +411,94 @@ export function DataTable<TData>({
|
|
|
310
411
|
) : null}
|
|
311
412
|
|
|
312
413
|
<div className="rounded-md border my-2 relative shadow-sm">
|
|
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
|
-
|
|
339
|
-
|
|
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">
|
|
340
489
|
<TableCell
|
|
341
|
-
|
|
342
|
-
className="h-
|
|
490
|
+
colSpan={columnsWithOptionalDragHandle.length + (isDragDisabled ? 0 : 1)}
|
|
491
|
+
className="h-24 text-center"
|
|
343
492
|
>
|
|
344
|
-
<
|
|
493
|
+
<Trans>No results</Trans>
|
|
345
494
|
</TableCell>
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
data-state={row.getIsSelected() && 'selected'}
|
|
354
|
-
className="animate-in fade-in duration-100"
|
|
355
|
-
>
|
|
356
|
-
{row.getVisibleCells().map(cell => (
|
|
357
|
-
<TableCell key={cell.id} className="h-12">
|
|
358
|
-
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
|
359
|
-
</TableCell>
|
|
360
|
-
))}
|
|
361
|
-
</TableRow>
|
|
362
|
-
))
|
|
363
|
-
) : (
|
|
364
|
-
<TableRow className="animate-in fade-in duration-100">
|
|
365
|
-
<TableCell colSpan={columns.length} className="h-24 text-center">
|
|
366
|
-
<Trans>No results</Trans>
|
|
367
|
-
</TableCell>
|
|
368
|
-
</TableRow>
|
|
369
|
-
)}
|
|
370
|
-
{children}
|
|
371
|
-
</TableBody>
|
|
372
|
-
</Table>
|
|
495
|
+
</TableRow>
|
|
496
|
+
)}
|
|
497
|
+
{children}
|
|
498
|
+
</TableBody>
|
|
499
|
+
</SortableContext>
|
|
500
|
+
</Table>
|
|
501
|
+
</DndContext>
|
|
373
502
|
<DataTableBulkActions bulkActions={bulkActions ?? []} table={table} />
|
|
374
503
|
</div>
|
|
375
504
|
{onPageChange && totalItems != null && <DataTablePagination table={table} />}
|
|
@@ -28,7 +28,7 @@ export const GlobalViewsBar: React.FC = () => {
|
|
|
28
28
|
);
|
|
29
29
|
|
|
30
30
|
const handleViewClick = (view: SavedView) => {
|
|
31
|
-
handleApplyView(view.filters, view.searchTerm);
|
|
31
|
+
handleApplyView(view.filters, view.columnConfig, view.searchTerm);
|
|
32
32
|
};
|
|
33
33
|
|
|
34
34
|
const isViewActive = (view: SavedView) => {
|
|
@@ -7,6 +7,8 @@ import { Input } from '../ui/input.js';
|
|
|
7
7
|
import { Label } from '../ui/label.js';
|
|
8
8
|
import { RadioGroup, RadioGroupItem } from '../ui/radio-group.js';
|
|
9
9
|
import { toast } from 'sonner';
|
|
10
|
+
import { usePage } from '@/vdb/hooks/use-page.js';
|
|
11
|
+
import { useUserSettings } from '@/vdb/hooks/use-user-settings.js';
|
|
10
12
|
|
|
11
13
|
interface SaveViewDialogProps {
|
|
12
14
|
open: boolean;
|
|
@@ -25,6 +27,21 @@ export const SaveViewDialog: React.FC<SaveViewDialogProps> = ({
|
|
|
25
27
|
const [scope, setScope] = useState<'user' | 'global'>('user');
|
|
26
28
|
const [saving, setSaving] = useState(false);
|
|
27
29
|
const { saveView, userViews, globalViews, canManageGlobalViews } = useSavedViews();
|
|
30
|
+
const { pageId } = usePage();
|
|
31
|
+
const { settings } = useUserSettings();
|
|
32
|
+
|
|
33
|
+
const defaultVisibility = {
|
|
34
|
+
id: false,
|
|
35
|
+
createdAt: false,
|
|
36
|
+
updatedAt: false,
|
|
37
|
+
type: false,
|
|
38
|
+
currencyCode: false,
|
|
39
|
+
}
|
|
40
|
+
const tableSettings = pageId ? settings.tableSettings?.[pageId] : undefined;
|
|
41
|
+
const columnVisibility = pageId
|
|
42
|
+
? (tableSettings?.columnVisibility ?? defaultVisibility)
|
|
43
|
+
: defaultVisibility;
|
|
44
|
+
const columnOrder = pageId ? (tableSettings?.columnOrder ?? []) : [];
|
|
28
45
|
|
|
29
46
|
const handleSave = async () => {
|
|
30
47
|
if (!name.trim()) {
|
|
@@ -45,6 +62,10 @@ export const SaveViewDialog: React.FC<SaveViewDialogProps> = ({
|
|
|
45
62
|
name: name.trim(),
|
|
46
63
|
scope,
|
|
47
64
|
filters,
|
|
65
|
+
columnConfig : {
|
|
66
|
+
columnVisibility,
|
|
67
|
+
columnOrder,
|
|
68
|
+
},
|
|
48
69
|
searchTerm,
|
|
49
70
|
});
|
|
50
71
|
toast.success(`View "${name}" saved successfully`);
|
|
@@ -16,9 +16,15 @@ import { usePage } from '@/vdb/hooks/use-page.js';
|
|
|
16
16
|
import { TypedDocumentNode } from '@graphql-typed-document-node/core';
|
|
17
17
|
import { Trans, useLingui } from '@lingui/react/macro';
|
|
18
18
|
import { useMutation } from '@tanstack/react-query';
|
|
19
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
AccessorFnColumnDef,
|
|
21
|
+
AccessorKeyColumnDef,
|
|
22
|
+
CellContext,
|
|
23
|
+
createColumnHelper,
|
|
24
|
+
Row,
|
|
25
|
+
} from '@tanstack/react-table';
|
|
20
26
|
import { EllipsisIcon, TrashIcon } from 'lucide-react';
|
|
21
|
-
import { useMemo } from 'react';
|
|
27
|
+
import { memo, useMemo } from 'react';
|
|
22
28
|
import { toast } from 'sonner';
|
|
23
29
|
import {
|
|
24
30
|
AdditionalColumns,
|
|
@@ -116,6 +122,25 @@ export function useGeneratedColumns<T extends TypedDocumentNode<any, any>>({
|
|
|
116
122
|
const customConfig = customizeColumns?.[fieldInfo.name as unknown as AllItemFieldKeys<T>] ?? {};
|
|
117
123
|
const { header, meta, cell: customCell, ...customConfigRest } = customConfig;
|
|
118
124
|
const enableColumnFilter = fieldInfo.isScalar && !facetedFilters?.[fieldInfo.name];
|
|
125
|
+
const displayComponentId =
|
|
126
|
+
pageId && pageBlock?.blockId
|
|
127
|
+
? generateDisplayComponentKey(pageId, pageBlock.blockId, fieldInfo.name)
|
|
128
|
+
: undefined;
|
|
129
|
+
|
|
130
|
+
// If a custom cell function is provided, use it directly (like additionalColumns does).
|
|
131
|
+
// This preserves the same behavior and prevents cell unmounting issues.
|
|
132
|
+
// Only use CellWrapper for columns without custom cells.
|
|
133
|
+
const cellFn =
|
|
134
|
+
typeof customCell === 'function'
|
|
135
|
+
? customCell
|
|
136
|
+
: (cellContext: CellContext<any, any>) => (
|
|
137
|
+
<CellWrapper
|
|
138
|
+
cellContext={cellContext}
|
|
139
|
+
fieldInfo={fieldInfo}
|
|
140
|
+
isCustomField={isCustomField}
|
|
141
|
+
displayComponentId={displayComponentId}
|
|
142
|
+
/>
|
|
143
|
+
);
|
|
119
144
|
|
|
120
145
|
return columnHelper.accessor(fieldInfo.name as any, {
|
|
121
146
|
id: fieldInfo.name,
|
|
@@ -126,28 +151,7 @@ export function useGeneratedColumns<T extends TypedDocumentNode<any, any>>({
|
|
|
126
151
|
// otherwise the TanStack Table with apply an "auto" function which somehow
|
|
127
152
|
// prevents certain filters from working.
|
|
128
153
|
filterFn: 'equalsString',
|
|
129
|
-
cell:
|
|
130
|
-
const { cell, row } = cellContext;
|
|
131
|
-
const cellValue = cell.getValue();
|
|
132
|
-
const value =
|
|
133
|
-
cellValue ??
|
|
134
|
-
(isCustomField ? row.original?.customFields?.[fieldInfo.name] : undefined);
|
|
135
|
-
const displayComponentId =
|
|
136
|
-
pageId && pageBlock?.blockId
|
|
137
|
-
? generateDisplayComponentKey(pageId, pageBlock.blockId, fieldInfo.name)
|
|
138
|
-
: undefined;
|
|
139
|
-
|
|
140
|
-
const CustomDisplayComponent =
|
|
141
|
-
displayComponentId && getDisplayComponent(displayComponentId);
|
|
142
|
-
|
|
143
|
-
if (CustomDisplayComponent) {
|
|
144
|
-
return <CustomDisplayComponent value={value} {...cellContext} />;
|
|
145
|
-
}
|
|
146
|
-
if (typeof customCell === 'function') {
|
|
147
|
-
return customCell(cellContext);
|
|
148
|
-
}
|
|
149
|
-
return <DefaultDisplayComponent value={value} fieldInfo={fieldInfo} />;
|
|
150
|
-
},
|
|
154
|
+
cell: cellFn,
|
|
151
155
|
header: headerContext => {
|
|
152
156
|
return (
|
|
153
157
|
<DataTableColumnHeader headerContext={headerContext} customConfig={customConfig} />
|
|
@@ -291,6 +295,34 @@ function DefaultDisplayComponent({ value, fieldInfo }: { value: any; fieldInfo:
|
|
|
291
295
|
return value;
|
|
292
296
|
}
|
|
293
297
|
|
|
298
|
+
/**
|
|
299
|
+
* A cell wrapper component for columns without custom cell functions.
|
|
300
|
+
* Handles default display logic including custom display components and field-type-based rendering.
|
|
301
|
+
*/
|
|
302
|
+
const CellWrapper = memo(function CellWrapper({
|
|
303
|
+
cellContext,
|
|
304
|
+
fieldInfo,
|
|
305
|
+
isCustomField,
|
|
306
|
+
displayComponentId,
|
|
307
|
+
}: {
|
|
308
|
+
cellContext: CellContext<any, any>;
|
|
309
|
+
fieldInfo: FieldInfo;
|
|
310
|
+
isCustomField: boolean;
|
|
311
|
+
displayComponentId?: string;
|
|
312
|
+
}) {
|
|
313
|
+
const { cell, row } = cellContext;
|
|
314
|
+
const cellValue = cell.getValue();
|
|
315
|
+
const value =
|
|
316
|
+
cellValue ?? (isCustomField ? (row.original as any)?.customFields?.[fieldInfo.name] : undefined);
|
|
317
|
+
|
|
318
|
+
const CustomDisplayComponent = displayComponentId && getDisplayComponent(displayComponentId);
|
|
319
|
+
|
|
320
|
+
if (CustomDisplayComponent) {
|
|
321
|
+
return <CustomDisplayComponent value={value} {...cellContext} />;
|
|
322
|
+
}
|
|
323
|
+
return <DefaultDisplayComponent value={value} fieldInfo={fieldInfo} />;
|
|
324
|
+
});
|
|
325
|
+
|
|
294
326
|
function DeleteMutationRowAction({
|
|
295
327
|
deleteMutation,
|
|
296
328
|
row,
|
|
@@ -44,7 +44,7 @@ export const ViewsSheet: React.FC<ViewsSheetProps> = ({ open, onOpenChange, type
|
|
|
44
44
|
const isGlobal = type === 'global';
|
|
45
45
|
|
|
46
46
|
const handleViewApply = (view: SavedView) => {
|
|
47
|
-
handleApplyView(view.filters, view.searchTerm);
|
|
47
|
+
handleApplyView(view.filters,view.columnConfig, view.searchTerm);
|
|
48
48
|
const viewName = view.name;
|
|
49
49
|
const message = isGlobal ? t`Applied global view "${viewName}"` : t`Applied view "${viewName}"`;
|
|
50
50
|
toast.success(message);
|
|
@@ -138,7 +138,7 @@ export function ChannelSwitcher() {
|
|
|
138
138
|
</div>
|
|
139
139
|
<ChannelCodeLabel code={channel.code} />
|
|
140
140
|
{channel.id === displayChannel?.id && (
|
|
141
|
-
<span className="
|
|
141
|
+
<span className="ms-auto text-xs text-muted-foreground">
|
|
142
142
|
<Trans context="current channel">Current</Trans>
|
|
143
143
|
</span>
|
|
144
144
|
)}
|
|
@@ -146,9 +146,9 @@ export function ChannelSwitcher() {
|
|
|
146
146
|
{/* Show language sub-menu for the current channel */}
|
|
147
147
|
{channel.id === displayChannel?.id && (
|
|
148
148
|
<DropdownMenuSub>
|
|
149
|
-
<DropdownMenuSubTrigger className="gap-2 p-2
|
|
149
|
+
<DropdownMenuSubTrigger className="gap-2 p-2 ps-4">
|
|
150
150
|
<Languages className="w-4 h-4" />
|
|
151
|
-
<div className="flex gap-1
|
|
151
|
+
<div className="flex gap-1 ms-2">
|
|
152
152
|
<span className="text-muted-foreground">Content: </span>
|
|
153
153
|
{formatLanguageName(contentLanguage)}
|
|
154
154
|
</div>
|
|
@@ -167,7 +167,7 @@ export function ChannelSwitcher() {
|
|
|
167
167
|
</div>
|
|
168
168
|
<span>{label}</span>
|
|
169
169
|
{contentLanguage === languageCode && (
|
|
170
|
-
<span className="
|
|
170
|
+
<span className="ms-auto text-xs text-muted-foreground">
|
|
171
171
|
<Trans context="active language">
|
|
172
172
|
Active
|
|
173
173
|
</Trans>
|
|
@@ -200,7 +200,9 @@ export function ChannelSwitcher() {
|
|
|
200
200
|
<div className="bg-background flex size-6 items-center justify-center rounded-md border">
|
|
201
201
|
<Plus className="size-4" />
|
|
202
202
|
</div>
|
|
203
|
-
<div className="text-muted-foreground font-medium">
|
|
203
|
+
<div className="text-muted-foreground font-medium">
|
|
204
|
+
<Trans>Add channel</Trans>
|
|
205
|
+
</div>
|
|
204
206
|
</Link>
|
|
205
207
|
</DropdownMenuItem>
|
|
206
208
|
</DropdownMenuContent>
|
|
@@ -215,7 +215,7 @@ export function NavMain({ items }: Readonly<{ items: Array<NavMenuSection | NavM
|
|
|
215
215
|
<SidebarMenuButton tooltip={item.title}>
|
|
216
216
|
{item.icon && <item.icon />}
|
|
217
217
|
<span>{i18n.t(item.title)}</span>
|
|
218
|
-
<ChevronRight className="
|
|
218
|
+
<ChevronRight className="ms-auto transition-transform duration-200 rtl:rotate-180 group-data-[state=open]/collapsible:rotate-90" />
|
|
219
219
|
</SidebarMenuButton>
|
|
220
220
|
</CollapsibleTrigger>
|
|
221
221
|
<CollapsibleContent>
|
|
@@ -280,7 +280,7 @@ export function NavMain({ items }: Readonly<{ items: Array<NavMenuSection | NavM
|
|
|
280
280
|
<SidebarMenuButton tooltip={i18n.t(item.title)}>
|
|
281
281
|
{item.icon && <item.icon />}
|
|
282
282
|
<span>{i18n.t(item.title)}</span>
|
|
283
|
-
<ChevronRight className="
|
|
283
|
+
<ChevronRight className="ms-auto transition-transform duration-200 rtl:rotate-180 group-data-[state=open]/collapsible:rotate-90" />
|
|
284
284
|
</SidebarMenuButton>
|
|
285
285
|
</CollapsibleTrigger>
|
|
286
286
|
<CollapsibleContent>
|
|
@@ -29,7 +29,9 @@ export function Alerts() {
|
|
|
29
29
|
</Button>
|
|
30
30
|
</DropdownMenuTrigger>
|
|
31
31
|
<DropdownMenuContent align="end" className="max-w-[800px] min-w-96">
|
|
32
|
-
<DropdownMenuLabel>
|
|
32
|
+
<DropdownMenuLabel>
|
|
33
|
+
<Trans>Alerts</Trans>
|
|
34
|
+
</DropdownMenuLabel>
|
|
33
35
|
<DropdownMenuSeparator />
|
|
34
36
|
<ScrollArea className="max-h-[500px]">
|
|
35
37
|
{activeCount > 0 ? (
|
|
@@ -23,16 +23,16 @@ interface AssignToChannelBulkActionProps {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
export function AssignToChannelBulkAction({
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
26
|
+
selection,
|
|
27
|
+
table,
|
|
28
|
+
entityType,
|
|
29
|
+
mutationFn,
|
|
30
|
+
requiredPermissions,
|
|
31
|
+
buildInput,
|
|
32
|
+
additionalFields,
|
|
33
|
+
additionalData = {},
|
|
34
|
+
onSuccess,
|
|
35
|
+
}: Readonly<AssignToChannelBulkActionProps>) {
|
|
36
36
|
const { refetchPaginatedList } = usePaginatedList();
|
|
37
37
|
const { channels } = useChannel();
|
|
38
38
|
const [dialogOpen, setDialogOpen] = useState(false);
|
|
@@ -70,7 +70,7 @@ export function AssignToChannelDialog({
|
|
|
70
70
|
onOpenChange(false);
|
|
71
71
|
},
|
|
72
72
|
onError: () => {
|
|
73
|
-
toast.error(`Failed to assign ${entityIdsLength} ${entityType} to channel`);
|
|
73
|
+
toast.error(t`Failed to assign ${entityIdsLength} ${entityType} to channel`);
|
|
74
74
|
},
|
|
75
75
|
});
|
|
76
76
|
|