@vendure/dashboard 3.5.2-master-202512040233 → 3.5.2-master-202512180239

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 (69) hide show
  1. package/dist/plugin/constants.js +2 -2
  2. package/dist/plugin/dashboard.plugin.js +1 -1
  3. package/dist/vite/constants.js +1 -0
  4. package/lingui.config.js +1 -0
  5. package/package.json +7 -7
  6. package/src/app/routeTree.gen.ts +1221 -0
  7. package/src/app/routes/_authenticated/_collections/collections.graphql.ts +1 -0
  8. package/src/app/routes/_authenticated/_collections/collections.tsx +249 -167
  9. package/src/app/routes/_authenticated/_collections/components/collection-bulk-actions.tsx +8 -0
  10. package/src/app/routes/_authenticated/_collections/components/move-collections-dialog.tsx +4 -0
  11. package/src/app/routes/_authenticated/_customers/components/customer-history/index.ts +0 -1
  12. package/src/app/routes/_authenticated/_global-settings/global-settings.tsx +1 -1
  13. package/src/app/routes/_authenticated/_orders/components/add-surcharge-form.tsx +139 -0
  14. package/src/app/routes/_authenticated/_orders/components/edit-order-table.tsx +3 -0
  15. package/src/app/routes/_authenticated/_orders/components/order-address.tsx +3 -3
  16. package/src/app/routes/_authenticated/_orders/components/order-modification-summary.tsx +49 -11
  17. package/src/app/routes/_authenticated/_orders/orders_.$id_.modify.tsx +9 -0
  18. package/src/app/routes/_authenticated/_orders/utils/use-modify-order.ts +23 -0
  19. package/src/app/routes/_authenticated/_product-variants/components/add-currency-dropdown.tsx +3 -3
  20. package/src/app/routes/_authenticated/_product-variants/components/add-stock-location-dropdown.tsx +2 -2
  21. package/src/app/routes/_authenticated/_products/products.graphql.ts +1 -0
  22. package/src/app/routes/_authenticated/_tax-rates/tax-rates_.$id.tsx +2 -9
  23. package/src/i18n/locales/bg.po +3436 -0
  24. package/src/lib/components/data-input/datetime-input.tsx +1 -1
  25. package/src/lib/components/data-input/number-input.tsx +24 -5
  26. package/src/lib/components/data-input/relation-selector.tsx +1 -1
  27. package/src/lib/components/data-input/struct-form-input.tsx +175 -174
  28. package/src/lib/components/data-table/data-table-utils.ts +241 -1
  29. package/src/lib/components/data-table/data-table.tsx +190 -60
  30. package/src/lib/components/layout/manage-languages-dialog.tsx +2 -25
  31. package/src/lib/components/shared/custom-fields-form.tsx +13 -8
  32. package/src/lib/components/shared/paginated-list-data-table.tsx +19 -0
  33. package/src/lib/components/ui/alert.tsx +1 -1
  34. package/src/lib/components/ui/carousel.tsx +2 -2
  35. package/src/lib/components/ui/chart.tsx +1 -1
  36. package/src/lib/components/ui/context-menu.tsx +1 -1
  37. package/src/lib/components/ui/drawer.tsx +1 -1
  38. package/src/lib/components/ui/grid-layout.tsx +1 -1
  39. package/src/lib/components/ui/input-group.tsx +1 -0
  40. package/src/lib/components/ui/input-otp.tsx +1 -1
  41. package/src/lib/components/ui/menubar.tsx +1 -1
  42. package/src/lib/components/ui/navigation-menu.tsx +1 -1
  43. package/src/lib/components/ui/progress.tsx +1 -1
  44. package/src/lib/components/ui/radio-group.tsx +1 -1
  45. package/src/lib/components/ui/resizable.tsx +1 -1
  46. package/src/lib/components/ui/select.tsx +1 -1
  47. package/src/lib/components/ui/slider.tsx +1 -1
  48. package/src/lib/components/ui/toggle-group.tsx +2 -2
  49. package/src/lib/components/ui/toggle.tsx +1 -1
  50. package/src/lib/framework/component-registry/component-registry.tsx +2 -6
  51. package/src/lib/framework/extension-api/display-component-extensions.tsx +4 -3
  52. package/src/lib/framework/extension-api/logic/detail-forms.ts +0 -13
  53. package/src/lib/framework/extension-api/types/data-table.ts +4 -2
  54. package/src/lib/framework/extension-api/types/navigation.ts +2 -2
  55. package/src/lib/framework/form-engine/use-generated-form.tsx +7 -1
  56. package/src/lib/framework/layout-engine/page-layout.tsx +1 -1
  57. package/src/lib/framework/nav-menu/nav-menu-extensions.ts +1 -1
  58. package/src/lib/framework/page/detail-page-route-loader.tsx +1 -1
  59. package/src/lib/framework/page/list-page.tsx +62 -38
  60. package/src/lib/framework/page/page-api.ts +1 -1
  61. package/src/lib/framework/page/use-detail-page.ts +4 -2
  62. package/src/lib/framework/page/use-extended-router.tsx +20 -16
  63. package/src/lib/framework/registry/registry-types.ts +2 -1
  64. package/src/lib/graphql/graphql-env.d.ts +8 -12
  65. package/src/lib/hooks/use-drag-and-drop.ts +86 -0
  66. package/src/lib/providers/channel-provider.tsx +11 -7
  67. package/LICENSE.md +0 -42
  68. package/src/app/routes/_authenticated/_facets/components/edit-facet-value.tsx +0 -129
  69. /package/src/{app/routes/_authenticated/_global-settings → lib}/utils/global-languages.ts +0 -0
