@vendure/dashboard 3.4.1-master-202508200231 → 3.4.1
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 +152 -152
- package/src/app/routes/_authenticated/_orders/components/order-modification-preview-dialog.tsx +2 -1
- package/src/app/routes/_authenticated/_products/components/add-product-variant-dialog.tsx +1 -0
- package/src/lib/components/data-input/affixed-input.tsx +19 -5
- package/src/lib/components/data-input/boolean-input.tsx +9 -0
- package/src/lib/components/data-input/checkbox-input.tsx +8 -0
- package/src/lib/components/data-input/combination-mode-input.tsx +11 -2
- package/src/lib/components/data-input/configurable-operation-list-input.tsx +26 -401
- package/src/lib/components/data-input/custom-field-list-input.tsx +18 -25
- package/src/lib/components/data-input/customer-group-input.tsx +7 -11
- package/src/lib/components/data-input/datetime-input.tsx +9 -13
- package/src/lib/components/data-input/default-relation-input.tsx +29 -9
- package/src/lib/components/data-input/facet-value-input.tsx +15 -13
- package/src/lib/components/data-input/index.ts +2 -2
- package/src/lib/components/data-input/money-input.tsx +27 -20
- package/src/lib/components/data-input/number-input.tsx +48 -0
- package/src/lib/components/data-input/password-input.tsx +16 -0
- package/src/lib/components/data-input/{product-multi-selector.tsx → product-multi-selector-input.tsx} +8 -15
- package/src/lib/components/data-input/relation-input.tsx +7 -6
- package/src/lib/components/data-input/rich-text-input.tsx +10 -13
- package/src/lib/components/data-input/select-with-options.tsx +29 -17
- package/src/lib/components/data-input/struct-form-input.tsx +54 -59
- package/src/lib/components/data-input/text-input.tsx +9 -0
- package/src/lib/components/data-input/textarea-input.tsx +16 -0
- package/src/lib/components/data-table/filters/data-table-number-filter.tsx +3 -0
- package/src/lib/components/data-table/use-generated-columns.tsx +16 -5
- package/src/lib/components/shared/configurable-operation-arg-input.tsx +3 -10
- package/src/lib/components/shared/configurable-operation-input.tsx +1 -6
- package/src/lib/components/shared/configurable-operation-multi-selector.tsx +8 -5
- package/src/lib/components/shared/configurable-operation-selector.tsx +5 -5
- package/src/lib/components/shared/custom-fields-form.tsx +20 -49
- package/src/lib/components/shared/multi-select.tsx +1 -1
- package/src/lib/framework/component-registry/component-registry.tsx +9 -32
- package/src/lib/framework/component-registry/display-component.tsx +28 -0
- package/src/lib/framework/extension-api/display-component-extensions.tsx +0 -14
- package/src/lib/framework/extension-api/input-component-extensions.tsx +52 -34
- package/src/lib/framework/extension-api/logic/data-table.ts +4 -27
- package/src/lib/framework/extension-api/logic/form-components.ts +3 -2
- package/src/lib/framework/extension-api/types/detail-forms.ts +2 -38
- package/src/lib/framework/extension-api/types/form-components.ts +2 -4
- package/src/lib/framework/form-engine/custom-form-component-extensions.ts +0 -23
- package/src/lib/framework/form-engine/custom-form-component.tsx +8 -25
- package/src/lib/framework/form-engine/default-input-for-type.tsx +35 -0
- package/src/lib/framework/form-engine/form-control-adapter.tsx +192 -0
- package/src/lib/framework/form-engine/form-engine-types.ts +163 -0
- package/src/lib/framework/form-engine/form-schema-tools.ts +55 -71
- package/src/lib/framework/form-engine/overridden-form-component.tsx +2 -2
- package/src/lib/framework/form-engine/utils.ts +223 -0
- package/src/lib/{components/shared → framework/form-engine}/value-transformers.ts +9 -9
- package/src/lib/framework/registry/registry-types.ts +3 -5
- package/src/lib/graphql/graphql-env.d.ts +11 -7
- package/src/lib/index.ts +28 -1
- package/src/lib/providers/server-config.tsx +1 -0
- package/src/lib/components/shared/direct-form-component-map.tsx +0 -393
- package/src/lib/components/shared/universal-field-definition.ts +0 -118
- package/src/lib/components/shared/universal-form-input.tsx +0 -175
- package/src/lib/components/shared/universal-input-components.tsx +0 -291
- package/src/lib/framework/component-registry/dynamic-component.tsx +0 -58
|
@@ -10,30 +10,22 @@ import {
|
|
|
10
10
|
import { Input } from '@/vdb/components/ui/input.js';
|
|
11
11
|
import { Switch } from '@/vdb/components/ui/switch.js';
|
|
12
12
|
import { useLocalFormat } from '@/vdb/hooks/use-local-format.js';
|
|
13
|
-
import { structCustomFieldFragment } from '@/vdb/providers/server-config.js';
|
|
14
|
-
import { ResultOf } from 'gql.tada';
|
|
15
13
|
import { CheckIcon, PencilIcon, X } from 'lucide-react';
|
|
16
14
|
import React, { useMemo, useState } from 'react';
|
|
17
|
-
import {
|
|
15
|
+
import { ControllerRenderProps, useFormContext, useWatch } from 'react-hook-form';
|
|
18
16
|
|
|
19
17
|
// Import the form input component we already have
|
|
18
|
+
import {
|
|
19
|
+
DashboardFormComponentProps,
|
|
20
|
+
StructCustomFieldConfig,
|
|
21
|
+
StructField,
|
|
22
|
+
} from '@/vdb/framework/form-engine/form-engine-types.js';
|
|
23
|
+
import { isStructFieldConfig } from '@/vdb/framework/form-engine/utils.js';
|
|
24
|
+
import { useUserSettings } from '@/vdb/hooks/use-user-settings.js';
|
|
20
25
|
import { CustomFieldListInput } from './custom-field-list-input.js';
|
|
21
26
|
import { DateTimeInput } from './datetime-input.js';
|
|
22
27
|
import { SelectWithOptions } from './select-with-options.js';
|
|
23
28
|
|
|
24
|
-
// Use the generated types from GraphQL fragments
|
|
25
|
-
type StructCustomFieldConfig = ResultOf<typeof structCustomFieldFragment>;
|
|
26
|
-
type StructField = StructCustomFieldConfig['fields'][number];
|
|
27
|
-
|
|
28
|
-
interface StructFormInputProps {
|
|
29
|
-
field: ControllerRenderProps<any, any>;
|
|
30
|
-
fieldDef: StructCustomFieldConfig;
|
|
31
|
-
control: Control<any, any>;
|
|
32
|
-
getTranslation: (
|
|
33
|
-
input: Array<{ languageCode: string; value: string }> | null | undefined,
|
|
34
|
-
) => string | undefined;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
29
|
interface DisplayModeProps {
|
|
38
30
|
fieldDef: StructCustomFieldConfig;
|
|
39
31
|
watchedStructValue: Record<string, any>;
|
|
@@ -84,42 +76,30 @@ function DisplayMode({
|
|
|
84
76
|
);
|
|
85
77
|
}
|
|
86
78
|
|
|
87
|
-
export function StructFormInput({
|
|
79
|
+
export function StructFormInput({ fieldDef, ...field }: Readonly<DashboardFormComponentProps>) {
|
|
88
80
|
const { formatDate } = useLocalFormat();
|
|
89
|
-
const isReadonly = fieldDef.readonly ?? false;
|
|
90
81
|
const [isEditing, setIsEditing] = useState(false);
|
|
82
|
+
const { control } = useFormContext();
|
|
83
|
+
const { value, name } = field;
|
|
91
84
|
|
|
92
85
|
// Watch the struct field for changes to update display mode
|
|
93
86
|
const watchedStructValue =
|
|
94
87
|
useWatch({
|
|
95
88
|
control,
|
|
96
|
-
name
|
|
97
|
-
defaultValue:
|
|
89
|
+
name,
|
|
90
|
+
defaultValue: value || {},
|
|
98
91
|
}) || {};
|
|
99
92
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}
|
|
107
|
-
return '-';
|
|
108
|
-
}
|
|
109
|
-
switch (structField.type) {
|
|
110
|
-
case 'boolean':
|
|
111
|
-
return (
|
|
112
|
-
<span className={`inline-flex items-center ${value ? 'text-green-600' : 'text-red-500'}`}>
|
|
113
|
-
{value ? <CheckIcon className="h-4 w-4" /> : <X className="h-4 w-4" />}
|
|
114
|
-
</span>
|
|
115
|
-
);
|
|
116
|
-
case 'datetime':
|
|
117
|
-
return value ? formatDate(value, { dateStyle: 'short', timeStyle: 'short' }) : '-';
|
|
118
|
-
default:
|
|
119
|
-
return value.toString();
|
|
120
|
-
}
|
|
93
|
+
const {
|
|
94
|
+
settings: { displayLanguage },
|
|
95
|
+
} = useUserSettings();
|
|
96
|
+
|
|
97
|
+
const getTranslation = (input: Array<{ languageCode: string; value: string }> | null | undefined) => {
|
|
98
|
+
return input?.find(t => t.languageCode === displayLanguage)?.value;
|
|
121
99
|
};
|
|
122
100
|
|
|
101
|
+
const isReadonly = fieldDef?.readonly === true;
|
|
102
|
+
|
|
123
103
|
// Helper function to render individual struct field inputs
|
|
124
104
|
const renderStructFieldInput = (
|
|
125
105
|
structField: StructField,
|
|
@@ -135,12 +115,7 @@ export function StructFormInput({ field, fieldDef, control, getTranslation }: St
|
|
|
135
115
|
const stringField = structField as any; // GraphQL union types need casting
|
|
136
116
|
if (stringField.options && stringField.options.length > 0) {
|
|
137
117
|
return (
|
|
138
|
-
<SelectWithOptions
|
|
139
|
-
field={singleField}
|
|
140
|
-
options={stringField.options}
|
|
141
|
-
disabled={isReadonly}
|
|
142
|
-
isListField={false}
|
|
143
|
-
/>
|
|
118
|
+
<SelectWithOptions {...singleField} fieldDef={stringField} isListField={false} />
|
|
144
119
|
);
|
|
145
120
|
}
|
|
146
121
|
return (
|
|
@@ -187,13 +162,7 @@ export function StructFormInput({ field, fieldDef, control, getTranslation }: St
|
|
|
187
162
|
/>
|
|
188
163
|
);
|
|
189
164
|
case 'datetime':
|
|
190
|
-
return
|
|
191
|
-
<DateTimeInput
|
|
192
|
-
value={singleField.value}
|
|
193
|
-
onChange={singleField.onChange}
|
|
194
|
-
disabled={isReadonly}
|
|
195
|
-
/>
|
|
196
|
-
);
|
|
165
|
+
return <DateTimeInput {...singleField} />;
|
|
197
166
|
default:
|
|
198
167
|
return (
|
|
199
168
|
<Input
|
|
@@ -213,8 +182,8 @@ export function StructFormInput({ field, fieldDef, control, getTranslation }: St
|
|
|
213
182
|
if (stringField.options && stringField.options.length > 0) {
|
|
214
183
|
return (
|
|
215
184
|
<SelectWithOptions
|
|
216
|
-
|
|
217
|
-
|
|
185
|
+
{...inputField}
|
|
186
|
+
fieldDef={stringField}
|
|
218
187
|
disabled={isReadonly}
|
|
219
188
|
isListField={isList}
|
|
220
189
|
/>
|
|
@@ -245,11 +214,10 @@ export function StructFormInput({ field, fieldDef, control, getTranslation }: St
|
|
|
245
214
|
|
|
246
215
|
return (
|
|
247
216
|
<CustomFieldListInput
|
|
248
|
-
|
|
217
|
+
{...inputField}
|
|
249
218
|
disabled={isReadonly}
|
|
250
219
|
renderInput={(index, listItemField) => renderSingleStructInput(listItemField)}
|
|
251
220
|
defaultValue={getDefaultValue()}
|
|
252
|
-
isFullWidth={needsFullWidth}
|
|
253
221
|
/>
|
|
254
222
|
);
|
|
255
223
|
}
|
|
@@ -275,7 +243,7 @@ export function StructFormInput({ field, fieldDef, control, getTranslation }: St
|
|
|
275
243
|
</Button>
|
|
276
244
|
</div>
|
|
277
245
|
)}
|
|
278
|
-
{fieldDef
|
|
246
|
+
{fieldDef?.fields?.map(structField => (
|
|
279
247
|
<FormField
|
|
280
248
|
key={structField.name}
|
|
281
249
|
control={control}
|
|
@@ -309,6 +277,33 @@ export function StructFormInput({ field, fieldDef, control, getTranslation }: St
|
|
|
309
277
|
[fieldDef, control, field.name, getTranslation, renderStructFieldInput, isReadonly],
|
|
310
278
|
);
|
|
311
279
|
|
|
280
|
+
if (!fieldDef || !isStructFieldConfig(fieldDef)) {
|
|
281
|
+
return null;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Helper function to format field value for display
|
|
285
|
+
const formatFieldValue = (value: any, structField: StructField) => {
|
|
286
|
+
if (value == null) return '-';
|
|
287
|
+
if (structField.list) {
|
|
288
|
+
if (Array.isArray(value)) {
|
|
289
|
+
return value.length ? value.join(', ') : '-';
|
|
290
|
+
}
|
|
291
|
+
return '-';
|
|
292
|
+
}
|
|
293
|
+
switch (structField.type) {
|
|
294
|
+
case 'boolean':
|
|
295
|
+
return (
|
|
296
|
+
<span className={`inline-flex items-center ${value ? 'text-green-600' : 'text-red-500'}`}>
|
|
297
|
+
{value ? <CheckIcon className="h-4 w-4" /> : <X className="h-4 w-4" />}
|
|
298
|
+
</span>
|
|
299
|
+
);
|
|
300
|
+
case 'datetime':
|
|
301
|
+
return value ? formatDate(value, { dateStyle: 'short', timeStyle: 'short' }) : '-';
|
|
302
|
+
default:
|
|
303
|
+
return value.toString();
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
|
|
312
307
|
return isEditing ? (
|
|
313
308
|
EditMode
|
|
314
309
|
) : (
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Simple built-in components using the single DashboardFormComponent interface
|
|
2
|
+
import { Input } from '@/vdb/components/ui/input.js';
|
|
3
|
+
import { DashboardFormComponent } from '@/vdb/framework/form-engine/form-engine-types.js';
|
|
4
|
+
import { isReadonlyField } from '@/vdb/framework/form-engine/utils.js';
|
|
5
|
+
|
|
6
|
+
export const TextInput: DashboardFormComponent = ({ value, onChange, fieldDef }) => {
|
|
7
|
+
const readOnly = isReadonlyField(fieldDef);
|
|
8
|
+
return <Input value={value || ''} onChange={e => onChange(e.target.value)} disabled={readOnly} />;
|
|
9
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Textarea } from '@/vdb/components/ui/textarea.js';
|
|
2
|
+
import { DashboardFormComponentProps } from '@/vdb/framework/form-engine/form-engine-types.js';
|
|
3
|
+
import { isReadonlyField } from '@/vdb/framework/form-engine/utils.js';
|
|
4
|
+
|
|
5
|
+
export function TextareaInput(props: Readonly<DashboardFormComponentProps>) {
|
|
6
|
+
const readOnly = props.disabled || isReadonlyField(props.fieldDef);
|
|
7
|
+
return (
|
|
8
|
+
<Textarea
|
|
9
|
+
ref={props.ref}
|
|
10
|
+
onBlur={props.onBlur}
|
|
11
|
+
value={props.value}
|
|
12
|
+
onChange={e => props.onChange(e.target.value)}
|
|
13
|
+
disabled={readOnly}
|
|
14
|
+
/>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
@@ -73,6 +73,9 @@ export function DataTableNumberFilter({
|
|
|
73
73
|
if (mode === 'money') {
|
|
74
74
|
return (
|
|
75
75
|
<MoneyInput
|
|
76
|
+
ref={() => {}}
|
|
77
|
+
onBlur={() => {}}
|
|
78
|
+
name="amount"
|
|
76
79
|
value={parseFloat(value) || 0}
|
|
77
80
|
onChange={newValue => onChange(newValue.toString())}
|
|
78
81
|
currency={activeChannel?.defaultCurrencyCode ?? 'USD'}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
import { DisplayComponent } from '@/vdb/framework/component-registry/
|
|
2
|
-
import {
|
|
1
|
+
import { DisplayComponent } from '@/vdb/framework/component-registry/display-component.js';
|
|
2
|
+
import {
|
|
3
|
+
FieldInfo,
|
|
4
|
+
getOperationVariablesFields,
|
|
5
|
+
getTypeFieldInfo,
|
|
6
|
+
} from '@/vdb/framework/document-introspection/get-document-structure.js';
|
|
3
7
|
import { api } from '@/vdb/graphql/api.js';
|
|
4
8
|
import { Trans, useLingui } from '@/vdb/lib/trans.js';
|
|
5
9
|
import { TypedDocumentNode } from '@graphql-typed-document-node/core';
|
|
@@ -250,14 +254,21 @@ function DeleteMutationRowAction({
|
|
|
250
254
|
}>) {
|
|
251
255
|
const { refetchPaginatedList } = usePaginatedList();
|
|
252
256
|
const { i18n } = useLingui();
|
|
253
|
-
|
|
257
|
+
|
|
254
258
|
// Inspect the mutation variables to determine if it expects 'id' or 'ids'
|
|
255
259
|
const mutationVariables = getOperationVariablesFields(deleteMutation);
|
|
256
260
|
const hasIdsParameter = mutationVariables.some(field => field.name === 'ids');
|
|
257
|
-
|
|
261
|
+
|
|
258
262
|
const { mutate: deleteMutationFn } = useMutation({
|
|
259
263
|
mutationFn: api.mutate(deleteMutation),
|
|
260
|
-
onSuccess: (result: {
|
|
264
|
+
onSuccess: (result: {
|
|
265
|
+
[key: string]:
|
|
266
|
+
| { result: 'DELETED' | 'NOT_DELETED'; message: string }
|
|
267
|
+
| {
|
|
268
|
+
result: 'DELETED' | 'NOT_DELETED';
|
|
269
|
+
message: string;
|
|
270
|
+
}[];
|
|
271
|
+
}) => {
|
|
261
272
|
const unwrappedResult = Object.values(result)[0];
|
|
262
273
|
// Handle both single result and array of results
|
|
263
274
|
const resultToCheck = Array.isArray(unwrappedResult) ? unwrappedResult[0] : unwrappedResult;
|
|
@@ -1,26 +1,21 @@
|
|
|
1
1
|
import { ConfigurableOperationDefFragment } from '@/vdb/graphql/fragments.js';
|
|
2
|
-
import {
|
|
3
|
-
import { UniversalFormInput } from './universal-form-input.js';
|
|
2
|
+
import { FormControlAdapter } from '../../framework/form-engine/form-control-adapter.js';
|
|
4
3
|
|
|
5
4
|
export interface ConfigurableOperationArgInputProps {
|
|
6
5
|
definition: ConfigurableOperationDefFragment['args'][number];
|
|
7
6
|
readOnly?: boolean;
|
|
8
7
|
value: string;
|
|
9
8
|
onChange: (value: any) => void;
|
|
10
|
-
position?: number;
|
|
11
9
|
}
|
|
12
10
|
|
|
13
11
|
export function ConfigurableOperationArgInput({
|
|
14
12
|
definition,
|
|
15
13
|
value,
|
|
16
14
|
onChange,
|
|
17
|
-
readOnly,
|
|
18
|
-
position,
|
|
19
15
|
}: Readonly<ConfigurableOperationArgInputProps>) {
|
|
20
|
-
const universalFieldDef = configArgToUniversal(definition);
|
|
21
16
|
return (
|
|
22
|
-
<
|
|
23
|
-
fieldDef={
|
|
17
|
+
<FormControlAdapter
|
|
18
|
+
fieldDef={definition}
|
|
24
19
|
field={{
|
|
25
20
|
value,
|
|
26
21
|
onChange,
|
|
@@ -29,8 +24,6 @@ export function ConfigurableOperationArgInput({
|
|
|
29
24
|
ref: () => {},
|
|
30
25
|
}}
|
|
31
26
|
valueMode="json-string"
|
|
32
|
-
disabled={readOnly}
|
|
33
|
-
position={position}
|
|
34
27
|
/>
|
|
35
28
|
);
|
|
36
29
|
}
|
|
@@ -22,7 +22,6 @@ export function ConfigurableOperationInput({
|
|
|
22
22
|
operationDefinition,
|
|
23
23
|
readonly,
|
|
24
24
|
removable,
|
|
25
|
-
position,
|
|
26
25
|
hideDescription,
|
|
27
26
|
value,
|
|
28
27
|
onChange,
|
|
@@ -90,10 +89,7 @@ export function ConfigurableOperationInput({
|
|
|
90
89
|
className={`grid gap-4 ${operationDefinition.args.length === 1 ? 'grid-cols-1' : 'grid-cols-1 sm:grid-cols-2'}`}
|
|
91
90
|
>
|
|
92
91
|
{operationDefinition.args
|
|
93
|
-
.filter(
|
|
94
|
-
arg =>
|
|
95
|
-
arg.ui?.component !== 'combination-mode-form-input',
|
|
96
|
-
)
|
|
92
|
+
.filter(arg => arg.ui?.component !== 'combination-mode-form-input')
|
|
97
93
|
.map(arg => {
|
|
98
94
|
const argValue =
|
|
99
95
|
value.arguments.find(a => a.name === arg.name)?.value || '';
|
|
@@ -114,7 +110,6 @@ export function ConfigurableOperationInput({
|
|
|
114
110
|
handleInputChange(arg.name, value)
|
|
115
111
|
}
|
|
116
112
|
readOnly={readonly}
|
|
117
|
-
position={position}
|
|
118
113
|
/>
|
|
119
114
|
</FormControl>
|
|
120
115
|
</FormItem>
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { CombinationModeInput } from '@/vdb/components/data-input/combination-mode-input.js';
|
|
1
2
|
import { Button } from '@/vdb/components/ui/button.js';
|
|
2
3
|
import {
|
|
3
4
|
DropdownMenu,
|
|
@@ -5,7 +6,6 @@ import {
|
|
|
5
6
|
DropdownMenuItem,
|
|
6
7
|
DropdownMenuTrigger,
|
|
7
8
|
} from '@/vdb/components/ui/dropdown-menu.js';
|
|
8
|
-
import { InputComponent } from '@/vdb/framework/component-registry/dynamic-component.js';
|
|
9
9
|
import { api } from '@/vdb/graphql/api.js';
|
|
10
10
|
import { ConfigurableOperationDefFragment } from '@/vdb/graphql/fragments.js';
|
|
11
11
|
import { Trans } from '@/vdb/lib/trans.js';
|
|
@@ -179,13 +179,14 @@ export function ConfigurableOperationMultiSelector({
|
|
|
179
179
|
if (!operationDef) {
|
|
180
180
|
return null;
|
|
181
181
|
}
|
|
182
|
-
const hasCombinationMode = operation.arguments.find(
|
|
182
|
+
const hasCombinationMode = operation.arguments.find(
|
|
183
|
+
arg => arg.name === 'combineWithAnd',
|
|
184
|
+
);
|
|
183
185
|
return (
|
|
184
186
|
<div key={index + operation.code}>
|
|
185
187
|
{index > 0 && hasCombinationMode ? (
|
|
186
188
|
<div className="my-2">
|
|
187
|
-
<
|
|
188
|
-
id="vendure:combinationModeInput"
|
|
189
|
+
<CombinationModeInput
|
|
189
190
|
value={
|
|
190
191
|
operation.arguments.find(arg => arg.name === 'combineWithAnd')
|
|
191
192
|
?.value ?? 'true'
|
|
@@ -193,6 +194,9 @@ export function ConfigurableOperationMultiSelector({
|
|
|
193
194
|
onChange={(newValue: boolean | string) =>
|
|
194
195
|
onCombinationModeChange(index, newValue)
|
|
195
196
|
}
|
|
197
|
+
name={''}
|
|
198
|
+
ref={() => {}}
|
|
199
|
+
onBlur={() => {}}
|
|
196
200
|
position={index}
|
|
197
201
|
/>
|
|
198
202
|
</div>
|
|
@@ -204,7 +208,6 @@ export function ConfigurableOperationMultiSelector({
|
|
|
204
208
|
value={operation}
|
|
205
209
|
onChange={value => onOperationValueChange(operation, value)}
|
|
206
210
|
onRemove={() => onOperationRemove(index)}
|
|
207
|
-
position={index}
|
|
208
211
|
/>
|
|
209
212
|
</div>
|
|
210
213
|
);
|
|
@@ -41,19 +41,19 @@ type QueryData = {
|
|
|
41
41
|
|
|
42
42
|
/**
|
|
43
43
|
* ConfigurableOperationSelector - A reusable component for selecting a single configurable operation
|
|
44
|
-
*
|
|
44
|
+
*
|
|
45
45
|
* This component provides a standardized interface for selecting configurable operations such as:
|
|
46
46
|
* - Payment method handlers
|
|
47
|
-
* - Payment eligibility checkers
|
|
47
|
+
* - Payment eligibility checkers
|
|
48
48
|
* - Shipping calculators
|
|
49
49
|
* - Shipping eligibility checkers
|
|
50
|
-
*
|
|
50
|
+
*
|
|
51
51
|
* Features:
|
|
52
52
|
* - Displays the selected operation with its configuration form
|
|
53
53
|
* - Provides a dropdown to select from available operations
|
|
54
54
|
* - Handles operation selection with default argument values
|
|
55
55
|
* - Supports removal of selected operations
|
|
56
|
-
*
|
|
56
|
+
*
|
|
57
57
|
* @example
|
|
58
58
|
* ```tsx
|
|
59
59
|
* <ConfigurableOperationSelector
|
|
@@ -122,7 +122,7 @@ export function ConfigurableOperationSelector({
|
|
|
122
122
|
<ConfigurableOperationInput
|
|
123
123
|
operationDefinition={operationDef}
|
|
124
124
|
value={value}
|
|
125
|
-
onChange={
|
|
125
|
+
onChange={v => onOperationValueChange(v)}
|
|
126
126
|
onRemove={() => onOperationRemove()}
|
|
127
127
|
/>
|
|
128
128
|
</div>
|
|
@@ -17,9 +17,8 @@ import { customFieldConfigFragment } from '@/vdb/providers/server-config.js';
|
|
|
17
17
|
import { ResultOf } from 'gql.tada';
|
|
18
18
|
import React, { useMemo } from 'react';
|
|
19
19
|
import { Control } from 'react-hook-form';
|
|
20
|
+
import { FormControlAdapter } from '../../framework/form-engine/form-control-adapter.js';
|
|
20
21
|
import { TranslatableFormField } from './translatable-form-field.js';
|
|
21
|
-
import { customFieldToUniversal } from './universal-field-definition.js';
|
|
22
|
-
import { UniversalFormInput } from './universal-form-input.js';
|
|
23
22
|
|
|
24
23
|
type CustomFieldConfig = ResultOf<typeof customFieldConfigFragment>;
|
|
25
24
|
|
|
@@ -30,15 +29,7 @@ interface CustomFieldsFormProps {
|
|
|
30
29
|
}
|
|
31
30
|
|
|
32
31
|
export function CustomFieldsForm({ entityType, control, formPathPrefix }: Readonly<CustomFieldsFormProps>) {
|
|
33
|
-
const {
|
|
34
|
-
settings: { displayLanguage },
|
|
35
|
-
} = useUserSettings();
|
|
36
32
|
const { i18n } = useLingui();
|
|
37
|
-
|
|
38
|
-
const getTranslation = (input: Array<{ languageCode: string; value: string }> | null | undefined) => {
|
|
39
|
-
return input?.find(t => t.languageCode === displayLanguage)?.value;
|
|
40
|
-
};
|
|
41
|
-
|
|
42
33
|
const customFields = useCustomFieldConfig(entityType);
|
|
43
34
|
|
|
44
35
|
const getCustomFieldBaseName = (fieldDef: CustomFieldConfig) => {
|
|
@@ -94,7 +85,6 @@ export function CustomFieldsForm({ entityType, control, formPathPrefix }: Readon
|
|
|
94
85
|
fieldDef={fieldDef}
|
|
95
86
|
control={control}
|
|
96
87
|
fieldName={getFieldName(fieldDef)}
|
|
97
|
-
getTranslation={getTranslation}
|
|
98
88
|
/>
|
|
99
89
|
))}
|
|
100
90
|
</div>
|
|
@@ -120,7 +110,6 @@ export function CustomFieldsForm({ entityType, control, formPathPrefix }: Readon
|
|
|
120
110
|
fieldDef={fieldDef}
|
|
121
111
|
control={control}
|
|
122
112
|
fieldName={getFieldName(fieldDef)}
|
|
123
|
-
getTranslation={getTranslation}
|
|
124
113
|
/>
|
|
125
114
|
))}
|
|
126
115
|
</div>
|
|
@@ -134,12 +123,16 @@ interface CustomFieldItemProps {
|
|
|
134
123
|
fieldDef: CustomFieldConfig;
|
|
135
124
|
control: Control<any, any>;
|
|
136
125
|
fieldName: string;
|
|
137
|
-
getTranslation: (
|
|
138
|
-
input: Array<{ languageCode: string; value: string }> | null | undefined,
|
|
139
|
-
) => string | undefined;
|
|
140
126
|
}
|
|
141
127
|
|
|
142
|
-
function CustomFieldItem({ fieldDef, control, fieldName
|
|
128
|
+
function CustomFieldItem({ fieldDef, control, fieldName }: Readonly<CustomFieldItemProps>) {
|
|
129
|
+
const {
|
|
130
|
+
settings: { displayLanguage },
|
|
131
|
+
} = useUserSettings();
|
|
132
|
+
|
|
133
|
+
const getTranslation = (input: Array<{ languageCode: string; value: string }> | null | undefined) => {
|
|
134
|
+
return input?.find(t => t.languageCode === displayLanguage)?.value;
|
|
135
|
+
};
|
|
143
136
|
const hasCustomFormComponent = fieldDef.ui?.component;
|
|
144
137
|
const isLocaleField = fieldDef.type === 'localeString' || fieldDef.type === 'localeText';
|
|
145
138
|
const shouldBeFullWidth = fieldDef.ui?.fullWidth === true;
|
|
@@ -160,22 +153,13 @@ function CustomFieldItem({ fieldDef, control, fieldName, getTranslation }: Reado
|
|
|
160
153
|
{hasCustomFormComponent ? (
|
|
161
154
|
<CustomFormComponent
|
|
162
155
|
fieldDef={fieldDef}
|
|
163
|
-
|
|
164
|
-
...props,
|
|
165
|
-
field: {
|
|
166
|
-
...field,
|
|
167
|
-
disabled: fieldDef.readonly ?? false,
|
|
168
|
-
},
|
|
169
|
-
}}
|
|
156
|
+
{...field}
|
|
170
157
|
/>
|
|
171
158
|
) : (
|
|
172
|
-
<
|
|
173
|
-
fieldDef={
|
|
159
|
+
<FormControlAdapter
|
|
160
|
+
fieldDef={fieldDef}
|
|
174
161
|
field={field}
|
|
175
162
|
valueMode="native"
|
|
176
|
-
disabled={fieldDef.readonly ?? false}
|
|
177
|
-
control={control}
|
|
178
|
-
getTranslation={getTranslation}
|
|
179
163
|
/>
|
|
180
164
|
)}
|
|
181
165
|
</FormControl>
|
|
@@ -203,13 +187,7 @@ function CustomFieldItem({ fieldDef, control, fieldName, getTranslation }: Reado
|
|
|
203
187
|
>
|
|
204
188
|
<CustomFormComponent
|
|
205
189
|
fieldDef={fieldDef}
|
|
206
|
-
fieldProps
|
|
207
|
-
...fieldProps,
|
|
208
|
-
field: {
|
|
209
|
-
...fieldProps.field,
|
|
210
|
-
disabled: fieldDef.readonly ?? false,
|
|
211
|
-
},
|
|
212
|
-
}}
|
|
190
|
+
{...fieldProps.field}
|
|
213
191
|
/>
|
|
214
192
|
</CustomFieldFormItem>
|
|
215
193
|
)}
|
|
@@ -234,14 +212,12 @@ function CustomFieldItem({ fieldDef, control, fieldName, getTranslation }: Reado
|
|
|
234
212
|
<FormLabel>{getTranslation(fieldDef.label) ?? fieldDef.name}</FormLabel>
|
|
235
213
|
<FormControl>
|
|
236
214
|
<CustomFieldListInput
|
|
237
|
-
|
|
215
|
+
{...field}
|
|
238
216
|
disabled={isReadonly}
|
|
239
217
|
renderInput={(index, inputField) => (
|
|
240
218
|
<StructFormInput
|
|
241
|
-
|
|
242
|
-
fieldDef={fieldDef
|
|
243
|
-
control={control}
|
|
244
|
-
getTranslation={getTranslation}
|
|
219
|
+
{...inputField}
|
|
220
|
+
fieldDef={fieldDef}
|
|
245
221
|
/>
|
|
246
222
|
)}
|
|
247
223
|
defaultValue={{}} // Empty struct object as default
|
|
@@ -268,10 +244,8 @@ function CustomFieldItem({ fieldDef, control, fieldName, getTranslation }: Reado
|
|
|
268
244
|
<FormLabel>{getTranslation(fieldDef.label) ?? fieldDef.name}</FormLabel>
|
|
269
245
|
<FormControl>
|
|
270
246
|
<StructFormInput
|
|
271
|
-
|
|
272
|
-
fieldDef={fieldDef
|
|
273
|
-
control={control}
|
|
274
|
-
getTranslation={getTranslation}
|
|
247
|
+
{...field}
|
|
248
|
+
fieldDef={fieldDef}
|
|
275
249
|
/>
|
|
276
250
|
</FormControl>
|
|
277
251
|
<FormDescription>{getTranslation(fieldDef.description)}</FormDescription>
|
|
@@ -295,13 +269,10 @@ function CustomFieldItem({ fieldDef, control, fieldName, getTranslation }: Reado
|
|
|
295
269
|
getTranslation={getTranslation}
|
|
296
270
|
fieldName={fieldDef.name}
|
|
297
271
|
>
|
|
298
|
-
<
|
|
299
|
-
fieldDef={
|
|
272
|
+
<FormControlAdapter
|
|
273
|
+
fieldDef={fieldDef}
|
|
300
274
|
field={field}
|
|
301
275
|
valueMode="native"
|
|
302
|
-
disabled={fieldDef.readonly ?? false}
|
|
303
|
-
control={control}
|
|
304
|
-
getTranslation={getTranslation}
|
|
305
276
|
/>
|
|
306
277
|
</CustomFieldFormItem>
|
|
307
278
|
)}
|
|
@@ -63,7 +63,7 @@ export function MultiSelect<T extends boolean>(props: MultiSelectProps<T>) {
|
|
|
63
63
|
|
|
64
64
|
const renderTrigger = () => {
|
|
65
65
|
if (multiple) {
|
|
66
|
-
const selectedValues = value
|
|
66
|
+
const selectedValues: string[] = typeof value === 'string' ? [value] : value;
|
|
67
67
|
return (
|
|
68
68
|
<Button
|
|
69
69
|
variant="outline"
|
|
@@ -1,28 +1,24 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DashboardFormComponent,
|
|
3
|
+
DashboardFormComponentProps,
|
|
4
|
+
} from '@/vdb/framework/form-engine/form-engine-types.js';
|
|
1
5
|
import * as React from 'react';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { addInputComponent, getInputComponent } from '../extension-api/input-component-extensions.js';
|
|
6
|
+
import { getDisplayComponent } from '../extension-api/display-component-extensions.js';
|
|
7
|
+
import { getInputComponent } from '../extension-api/input-component-extensions.js';
|
|
5
8
|
|
|
6
9
|
export interface ComponentRegistryEntry<Props extends Record<string, any>> {
|
|
7
10
|
component: React.ComponentType<Props>;
|
|
8
11
|
}
|
|
9
12
|
|
|
10
|
-
//
|
|
11
|
-
|
|
13
|
+
// Display component interface (unchanged)
|
|
12
14
|
export interface DataDisplayComponentProps {
|
|
13
15
|
value: any;
|
|
14
|
-
[key: string]: any;
|
|
15
|
-
}
|
|
16
16
|
|
|
17
|
-
export interface DataInputComponentProps<
|
|
18
|
-
TFieldValues extends FieldValues = FieldValues,
|
|
19
|
-
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
20
|
-
> extends ControllerRenderProps<TFieldValues, TName> {
|
|
21
17
|
[key: string]: any;
|
|
22
18
|
}
|
|
23
19
|
|
|
24
20
|
export type DataDisplayComponent = React.ComponentType<DataDisplayComponentProps>;
|
|
25
|
-
export type
|
|
21
|
+
export type { DashboardFormComponentProps as DataInputComponentProps };
|
|
26
22
|
|
|
27
23
|
// Component registry hook that uses the global registry
|
|
28
24
|
export function useComponentRegistry() {
|
|
@@ -30,27 +26,8 @@ export function useComponentRegistry() {
|
|
|
30
26
|
getDisplayComponent: (id: string): DataDisplayComponent | undefined => {
|
|
31
27
|
return getDisplayComponent(id);
|
|
32
28
|
},
|
|
33
|
-
getInputComponent: (id: string):
|
|
29
|
+
getInputComponent: (id: string): DashboardFormComponent | undefined => {
|
|
34
30
|
return getInputComponent(id);
|
|
35
31
|
},
|
|
36
32
|
};
|
|
37
33
|
}
|
|
38
|
-
|
|
39
|
-
// Legacy registration functions - these now delegate to the global registry
|
|
40
|
-
export function registerInputComponent(
|
|
41
|
-
pageId: string,
|
|
42
|
-
blockId: string,
|
|
43
|
-
field: string,
|
|
44
|
-
component: DataInputComponent,
|
|
45
|
-
) {
|
|
46
|
-
addInputComponent({ pageId, blockId, field, component });
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export function registerDisplayComponent(
|
|
50
|
-
pageId: string,
|
|
51
|
-
blockId: string,
|
|
52
|
-
field: string,
|
|
53
|
-
component: DataDisplayComponent,
|
|
54
|
-
) {
|
|
55
|
-
addDisplayComponent({ pageId, blockId, field, component });
|
|
56
|
-
}
|