@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
@@ -3,11 +3,14 @@ import { Input } from '@/vdb/components/ui/input.js';
3
3
 
4
4
  import { DashboardFormComponentProps } from '@/vdb/framework/form-engine/form-engine-types.js';
5
5
  import { isReadonlyField } from '@/vdb/framework/form-engine/utils.js';
6
+ import { ReactNode } from 'react';
6
7
 
7
8
  export type NumberInputProps = DashboardFormComponentProps & {
8
9
  min?: number;
9
10
  max?: number;
10
11
  step?: number;
12
+ prefix?: ReactNode;
13
+ suffix?: ReactNode;
11
14
  };
12
15
 
13
16
  /**
@@ -17,28 +20,43 @@ export type NumberInputProps = DashboardFormComponentProps & {
17
20
  * @docsCategory form-components
18
21
  * @docsPage NumberInput
19
22
  */
20
- export function NumberInput({ fieldDef, onChange, ...fieldProps }: Readonly<NumberInputProps>) {
23
+ export function NumberInput({
24
+ fieldDef,
25
+ onChange,
26
+ prefix: overridePrefix,
27
+ suffix: overrideSuffix,
28
+ ...fieldProps
29
+ }: Readonly<NumberInputProps>) {
21
30
  const readOnly = fieldProps.disabled || isReadonlyField(fieldDef);
22
31
  const isFloat = fieldDef ? fieldDef.type === 'float' : false;
23
32
  const min = fieldProps.min ?? fieldDef?.ui?.min;
24
33
  const max = fieldProps.max ?? fieldDef?.ui?.max;
25
34
  const step = fieldProps.step ?? (fieldDef?.ui?.step || (isFloat ? 0.01 : 1));
26
- const prefix = fieldDef?.ui?.prefix;
27
- const suffix = fieldDef?.ui?.suffix;
35
+ const prefix = overridePrefix ?? fieldDef?.ui?.prefix;
36
+ const suffix = overrideSuffix ?? fieldDef?.ui?.suffix;
28
37
  const shouldUseAffixedInput = prefix || suffix;
38
+ const value = fieldProps.value ?? '';
29
39
  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
30
40
  if (readOnly) return;
31
- const numValue = e.target.valueAsNumber;
41
+
42
+ let numValue = e.target.valueAsNumber;
43
+
44
+ if (Number.isNaN(numValue) && e.target.value) {
45
+ const normalized = e.target.value.replace(',', '.');
46
+ numValue = Number(normalized);
47
+ }
48
+
32
49
  if (Number.isNaN(numValue)) {
33
50
  onChange(null);
34
51
  } else {
35
- onChange(e.target.valueAsNumber);
52
+ onChange(numValue);
36
53
  }
37
54
  };
38
55
  if (shouldUseAffixedInput) {
39
56
  return (
40
57
  <AffixedInput
41
58
  {...fieldProps}
59
+ value={value}
42
60
  type="number"
43
61
  onChange={handleChange}
44
62
  min={min}
@@ -57,6 +75,7 @@ export function NumberInput({ fieldDef, onChange, ...fieldProps }: Readonly<Numb
57
75
  type="number"
58
76
  onChange={handleChange}
59
77
  {...fieldProps}
78
+ value={value}
60
79
  min={min}
61
80
  max={max}
62
81
  step={step}
@@ -1,8 +1,10 @@
1
1
  'use client';
2
-
2
+ import { useUserSettings } from '@/vdb/hooks/use-user-settings.js';
3
3
  import { ColumnFiltersState, SortingState, Table } from '@tanstack/react-table';
4
4
  import React, { createContext, ReactNode, useContext } from 'react';
5
5
 
6
+ export type ColumnConfig = { columnOrder: string[]; columnVisibility: Record<string, boolean> };
7
+
6
8
  interface DataTableContextValue {
7
9
  columnFilters: ColumnFiltersState;
8
10
  setColumnFilters: React.Dispatch<React.SetStateAction<ColumnFiltersState>>;
@@ -16,7 +18,7 @@ interface DataTableContextValue {
16
18
  onRefresh?: () => void;
17
19
  isLoading?: boolean;
18
20
  table?: Table<any>;
19
- handleApplyView: (filters: ColumnFiltersState, searchTerm?: string) => void;
21
+ handleApplyView: (filters: ColumnFiltersState, columnConfig: ColumnConfig, searchTerm?: string) => void;
20
22
  }
21
23
 
22
24
  const DataTableContext = createContext<DataTableContextValue | undefined>(undefined);
@@ -52,7 +54,13 @@ export function DataTableProvider({
52
54
  isLoading,
53
55
  table,
54
56
  }: DataTableProviderProps) {
55
- const handleApplyView = (filters: ColumnFiltersState, viewSearchTerm?: string) => {
57
+ const { setTableSettings } = useUserSettings();
58
+
59
+ const handleApplyView = (
60
+ filters: ColumnFiltersState,
61
+ columnConfig: ColumnConfig,
62
+ viewSearchTerm?: string,
63
+ ) => {
56
64
  setColumnFilters(filters);
57
65
  if (viewSearchTerm !== undefined && onSearchTermChange) {
58
66
  setSearchTerm(viewSearchTerm);
@@ -61,6 +69,13 @@ export function DataTableProvider({
61
69
  if (onFilterChange && table) {
62
70
  onFilterChange(table, filters);
63
71
  }
72
+
73
+ if (pageId && columnConfig.columnOrder) {
74
+ setTableSettings(pageId, 'columnOrder', columnConfig.columnOrder);
75
+ }
76
+ if (pageId && columnConfig.columnVisibility) {
77
+ setTableSettings(pageId, 'columnVisibility', columnConfig.columnVisibility);
78
+ }
64
79
  };
65
80
 
66
81
  const value: DataTableContextValue = {
@@ -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
+ }