@@ -185,7 +185,7 @@ function bcpTagToDatePickerLocale(
185
185
  case 'pt-BR':
186
186
  return module.ptBR;
187
187
  default: {
188
- const lang = tag.split('-').at(0);
188
+ const lang = tag.split('-')[0];
189
189
  return lang ? module[lang as keyof typeof module] : undefined;
190
190
  }
191
191
  }
@@ -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}
@@ -28,7 +28,7 @@ export interface RelationSelectorConfig<T = any> {
28
28
  /** Number of items to load per page */
29
29
  pageSize?: number;
30
30
  /** Placeholder text for the search input */
31
- placeholder?: React.ReactNode;
31
+ placeholder?: string;
32
32
  /** Whether to enable multi-select mode */
33
33
  multiple?: boolean;
34
34
  /** Custom filter function for search */
@@ -20,7 +20,7 @@ import {
20
20
  StructCustomFieldConfig,
21
21
  StructField,
22
22
  } from '@/vdb/framework/form-engine/form-engine-types.js';
23
- import { isStructFieldConfig } from '@/vdb/framework/form-engine/utils.js';
23
+ import { isReadonlyField, isStructFieldConfig } from '@/vdb/framework/form-engine/utils.js';
24
24
  import { useUserSettings } from '@/vdb/hooks/use-user-settings.js';
25
25
  import { CustomFieldListInput } from './custom-field-list-input.js';
26
26
  import { DateTimeInput } from './datetime-input.js';
@@ -98,185 +98,61 @@ export function StructFormInput({ fieldDef, ...field }: Readonly<DashboardFormCo
98
98
  return input?.find(t => t.languageCode === displayLanguage)?.value;
99
99
  };
100
100
 
101
- const isReadonly = fieldDef?.readonly === true;
102
-
103
- // Helper function to render individual struct field inputs
104
- const renderStructFieldInput = (
105
- structField: StructField,
106
- inputField: ControllerRenderProps<any, any>,
107
- ) => {
108
- const isList = structField.list ?? false;
109
-
110
- // Helper function to render single input for a struct field
111
- const renderSingleStructInput = (singleField: ControllerRenderProps<any, any>) => {
112
- switch (structField.type) {
113
- case 'string': {
114
- // Check if the field has options (dropdown)
115
- const stringField = structField as any; // GraphQL union types need casting
116
- if (stringField.options && stringField.options.length > 0) {
117
- return (
118
- <SelectWithOptions {...singleField} fieldDef={stringField} isListField={false} />
119
- );
120
- }
121
- return (
122
- <Input
123
- value={singleField.value ?? ''}
124
- onChange={e => singleField.onChange(e.target.value)}
125
- onBlur={singleField.onBlur}
126
- name={singleField.name}
127
- disabled={isReadonly}
128
- />
129
- );
130
- }
131
- case 'int':
132
- case 'float': {
133
- const isFloat = structField.type === 'float';
134
- const numericField = structField as any; // GraphQL union types need casting
135
- const min = isFloat ? numericField.floatMin : numericField.intMin;
136
- const max = isFloat ? numericField.floatMax : numericField.intMax;
137
- const step = isFloat ? numericField.floatStep : numericField.intStep;
138
-
139
- return (
140
- <Input
141
- type="number"
142
- value={singleField.value ?? ''}
143
- onChange={e => {
144
- const value = e.target.valueAsNumber;
145
- singleField.onChange(isNaN(value) ? undefined : value);
146
- }}
147
- onBlur={singleField.onBlur}
148
- name={singleField.name}
149
- disabled={isReadonly}
150
- min={min}
151
- max={max}
152
- step={step}
153
- />
154
- );
155
- }
156
- case 'boolean':
157
- return (
158
- <Switch
159
- checked={singleField.value}
160
- onCheckedChange={singleField.onChange}
161
- disabled={isReadonly}
162
- />
163
- );
164
- case 'datetime':
165
- return <DateTimeInput {...singleField} />;
166
- default:
167
- return (
168
- <Input
169
- value={singleField.value ?? ''}
170
- onChange={e => singleField.onChange(e.target.value)}
171
- onBlur={singleField.onBlur}
172
- name={singleField.name}
173
- disabled={isReadonly}
174
- />
175
- );
176
- }
177
- };
178
-
179
- // Handle string fields with options (dropdown) - already handles list case with multi-select
180
- if (structField.type === 'string') {
181
- const stringField = structField as any; // GraphQL union types need casting
182
- if (stringField.options && stringField.options.length > 0) {
183
- return (
184
- <SelectWithOptions
185
- {...inputField}
186
- fieldDef={stringField}
187
- disabled={isReadonly}
188
- isListField={isList}
189
- />
190
- );
191
- }
192
- }
193
-
194
- // For list struct fields, wrap with list input
195
- if (isList) {
196
- const getDefaultValue = () => {
197
- switch (structField.type) {
198
- case 'string':
199
- return '';
200
- case 'int':
201
- case 'float':
202
- return 0;
203
- case 'boolean':
204
- return false;
205
- case 'datetime':
206
- return '';
207
- default:
208
- return '';
209
- }
210
- };
211
-
212
- // Determine if the field type needs full width
213
- const needsFullWidth = structField.type === 'text' || structField.type === 'localeText';
214
-
215
- return (
216
- <CustomFieldListInput
217
- {...inputField}
218
- disabled={isReadonly}
219
- renderInput={(index, listItemField) => renderSingleStructInput(listItemField)}
220
- defaultValue={getDefaultValue()}
221
- />
222
- );
223
- }
224
-
225
- // For non-list fields, render directly
226
- return renderSingleStructInput(inputField);
227
- };
101
+ const isReadonly = isReadonlyField(fieldDef);
228
102
 
229
103
  // Edit mode - memoized to prevent focus loss from re-renders
230
104
  const EditMode = useMemo(
231
- () => (
232
- <div className="space-y-4 border rounded-md p-4">
233
- {!isReadonly && (
234
- <div className="flex justify-end">
235
- <Button
236
- variant="ghost"
237
- size="sm"
238
- onClick={() => setIsEditing(false)}
239
- className="h-8 w-8 p-0 text-muted-foreground hover:text-foreground"
240
- >
241
- <CheckIcon className="h-4 w-4" />
242
- <span className="sr-only">Done</span>
243
- </Button>
244
- </div>
245
- )}
246
- {fieldDef?.fields?.map(structField => (
247
- <FormField
248
- key={structField.name}
249
- control={control}
250
- name={`${field.name}.${structField.name}`}
251
- render={({ field: structInputField }) => (
252
- <FormItem>
253
- <div className="flex items-baseline gap-4">
254
- <div className="flex-1">
255
- <FormLabel>
256
- {getTranslation(structField.label) ?? structField.name}
257
- </FormLabel>
258
- {getTranslation(structField.description) && (
259
- <FormDescription>
260
- {getTranslation(structField.description)}
261
- </FormDescription>
262
- )}
105
+ () =>
106
+ fieldDef && isStructFieldConfig(fieldDef) ? (
107
+ <div className="space-y-4 border rounded-md p-4">
108
+ {!isReadonly && (
109
+ <div className="flex justify-end">
110
+ <Button
111
+ variant="ghost"
112
+ size="sm"
113
+ onClick={() => setIsEditing(false)}
114
+ className="h-8 w-8 p-0 text-muted-foreground hover:text-foreground"
115
+ >
116
+ <CheckIcon className="h-4 w-4" />
117
+ <span className="sr-only">Done</span>
118
+ </Button>
119
+ </div>
120
+ )}
121
+ {fieldDef?.fields.map(structField => (
122
+ <FormField
123
+ key={structField.name}
124
+ control={control}
125
+ name={`${field.name}.${structField.name}`}
126
+ render={({ field: structInputField }) => (
127
+ <FormItem>
128
+ <div className="flex items-baseline gap-4">
129
+ <div className="flex-1">
130
+ <FormLabel>
131
+ {getTranslation(structField.label) ?? structField.name}
132
+ </FormLabel>
133
+ {getTranslation(structField.description) && (
134
+ <FormDescription>
135
+ {getTranslation(structField.description)}
136
+ </FormDescription>
137
+ )}
138
+ </div>
139
+ <div className="flex-[2]">
140
+ <FormControl>
141
+ {renderStructFieldInput(structField, structInputField)}
142
+ </FormControl>
143
+ <FormMessage />
144
+ </div>
263
145
  </div>
264
- <div className="flex-[2]">
265
- <FormControl>
266
- {renderStructFieldInput(structField, structInputField)}
267
- </FormControl>
268
- <FormMessage />
269
- </div>
270
- </div>
271
- </FormItem>
272
- )}
273
- />
274
- ))}
275
- </div>
276
- ),
277
- [fieldDef, control, field.name, getTranslation, renderStructFieldInput, isReadonly],
146
+ </FormItem>
147
+ )}
148
+ />
149
+ ))}
150
+ </div>
151
+ ) : null,
152
+ [fieldDef, control, field.name, getTranslation, isReadonly],
278
153
  );
279
154
 
155
+ // Early return if not a struct field config
280
156
  if (!fieldDef || !isStructFieldConfig(fieldDef)) {
281
157
  return null;
282
158
  }
@@ -317,3 +193,128 @@ export function StructFormInput({ fieldDef, ...field }: Readonly<DashboardFormCo
317
193
  />
318
194
  );
319
195
  }
