@vendure/dashboard 3.5.1-master-202511120232 → 3.5.1-master-202511140232
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/vite/utils/compiler.d.ts +1 -0
- package/dist/vite/utils/compiler.js +5 -4
- package/dist/vite/utils/get-dashboard-paths.d.ts +5 -0
- package/dist/vite/utils/get-dashboard-paths.js +20 -0
- package/dist/vite/vite-plugin-tailwind-source.js +2 -15
- package/dist/vite/vite-plugin-translations.d.ts +10 -1
- package/dist/vite/vite-plugin-translations.js +156 -45
- package/dist/vite/vite-plugin-vendure-dashboard.d.ts +12 -0
- package/dist/vite/vite-plugin-vendure-dashboard.js +1 -0
- package/package.json +4 -4
- package/src/app/routes/_authenticated/_zones/components/zone-bulk-actions.tsx +49 -1
- package/src/app/routes/_authenticated/_zones/components/zone-countries-table.tsx +34 -16
- package/src/lib/components/data-input/string-list-input.tsx +181 -26
- package/src/lib/components/data-table/use-generated-columns.tsx +1 -1
- package/src/lib/components/layout/manage-languages-dialog.tsx +2 -1
- package/src/lib/components/shared/custom-fields-form.tsx +1 -1
- package/src/lib/components/shared/multi-select.tsx +1 -1
- package/src/lib/components/shared/navigation-confirmation.tsx +1 -1
- package/src/lib/graphql/graphql-env.d.ts +36 -13
- package/src/lib/lib/load-i18n-messages.ts +4 -1
- package/src/lib/virtual.d.ts +3 -0
|
@@ -1,13 +1,144 @@
|
|
|
1
|
-
import { X } from 'lucide-react';
|
|
2
|
-
import { KeyboardEvent, useId, useRef, useState } from 'react';
|
|
1
|
+
import { GripVertical, X } from 'lucide-react';
|
|
2
|
+
import { KeyboardEvent, useEffect, useId, useRef, useState } from 'react';
|
|
3
3
|
|
|
4
4
|
import { Badge } from '@/vdb/components/ui/badge.js';
|
|
5
5
|
import { Input } from '@/vdb/components/ui/input.js';
|
|
6
6
|
import type { DashboardFormComponentProps } from '@/vdb/framework/form-engine/form-engine-types.js';
|
|
7
7
|
import { isReadonlyField } from '@/vdb/framework/form-engine/utils.js';
|
|
8
8
|
import { cn } from '@/vdb/lib/utils.js';
|
|
9
|
+
import {
|
|
10
|
+
closestCenter,
|
|
11
|
+
DndContext,
|
|
12
|
+
type DragEndEvent,
|
|
13
|
+
KeyboardSensor,
|
|
14
|
+
PointerSensor,
|
|
15
|
+
useSensor,
|
|
16
|
+
useSensors,
|
|
17
|
+
} from '@dnd-kit/core';
|
|
18
|
+
import {
|
|
19
|
+
arrayMove,
|
|
20
|
+
SortableContext,
|
|
21
|
+
sortableKeyboardCoordinates,
|
|
22
|
+
useSortable,
|
|
23
|
+
verticalListSortingStrategy,
|
|
24
|
+
} from '@dnd-kit/sortable';
|
|
25
|
+
import { CSS } from '@dnd-kit/utilities';
|
|
9
26
|
import { useLingui } from '@lingui/react';
|
|
10
27
|
|
|
28
|
+
interface SortableItemProps {
|
|
29
|
+
id: string;
|
|
30
|
+
item: string;
|
|
31
|
+
isDisabled: boolean;
|
|
32
|
+
isEditing: boolean;
|
|
33
|
+
onRemove: () => void;
|
|
34
|
+
onEdit: () => void;
|
|
35
|
+
onSave: (newValue: string) => void;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function SortableItem({ id, item, isDisabled, isEditing, onRemove, onEdit, onSave }: SortableItemProps) {
|
|
39
|
+
const [editValue, setEditValue] = useState(item);
|
|
40
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
41
|
+
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
|
|
42
|
+
id,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const style = {
|
|
46
|
+
transform: CSS.Transform.toString(transform),
|
|
47
|
+
transition,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const handleSave = () => {
|
|
51
|
+
const trimmedValue = editValue.trim();
|
|
52
|
+
if (trimmedValue && trimmedValue !== item) {
|
|
53
|
+
onSave(trimmedValue);
|
|
54
|
+
} else {
|
|
55
|
+
setEditValue(item);
|
|
56
|
+
onSave(item);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const handleKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
|
|
61
|
+
if (e.key === 'Enter') {
|
|
62
|
+
e.preventDefault();
|
|
63
|
+
handleSave();
|
|
64
|
+
} else if (e.key === 'Escape') {
|
|
65
|
+
setEditValue(item);
|
|
66
|
+
onSave(item);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// Focus and select input when entering edit mode
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
if (isEditing && inputRef.current) {
|
|
73
|
+
inputRef.current.focus();
|
|
74
|
+
inputRef.current.select();
|
|
75
|
+
}
|
|
76
|
+
}, [isEditing]);
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<Badge
|
|
80
|
+
ref={setNodeRef}
|
|
81
|
+
style={style}
|
|
82
|
+
variant="secondary"
|
|
83
|
+
className={cn(
|
|
84
|
+
isDragging && 'opacity-50',
|
|
85
|
+
'flex items-center gap-1',
|
|
86
|
+
isEditing && 'border-muted-foreground/30',
|
|
87
|
+
)}
|
|
88
|
+
>
|
|
89
|
+
{!isDisabled && (
|
|
90
|
+
<button
|
|
91
|
+
type="button"
|
|
92
|
+
className={cn(
|
|
93
|
+
'cursor-grab active:cursor-grabbing text-muted-foreground',
|
|
94
|
+
'hover:bg-muted rounded p-0.5',
|
|
95
|
+
)}
|
|
96
|
+
{...attributes}
|
|
97
|
+
{...listeners}
|
|
98
|
+
aria-label={`Drag ${item}`}
|
|
99
|
+
>
|
|
100
|
+
<GripVertical className="h-3 w-3" />
|
|
101
|
+
</button>
|
|
102
|
+
)}
|
|
103
|
+
{isEditing ? (
|
|
104
|
+
<input
|
|
105
|
+
ref={inputRef}
|
|
106
|
+
type="text"
|
|
107
|
+
value={editValue}
|
|
108
|
+
onChange={e => setEditValue(e.target.value)}
|
|
109
|
+
onKeyDown={handleKeyDown}
|
|
110
|
+
onBlur={handleSave}
|
|
111
|
+
className="bg-transparent border-none outline-none focus:ring-0 p-0 h-auto min-w-[60px] w-auto"
|
|
112
|
+
style={{ width: `${Math.max(editValue.length * 8, 60)}px` }}
|
|
113
|
+
/>
|
|
114
|
+
) : (
|
|
115
|
+
<span
|
|
116
|
+
onClick={!isDisabled ? onEdit : undefined}
|
|
117
|
+
className={cn(!isDisabled && 'cursor-text hover:underline')}
|
|
118
|
+
>
|
|
119
|
+
{item}
|
|
120
|
+
</span>
|
|
121
|
+
)}
|
|
122
|
+
{!isDisabled && (
|
|
123
|
+
<button
|
|
124
|
+
type="button"
|
|
125
|
+
onClick={e => {
|
|
126
|
+
e.stopPropagation();
|
|
127
|
+
onRemove();
|
|
128
|
+
}}
|
|
129
|
+
className={cn(
|
|
130
|
+
'ml-1 rounded-full outline-none ring-offset-background text-muted-foreground',
|
|
131
|
+
'hover:bg-muted focus:ring-2 focus:ring-ring focus:ring-offset-2',
|
|
132
|
+
)}
|
|
133
|
+
aria-label={`Remove ${item}`}
|
|
134
|
+
>
|
|
135
|
+
<X className="h-3 w-3" />
|
|
136
|
+
</button>
|
|
137
|
+
)}
|
|
138
|
+
</Badge>
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
11
142
|
export function StringListInput({
|
|
12
143
|
value,
|
|
13
144
|
onChange,
|
|
@@ -17,13 +148,21 @@ export function StringListInput({
|
|
|
17
148
|
fieldDef,
|
|
18
149
|
}: DashboardFormComponentProps) {
|
|
19
150
|
const [inputValue, setInputValue] = useState('');
|
|
151
|
+
const [editingIndex, setEditingIndex] = useState<number | null>(null);
|
|
20
152
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
21
153
|
const { i18n } = useLingui();
|
|
22
|
-
const isDisabled = isReadonlyField(fieldDef) || disabled;
|
|
154
|
+
const isDisabled = isReadonlyField(fieldDef) || disabled || false;
|
|
23
155
|
const id = useId();
|
|
24
156
|
|
|
25
157
|
const items = Array.isArray(value) ? value : [];
|
|
26
158
|
|
|
159
|
+
const sensors = useSensors(
|
|
160
|
+
useSensor(PointerSensor),
|
|
161
|
+
useSensor(KeyboardSensor, {
|
|
162
|
+
coordinateGetter: sortableKeyboardCoordinates,
|
|
163
|
+
}),
|
|
164
|
+
);
|
|
165
|
+
|
|
27
166
|
const addItem = (item: string) => {
|
|
28
167
|
const trimmedItem = item.trim();
|
|
29
168
|
if (trimmedItem) {
|
|
@@ -36,6 +175,24 @@ export function StringListInput({
|
|
|
36
175
|
onChange(items.filter((_, index) => index !== indexToRemove));
|
|
37
176
|
};
|
|
38
177
|
|
|
178
|
+
const editItem = (index: number, newValue: string) => {
|
|
179
|
+
const newItems = [...items];
|
|
180
|
+
newItems[index] = newValue;
|
|
181
|
+
onChange(newItems);
|
|
182
|
+
setEditingIndex(null);
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
const handleDragEnd = (event: DragEndEvent) => {
|
|
186
|
+
const { active, over } = event;
|
|
187
|
+
|
|
188
|
+
if (over && active.id !== over.id) {
|
|
189
|
+
const oldIndex = items.findIndex((_, idx) => `${id}-${idx}` === active.id);
|
|
190
|
+
const newIndex = items.findIndex((_, idx) => `${id}-${idx}` === over.id);
|
|
191
|
+
|
|
192
|
+
onChange(arrayMove(items, oldIndex, newIndex));
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
|
|
39
196
|
const handleKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
|
|
40
197
|
if (e.key === 'Enter' || e.key === ',') {
|
|
41
198
|
e.preventDefault();
|
|
@@ -74,29 +231,27 @@ export function StringListInput({
|
|
|
74
231
|
className="min-w-[120px]"
|
|
75
232
|
/>
|
|
76
233
|
)}
|
|
77
|
-
<
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
)}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
))}
|
|
99
|
-
</div>
|
|
234
|
+
<DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
|
|
235
|
+
<SortableContext
|
|
236
|
+
items={items.map((_, index) => `${id}-${index}`)}
|
|
237
|
+
strategy={verticalListSortingStrategy}
|
|
238
|
+
>
|
|
239
|
+
<div className="flex flex-wrap gap-1 items-start justify-start">
|
|
240
|
+
{items.map((item, index) => (
|
|
241
|
+
<SortableItem
|
|
242
|
+
key={`${id}-${index}`}
|
|
243
|
+
id={`${id}-${index}`}
|
|
244
|
+
item={item}
|
|
245
|
+
isDisabled={isDisabled}
|
|
246
|
+
isEditing={editingIndex === index}
|
|
247
|
+
onRemove={() => removeItem(index)}
|
|
248
|
+
onEdit={() => setEditingIndex(index)}
|
|
249
|
+
onSave={newValue => editItem(index, newValue)}
|
|
250
|
+
/>
|
|
251
|
+
))}
|
|
252
|
+
</div>
|
|
253
|
+
</SortableContext>
|
|
254
|
+
</DndContext>
|
|
100
255
|
</div>
|
|
101
256
|
);
|
|
102
257
|
}
|
|
@@ -163,7 +163,7 @@ export function useGeneratedColumns<T extends TypedDocumentNode<any, any>>({
|
|
|
163
163
|
if (!id) {
|
|
164
164
|
throw new Error('Column id is required');
|
|
165
165
|
}
|
|
166
|
-
finalColumns.push(columnHelper.accessor(id as any, { ...column, id
|
|
166
|
+
finalColumns.push(columnHelper.accessor(id as any, { enableColumnFilter: false, ...column, id }));
|
|
167
167
|
}
|
|
168
168
|
|
|
169
169
|
if (defaultColumnOrder) {
|
|
@@ -72,7 +72,7 @@ export function CustomFieldsForm({ entityType, control, formPathPrefix }: Readon
|
|
|
72
72
|
const shouldShowTabs = useMemo(() => {
|
|
73
73
|
if (!customFields) return false;
|
|
74
74
|
const hasTabbedFields = customFields.some(field => field.ui?.tab);
|
|
75
|
-
return hasTabbedFields
|
|
75
|
+
return hasTabbedFields && groupedFields.length > 1;
|
|
76
76
|
}, [customFields, groupedFields.length]);
|
|
77
77
|
|
|
78
78
|
if (!shouldShowTabs) {
|
|
@@ -127,7 +127,7 @@ export function MultiSelect<T extends boolean>(props: MultiSelectProps<T>) {
|
|
|
127
127
|
return (
|
|
128
128
|
<Popover>
|
|
129
129
|
<PopoverTrigger asChild>{renderTrigger()}</PopoverTrigger>
|
|
130
|
-
<PopoverContent className="w-[200px] p-0" side="bottom" align="start">
|
|
130
|
+
<PopoverContent className="w-[200px] p-0" side="bottom" align="start" onWheel={(e) => e.stopPropagation()}>
|
|
131
131
|
{(showSearch === true || items.length > 10) && (
|
|
132
132
|
<div className="p-2">
|
|
133
133
|
<Input
|
|
@@ -34,7 +34,7 @@ export function NavigationConfirmation(props: Readonly<NavigationConfirmationPro
|
|
|
34
34
|
return props.form.formState.isDirty;
|
|
35
35
|
},
|
|
36
36
|
withResolver: true,
|
|
37
|
-
enableBeforeUnload:
|
|
37
|
+
enableBeforeUnload: () => props.form.formState.isDirty,
|
|
38
38
|
});
|
|
39
39
|
return (
|
|
40
40
|
<Dialog open={status === 'blocked'} onOpenChange={reset}>
|