@open-mercato/ui 0.6.6-develop.6241.1.47c01ad4ed → 0.6.6-develop.6245.1.2be3b151f8

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.
@@ -3,7 +3,10 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import { Cog, GripVertical, Languages, Pencil, Plus, Trash2 } from "lucide-react";
5
5
  import { Button } from "../../primitives/button.js";
6
+ import { CheckboxField } from "../../primitives/checkbox-field.js";
7
+ import { FormField } from "../../primitives/form-field.js";
6
8
  import { IconButton } from "../../primitives/icon-button.js";
9
+ import { Input } from "../../primitives/input.js";
7
10
  import {
8
11
  Select,
9
12
  SelectContent,
@@ -30,6 +33,15 @@ const DEFAULT_KIND_OPTIONS = CUSTOM_FIELD_KINDS.map((k) => ({
30
33
  value: k,
31
34
  label: k.charAt(0).toUpperCase() + k.slice(1)
32
35
  }));
36
+ const DEFAULT_VALUE_NONE = "__open_mercato_no_default__";
37
+ function getDefaultValueNoneOptionValue(options) {
38
+ const optionValues = new Set(options.map((option) => String(option.value)));
39
+ let candidate = DEFAULT_VALUE_NONE;
40
+ while (optionValues.has(candidate)) {
41
+ candidate = `${candidate}_`;
42
+ }
43
+ return candidate;
44
+ }
33
45
  const FIELDSET_ICON_OPTIONS = [
34
46
  { value: "layers", label: "Layers" },
35
47
  { value: "tag", label: "Tag" },
@@ -271,70 +283,59 @@ function FieldDefinitionsEditor({
271
283
  )
272
284
  ] }),
273
285
  resolvedActiveFieldset && activeFieldsetConfig ? /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 md:grid-cols-4 gap-3", children: [
274
- /* @__PURE__ */ jsxs("div", { children: [
275
- /* @__PURE__ */ jsx("label", { className: "text-xs", children: "Code" }),
276
- /* @__PURE__ */ jsx(
277
- "input",
278
- {
279
- className: "border rounded w-full px-2 py-1 text-sm font-mono",
280
- value: activeFieldsetConfig.code,
281
- onChange: (event) => handleFieldsetCodeInput(activeFieldsetConfig.code, event.target.value)
282
- }
283
- )
284
- ] }),
285
- /* @__PURE__ */ jsxs("div", { children: [
286
- /* @__PURE__ */ jsx("label", { className: "text-xs", children: "Label" }),
287
- /* @__PURE__ */ jsx(
288
- "input",
289
- {
290
- className: "border rounded w-full px-2 py-1 text-sm",
291
- value: activeFieldsetConfig.label,
292
- onChange: (event) => handleFieldsetPatch(activeFieldsetConfig.code, { label: event.target.value })
293
- }
294
- )
295
- ] }),
296
- /* @__PURE__ */ jsxs("div", { children: [
297
- /* @__PURE__ */ jsx("label", { className: "text-xs", children: "Icon" }),
298
- /* @__PURE__ */ jsxs(
299
- Select,
300
- {
301
- value: activeFieldsetConfig.icon || void 0,
302
- onValueChange: (value) => handleFieldsetPatch(activeFieldsetConfig.code, {
303
- icon: value || void 0
304
- }),
305
- children: [
306
- /* @__PURE__ */ jsx(SelectTrigger, { size: "sm", children: /* @__PURE__ */ jsx(SelectValue, { placeholder: "Default" }) }),
307
- /* @__PURE__ */ jsx(SelectContent, { children: FIELDSET_ICON_OPTIONS.map((option) => /* @__PURE__ */ jsx(SelectItem, { value: option.value, children: option.label }, option.value)) })
308
- ]
309
- }
310
- )
311
- ] }),
312
- /* @__PURE__ */ jsxs("div", { children: [
313
- /* @__PURE__ */ jsx("label", { className: "text-xs", children: "Description" }),
314
- /* @__PURE__ */ jsx(
315
- "input",
316
- {
317
- className: "border rounded w-full px-2 py-1 text-sm",
318
- value: activeFieldsetConfig.description ?? "",
319
- onChange: (event) => handleFieldsetPatch(activeFieldsetConfig.code, { description: event.target.value })
320
- }
321
- )
322
- ] })
286
+ /* @__PURE__ */ jsx(FormField, { label: "Code", children: /* @__PURE__ */ jsx(
287
+ Input,
288
+ {
289
+ size: "sm",
290
+ inputClassName: "font-mono",
291
+ value: activeFieldsetConfig.code,
292
+ onChange: (event) => handleFieldsetCodeInput(activeFieldsetConfig.code, event.target.value)
293
+ }
294
+ ) }),
295
+ /* @__PURE__ */ jsx(FormField, { label: "Label", children: /* @__PURE__ */ jsx(
296
+ Input,
297
+ {
298
+ size: "sm",
299
+ value: activeFieldsetConfig.label,
300
+ onChange: (event) => handleFieldsetPatch(activeFieldsetConfig.code, { label: event.target.value })
301
+ }
302
+ ) }),
303
+ /* @__PURE__ */ jsx(FormField, { label: "Icon", children: /* @__PURE__ */ jsxs(
304
+ Select,
305
+ {
306
+ value: activeFieldsetConfig.icon || void 0,
307
+ onValueChange: (value) => handleFieldsetPatch(activeFieldsetConfig.code, {
308
+ icon: value || void 0
309
+ }),
310
+ children: [
311
+ /* @__PURE__ */ jsx(SelectTrigger, { size: "sm", children: /* @__PURE__ */ jsx(SelectValue, { placeholder: "Default" }) }),
312
+ /* @__PURE__ */ jsx(SelectContent, { children: FIELDSET_ICON_OPTIONS.map((option) => /* @__PURE__ */ jsx(SelectItem, { value: option.value, children: option.label }, option.value)) })
313
+ ]
314
+ }
315
+ ) }),
316
+ /* @__PURE__ */ jsx(FormField, { label: "Description", children: /* @__PURE__ */ jsx(
317
+ Input,
318
+ {
319
+ size: "sm",
320
+ value: activeFieldsetConfig.description ?? "",
321
+ onChange: (event) => handleFieldsetPatch(activeFieldsetConfig.code, { description: event.target.value })
322
+ }
323
+ ) })
323
324
  ] }) : null,
