@vendure/dashboard 3.3.5-master-202506251305 → 3.3.5-master-202506251318
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/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vendure/dashboard",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "3.3.5-master-
|
|
4
|
+
"version": "3.3.5-master-202506251318",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -86,8 +86,8 @@
|
|
|
86
86
|
"@types/react-dom": "^19.0.4",
|
|
87
87
|
"@types/react-grid-layout": "^1.3.5",
|
|
88
88
|
"@uidotdev/usehooks": "^2.4.1",
|
|
89
|
-
"@vendure/common": "^3.3.5-master-
|
|
90
|
-
"@vendure/core": "^3.3.5-master-
|
|
89
|
+
"@vendure/common": "^3.3.5-master-202506251318",
|
|
90
|
+
"@vendure/core": "^3.3.5-master-202506251318",
|
|
91
91
|
"@vitejs/plugin-react": "^4.3.4",
|
|
92
92
|
"awesome-graphql-client": "^2.1.0",
|
|
93
93
|
"class-variance-authority": "^0.7.1",
|
|
@@ -130,5 +130,5 @@
|
|
|
130
130
|
"lightningcss-linux-arm64-musl": "^1.29.3",
|
|
131
131
|
"lightningcss-linux-x64-musl": "^1.29.1"
|
|
132
132
|
},
|
|
133
|
-
"gitHead": "
|
|
133
|
+
"gitHead": "ef8a39dcc2b48b73b38647c8dd96a7891d598e7a"
|
|
134
134
|
}
|
|
@@ -7,12 +7,15 @@ import {
|
|
|
7
7
|
FormMessage,
|
|
8
8
|
} from '@/components/ui/form.js';
|
|
9
9
|
import { Input } from '@/components/ui/input.js';
|
|
10
|
+
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs.js';
|
|
10
11
|
import { CustomFormComponent } from '@/framework/form-engine/custom-form-component.js';
|
|
11
12
|
import { useCustomFieldConfig } from '@/hooks/use-custom-field-config.js';
|
|
12
13
|
import { useUserSettings } from '@/hooks/use-user-settings.js';
|
|
14
|
+
import { useLingui } from '@/lib/trans.js';
|
|
13
15
|
import { customFieldConfigFragment } from '@/providers/server-config.js';
|
|
14
16
|
import { CustomFieldType } from '@vendure/common/lib/shared-types';
|
|
15
17
|
import { ResultOf } from 'gql.tada';
|
|
18
|
+
import React, { useMemo } from 'react';
|
|
16
19
|
import { Control, ControllerRenderProps } from 'react-hook-form';
|
|
17
20
|
import { Switch } from '../ui/switch.js';
|
|
18
21
|
import { TranslatableFormField } from './translatable-form-field.js';
|
|
@@ -29,6 +32,7 @@ export function CustomFieldsForm({ entityType, control, formPathPrefix }: Custom
|
|
|
29
32
|
const {
|
|
30
33
|
settings: { displayLanguage },
|
|
31
34
|
} = useUserSettings();
|
|
35
|
+
const { i18n } = useLingui();
|
|
32
36
|
|
|
33
37
|
const getTranslation = (input: Array<{ languageCode: string; value: string }> | null | undefined) => {
|
|
34
38
|
return input?.find(t => t.languageCode === displayLanguage)?.value;
|
|
@@ -42,18 +46,80 @@ export function CustomFieldsForm({ entityType, control, formPathPrefix }: Custom
|
|
|
42
46
|
: `customFields.${fieldDefName}`;
|
|
43
47
|
};
|
|
44
48
|
|
|
49
|
+
// Group custom fields by tabs
|
|
50
|
+
const groupedFields = useMemo(() => {
|
|
51
|
+
if (!customFields) return [];
|
|
52
|
+
|
|
53
|
+
const tabMap = new Map<string, CustomFieldConfig[]>();
|
|
54
|
+
const defaultTabName = '__default_tab__';
|
|
55
|
+
|
|
56
|
+
for (const field of customFields) {
|
|
57
|
+
const tabName = field.ui?.tab ?? defaultTabName;
|
|
58
|
+
if (tabMap.has(tabName)) {
|
|
59
|
+
tabMap.get(tabName)?.push(field);
|
|
60
|
+
} else {
|
|
61
|
+
tabMap.set(tabName, [field]);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return Array.from(tabMap.entries())
|
|
66
|
+
.sort((a, b) => (a[0] === defaultTabName ? -1 : 1))
|
|
67
|
+
.map(([tabName, customFields]) => ({
|
|
68
|
+
tabName: tabName === defaultTabName ? 'general' : tabName,
|
|
69
|
+
customFields,
|
|
70
|
+
}));
|
|
71
|
+
}, [customFields]);
|
|
72
|
+
|
|
73
|
+
// Check if we should show tabs (more than one tab or at least one field has a tab)
|
|
74
|
+
const shouldShowTabs = useMemo(() => {
|
|
75
|
+
if (!customFields) return false;
|
|
76
|
+
const hasTabbedFields = customFields.some(field => field.ui?.tab);
|
|
77
|
+
return hasTabbedFields || groupedFields.length > 1;
|
|
78
|
+
}, [customFields, groupedFields.length]);
|
|
79
|
+
|
|
80
|
+
if (!shouldShowTabs) {
|
|
81
|
+
// Single tab view - use the original grid layout
|
|
82
|
+
return (
|
|
83
|
+
<div className="grid grid-cols-2 gap-4">
|
|
84
|
+
{customFields?.map(fieldDef => (
|
|
85
|
+
<CustomFieldItem
|
|
86
|
+
key={fieldDef.name}
|
|
87
|
+
fieldDef={fieldDef}
|
|
88
|
+
control={control}
|
|
89
|
+
fieldName={getFieldName(fieldDef.name)}
|
|
90
|
+
getTranslation={getTranslation}
|
|
91
|
+
/>
|
|
92
|
+
))}
|
|
93
|
+
</div>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Tabbed view
|
|
45
98
|
return (
|
|
46
|
-
<
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
key={
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
99
|
+
<Tabs defaultValue={groupedFields[0]?.tabName} className="w-full">
|
|
100
|
+
<TabsList>
|
|
101
|
+
{groupedFields.map(group => (
|
|
102
|
+
<TabsTrigger key={group.tabName} value={group.tabName}>
|
|
103
|
+
{group.tabName === 'general' ? i18n.t('General') : group.tabName}
|
|
104
|
+
</TabsTrigger>
|
|
105
|
+
))}
|
|
106
|
+
</TabsList>
|
|
107
|
+
{groupedFields.map(group => (
|
|
108
|
+
<TabsContent key={group.tabName} value={group.tabName} className="mt-4">
|
|
109
|
+
<div className="grid grid-cols-2 gap-4">
|
|
110
|
+
{group.customFields.map(fieldDef => (
|
|
111
|
+
<CustomFieldItem
|
|
112
|
+
key={fieldDef.name}
|
|
113
|
+
fieldDef={fieldDef}
|
|
114
|
+
control={control}
|
|
115
|
+
fieldName={getFieldName(fieldDef.name)}
|
|
116
|
+
getTranslation={getTranslation}
|
|
117
|
+
/>
|
|
118
|
+
))}
|
|
119
|
+
</div>
|
|
120
|
+
</TabsContent>
|
|
55
121
|
))}
|
|
56
|
-
</
|
|
122
|
+
</Tabs>
|
|
57
123
|
);
|
|
58
124
|
}
|
|
59
125
|
|
|
@@ -69,83 +135,91 @@ interface CustomFieldItemProps {
|
|
|
69
135
|
function CustomFieldItem({ fieldDef, control, fieldName, getTranslation }: CustomFieldItemProps) {
|
|
70
136
|
const hasCustomFormComponent = fieldDef.ui && fieldDef.ui.component;
|
|
71
137
|
const isLocaleField = fieldDef.type === 'localeString' || fieldDef.type === 'localeText';
|
|
138
|
+
const shouldBeFullWidth = fieldDef.ui?.fullWidth === true;
|
|
139
|
+
const containerClassName = shouldBeFullWidth ? 'col-span-2' : '';
|
|
72
140
|
|
|
73
141
|
// For locale fields, always use TranslatableFormField regardless of custom components
|
|
74
142
|
if (isLocaleField) {
|
|
75
143
|
return (
|
|
76
|
-
<
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
<
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
144
|
+
<div className={containerClassName}>
|
|
145
|
+
<TranslatableFormField
|
|
146
|
+
control={control}
|
|
147
|
+
name={fieldName}
|
|
148
|
+
render={({ field, ...props }) => (
|
|
149
|
+
<FormItem>
|
|
150
|
+
<FormLabel>{getTranslation(fieldDef.label) ?? field.name}</FormLabel>
|
|
151
|
+
<FormControl>
|
|
152
|
+
{hasCustomFormComponent ? (
|
|
153
|
+
<CustomFormComponent
|
|
154
|
+
fieldDef={fieldDef}
|
|
155
|
+
fieldProps={{
|
|
156
|
+
...props,
|
|
157
|
+
field: {
|
|
158
|
+
...field,
|
|
159
|
+
disabled: fieldDef.readonly ?? false,
|
|
160
|
+
},
|
|
161
|
+
}}
|
|
162
|
+
/>
|
|
163
|
+
) : (
|
|
164
|
+
<FormInputForType fieldDef={fieldDef} field={field} />
|
|
165
|
+
)}
|
|
166
|
+
</FormControl>
|
|
167
|
+
<FormDescription>{getTranslation(fieldDef.description)}</FormDescription>
|
|
168
|
+
<FormMessage />
|
|
169
|
+
</FormItem>
|
|
170
|
+
)}
|
|
171
|
+
/>
|
|
172
|
+
</div>
|
|
103
173
|
);
|
|
104
174
|
}
|
|
105
175
|
|
|
106
176
|
// For non-locale fields with custom components
|
|
107
177
|
if (hasCustomFormComponent) {
|
|
108
178
|
return (
|
|
179
|
+
<div className={containerClassName}>
|
|
180
|
+
<FormField
|
|
181
|
+
control={control}
|
|
182
|
+
name={fieldName}
|
|
183
|
+
render={fieldProps => (
|
|
184
|
+
<CustomFieldFormItem
|
|
185
|
+
fieldDef={fieldDef}
|
|
186
|
+
getTranslation={getTranslation}
|
|
187
|
+
fieldName={fieldProps.field.name}
|
|
188
|
+
>
|
|
189
|
+
<CustomFormComponent
|
|
190
|
+
fieldDef={fieldDef}
|
|
191
|
+
fieldProps={{
|
|
192
|
+
...fieldProps,
|
|
193
|
+
field: {
|
|
194
|
+
...fieldProps.field,
|
|
195
|
+
disabled: fieldDef.readonly ?? false,
|
|
196
|
+
},
|
|
197
|
+
}}
|
|
198
|
+
/>
|
|
199
|
+
</CustomFieldFormItem>
|
|
200
|
+
)}
|
|
201
|
+
/>
|
|
202
|
+
</div>
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// For regular fields without custom components
|
|
207
|
+
return (
|
|
208
|
+
<div className={containerClassName}>
|
|
109
209
|
<FormField
|
|
110
210
|
control={control}
|
|
111
211
|
name={fieldName}
|
|
112
|
-
render={
|
|
212
|
+
render={({ field }) => (
|
|
113
213
|
<CustomFieldFormItem
|
|
114
214
|
fieldDef={fieldDef}
|
|
115
215
|
getTranslation={getTranslation}
|
|
116
|
-
fieldName={
|
|
216
|
+
fieldName={field.name}
|
|
117
217
|
>
|
|
118
|
-
<
|
|
119
|
-
fieldDef={fieldDef}
|
|
120
|
-
fieldProps={{
|
|
121
|
-
...fieldProps,
|
|
122
|
-
field: {
|
|
123
|
-
...fieldProps.field,
|
|
124
|
-
disabled: fieldDef.readonly ?? false,
|
|
125
|
-
},
|
|
126
|
-
}}
|
|
127
|
-
/>
|
|
218
|
+
<FormInputForType fieldDef={fieldDef} field={field} />
|
|
128
219
|
</CustomFieldFormItem>
|
|
129
220
|
)}
|
|
130
221
|
/>
|
|
131
|
-
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// For regular fields without custom components
|
|
135
|
-
return (
|
|
136
|
-
<FormField
|
|
137
|
-
control={control}
|
|
138
|
-
name={fieldName}
|
|
139
|
-
render={({ field }) => (
|
|
140
|
-
<CustomFieldFormItem
|
|
141
|
-
fieldDef={fieldDef}
|
|
142
|
-
getTranslation={getTranslation}
|
|
143
|
-
fieldName={field.name}
|
|
144
|
-
>
|
|
145
|
-
<FormInputForType fieldDef={fieldDef} field={field} />
|
|
146
|
-
</CustomFieldFormItem>
|
|
147
|
-
)}
|
|
148
|
-
/>
|
|
222
|
+
</div>
|
|
149
223
|
);
|
|
150
224
|
}
|
|
151
225
|
|