196
+
197
+ // Helper function to render individual struct field inputs
198
+ const renderStructFieldInput = (
199
+ structField: StructField,
200
+ inputField: ControllerRenderProps<any, any>,
201
+ isReadonly: boolean = false,
202
+ ) => {
203
+ const isList = structField.list ?? false;
204
+
205
+ // Helper function to render single input for a struct field
206
+ const renderSingleStructInput = (singleField: ControllerRenderProps<any, any>) => {
207
+ switch (structField.type) {
208
+ case 'string': {
209
+ // Check if the field has options (dropdown)
210
+ const stringField = structField as any; // GraphQL union types need casting
211
+ if (stringField.options && stringField.options.length > 0) {
212
+ return <SelectWithOptions {...singleField} fieldDef={stringField} isListField={false} />;
213
+ }
214
+ return (
215
+ <Input
216
+ value={singleField.value ?? ''}
217
+ onChange={e => singleField.onChange(e.target.value)}
218
+ onBlur={singleField.onBlur}
219
+ name={singleField.name}
220
+ disabled={isReadonly}
221
+ />
222
+ );
223
+ }
224
+ case 'int':
225
+ case 'float': {
226
+ const isFloat = structField.type === 'float';
227
+ const numericField = structField as any; // GraphQL union types need casting
228
+ const min = isFloat ? numericField.floatMin : numericField.intMin;
229
+ const max = isFloat ? numericField.floatMax : numericField.intMax;
230
+ const step = isFloat ? numericField.floatStep : numericField.intStep;
231
+
232
+ return (
233
+ <Input
234
+ type="number"
235
+ value={singleField.value ?? ''}
236
+ onChange={e => {
237
+ const value = e.target.valueAsNumber;
238
+ singleField.onChange(isNaN(value) ? undefined : value);
239
+ }}
240
+ onBlur={singleField.onBlur}
241
+ name={singleField.name}
242
+ disabled={isReadonly}
243
+ min={min}
244
+ max={max}
245
+ step={step}
246
+ />
247
+ );
248
+ }
249
+ case 'boolean':
250
+ return (
251
+ <Switch
252
+ checked={singleField.value}
253
+ onCheckedChange={singleField.onChange}
254
+ disabled={isReadonly}
255
+ />
256
+ );
257
+ case 'datetime':
258
+ return <DateTimeInput {...singleField} />;
259
+ default:
260
+ return (
261
+ <Input
262
+ value={singleField.value ?? ''}
263
+ onChange={e => singleField.onChange(e.target.value)}
264
+ onBlur={singleField.onBlur}
265
+ name={singleField.name}
266
+ disabled={isReadonly}
267
+ />
268
+ );
269
+ }
270
+ };
271
+
272
+ // Handle string fields with options (dropdown) - already handles list case with multi-select
273
+ if (structField.type === 'string') {
274
+ const stringField = structField as any; // GraphQL union types need casting
275
+ if (stringField.options && stringField.options.length > 0) {
276
+ return (
277
+ <SelectWithOptions
278
+ {...inputField}
279
+ fieldDef={stringField}
280
+ disabled={isReadonly}
281
+ isListField={isList}
282
+ />
283
+ );
284
+ }
285
+ }
286
+
287
+ // For list struct fields, wrap with list input
288
+ if (isList) {
289
+ const getDefaultValue = () => {
290
+ switch (structField.type) {
291
+ case 'string':
292
+ return '';
293
+ case 'int':
294
+ case 'float':
295
+ return 0;
296
+ case 'boolean':
297
+ return false;
298
+ case 'datetime':
299
+ return '';
300
+ default:
301
+ return '';
302
+ }
303
+ };
304
+
305
+ // Determine if the field type needs full width
306
+ const needsFullWidth = structField.type === 'text' || structField.type === 'localeText';
307
+
308
+ return (
309
+ <CustomFieldListInput
310
+ {...inputField}
311
+ disabled={isReadonly}
312
+ renderInput={(index, listItemField) => renderSingleStructInput(listItemField)}
313
+ defaultValue={getDefaultValue()}
314
+ />
315
+ );
316
+ }
317
+
318
+ // For non-list fields, render directly
319
+ return renderSingleStructInput(inputField);
320
+ };