324
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-xs", children: [
325
- /* @__PURE__ */ jsxs("label", { className: "inline-flex items-center gap-2", children: [
326
- /* @__PURE__ */ jsx(
327
- "input",
328
- {
329
- type: "checkbox",
330
- disabled: !canToggleSingleFieldset,
331
- checked: singleFieldsetChecked,
332
- onChange: (event) => onSingleFieldsetPerRecordChange?.(event.target.checked)
333
- }
334
- ),
335
- "Single fieldset per entity"
336
- ] }),
337
- !canToggleSingleFieldset ? /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "(add at least two fieldsets to toggle)" }) : null
325
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
326
+ /* @__PURE__ */ jsx(
327
+ CheckboxField,
328
+ {
329
+ size: "sm",
330
+ disabled: !canToggleSingleFieldset,
331
+ checked: singleFieldsetChecked,
332
+ onCheckedChange: (checked) => onSingleFieldsetPerRecordChange?.(checked === true),
333
+ label: "Single fieldset per entity",
334
+ containerClassName: "text-xs",
335
+ contentClassName: "gap-1 [&_label]:text-xs [&_label]:font-normal"
336
+ }
337
+ ),
338
+ !canToggleSingleFieldset ? /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "(add at least two fieldsets to toggle)" }) : null
338
339
  ] })
339
340
  ] }) : /* @__PURE__ */ jsxs("div", { className: "rounded border border-dashed bg-muted/30 p-4 text-sm text-muted-foreground flex flex-col gap-3", children: [
340
341
  /* @__PURE__ */ jsx("div", { children: "No fieldsets defined yet. Fieldsets let you group custom fields for different variants of the same entity (e.g., Fashion vs. Sport products)." }),
@@ -351,7 +352,7 @@ function FieldDefinitionsEditor({
351
352
  }
352
353
  ) })
353
354
  ] }),
