@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.
Files changed (67) hide show
  1. package/dist/plugin/constants.js +21 -2
  2. package/dist/plugin/dashboard.plugin.js +1 -1
  3. package/package.json +3 -3
  4. package/src/app/routeTree.gen.ts +1135 -1072
  5. package/src/app/routes/_authenticated/_collections/collections.graphql.ts +1 -0
  6. package/src/app/routes/_authenticated/_collections/collections.tsx +249 -167
  7. package/src/app/routes/_authenticated/_collections/components/collection-bulk-actions.tsx +8 -0
  8. package/src/app/routes/_authenticated/_collections/components/move-collections-dialog.tsx +4 -0
  9. package/src/app/routes/_authenticated/_facets/components/facet-values-sheet.tsx +4 -1
  10. package/src/app/routes/_authenticated/_facets/components/facet-values-table.tsx +1 -1
  11. package/src/app/routes/_authenticated/_facets/facets.tsx +22 -38
  12. package/src/app/routes/_authenticated/_orders/components/order-table-totals.tsx +16 -1
  13. package/src/app/routes/_authenticated/_product-variants/product-variants.graphql.ts +0 -1
  14. package/src/app/routes/_authenticated/_product-variants/product-variants_.$id.tsx +2 -2
  15. package/src/app/routes/_authenticated/_products/products.graphql.ts +5 -0
  16. package/src/app/routes/_authenticated/_products/products_.$id.tsx +24 -1
  17. package/src/app/routes/_authenticated/_system/components/payload-dialog.tsx +9 -2
  18. package/src/app/routes/_authenticated/_system/job-queue.tsx +11 -2
  19. package/src/app/routes/_authenticated/_tax-rates/tax-rates_.$id.tsx +2 -9
  20. package/src/app/routes/_authenticated/_zones/zones.tsx +1 -0
  21. package/src/i18n/locales/ar.po +177 -141
  22. package/src/i18n/locales/cs.po +177 -141
  23. package/src/i18n/locales/de.po +177 -141
  24. package/src/i18n/locales/en.po +177 -141
  25. package/src/i18n/locales/es.po +177 -141
  26. package/src/i18n/locales/fa.po +177 -141
  27. package/src/i18n/locales/fr.po +177 -141
  28. package/src/i18n/locales/he.po +177 -141
  29. package/src/i18n/locales/hr.po +177 -141
  30. package/src/i18n/locales/it.po +177 -141
  31. package/src/i18n/locales/ja.po +177 -141
  32. package/src/i18n/locales/nb.po +177 -141
  33. package/src/i18n/locales/ne.po +177 -141
  34. package/src/i18n/locales/pl.po +177 -141
  35. package/src/i18n/locales/pt_BR.po +177 -141
  36. package/src/i18n/locales/pt_PT.po +177 -141
  37. package/src/i18n/locales/ru.po +177 -141
  38. package/src/i18n/locales/sv.po +177 -141
  39. package/src/i18n/locales/tr.po +177 -141
  40. package/src/i18n/locales/uk.po +177 -141
  41. package/src/i18n/locales/zh_Hans.po +177 -141
  42. package/src/i18n/locales/zh_Hant.po +177 -141
  43. package/src/lib/components/data-input/number-input.tsx +24 -5
  44. package/src/lib/components/data-table/data-table-context.tsx +18 -3
  45. package/src/lib/components/data-table/data-table-utils.ts +241 -1
  46. package/src/lib/components/data-table/data-table.tsx +189 -60
  47. package/src/lib/components/data-table/global-views-bar.tsx +1 -1
  48. package/src/lib/components/data-table/save-view-dialog.tsx +21 -0
  49. package/src/lib/components/data-table/use-generated-columns.tsx +56 -24
  50. package/src/lib/components/data-table/views-sheet.tsx +1 -1
  51. package/src/lib/components/layout/channel-switcher.tsx +7 -5
  52. package/src/lib/components/layout/nav-main.tsx +2 -2
  53. package/src/lib/components/shared/alerts.tsx +3 -1
  54. package/src/lib/components/shared/assign-to-channel-bulk-action.tsx +10 -10
  55. package/src/lib/components/shared/assign-to-channel-dialog.tsx +1 -1
  56. package/src/lib/components/shared/assigned-channels.tsx +108 -0
  57. package/src/lib/components/shared/assigned-facet-values.tsx +5 -7
  58. package/src/lib/components/shared/channel-chip.tsx +43 -0
  59. package/src/lib/components/shared/paginated-list-data-table.tsx +19 -0
  60. package/src/lib/components/ui/alert.tsx +1 -1
  61. package/src/lib/components/ui/dropdown-menu.tsx +4 -1
  62. package/src/lib/components/ui/sidebar.tsx +2 -1
  63. package/src/lib/framework/page/list-page.tsx +62 -38
  64. package/src/lib/hooks/use-drag-and-drop.ts +86 -0
  65. package/src/lib/hooks/use-saved-views.ts +1 -0
  66. package/src/lib/providers/channel-provider.tsx +7 -1
  67. 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
- value = [],
27
- facetValues,
28
- canUpdate = true,
29
- onBlur,
30
- onChange,
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-foreground',
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="ml-auto size-4" />
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
- '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',
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
- listQuery={listQuery}
547
- deleteMutation={deleteMutation}
548
- transformVariables={transformVariables}
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