@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
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
|
3
|
+
import { toast } from 'sonner';
|
|
4
|
+
import { Plus } from 'lucide-react';
|
|
5
|
+
|
|
6
|
+
import { ChannelChip } from '@/vdb/components/shared/channel-chip.js';
|
|
7
|
+
import { AssignToChannelDialog } from '@/vdb/components/shared/assign-to-channel-dialog.js';
|
|
8
|
+
import { usePriceFactor } from '@/vdb/components/shared/assign-to-channel-dialog.js';
|
|
9
|
+
import { Button } from '@/vdb/components/ui/button.js';
|
|
10
|
+
import { useChannel } from '@/vdb/hooks/use-channel.js';
|
|
11
|
+
import { Trans, useLingui } from '@lingui/react/macro';
|
|
12
|
+
import { DEFAULT_CHANNEL_CODE } from '@/vdb/constants.js';
|
|
13
|
+
import type { SimpleChannel } from '@/vdb/providers/channel-provider.js';
|
|
14
|
+
|
|
15
|
+
interface AssignedChannelsProps {
|
|
16
|
+
channels: SimpleChannel[];
|
|
17
|
+
entityId: string;
|
|
18
|
+
canUpdate?: boolean;
|
|
19
|
+
assignMutationFn: (variables: any) => Promise<any>;
|
|
20
|
+
removeMutationFn: (variables: any) => Promise<any>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function AssignedChannels({
|
|
24
|
+
channels,
|
|
25
|
+
entityId,
|
|
26
|
+
canUpdate = true,
|
|
27
|
+
assignMutationFn,
|
|
28
|
+
removeMutationFn,
|
|
29
|
+
}: AssignedChannelsProps) {
|
|
30
|
+
const { t } = useLingui();
|
|
31
|
+
const queryClient = useQueryClient();
|
|
32
|
+
const { activeChannel, channels: allChannels } = useChannel();
|
|
33
|
+
const [assignDialogOpen, setAssignDialogOpen] = useState(false);
|
|
34
|
+
const { priceFactor, priceFactorField } = usePriceFactor();
|
|
35
|
+
|
|
36
|
+
const { mutate: removeFromChannel, isPending: isRemoving } = useMutation({
|
|
37
|
+
mutationFn: removeMutationFn,
|
|
38
|
+
onSuccess: () => {
|
|
39
|
+
toast.success(t`Successfully removed product from channel`);
|
|
40
|
+
queryClient.invalidateQueries({ queryKey: ['DetailPage', 'product', { id: entityId }] });
|
|
41
|
+
},
|
|
42
|
+
onError: () => {
|
|
43
|
+
toast.error(t`Failed to remove product from channel`);
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
async function onRemoveHandler(channelId: string) {
|
|
48
|
+
if (channelId === activeChannel?.id) {
|
|
49
|
+
toast.error(t`Cannot remove from active channel`);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
removeFromChannel({
|
|
53
|
+
input: {
|
|
54
|
+
productIds: [entityId],
|
|
55
|
+
channelId,
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const handleAssignSuccess = () => {
|
|
61
|
+
queryClient.invalidateQueries({ queryKey: ['DetailPage', 'product', { id: entityId }] });
|
|
62
|
+
setAssignDialogOpen(false);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// Only show add button if there are more channels available
|
|
66
|
+
const availableChannels = allChannels.filter(ch => !channels.map(c => c.id).includes(ch.id));
|
|
67
|
+
const showAddButton = canUpdate && availableChannels.length > 0;
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<>
|
|
71
|
+
<div className="flex flex-wrap gap-1 mb-2">
|
|
72
|
+
{channels.filter(c => c.code !== DEFAULT_CHANNEL_CODE).map((channel: SimpleChannel) => {
|
|
73
|
+
return (
|
|
74
|
+
<ChannelChip key={channel.id} channel={channel} removable={canUpdate && channel.id !== activeChannel?.id} onRemove={onRemoveHandler} />
|
|
75
|
+
);
|
|
76
|
+
})}
|
|
77
|
+
</div>
|
|
78
|
+
{showAddButton && (
|
|
79
|
+
<>
|
|
80
|
+
<Button
|
|
81
|
+
type="button"
|
|
82
|
+
variant="outline"
|
|
83
|
+
size="sm"
|
|
84
|
+
onClick={() => setAssignDialogOpen(true)}
|
|
85
|
+
disabled={isRemoving}
|
|
86
|
+
>
|
|
87
|
+
<Plus className="h-4 w-4 mr-1" />
|
|
88
|
+
<Trans>Assign to channel</Trans>
|
|
89
|
+
</Button>
|
|
90
|
+
<AssignToChannelDialog
|
|
91
|
+
entityType="product"
|
|
92
|
+
open={assignDialogOpen}
|
|
93
|
+
onOpenChange={setAssignDialogOpen}
|
|
94
|
+
entityIds={[entityId]}
|
|
95
|
+
mutationFn={assignMutationFn}
|
|
96
|
+
onSuccess={handleAssignSuccess}
|
|
97
|
+
buildInput={(channelId: string) => ({
|
|
98
|
+
productIds: [entityId],
|
|
99
|
+
channelId,
|
|
100
|
+
priceFactor,
|
|
101
|
+
})}
|
|
102
|
+
additionalFields={priceFactorField}
|
|
103
|
+
/>
|
|
104
|
+
</>
|
|
105
|
+
)}
|
|
106
|
+
</>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
@@ -18,17 +18,15 @@ interface AssignedFacetValuesProps {
|
|
|
18
18
|
value?: string[] | null;
|
|
19
19
|
facetValues: FacetValue[];
|
|
20
20
|
canUpdate?: boolean;
|
|
21
|
-
onBlur?: () => void;
|
|
22
21
|
onChange?: (value: string[]) => void;
|
|
23
22
|
}
|
|
24
23
|
|
|
25
24
|
export function AssignedFacetValues({
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}: AssignedFacetValuesProps) {
|
|
25
|
+
value = [],
|
|
26
|
+
facetValues,
|
|
27
|
+
canUpdate = true,
|
|
28
|
+
onChange,
|
|
29
|
+
}: Readonly<AssignedFacetValuesProps>) {
|
|
32
30
|
const [knownFacetValues, setKnownFacetValues] = useState<FacetValue[]>(facetValues);
|
|
33
31
|
|
|
34
32
|
function onSelectHandler(facetValue: FacetValue) {
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Badge } from '@/vdb/components/ui/badge.js';
|
|
2
|
+
import { X } from 'lucide-react';
|
|
3
|
+
import type { SimpleChannel } from '@/vdb/providers/channel-provider.js';
|
|
4
|
+
|
|
5
|
+
interface ChannelChipProps {
|
|
6
|
+
channel: SimpleChannel;
|
|
7
|
+
removable?: boolean;
|
|
8
|
+
onRemove?: (id: string) => void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @description
|
|
13
|
+
* A component for displaying a channel as a chip.
|
|
14
|
+
*
|
|
15
|
+
* @docsCategory components
|
|
16
|
+
* @since 3.5.2
|
|
17
|
+
*/
|
|
18
|
+
export function ChannelChip({
|
|
19
|
+
channel,
|
|
20
|
+
removable = true,
|
|
21
|
+
onRemove,
|
|
22
|
+
}: Readonly<ChannelChipProps>) {
|
|
23
|
+
return (
|
|
24
|
+
<Badge
|
|
25
|
+
variant="secondary"
|
|
26
|
+
className="flex items-center gap-2 py-0.5 pl-2 pr-1 h-6 hover:bg-secondary/80"
|
|
27
|
+
>
|
|
28
|
+
<div className="flex items-center gap-1.5">
|
|
29
|
+
<span className="font-medium">{channel.code}</span>
|
|
30
|
+
</div>
|
|
31
|
+
{removable && (
|
|
32
|
+
<button
|
|
33
|
+
type="button"
|
|
34
|
+
className="ml-0.5 inline-flex h-4 w-4 items-center justify-center rounded-full hover:bg-muted/30 hover:cursor-pointer"
|
|
35
|
+
onClick={() => onRemove?.(channel.id)}
|
|
36
|
+
aria-label={`Remove ${channel.code} from ${channel.token}`}
|
|
37
|
+
>
|
|
38
|
+
<X className="h-3 w-3" />
|
|
39
|
+
</button>
|
|
40
|
+
)}
|
|
41
|
+
</Badge>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
@@ -234,6 +234,21 @@ export interface PaginatedListDataTableProps<
|
|
|
234
234
|
* the list needs to be refreshed.
|
|
235
235
|
*/
|
|
236
236
|
registerRefresher?: PaginatedListRefresherRegisterFn;
|
|
237
|
+
/**
|
|
238
|
+
* @description
|
|
239
|
+
* Callback when items are reordered via drag and drop.
|
|
240
|
+
* When provided, enables drag-and-drop functionality.
|
|
241
|
+
*/
|
|
242
|
+
onReorder?: (
|
|
243
|
+
oldIndex: number,
|
|
244
|
+
newIndex: number,
|
|
245
|
+
item: PaginatedListItemFields<T>,
|
|
246
|
+
) => void | Promise<void>;
|
|
247
|
+
/**
|
|
248
|
+
* @description
|
|
249
|
+
* When true, drag and drop will be disabled. This will only have an effect if the onReorder prop is also set
|
|
250
|
+
*/
|
|
251
|
+
disableDragAndDrop?: boolean;
|
|
237
252
|
}
|
|
238
253
|
|
|
239
254
|
export const PaginatedListDataTableKey = 'PaginatedListDataTable';
|
|
@@ -378,6 +393,8 @@ export function PaginatedListDataTable<
|
|
|
378
393
|
setTableOptions,
|
|
379
394
|
transformData,
|
|
380
395
|
registerRefresher,
|
|
396
|
+
onReorder,
|
|
397
|
+
disableDragAndDrop = false,
|
|
381
398
|
}: Readonly<PaginatedListDataTableProps<T, U, V, AC>>) {
|
|
382
399
|
const [searchTerm, setSearchTerm] = React.useState<string>('');
|
|
383
400
|
const debouncedSearchTerm = useDebounce(searchTerm, 500);
|
|
@@ -498,6 +515,8 @@ export function PaginatedListDataTable<
|
|
|
498
515
|
bulkActions={bulkActions}
|
|
499
516
|
setTableOptions={setTableOptions}
|
|
500
517
|
onRefresh={refetchPaginatedList}
|
|
518
|
+
onReorder={onReorder}
|
|
519
|
+
disableDragAndDrop={disableDragAndDrop}
|
|
501
520
|
/>
|
|
502
521
|
</PaginatedListContext.Provider>
|
|
503
522
|
);
|
|
@@ -8,7 +8,7 @@ const alertVariants = cva(
|
|
|
8
8
|
{
|
|
9
9
|
variants: {
|
|
10
10
|
variant: {
|
|
11
|
-
default: 'bg-background text-
|
|
11
|
+
default: 'bg-background text-primary/80',
|
|
12
12
|
destructive:
|
|
13
13
|
'border-destructive/50 text-destructive dark:text-destructive-foreground/80 dark:border-destructive [&>svg]:text-current dark:bg-destructive/50',
|
|
14
14
|
},
|
|
@@ -161,6 +161,8 @@ function DropdownMenuSub({ ...props }: React.ComponentProps<typeof DropdownMenuP
|
|
|
161
161
|
return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />;
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
+
const customDropdownMenuSubTriggerClassNames = 'data-[inset]:ps-8';
|
|
165
|
+
const customChevronIconClassNames = 'ms-auto rtl:rotate-180';
|
|
164
166
|
function DropdownMenuSubTrigger({
|
|
165
167
|
className,
|
|
166
168
|
inset,
|
|
@@ -175,12 +177,13 @@ function DropdownMenuSubTrigger({
|
|
|
175
177
|
data-inset={inset}
|
|
176
178
|
className={cn(
|
|
177
179
|
'focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8',
|
|
180
|
+
customDropdownMenuSubTriggerClassNames,
|
|
178
181
|
className,
|
|
179
182
|
)}
|
|
180
183
|
{...props}
|
|
181
184
|
>
|
|
182
185
|
{children}
|
|
183
|
-
<ChevronRightIcon className=
|
|
186
|
+
<ChevronRightIcon className={cn('size-4', customChevronIconClassNames)} />
|
|
184
187
|
</DropdownMenuPrimitive.SubTrigger>
|
|
185
188
|
);
|
|
186
189
|
}
|
|
@@ -443,8 +443,9 @@ function SidebarMenuItem({ className, ...props }: React.ComponentProps<'li'>) {
|
|
|
443
443
|
);
|
|
444
444
|
}
|
|
445
445
|
|
|
446
|
+
const customLogicalPropsClassNames = 'text-start group-has-data-[sidebar=menu-action]/menu-item:pe-8';
|
|
446
447
|
const sidebarMenuButtonVariants = cva(
|
|
447
|
-
|
|
448
|
+
`peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 ${customLogicalPropsClassNames}`,
|
|
448
449
|
{
|
|
449
450
|
variants: {
|
|
450
451
|
variant: {
|
|
@@ -361,6 +361,22 @@ export interface ListPageProps<
|
|
|
361
361
|
* the list needs to be refreshed.
|
|
362
362
|
*/
|
|
363
363
|
registerRefresher?: PaginatedListRefresherRegisterFn;
|
|
364
|
+
/**
|
|
365
|
+
* @description
|
|
366
|
+
* Callback when items are reordered via drag and drop.
|
|
367
|
+
* Only applies to top-level items. When provided, enables drag-and-drop functionality.
|
|
368
|
+
*
|
|
369
|
+
* @param oldIndex - The original index of the dragged item
|
|
370
|
+
* @param newIndex - The new index where the item was dropped
|
|
371
|
+
* @param item - The data of the item that was moved
|
|
372
|
+
*/
|
|
373
|
+
onReorder?: (oldIndex: number, newIndex: number, item: any) => void | Promise<void>;
|
|
374
|
+
/**
|
|
375
|
+
* @description
|
|
376
|
+
* When true, drag and drop will be disabled. This will only have an effect if the onReorder prop is also set Useful when filtering or searching.
|
|
377
|
+
* Defaults to false. Only relevant when `onReorder` is provided.
|
|
378
|
+
*/
|
|
379
|
+
disableDragAndDrop?: boolean;
|
|
364
380
|
}
|
|
365
381
|
|
|
366
382
|
/**
|
|
@@ -481,6 +497,8 @@ export function ListPage<
|
|
|
481
497
|
setTableOptions,
|
|
482
498
|
bulkActions,
|
|
483
499
|
registerRefresher,
|
|
500
|
+
onReorder,
|
|
501
|
+
disableDragAndDrop = false,
|
|
484
502
|
}: Readonly<ListPageProps<T, U, V, AC>>) {
|
|
485
503
|
const route = typeof routeOrFn === 'function' ? routeOrFn() : routeOrFn;
|
|
486
504
|
const routeSearch = route.useSearch();
|
|
@@ -536,6 +554,47 @@ export function ListPage<
|
|
|
536
554
|
});
|
|
537
555
|
}
|
|
538
556
|
|
|
557
|
+
const commonTableProps = {
|
|
558
|
+
listQuery,
|
|
559
|
+
deleteMutation,
|
|
560
|
+
transformVariables,
|
|
561
|
+
customizeColumns: customizeColumns as any,
|
|
562
|
+
additionalColumns: additionalColumns as any,
|
|
563
|
+
defaultColumnOrder: columnOrder as any,
|
|
564
|
+
defaultVisibility: columnVisibility as any,
|
|
565
|
+
onSearchTermChange,
|
|
566
|
+
page: pagination.page,
|
|
567
|
+
itemsPerPage: pagination.itemsPerPage,
|
|
568
|
+
sorting,
|
|
569
|
+
columnFilters,
|
|
570
|
+
onPageChange: (table: Table<any>, page: number, perPage: number) => {
|
|
571
|
+
persistListStateToUrl(table, { page, perPage });
|
|
572
|
+
if (pageId) {
|
|
573
|
+
setTableSettings(pageId, 'pageSize', perPage);
|
|
574
|
+
}
|
|
575
|
+
},
|
|
576
|
+
onSortChange: (table: Table<any>, sorting: SortingState) => {
|
|
577
|
+
persistListStateToUrl(table, { sort: sorting });
|
|
578
|
+
},
|
|
579
|
+
onFilterChange: (table: Table<any>, filters: ColumnFiltersState) => {
|
|
580
|
+
persistListStateToUrl(table, { filters });
|
|
581
|
+
if (pageId) {
|
|
582
|
+
setTableSettings(pageId, 'columnFilters', filters);
|
|
583
|
+
}
|
|
584
|
+
},
|
|
585
|
+
onColumnVisibilityChange: (table: Table<any>, columnVisibility: any) => {
|
|
586
|
+
if (pageId) {
|
|
587
|
+
setTableSettings(pageId, 'columnVisibility', columnVisibility);
|
|
588
|
+
}
|
|
589
|
+
},
|
|
590
|
+
facetedFilters,
|
|
591
|
+
rowActions,
|
|
592
|
+
bulkActions,
|
|
593
|
+
setTableOptions,
|
|
594
|
+
transformData,
|
|
595
|
+
registerRefresher,
|
|
596
|
+
};
|
|
597
|
+
|
|
539
598
|
return (
|
|
540
599
|
<Page pageId={pageId}>
|
|
541
600
|
<PageTitle>{title}</PageTitle>
|
|
@@ -543,44 +602,9 @@ export function ListPage<
|
|
|
543
602
|
<PageLayout>
|
|
544
603
|
<FullWidthPageBlock blockId="list-table">
|
|
545
604
|
<PaginatedListDataTable
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
customizeColumns={customizeColumns as any}
|
|
550
|
-
additionalColumns={additionalColumns as any}
|
|
551
|
-
defaultColumnOrder={columnOrder as any}
|
|
552
|
-
defaultVisibility={columnVisibility as any}
|
|
553
|
-
onSearchTermChange={onSearchTermChange}
|
|
554
|
-
page={pagination.page}
|
|
555
|
-
itemsPerPage={pagination.itemsPerPage}
|
|
556
|
-
sorting={sorting}
|
|
557
|
-
columnFilters={columnFilters}
|
|
558
|
-
onPageChange={(table, page, perPage) => {
|
|
559
|
-
persistListStateToUrl(table, { page, perPage });
|
|
560
|
-
if (pageId) {
|
|
561
|
-
setTableSettings(pageId, 'pageSize', perPage);
|
|
562
|
-
}
|
|
563
|
-
}}
|
|
564
|
-
onSortChange={(table, sorting) => {
|
|
565
|
-
persistListStateToUrl(table, { sort: sorting });
|
|
566
|
-
}}
|
|
567
|
-
onFilterChange={(table, filters) => {
|
|
568
|
-
persistListStateToUrl(table, { filters });
|
|
569
|
-
if (pageId) {
|
|
570
|
-
setTableSettings(pageId, 'columnFilters', filters);
|
|
571
|
-
}
|
|
572
|
-
}}
|
|
573
|
-
onColumnVisibilityChange={(table, columnVisibility) => {
|
|
574
|
-
if (pageId) {
|
|
575
|
-
setTableSettings(pageId, 'columnVisibility', columnVisibility);
|
|
576
|
-
}
|
|
577
|
-
}}
|
|
578
|
-
facetedFilters={facetedFilters}
|
|
579
|
-
rowActions={rowActions}
|
|
580
|
-
bulkActions={bulkActions}
|
|
581
|
-
setTableOptions={setTableOptions}
|
|
582
|
-
transformData={transformData}
|
|
583
|
-
registerRefresher={registerRefresher}
|
|
605
|
+
{...commonTableProps}
|
|
606
|
+
onReorder={onReorder}
|
|
607
|
+
disableDragAndDrop={disableDragAndDrop}
|
|
584
608
|
/>
|
|
585
609
|
</FullWidthPageBlock>
|
|
586
610
|
</PageLayout>
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { DragEndEvent, KeyboardSensor, PointerSensor, useSensor, useSensors } from '@dnd-kit/core';
|
|
2
|
+
import { arrayMove, sortableKeyboardCoordinates } from '@dnd-kit/sortable';
|
|
3
|
+
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
4
|
+
|
|
5
|
+
interface UseDragAndDropOptions<TData> {
|
|
6
|
+
data: TData[];
|
|
7
|
+
onReorder?: (oldIndex: number, newIndex: number, item: TData, allItems?: TData[]) => void | Promise<void>;
|
|
8
|
+
onError?: (error: Error) => void;
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @description
|
|
14
|
+
* Provides the sensors and state management for drag and drop functionality.
|
|
15
|
+
*
|
|
16
|
+
*
|
|
17
|
+
* @docsCategory hooks
|
|
18
|
+
* @docsPage useDragAndDrop
|
|
19
|
+
* @docsWeight 0
|
|
20
|
+
* @since 3.3.0
|
|
21
|
+
*/
|
|
22
|
+
export function useDragAndDrop<TData = any>(options: UseDragAndDropOptions<TData>) {
|
|
23
|
+
const sensors = useSensors(
|
|
24
|
+
useSensor(PointerSensor),
|
|
25
|
+
useSensor(KeyboardSensor, {
|
|
26
|
+
coordinateGetter: sortableKeyboardCoordinates,
|
|
27
|
+
}),
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
const { data, onReorder, disabled = false } = options;
|
|
31
|
+
|
|
32
|
+
const [localData, setLocalData] = useState<TData[]>(data);
|
|
33
|
+
const [isReordering, setIsReordering] = useState(false);
|
|
34
|
+
|
|
35
|
+
// Update local data when data prop changes (but not during reordering)
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
if (!isReordering) {
|
|
38
|
+
setLocalData(data);
|
|
39
|
+
}
|
|
40
|
+
}, [data, isReordering]);
|
|
41
|
+
|
|
42
|
+
const handleDragEnd = useCallback(
|
|
43
|
+
async (event: DragEndEvent) => {
|
|
44
|
+
const { active, over } = event;
|
|
45
|
+
|
|
46
|
+
if (!over || active.id === over.id || !onReorder || disabled) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const oldIndex = localData.findIndex(item => (item as { id: string }).id === active.id);
|
|
51
|
+
const newIndex = localData.findIndex(item => (item as { id: string }).id === over.id);
|
|
52
|
+
|
|
53
|
+
if (oldIndex === -1 || newIndex === -1) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Optimistically update the UI
|
|
58
|
+
const originalState = [...localData];
|
|
59
|
+
const newData = arrayMove(localData, oldIndex, newIndex);
|
|
60
|
+
setLocalData(newData);
|
|
61
|
+
setIsReordering(true);
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
// Call the user's onReorder callback with all items for context
|
|
65
|
+
await onReorder(oldIndex, newIndex, localData[oldIndex], localData);
|
|
66
|
+
} catch (error) {
|
|
67
|
+
// Revert on error
|
|
68
|
+
setLocalData(originalState);
|
|
69
|
+
options.onError?.(error as Error);
|
|
70
|
+
} finally {
|
|
71
|
+
setIsReordering(false);
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
[localData, onReorder, disabled],
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
const itemIds = useMemo(() => localData.map(item => (item as { id: string }).id), [localData]);
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
sensors,
|
|
81
|
+
localData,
|
|
82
|
+
handleDragEnd,
|
|
83
|
+
itemIds,
|
|
84
|
+
isReordering,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
@@ -113,6 +113,7 @@ export function useSavedViews() {
|
|
|
113
113
|
scope: input.scope,
|
|
114
114
|
filters: input.filters,
|
|
115
115
|
searchTerm: input.searchTerm,
|
|
116
|
+
columnConfig: input.columnConfig,
|
|
116
117
|
pageId,
|
|
117
118
|
blockId: blockId === 'default' ? undefined : blockId,
|
|
118
119
|
createdAt: new Date().toISOString(),
|
|
@@ -51,7 +51,13 @@ const channelsDocument = graphql(
|
|
|
51
51
|
|
|
52
52
|
// Define the type for a channel
|
|
53
53
|
type ActiveChannel = ResultOf<typeof activeChannelDocument>['activeChannel'];
|
|
54
|
-
type Channel = ResultOf<typeof channelFragment>;
|
|
54
|
+
export type Channel = ResultOf<typeof channelFragment>;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Simplified channel type with only the basic fields (id, code, token)
|
|
58
|
+
* Used in components that don't need the full channel information
|
|
59
|
+
*/
|
|
60
|
+
export type SimpleChannel = Pick<Channel, 'id' | 'code' | 'token'>;
|
|
55
61
|
|
|
56
62
|
/**
|
|
57
63
|
* @description
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { ColumnFiltersState } from '@tanstack/react-table';
|
|
2
|
+
import { ColumnConfig } from '../components/data-table/data-table-context.js';
|
|
2
3
|
|
|
3
4
|
export interface SavedView {
|
|
4
5
|
id: string;
|
|
5
6
|
name: string;
|
|
6
7
|
scope: 'user' | 'global';
|
|
7
8
|
filters: ColumnFiltersState;
|
|
9
|
+
columnConfig: ColumnConfig;
|
|
8
10
|
searchTerm?: string;
|
|
9
11
|
pageId?: string;
|
|
10
12
|
blockId?: string;
|
|
@@ -28,6 +30,7 @@ export interface SaveViewInput {
|
|
|
28
30
|
name: string;
|
|
29
31
|
scope: 'user' | 'global';
|
|
30
32
|
filters: ColumnFiltersState;
|
|
33
|
+
columnConfig: ColumnConfig;
|
|
31
34
|
searchTerm?: string;
|
|
32
35
|
}
|
|
33
36
|
|