354
- orderNotice?.dirty && /* @__PURE__ */ jsx("div", { className: "sticky top-0 z-sticky -mt-1 -mb-1", children: /* @__PURE__ */ jsx("div", { className: "inline-flex items-center gap-2 text-xs px-2 py-1 rounded border bg-amber-50 text-amber-800 shadow-sm", children: orderNotice?.saving ? "Saving order\u2026" : orderNotice?.message ?? "Reordered \u2014 saving soon" }) }),
355
+ orderNotice?.dirty && /* @__PURE__ */ jsx("div", { className: "sticky top-0 z-sticky -mt-1 -mb-1", children: /* @__PURE__ */ jsx("div", { className: "inline-flex items-center gap-2 text-xs px-2 py-1 rounded border border-status-warning-border bg-status-warning-bg text-status-warning-text shadow-sm", children: orderNotice?.saving ? "Saving order\u2026" : orderNotice?.message ?? "Reordered \u2014 saving soon" }) }),
355
356
  filteredDefinitions.map(({ definition, index }) => {
356
357
  const assignedFieldset = typeof definition.configJson?.fieldset === "string" ? definition.configJson.fieldset : null;
357
358
  const groupOptions = assignedFieldset ? fieldsets.find((fs) => fs.code === assignedFieldset)?.groups ?? [] : availableGroups;
@@ -432,7 +433,7 @@ function FieldDefinitionsEditor({
432
433
  Button,
433
434
  {
434
435
  variant: "link",
435
- className: "h-auto p-0 text-blue-600",
436
+ className: "h-auto p-0",
436
437
  onClick: () => onRestoreField(key),
437
438
  children: key
438
439
  }
@@ -655,49 +656,48 @@ const FieldDefinitionCard = React.memo(function FieldDefinitionCard2({
655
656
  "Drag to reorder"
656
657
  ] }),
657
658
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
658
- /* @__PURE__ */ jsxs("label", { className: "inline-flex items-center gap-2 text-sm", children: [
659
- /* @__PURE__ */ jsx("input", { type: "checkbox", checked: local.isActive !== false, onChange: (event) => {
660
- apply({ isActive: event.target.checked }, true);
661
- } }),
662
- " Active"
663
- ] }),
664
- onTranslate && /* @__PURE__ */ jsx(IconButton, { variant: "outline", size: "sm", onClick: onTranslate, "aria-label": "Translate field", children: /* @__PURE__ */ jsx(Languages, { className: "h-4 w-4" }) }),
665
- /* @__PURE__ */ jsx(IconButton, { variant: "outline", size: "sm", onClick: onRemove, "aria-label": "Remove field", children: /* @__PURE__ */ jsx(Trash2, { className: "h-4 w-4" }) })
666
- ] })
667
- ] }),
668
- /* @__PURE__ */ jsxs("div", { className: "mt-3 grid grid-cols-1 md:grid-cols-12 gap-3 items-center", children: [
669
- /* @__PURE__ */ jsxs("div", { className: "md:col-span-6", children: [
670
- /* @__PURE__ */ jsx("label", { className: "text-xs", children: "Key" }),
671
659
  /* @__PURE__ */ jsx(
672
- "input",
673
- {
674
- className: `rounded w-full px-2 py-1 text-sm font-mono ${error?.key ? "border-red-500 border" : "border"}`,
675
- placeholder: "snake_case",
676
- value: local.key,
677
- onChange: (event) => apply({ key: event.target.value }),
678
- onBlur: commit
679
- }
680
- ),
681
- error?.key ? /* @__PURE__ */ jsx("div", { className: "text-xs text-red-600 mt-1", children: error.key }) : null
682
- ] }),
683
- /* @__PURE__ */ jsxs("div", { className: "md:col-span-6", children: [
684
- /* @__PURE__ */ jsx("label", { className: "text-xs", children: "Kind" }),
685
- /* @__PURE__ */ jsxs(
686
- Select,
660
+ CheckboxField,
687
661
  {
688
- value: local.kind || void 0,
689
- onValueChange: (value) => {
690
- apply({ kind: value ?? "" }, true);
662
+ size: "sm",
663
+ checked: local.isActive !== false,
664
+ onCheckedChange: (checked) => {
665
+ apply({ isActive: checked === true }, true);
691
666
  },
692
- children: [
693
- /* @__PURE__ */ jsx(SelectTrigger, { className: error?.kind ? "border-destructive" : void 0, children: /* @__PURE__ */ jsx(SelectValue, {}) }),
694
- /* @__PURE__ */ jsx(SelectContent, { children: kindOptions.map((option) => /* @__PURE__ */ jsx(SelectItem, { value: option.value, children: option.label }, option.value)) })
695
- ]
667
+ label: "Active",
668
+ containerClassName: "text-sm",
669
+ contentClassName: "gap-1 [&_label]:text-sm [&_label]:font-normal"
696
670
  }
697
671
  ),
698
- error?.kind ? /* @__PURE__ */ jsx("div", { className: "text-xs text-red-600 mt-1", children: error.kind }) : null
672
+ onTranslate && /* @__PURE__ */ jsx(IconButton, { variant: "outline", size: "sm", onClick: onTranslate, "aria-label": "Translate field", children: /* @__PURE__ */ jsx(Languages, { className: "h-4 w-4" }) }),
673
+ /* @__PURE__ */ jsx(IconButton, { variant: "outline", size: "sm", onClick: onRemove, "aria-label": "Remove field", children: /* @__PURE__ */ jsx(Trash2, { className: "h-4 w-4" }) })
699
674
  ] })
700
675
  ] }),
676
+ /* @__PURE__ */ jsxs("div", { className: "mt-3 grid grid-cols-1 md:grid-cols-12 gap-3 items-center", children: [
677
+ /* @__PURE__ */ jsx(FormField, { label: "Key", error: error?.key, className: "md:col-span-6", children: /* @__PURE__ */ jsx(
678
+ Input,
679
+ {
680
+ inputClassName: "font-mono",
681
+ placeholder: "snake_case",
682
+ value: local.key,
683
+ onChange: (event) => apply({ key: event.target.value }),
684
+ onBlur: commit
685
+ }
686
+ ) }),
687
+ /* @__PURE__ */ jsx(FormField, { label: "Kind", error: error?.kind, className: "md:col-span-6", children: /* @__PURE__ */ jsxs(
688
+ Select,
689
+ {
690
+ value: local.kind || void 0,
691
+ onValueChange: (value) => {
692
+ apply({ kind: value ?? "" }, true);
693
+ },
694
+ children: [
695
+ /* @__PURE__ */ jsx(SelectTrigger, { "aria-invalid": !!error?.kind, children: /* @__PURE__ */ jsx(SelectValue, {}) }),
696
+ /* @__PURE__ */ jsx(SelectContent, { children: kindOptions.map((option) => /* @__PURE__ */ jsx(SelectItem, { value: option.value, children: option.label }, option.value)) })
697
+ ]
698
+ }
699
+ ) })
700
+ ] }),
701
701
  allowFieldsetSelection ? /* @__PURE__ */ jsxs("div", { className: "mt-3 grid grid-cols-1 md:grid-cols-2 gap-3", children: [
702
702
  /* @__PURE__ */ jsxs("div", { children: [
703
703
  /* @__PURE__ */ jsx("label", { className: "text-xs", children: "Assign to fieldset" }),
@@ -751,30 +751,22 @@ const FieldDefinitionCard = React.memo(function FieldDefinitionCard2({
751
751
  ] }) : null
752
752
  ] }) : null,
753
753
  /* @__PURE__ */ jsxs("div", { className: "mt-3 grid grid-cols-1 md:grid-cols-2 gap-3", children: [
754
- /* @__PURE__ */ jsxs("div", { children: [
755
- /* @__PURE__ */ jsx("label", { className: "text-xs", children: "Label" }),
756
- /* @__PURE__ */ jsx(
757
- "input",
758
- {
759
- className: "border rounded w-full px-2 py-1 text-sm",
760
- value: typeof local.configJson?.label === "string" ? local.configJson.label : "",
761
- onChange: (event) => apply({ configJson: { ...local.configJson || {}, label: event.target.value } }),
762
- onBlur: commit
763
- }
764
- )
765
- ] }),
766
- /* @__PURE__ */ jsxs("div", { children: [
767
- /* @__PURE__ */ jsx("label", { className: "text-xs", children: "Description" }),
768
- /* @__PURE__ */ jsx(
769
- "input",
770
- {
771
- className: "border rounded w-full px-2 py-1 text-sm",
772
- value: typeof local.configJson?.description === "string" ? local.configJson.description : "",
773
- onChange: (event) => apply({ configJson: { ...local.configJson || {}, description: event.target.value } }),
774
- onBlur: commit
775
- }
776
- )
777
- ] }),
754
+ /* @__PURE__ */ jsx(FormField, { label: "Label", children: /* @__PURE__ */ jsx(
755
+ Input,
756
+ {
757
+ value: typeof local.configJson?.label === "string" ? local.configJson.label : "",
758
+ onChange: (event) => apply({ configJson: { ...local.configJson || {}, label: event.target.value } }),
759
+ onBlur: commit
760
+ }
761
+ ) }),
762
+ /* @__PURE__ */ jsx(FormField, { label: "Description", children: /* @__PURE__ */ jsx(
763
+ Input,
764
+ {
765
+ value: typeof local.configJson?.description === "string" ? local.configJson.description : "",
766
+ onChange: (event) => apply({ configJson: { ...local.configJson || {}, description: event.target.value } }),
767
+ onBlur: commit
768
+ }
769
+ ) }),
778
770
  (local.kind === "text" || local.kind === "multiline") && /* @__PURE__ */ jsxs(Fragment, { children: [
779
771
  /* @__PURE__ */ jsxs("div", { children: [
780
772
  /* @__PURE__ */ jsx("label", { className: "text-xs", children: "Editor" }),
@@ -797,12 +789,19 @@ const FieldDefinitionCard = React.memo(function FieldDefinitionCard2({
797
789
  )
798
790
  ] }),
799
791
  local.kind === "text" && /* @__PURE__ */ jsxs(Fragment, { children: [
800
- /* @__PURE__ */ jsx("div", { className: "md:col-span-2", children: /* @__PURE__ */ jsxs("label", { className: "inline-flex items-center gap-2 text-xs", children: [
801
- /* @__PURE__ */ jsx("input", { type: "checkbox", checked: !!local.configJson?.multi, onChange: (event) => {
802
- apply({ configJson: { ...local.configJson || {}, multi: event.target.checked } }, true);
803
- } }),
804
- " Multiple"
805
- ] }) }),
792
+ /* @__PURE__ */ jsx(
793
+ CheckboxField,
794
+ {
795
+ size: "sm",
796
+ checked: !!local.configJson?.multi,
797
+ onCheckedChange: (checked) => {
798
+ apply({ configJson: { ...local.configJson || {}, multi: checked === true } }, true);
799
+ },
800
+ label: "Multiple",
801
+ containerClassName: "md:col-span-2",
802
+ contentClassName: "gap-1 [&_label]:text-xs [&_label]:font-normal"
803
+ }
804
+ ),
806
805
  !!local.configJson?.multi && /* @__PURE__ */ jsxs("div", { className: "md:col-span-2", children: [
807
806
  /* @__PURE__ */ jsx("label", { className: "text-xs", children: "Multi-select input style" }),
808
807
  /* @__PURE__ */ jsxs(
@@ -843,7 +842,7 @@ const FieldDefinitionCard = React.memo(function FieldDefinitionCard2({
843
842
  {
844
843
  variant: "ghost",
845
844
  size: "xs",
846
- className: "text-red-500 hover:text-red-700",
845
+ className: "text-destructive hover:text-destructive/80",
847
846
  onClick: () => handleRemoveOption(idx),
848
847
  "aria-label": "Remove option",
849
848
  children: /* @__PURE__ */ jsx(Trash2, { className: "h-3.5 w-3.5" })
@@ -868,60 +867,49 @@ const FieldDefinitionCard = React.memo(function FieldDefinitionCard2({
868
867
  ) })
869
868
  ] }),
870
869
  (local.kind === "select" || local.kind === "relation") && /* @__PURE__ */ jsxs(Fragment, { children: [
871
- /* @__PURE__ */ jsxs("div", { children: [
872
- /* @__PURE__ */ jsx("label", { className: "text-xs", children: "Options URL" }),
873
- /* @__PURE__ */ jsx(
874
- "input",
875
- {
876
- className: "border rounded w-full px-2 py-1 text-sm",
877
- placeholder: "/api/...",
878
- value: typeof local.configJson?.optionsUrl === "string" ? local.configJson.optionsUrl : "",
879
- onChange: (event) => apply({ configJson: { ...local.configJson || {}, optionsUrl: event.target.value } }),
880
- onBlur: commit
881
- }
882
- )
883
- ] }),
884
- local.kind === "relation" && /* @__PURE__ */ jsxs("div", { children: [
885
- /* @__PURE__ */ jsx("label", { className: "text-xs", children: "Related Entity ID" }),
886
- /* @__PURE__ */ jsx(
887
- "input",
888
- {
889
- className: "border rounded w-full px-2 py-1 text-sm font-mono",
890
- placeholder: "module:entity",
891
- value: typeof local.configJson?.relatedEntityId === "string" ? local.configJson.relatedEntityId : "",
892
- onChange: (event) => {
893
- const relatedEntityId = event.target.value;
894
- const defOptionsUrl = relatedEntityId ? `/api/entities/relations/options?entityId=${encodeURIComponent(relatedEntityId)}` : "";
895
- apply({
896
- configJson: {
897
- ...local.configJson || {},
898
- relatedEntityId,
899
- optionsUrl: local.configJson?.optionsUrl || defOptionsUrl
900
- }
901
- });
902
- },
903
- onBlur: commit
904
- }
905
- )
906
- ] })
870
+ /* @__PURE__ */ jsx(FormField, { label: "Options URL", children: /* @__PURE__ */ jsx(
871
+ Input,
872
+ {
873
+ placeholder: "/api/...",
874
+ value: typeof local.configJson?.optionsUrl === "string" ? local.configJson.optionsUrl : "",
875
+ onChange: (event) => apply({ configJson: { ...local.configJson || {}, optionsUrl: event.target.value } }),
876
+ onBlur: commit
877
+ }
878
+ ) }),
879
+ local.kind === "relation" && /* @__PURE__ */ jsx(FormField, { label: "Related Entity ID", children: /* @__PURE__ */ jsx(
880
+ Input,
881
+ {
882
+ inputClassName: "font-mono",
883
+ placeholder: "module:entity",
884
+ value: typeof local.configJson?.relatedEntityId === "string" ? local.configJson.relatedEntityId : "",
885
+ onChange: (event) => {
886
+ const relatedEntityId = event.target.value;
887
+ const defOptionsUrl = relatedEntityId ? `/api/entities/relations/options?entityId=${encodeURIComponent(relatedEntityId)}` : "";
888
+ apply({
889
+ configJson: {
890
+ ...local.configJson || {},
891
+ relatedEntityId,
892
+ optionsUrl: local.configJson?.optionsUrl || defOptionsUrl
893
+ }
894
+ });
895
+ },
896
+ onBlur: commit
897
+ }
898
+ ) })
907
899
  ] }),
908
900
  local.kind === "currency" && /* @__PURE__ */ jsxs("div", { className: "md:col-span-2", children: [
909
901
  /* @__PURE__ */ jsx("label", { className: "text-xs", children: "Options source" }),
910
902
  /* @__PURE__ */ jsx("div", { className: "rounded border bg-muted px-2 py-1 text-xs text-muted-foreground", children: "/api/currencies/options" })
911
903
  ] }),
912
- (local.kind === "integer" || local.kind === "float") && /* @__PURE__ */ jsxs("div", { className: "md:col-span-2", children: [
913
- /* @__PURE__ */ jsx("label", { className: "text-xs", children: "Units (optional)" }),
914
- /* @__PURE__ */ jsx(
915
- "input",
916
- {
917
- className: "border rounded w-full px-2 py-1 text-sm",
918
- placeholder: "kg, cm, etc.",
919
- value: typeof local.configJson?.unit === "string" ? local.configJson.unit : "",
920
- onChange: (event) => apply({ configJson: { ...local.configJson || {}, unit: event.target.value } }),
921
- onBlur: commit
922
- }
923
- )
924
- ] })
904
+ (local.kind === "integer" || local.kind === "float") && /* @__PURE__ */ jsx(FormField, { label: "Units (optional)", className: "md:col-span-2", children: /* @__PURE__ */ jsx(
905
+ Input,
906
+ {
907
+ placeholder: "kg, cm, etc.",
908
+ value: typeof local.configJson?.unit === "string" ? local.configJson.unit : "",
909
+ onChange: (event) => apply({ configJson: { ...local.configJson || {}, unit: event.target.value } }),
910
+ onBlur: commit
911
+ }
912
+ ) })
925
913
  ] }),
926
914
  /* @__PURE__ */ jsxs("div", { className: "mt-3 pt-3 border-t", children: [
927
915
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-2", children: [
@@ -980,9 +968,8 @@ const FieldDefinitionCard = React.memo(function FieldDefinitionCard2({
980
968
  }
981
969
  ) }),
982
970
  /* @__PURE__ */ jsx("div", { className: "md:col-span-4", children: /* @__PURE__ */ jsx(
983
- "input",
971
+ Input,
984
972
  {
985
- className: "border rounded w-full px-2 py-1 text-sm",
986
973
  placeholder: rule?.rule === "regex" ? "Pattern (e.g. ^[a-z]+$)" : ["lt", "lte", "gt", "gte"].includes(rule?.rule) ? "Number" : "\u2014",
987
974
  value: rule?.param ?? "",
988
975
  onChange: (event) => {
@@ -999,9 +986,8 @@ const FieldDefinitionCard = React.memo(function FieldDefinitionCard2({
999
986
  }
1000
987
  ) }),
1001
988
  /* @__PURE__ */ jsx("div", { className: "md:col-span-4", children: /* @__PURE__ */ jsx(
1002
- "input",
989
+ Input,
1003
990
  {
1004
- className: "border rounded w-full px-2 py-1 text-sm",
1005
991
  placeholder: "Error message",
1006
992
  value: rule?.message || "",
1007
993
  onChange: (event) => {
@@ -1053,152 +1039,137 @@ const FieldDefinitionCard = React.memo(function FieldDefinitionCard2({
1053
1039
  const currentDefault = local.configJson?.defaultValue;
1054
1040
  if (local.kind === "boolean") {
1055
1041
  const boolDefault = currentDefault === true ? "true" : currentDefault === false ? "false" : "";
1056
- return /* @__PURE__ */ jsxs("div", { className: "mt-3", children: [
1057
- /* @__PURE__ */ jsx("label", { className: "text-xs", children: t("entities.customFields.fields.defaultValue", "Default value") }),
1058
- /* @__PURE__ */ jsxs(
1059
- "select",
1042
+ return /* @__PURE__ */ jsx(FormField, { label: t("entities.customFields.fields.defaultValue", "Default value"), className: "mt-3", children: /* @__PURE__ */ jsxs(
1043
+ Select,
1044
+ {
1045
+ value: boolDefault || DEFAULT_VALUE_NONE,
1046
+ onValueChange: (raw) => {
1047
+ const value = raw === "true" ? true : raw === "false" ? false : void 0;
1048
+ apply({ configJson: { ...local.configJson || {}, defaultValue: value } }, true);
1049
+ },
1050
+ children: [
1051
+ /* @__PURE__ */ jsx(SelectTrigger, { children: /* @__PURE__ */ jsx(SelectValue, {}) }),
1052
+ /* @__PURE__ */ jsxs(SelectContent, { children: [
1053
+ /* @__PURE__ */ jsx(SelectItem, { value: DEFAULT_VALUE_NONE, children: t("entities.customFields.fields.defaultValueNone", "No default") }),
1054
+ /* @__PURE__ */ jsx(SelectItem, { value: "true", children: t("entities.customFields.fields.defaultValueTrue", "Checked (true)") }),
1055
+ /* @__PURE__ */ jsx(SelectItem, { value: "false", children: t("entities.customFields.fields.defaultValueFalse", "Unchecked (false)") })
1056
+ ] })
1057
+ ]
1058
+ }
1059
+ ) });
1060
+ }
1061
+ if (local.kind === "select" && !local.configJson?.multi) {
1062
+ const opts = normalizeCustomFieldOptions(local.configJson?.options || []);
1063
+ if (opts.length > 0) {
1064
+ const noneValue = getDefaultValueNoneOptionValue(opts);
1065
+ return /* @__PURE__ */ jsx(FormField, { label: t("entities.customFields.fields.defaultValue", "Default value"), className: "mt-3", children: /* @__PURE__ */ jsxs(
1066
+ Select,
1060
1067
  {
1061
- className: "border rounded w-full px-2 py-1 text-sm",
1062
- value: boolDefault,
1063
- onChange: (event) => {
1064
- const raw = event.target.value;
1065
- const value = raw === "true" ? true : raw === "false" ? false : void 0;
1066
- apply({ configJson: { ...local.configJson || {}, defaultValue: value } }, true);
1068
+ value: typeof currentDefault === "string" || typeof currentDefault === "number" ? String(currentDefault) : "",
1069
+ onValueChange: (raw) => {
1070
+ if (raw === noneValue) {
1071
+ apply({ configJson: { ...local.configJson || {}, defaultValue: void 0 } }, true);
1072
+ return;
1073
+ }
1074
+ const matched = opts.find((o) => String(o.value) === raw);
1075
+ const typed = matched ? matched.value : raw;
1076
+ apply({ configJson: { ...local.configJson || {}, defaultValue: typed } }, true);
1067
1077
  },
1068
1078
  children: [
1069
- /* @__PURE__ */ jsx("option", { value: "", children: t("entities.customFields.fields.defaultValueNone", "No default") }),
1070
- /* @__PURE__ */ jsx("option", { value: "true", children: t("entities.customFields.fields.defaultValueTrue", "Checked (true)") }),
1071
- /* @__PURE__ */ jsx("option", { value: "false", children: t("entities.customFields.fields.defaultValueFalse", "Unchecked (false)") })
1079
+ /* @__PURE__ */ jsx(SelectTrigger, { children: /* @__PURE__ */ jsx(SelectValue, { placeholder: t("entities.customFields.fields.defaultValueNone", "No default") }) }),
1080
+ /* @__PURE__ */ jsxs(SelectContent, { children: [
1081
+ /* @__PURE__ */ jsx(SelectItem, { value: noneValue, children: t("entities.customFields.fields.defaultValueNone", "No default") }),
1082
+ opts.map((option) => /* @__PURE__ */ jsx(SelectItem, { value: String(option.value), children: option.label }, String(option.value)))
1083
+ ] })
1072
1084
  ]
1073
1085
  }
1074
- )
1075
- ] });
1076
- }
1077
- if (local.kind === "select" && !local.configJson?.multi) {
1078
- const opts = normalizeCustomFieldOptions(local.configJson?.options || []);
1079
- if (opts.length > 0) {
1080
- return /* @__PURE__ */ jsxs("div", { className: "mt-3", children: [
1081
- /* @__PURE__ */ jsx("label", { className: "text-xs", children: t("entities.customFields.fields.defaultValue", "Default value") }),
1082
- /* @__PURE__ */ jsxs(
1083
- "select",
1084
- {
1085
- className: "border rounded w-full px-2 py-1 text-sm",
1086
- value: typeof currentDefault === "string" || typeof currentDefault === "number" ? String(currentDefault) : "",
1087
- onChange: (event) => {
1088
- const raw = event.target.value;
1089
- if (!raw) {
1090
- apply({ configJson: { ...local.configJson || {}, defaultValue: void 0 } }, true);
1091
- return;
1092
- }
1093
- const matched = opts.find((o) => String(o.value) === raw);
1094
- const typed = matched ? matched.value : raw;
1095
- apply({ configJson: { ...local.configJson || {}, defaultValue: typed } }, true);
1096
- },
1097
- children: [
1098
- /* @__PURE__ */ jsx("option", { value: "", children: t("entities.customFields.fields.defaultValueNone", "No default") }),
1099
- opts.map((option) => /* @__PURE__ */ jsx("option", { value: String(option.value), children: option.label }, String(option.value)))
1100
- ]
1101
- }
1102
- )
1103
- ] });
1086
+ ) });
1104
1087
  }
1105
1088
  }
1106
1089
  if (local.kind === "integer" || local.kind === "float") {
1107
- return /* @__PURE__ */ jsxs("div", { className: "mt-3", children: [
1108
- /* @__PURE__ */ jsx("label", { className: "text-xs", children: t("entities.customFields.fields.defaultValue", "Default value") }),
1109
- /* @__PURE__ */ jsx(
1110
- "input",
1111
- {
1112
- type: "number",
1113
- step: local.kind === "integer" ? "1" : "any",
1114
- className: "border rounded w-full px-2 py-1 text-sm",
1115
- value: typeof currentDefault === "number" ? String(currentDefault) : "",
1116
- onChange: (event) => {
1117
- const raw = event.target.value;
1118
- const parsed = raw === "" ? void 0 : local.kind === "integer" ? parseInt(raw, 10) : parseFloat(raw);
1119
- const value = parsed !== void 0 && !isNaN(parsed) ? parsed : void 0;
1120
- apply({ configJson: { ...local.configJson || {}, defaultValue: value } });
1121
- },
1122
- onBlur: commit,
1123
- placeholder: t("entities.customFields.fields.defaultValuePlaceholder", "No default")
1124
- }
1125
- )
1126
- ] });
1090
+ return /* @__PURE__ */ jsx(FormField, { label: t("entities.customFields.fields.defaultValue", "Default value"), className: "mt-3", children: /* @__PURE__ */ jsx(
1091
+ Input,
1092
+ {
1093
+ type: "number",
1094
+ step: local.kind === "integer" ? "1" : "any",
1095
+ value: typeof currentDefault === "number" ? String(currentDefault) : "",
1096
+ onChange: (event) => {
1097
+ const raw = event.target.value;
1098
+ const parsed = raw === "" ? void 0 : local.kind === "integer" ? parseInt(raw, 10) : parseFloat(raw);
1099
+ const value = parsed !== void 0 && !isNaN(parsed) ? parsed : void 0;
1100
+ apply({ configJson: { ...local.configJson || {}, defaultValue: value } });
1101
+ },
1102
+ onBlur: commit,
1103
+ placeholder: t("entities.customFields.fields.defaultValuePlaceholder", "No default")
1104
+ }
1105
+ ) });
1127
1106
  }
1128
1107
  if (["text", "multiline", "date", "datetime", "currency"].includes(local.kind)) {
1129
- return /* @__PURE__ */ jsxs("div", { className: "mt-3", children: [
1130
- /* @__PURE__ */ jsx("label", { className: "text-xs", children: t("entities.customFields.fields.defaultValue", "Default value") }),
1131
- /* @__PURE__ */ jsx(
1132
- "input",
1133
- {
1134
- className: "border rounded w-full px-2 py-1 text-sm",
1135
- value: typeof currentDefault === "string" ? currentDefault : "",
1136
- onChange: (event) => {
1137
- const value = event.target.value;
1138
- apply({ configJson: { ...local.configJson || {}, defaultValue: value || void 0 } });
1139
- },
1140
- onBlur: commit,
1141
- placeholder: t("entities.customFields.fields.defaultValuePlaceholder", "No default")
1142
- }
1143
- )
1144
- ] });
1108
+ return /* @__PURE__ */ jsx(FormField, { label: t("entities.customFields.fields.defaultValue", "Default value"), className: "mt-3", children: /* @__PURE__ */ jsx(
1109
+ Input,
1110
+ {
1111
+ value: typeof currentDefault === "string" ? currentDefault : "",
1112
+ onChange: (event) => {
1113
+ const value = event.target.value;
1114
+ apply({ configJson: { ...local.configJson || {}, defaultValue: value || void 0 } });
1115
+ },
1116
+ onBlur: commit,
1117
+ placeholder: t("entities.customFields.fields.defaultValuePlaceholder", "No default")
1118
+ }
1119
+ ) });
1145
1120
  }
1146
1121
  return null;
1147
1122
  })(),
1148
1123
  /* @__PURE__ */ jsxs("div", { className: "mt-3 pt-2 border-t flex flex-wrap items-center gap-4", children: [
1149
1124
  /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: t("entities.customFields.fields.visibility", "Visibility:") }),
1150
- /* @__PURE__ */ jsxs("label", { className: "inline-flex items-center gap-2 text-xs", children: [
1151
- /* @__PURE__ */ jsx(
1152
- "input",
1153
- {
1154
- type: "checkbox",
1155
- checked: local.configJson?.listVisible !== false,
1156
- onChange: (event) => {
1157
- apply({ configJson: { ...local.configJson || {}, listVisible: event.target.checked } }, true);
1158
- }
1159
- }
1160
- ),
1161
- t("entities.customFields.fields.listVisible", "List")
1162
- ] }),
1163
- /* @__PURE__ */ jsxs("label", { className: "inline-flex items-center gap-2 text-xs", children: [
1164
- /* @__PURE__ */ jsx(
1165
- "input",
1166
- {
1167
- type: "checkbox",
1168
- checked: !!local.configJson?.filterable,
1169
- onChange: (event) => {
1170
- apply({ configJson: { ...local.configJson || {}, filterable: event.target.checked } }, true);
1171
- }
1172
- }
1173
- ),
1174
- t("entities.customFields.fields.filterable", "Filter")
1175
- ] }),
1176
- /* @__PURE__ */ jsxs("label", { className: "inline-flex items-center gap-2 text-xs", children: [
1177
- /* @__PURE__ */ jsx(
1178
- "input",
1179
- {
1180
- type: "checkbox",
1181
- checked: local.configJson?.formEditable !== false,
1182
- onChange: (event) => {
1183
- apply({ configJson: { ...local.configJson || {}, formEditable: event.target.checked } }, true);
1184
- }
1185
- }
1186
- ),
1187
- t("entities.customFields.fields.formEditable", "Form")
1188
- ] }),
1189
- /* @__PURE__ */ jsxs("label", { className: "inline-flex items-center gap-2 text-xs", children: [
1190
- /* @__PURE__ */ jsx(
1191
- "input",
1192
- {
1193
- type: "checkbox",
1194
- checked: !!local.configJson?.encrypted,
1195
- onChange: (event) => {
1196
- apply({ configJson: { ...local.configJson || {}, encrypted: event.target.checked } }, true);
1197
- }
1198
- }
1199
- ),
1200
- t("entities.customFields.fields.encrypted", "Encrypted")
1201
- ] })
1125
+ /* @__PURE__ */ jsx(
1126
+ CheckboxField,
1127
+ {
1128
+ size: "sm",
1129
+ checked: local.configJson?.listVisible !== false,
1130
+ onCheckedChange: (checked) => {
1131
+ apply({ configJson: { ...local.configJson || {}, listVisible: checked === true } }, true);
1132
+ },
1133
+ label: t("entities.customFields.fields.listVisible", "List"),
1134
+ contentClassName: "gap-1 [&_label]:text-xs [&_label]:font-normal"
1135
+ }
1136
+ ),
1137
+ /* @__PURE__ */ jsx(
1138
+ CheckboxField,
1139
+ {
1140
+ size: "sm",
1141
+ checked: !!local.configJson?.filterable,
1142
+ onCheckedChange: (checked) => {
1143
+ apply({ configJson: { ...local.configJson || {}, filterable: checked === true } }, true);
1144
+ },
1145
+ label: t("entities.customFields.fields.filterable", "Filter"),
1146
+ contentClassName: "gap-1 [&_label]:text-xs [&_label]:font-normal"
1147
+ }
1148
+ ),
1149
+ /* @__PURE__ */ jsx(
1150
+ CheckboxField,
1151
+ {
1152
+ size: "sm",
1153
+ checked: local.configJson?.formEditable !== false,
1154
+ onCheckedChange: (checked) => {
1155
+ apply({ configJson: { ...local.configJson || {}, formEditable: checked === true } }, true);
1156
+ },
1157
+ label: t("entities.customFields.fields.formEditable", "Form"),
1158
+ contentClassName: "gap-1 [&_label]:text-xs [&_label]:font-normal"
1159
+ }
1160
+ ),
1161
+ /* @__PURE__ */ jsx(
1162
+ CheckboxField,
1163
+ {
1164
+ size: "sm",
1165
+ checked: !!local.configJson?.encrypted,
1166
+ onCheckedChange: (checked) => {
1167
+ apply({ configJson: { ...local.configJson || {}, encrypted: checked === true } }, true);
1168
+ },
1169
+ label: t("entities.customFields.fields.encrypted", "Encrypted"),
1170
+ contentClassName: "gap-1 [&_label]:text-xs [&_label]:font-normal"
1171
+ }
1172
+ )
1202
1173
  ] })
1203
1174
  ] }),
1204
1175
  /* @__PURE__ */ jsx(
@@ -1215,34 +1186,26 @@ const FieldDefinitionCard = React.memo(function FieldDefinitionCard2({
1215
1186
  /* @__PURE__ */ jsx(DialogDescription, { children: "Provide the stored value and optional label shown to users." })
1216
1187
  ] }),
1217
1188
  /* @__PURE__ */ jsxs("div", { className: "space-y-4 py-2", children: [
1218
- /* @__PURE__ */ jsxs("div", { children: [
1219
- /* @__PURE__ */ jsx("label", { className: "text-xs", children: "Value" }),
1220
- /* @__PURE__ */ jsx(
1221
- "input",
1222
- {
1223
- className: "mt-1 w-full rounded border px-2 py-1 text-sm font-mono",
1224
- placeholder: "unique_value",
1225
- value: optionValueDraft,
1226
- onChange: (event) => {
1227
- setOptionFormError(null);
1228
- setOptionValueDraft(event.target.value);
1229
- }
1230
- }
1231
- ),
1232
- optionFormError ? /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-red-600", children: optionFormError }) : null
1233
- ] }),
1234
- /* @__PURE__ */ jsxs("div", { children: [
1235
- /* @__PURE__ */ jsx("label", { className: "text-xs", children: "Label" }),
1236
- /* @__PURE__ */ jsx(
1237
- "input",
1238
- {
1239
- className: "mt-1 w-full rounded border px-2 py-1 text-sm",
1240
- placeholder: "Label shown to users (optional)",
1241
- value: optionLabelDraft,
1242
- onChange: (event) => setOptionLabelDraft(event.target.value)
1189
+ /* @__PURE__ */ jsx(FormField, { label: "Value", error: optionFormError ?? void 0, children: /* @__PURE__ */ jsx(
1190
+ Input,
1191
+ {
1192
+ inputClassName: "font-mono",
1193
+ placeholder: "unique_value",
1194
+ value: optionValueDraft,
1195
+ onChange: (event) => {
1196
+ setOptionFormError(null);
1197
+ setOptionValueDraft(event.target.value);
1243
1198
  }
1244
- )
1245
- ] })
1199
+ }
1200
+ ) }),
1201
+ /* @__PURE__ */ jsx(FormField, { label: "Label", children: /* @__PURE__ */ jsx(
1202
+ Input,
1203
+ {
1204
+ placeholder: "Label shown to users (optional)",
1205
+ value: optionLabelDraft,
1206
+ onChange: (event) => setOptionLabelDraft(event.target.value)
1207
+ }
1208
+ ) })
1246
1209
  ] }),
1247
1210
  /* @__PURE__ */ jsxs(DialogFooter, { children: [
1248
1211
  /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: handleCloseOptionDialog, children: "Cancel" }),
@@ -1269,47 +1232,35 @@ const FieldDefinitionCard = React.memo(function FieldDefinitionCard2({
1269
1232
  /* @__PURE__ */ jsx(DialogDescription, { children: editingGroupCode ? "Update the selected group for this fieldset." : "Add a reusable group for this fieldset." })
1270
1233
  ] }),
1271
1234
  /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
1272
- /* @__PURE__ */ jsxs("div", { children: [
1273
- /* @__PURE__ */ jsx("label", { className: "text-xs font-medium", children: "Group code" }),
1274
- /* @__PURE__ */ jsx(
1275
- "input",
1276
- {
1277
- className: "mt-1 w-full rounded border px-2 py-1 text-sm font-mono disabled:cursor-not-allowed disabled:bg-muted/50 disabled:text-muted-foreground",
1278
- value: groupDraft.code,
1279
- onChange: (event) => {
1280
- setGroupDraft((prev) => ({ ...prev, code: event.target.value }));
1281
- if (groupError) setGroupError(null);
1282
- },
1283
- disabled: !!editingGroupCode,
1284
- placeholder: "e.g. buying_committee"
1285
- }
1286
- ),
1287
- groupError ? /* @__PURE__ */ jsx("div", { className: "text-xs text-red-600 mt-1", children: groupError }) : null
1288
- ] }),
1289
- /* @__PURE__ */ jsxs("div", { children: [
1290
- /* @__PURE__ */ jsx("label", { className: "text-xs font-medium", children: "Label" }),
1291
- /* @__PURE__ */ jsx(
1292
- "input",
1293
- {
1294
- className: "mt-1 w-full rounded border px-2 py-1 text-sm",
1295
- value: groupDraft.title,
1296
- onChange: (event) => setGroupDraft((prev) => ({ ...prev, title: event.target.value })),
1297
- placeholder: "Buying committee"
1298
- }
1299
- )
1300
- ] }),
1301
- /* @__PURE__ */ jsxs("div", { children: [
1302
- /* @__PURE__ */ jsx("label", { className: "text-xs font-medium", children: "Hint" }),
1303
- /* @__PURE__ */ jsx(
1304
- "input",
1305
- {
1306
- className: "mt-1 w-full rounded border px-2 py-1 text-sm",
1307
- value: groupDraft.hint,
1308
- onChange: (event) => setGroupDraft((prev) => ({ ...prev, hint: event.target.value })),
1309
- placeholder: "Visible to merchandisers"
1310
- }
1311
- )
1312
- ] }),
1235
+ /* @__PURE__ */ jsx(FormField, { label: "Group code", error: groupError ?? void 0, disabled: !!editingGroupCode, children: /* @__PURE__ */ jsx(
1236
+ Input,
1237
+ {
1238
+ inputClassName: "font-mono",
1239
+ value: groupDraft.code,
1240
+ onChange: (event) => {
1241
+ setGroupDraft((prev) => ({ ...prev, code: event.target.value }));
1242
+ if (groupError) setGroupError(null);
1243
+ },
1244
+ disabled: !!editingGroupCode,
1245
+ placeholder: "e.g. buying_committee"
1246
+ }
1247
+ ) }),
1248
+ /* @__PURE__ */ jsx(FormField, { label: "Label", children: /* @__PURE__ */ jsx(
1249
+ Input,
1250
+ {
1251
+ value: groupDraft.title,
1252
+ onChange: (event) => setGroupDraft((prev) => ({ ...prev, title: event.target.value })),
1253
+ placeholder: "Buying committee"
1254
+ }
1255
+ ) }),
1256
+ /* @__PURE__ */ jsx(FormField, { label: "Hint", children: /* @__PURE__ */ jsx(
1257
+ Input,
1258
+ {
1259
+ value: groupDraft.hint,
1260
+ onChange: (event) => setGroupDraft((prev) => ({ ...prev, hint: event.target.value })),
1261
+ placeholder: "Visible to merchandisers"
1262
+ }
1263
+ ) }),
1313
1264
  currentFieldsetValue && groupOptions.length > 0 ? /* @__PURE__ */ jsxs("div", { children: [
1314
1265
  /* @__PURE__ */ jsx("div", { className: "text-xs font-medium mb-1", children: "Existing groups" }),
1315
1266
  /* @__PURE__ */ jsx("div", { className: "space-y-2 max-h-40 overflow-y-auto", children: groupOptions.map((group) => /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between rounded border px-3 py-2 text-sm", children: [
@@ -1335,7 +1286,7 @@ const FieldDefinitionCard = React.memo(function FieldDefinitionCard2({
1335
1286
  {
1336
1287
  variant: "ghost",
1337
1288
  size: "xs",
1338
- className: "text-red-500 hover:text-red-600",
1289
+ className: "text-destructive hover:text-destructive/80",
1339
1290
  onClick: () => handleRemoveGroupEntry(group.code),
1340
1291
  "aria-label": `Delete ${group.code}`,
1341
1292
  children: /* @__PURE__ */ jsx(Trash2, { className: "h-4 w-4" })