@open-mercato/ui 0.4.2-canary-c02407ff85

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 (319) hide show
  1. package/build.mjs +62 -0
  2. package/dist/backend/AppShell.js +902 -0
  3. package/dist/backend/AppShell.js.map +7 -0
  4. package/dist/backend/ConfirmDialog.js +17 -0
  5. package/dist/backend/ConfirmDialog.js.map +7 -0
  6. package/dist/backend/ContextHelp.js +31 -0
  7. package/dist/backend/ContextHelp.js.map +7 -0
  8. package/dist/backend/CrudForm.js +2028 -0
  9. package/dist/backend/CrudForm.js.map +7 -0
  10. package/dist/backend/DataTable.js +1363 -0
  11. package/dist/backend/DataTable.js.map +7 -0
  12. package/dist/backend/EmptyState.js +52 -0
  13. package/dist/backend/EmptyState.js.map +7 -0
  14. package/dist/backend/FilterBar.js +140 -0
  15. package/dist/backend/FilterBar.js.map +7 -0
  16. package/dist/backend/FilterOverlay.js +279 -0
  17. package/dist/backend/FilterOverlay.js.map +7 -0
  18. package/dist/backend/FlashMessages.js +66 -0
  19. package/dist/backend/FlashMessages.js.map +7 -0
  20. package/dist/backend/JsonBuilder.js +322 -0
  21. package/dist/backend/JsonBuilder.js.map +7 -0
  22. package/dist/backend/JsonDisplay.js +203 -0
  23. package/dist/backend/JsonDisplay.js.map +7 -0
  24. package/dist/backend/Page.js +27 -0
  25. package/dist/backend/Page.js.map +7 -0
  26. package/dist/backend/PerspectiveSidebar.js +282 -0
  27. package/dist/backend/PerspectiveSidebar.js.map +7 -0
  28. package/dist/backend/RowActions.js +148 -0
  29. package/dist/backend/RowActions.js.map +7 -0
  30. package/dist/backend/TruncatedCell.js +92 -0
  31. package/dist/backend/TruncatedCell.js.map +7 -0
  32. package/dist/backend/UserMenu.js +107 -0
  33. package/dist/backend/UserMenu.js.map +7 -0
  34. package/dist/backend/ValueIcons.js +34 -0
  35. package/dist/backend/ValueIcons.js.map +7 -0
  36. package/dist/backend/custom-fields/FieldDefinitionsEditor.js +1264 -0
  37. package/dist/backend/custom-fields/FieldDefinitionsEditor.js.map +7 -0
  38. package/dist/backend/custom-fields/FieldDefinitionsManager.js +332 -0
  39. package/dist/backend/custom-fields/FieldDefinitionsManager.js.map +7 -0
  40. package/dist/backend/dashboard/DashboardScreen.js +578 -0
  41. package/dist/backend/dashboard/DashboardScreen.js.map +7 -0
  42. package/dist/backend/dashboard/index.js +5 -0
  43. package/dist/backend/dashboard/index.js.map +7 -0
  44. package/dist/backend/dashboard/widgetRegistry.js +55 -0
  45. package/dist/backend/dashboard/widgetRegistry.js.map +7 -0
  46. package/dist/backend/detail/ActivitiesSection.js +962 -0
  47. package/dist/backend/detail/ActivitiesSection.js.map +7 -0
  48. package/dist/backend/detail/AddressEditor.js +413 -0
  49. package/dist/backend/detail/AddressEditor.js.map +7 -0
  50. package/dist/backend/detail/AddressTiles.js +437 -0
  51. package/dist/backend/detail/AddressTiles.js.map +7 -0
  52. package/dist/backend/detail/AddressesSection.js +264 -0
  53. package/dist/backend/detail/AddressesSection.js.map +7 -0
  54. package/dist/backend/detail/AttachmentDeleteDialog.js +41 -0
  55. package/dist/backend/detail/AttachmentDeleteDialog.js.map +7 -0
  56. package/dist/backend/detail/AttachmentMetadataDialog.js +517 -0
  57. package/dist/backend/detail/AttachmentMetadataDialog.js.map +7 -0
  58. package/dist/backend/detail/AttachmentsSection.js +367 -0
  59. package/dist/backend/detail/AttachmentsSection.js.map +7 -0
  60. package/dist/backend/detail/CustomDataSection.js +433 -0
  61. package/dist/backend/detail/CustomDataSection.js.map +7 -0
  62. package/dist/backend/detail/DetailFieldsSection.js +75 -0
  63. package/dist/backend/detail/DetailFieldsSection.js.map +7 -0
  64. package/dist/backend/detail/ErrorMessage.js +28 -0
  65. package/dist/backend/detail/ErrorMessage.js.map +7 -0
  66. package/dist/backend/detail/InlineEditors.js +681 -0
  67. package/dist/backend/detail/InlineEditors.js.map +7 -0
  68. package/dist/backend/detail/LoadingMessage.js +14 -0
  69. package/dist/backend/detail/LoadingMessage.js.map +7 -0
  70. package/dist/backend/detail/NotesSection.js +1032 -0
  71. package/dist/backend/detail/NotesSection.js.map +7 -0
  72. package/dist/backend/detail/TabEmptyState.js +25 -0
  73. package/dist/backend/detail/TabEmptyState.js.map +7 -0
  74. package/dist/backend/detail/TagsSection.js +254 -0
  75. package/dist/backend/detail/TagsSection.js.map +7 -0
  76. package/dist/backend/detail/addressFormat.js +77 -0
  77. package/dist/backend/detail/addressFormat.js.map +7 -0
  78. package/dist/backend/detail/index.js +34 -0
  79. package/dist/backend/detail/index.js.map +7 -0
  80. package/dist/backend/fields/registry.generated.js +8 -0
  81. package/dist/backend/fields/registry.generated.js.map +7 -0
  82. package/dist/backend/fields/registry.js +29 -0
  83. package/dist/backend/fields/registry.js.map +7 -0
  84. package/dist/backend/indexes/PartialIndexBanner.js +58 -0
  85. package/dist/backend/indexes/PartialIndexBanner.js.map +7 -0
  86. package/dist/backend/indexes/store.js +62 -0
  87. package/dist/backend/indexes/store.js.map +7 -0
  88. package/dist/backend/injection/InjectionSpot.js +179 -0
  89. package/dist/backend/injection/InjectionSpot.js.map +7 -0
  90. package/dist/backend/injection/PageInjectionBoundary.js +26 -0
  91. package/dist/backend/injection/PageInjectionBoundary.js.map +7 -0
  92. package/dist/backend/injection/helpers.js +26 -0
  93. package/dist/backend/injection/helpers.js.map +7 -0
  94. package/dist/backend/injection/widgetRegistry.js +55 -0
  95. package/dist/backend/injection/widgetRegistry.js.map +7 -0
  96. package/dist/backend/inputs/ComboboxInput.js +225 -0
  97. package/dist/backend/inputs/ComboboxInput.js.map +7 -0
  98. package/dist/backend/inputs/LookupSelect.js +191 -0
  99. package/dist/backend/inputs/LookupSelect.js.map +7 -0
  100. package/dist/backend/inputs/PhoneNumberField.js +100 -0
  101. package/dist/backend/inputs/PhoneNumberField.js.map +7 -0
  102. package/dist/backend/inputs/SwitchableMarkdownInput.js +92 -0
  103. package/dist/backend/inputs/SwitchableMarkdownInput.js.map +7 -0
  104. package/dist/backend/inputs/TagsInput.js +222 -0
  105. package/dist/backend/inputs/TagsInput.js.map +7 -0
  106. package/dist/backend/inputs/index.js +6 -0
  107. package/dist/backend/inputs/index.js.map +7 -0
  108. package/dist/backend/operations/LastOperationBanner.js +80 -0
  109. package/dist/backend/operations/LastOperationBanner.js.map +7 -0
  110. package/dist/backend/operations/store.js +183 -0
  111. package/dist/backend/operations/store.js.map +7 -0
  112. package/dist/backend/schedule/ScheduleAgenda.js +107 -0
  113. package/dist/backend/schedule/ScheduleAgenda.js.map +7 -0
  114. package/dist/backend/schedule/ScheduleGrid.js +107 -0
  115. package/dist/backend/schedule/ScheduleGrid.js.map +7 -0
  116. package/dist/backend/schedule/ScheduleToolbar.js +166 -0
  117. package/dist/backend/schedule/ScheduleToolbar.js.map +7 -0
  118. package/dist/backend/schedule/ScheduleView.js +165 -0
  119. package/dist/backend/schedule/ScheduleView.js.map +7 -0
  120. package/dist/backend/schedule/index.js +6 -0
  121. package/dist/backend/schedule/index.js.map +7 -0
  122. package/dist/backend/schedule/recurrence.js +83 -0
  123. package/dist/backend/schedule/recurrence.js.map +7 -0
  124. package/dist/backend/schedule/types.js +1 -0
  125. package/dist/backend/schedule/types.js.map +7 -0
  126. package/dist/backend/upgrades/UpgradeActionBanner.js +91 -0
  127. package/dist/backend/upgrades/UpgradeActionBanner.js.map +7 -0
  128. package/dist/backend/utils/api.js +127 -0
  129. package/dist/backend/utils/api.js.map +7 -0
  130. package/dist/backend/utils/apiCall.js +48 -0
  131. package/dist/backend/utils/apiCall.js.map +7 -0
  132. package/dist/backend/utils/crud.js +126 -0
  133. package/dist/backend/utils/crud.js.map +7 -0
  134. package/dist/backend/utils/customFieldColumns.js +56 -0
  135. package/dist/backend/utils/customFieldColumns.js.map +7 -0
  136. package/dist/backend/utils/customFieldDefs.js +143 -0
  137. package/dist/backend/utils/customFieldDefs.js.map +7 -0
  138. package/dist/backend/utils/customFieldFilters.js +126 -0
  139. package/dist/backend/utils/customFieldFilters.js.map +7 -0
  140. package/dist/backend/utils/customFieldForms.js +162 -0
  141. package/dist/backend/utils/customFieldForms.js.map +7 -0
  142. package/dist/backend/utils/customFieldValues.js +26 -0
  143. package/dist/backend/utils/customFieldValues.js.map +7 -0
  144. package/dist/backend/utils/flash.js +16 -0
  145. package/dist/backend/utils/flash.js.map +7 -0
  146. package/dist/backend/utils/nav.js +185 -0
  147. package/dist/backend/utils/nav.js.map +7 -0
  148. package/dist/backend/utils/serverErrors.js +230 -0
  149. package/dist/backend/utils/serverErrors.js.map +7 -0
  150. package/dist/frontend/AuthFooter.js +23 -0
  151. package/dist/frontend/AuthFooter.js.map +7 -0
  152. package/dist/frontend/LanguageSwitcher.js +57 -0
  153. package/dist/frontend/LanguageSwitcher.js.map +7 -0
  154. package/dist/frontend/Layout.js +14 -0
  155. package/dist/frontend/Layout.js.map +7 -0
  156. package/dist/index.js +32 -0
  157. package/dist/index.js.map +7 -0
  158. package/dist/primitives/DataLoader.js +67 -0
  159. package/dist/primitives/DataLoader.js.map +7 -0
  160. package/dist/primitives/ErrorNotice.js +20 -0
  161. package/dist/primitives/ErrorNotice.js.map +7 -0
  162. package/dist/primitives/alert.js +38 -0
  163. package/dist/primitives/alert.js.map +7 -0
  164. package/dist/primitives/badge.js +28 -0
  165. package/dist/primitives/badge.js.map +7 -0
  166. package/dist/primitives/button.js +44 -0
  167. package/dist/primitives/button.js.map +7 -0
  168. package/dist/primitives/card.js +91 -0
  169. package/dist/primitives/card.js.map +7 -0
  170. package/dist/primitives/checkbox.js +28 -0
  171. package/dist/primitives/checkbox.js.map +7 -0
  172. package/dist/primitives/dialog.js +90 -0
  173. package/dist/primitives/dialog.js.map +7 -0
  174. package/dist/primitives/input.js +22 -0
  175. package/dist/primitives/input.js.map +7 -0
  176. package/dist/primitives/label.js +21 -0
  177. package/dist/primitives/label.js.map +7 -0
  178. package/dist/primitives/separator.js +9 -0
  179. package/dist/primitives/separator.js.map +7 -0
  180. package/dist/primitives/spinner.js +24 -0
  181. package/dist/primitives/spinner.js.map +7 -0
  182. package/dist/primitives/switch.js +80 -0
  183. package/dist/primitives/switch.js.map +7 -0
  184. package/dist/primitives/table.js +29 -0
  185. package/dist/primitives/table.js.map +7 -0
  186. package/dist/primitives/tabs.js +87 -0
  187. package/dist/primitives/tabs.js.map +7 -0
  188. package/dist/primitives/textarea.js +21 -0
  189. package/dist/primitives/textarea.js.map +7 -0
  190. package/dist/primitives/tooltip.js +60 -0
  191. package/dist/primitives/tooltip.js.map +7 -0
  192. package/dist/theme/QueryProvider.js +44 -0
  193. package/dist/theme/QueryProvider.js.map +7 -0
  194. package/dist/theme/ThemeProvider.js +95 -0
  195. package/dist/theme/ThemeProvider.js.map +7 -0
  196. package/dist/theme/ThemeToggle.js +88 -0
  197. package/dist/theme/ThemeToggle.js.map +7 -0
  198. package/dist/theme/index.js +10 -0
  199. package/dist/theme/index.js.map +7 -0
  200. package/dist/types/react-big-calendar.d.js +1 -0
  201. package/dist/types/react-big-calendar.d.js.map +7 -0
  202. package/jest.config.cjs +23 -0
  203. package/jest.setup.ts +55 -0
  204. package/package.json +105 -0
  205. package/src/backend/AppShell.tsx +1096 -0
  206. package/src/backend/ConfirmDialog.tsx +19 -0
  207. package/src/backend/ContextHelp.tsx +38 -0
  208. package/src/backend/CrudForm.tsx +2503 -0
  209. package/src/backend/DataTable.tsx +1730 -0
  210. package/src/backend/EmptyState.tsx +65 -0
  211. package/src/backend/FilterBar.tsx +161 -0
  212. package/src/backend/FilterOverlay.tsx +328 -0
  213. package/src/backend/FlashMessages.tsx +82 -0
  214. package/src/backend/JsonBuilder.tsx +362 -0
  215. package/src/backend/JsonDisplay.tsx +254 -0
  216. package/src/backend/Page.tsx +30 -0
  217. package/src/backend/PerspectiveSidebar.tsx +337 -0
  218. package/src/backend/RowActions.tsx +151 -0
  219. package/src/backend/TruncatedCell.tsx +133 -0
  220. package/src/backend/UserMenu.tsx +118 -0
  221. package/src/backend/ValueIcons.tsx +48 -0
  222. package/src/backend/__tests__/AppShell.test.tsx +115 -0
  223. package/src/backend/__tests__/CrudForm.render.test.tsx +30 -0
  224. package/src/backend/__tests__/DataTable.render.test.tsx +48 -0
  225. package/src/backend/__tests__/custom-field-filters.test.ts +72 -0
  226. package/src/backend/__tests__/custom-field-forms.test.ts +54 -0
  227. package/src/backend/__tests__/serverErrors.test.ts +83 -0
  228. package/src/backend/custom-fields/FieldDefinitionsEditor.tsx +1292 -0
  229. package/src/backend/custom-fields/FieldDefinitionsManager.tsx +381 -0
  230. package/src/backend/dashboard/DashboardScreen.tsx +684 -0
  231. package/src/backend/dashboard/__tests__/DashboardScreen.test.tsx +112 -0
  232. package/src/backend/dashboard/index.ts +1 -0
  233. package/src/backend/dashboard/widgetRegistry.ts +68 -0
  234. package/src/backend/detail/ActivitiesSection.tsx +1284 -0
  235. package/src/backend/detail/AddressEditor.tsx +472 -0
  236. package/src/backend/detail/AddressTiles.tsx +587 -0
  237. package/src/backend/detail/AddressesSection.tsx +346 -0
  238. package/src/backend/detail/AttachmentDeleteDialog.tsx +56 -0
  239. package/src/backend/detail/AttachmentMetadataDialog.tsx +672 -0
  240. package/src/backend/detail/AttachmentsSection.tsx +414 -0
  241. package/src/backend/detail/CustomDataSection.tsx +530 -0
  242. package/src/backend/detail/DetailFieldsSection.tsx +147 -0
  243. package/src/backend/detail/ErrorMessage.tsx +32 -0
  244. package/src/backend/detail/InlineEditors.tsx +877 -0
  245. package/src/backend/detail/LoadingMessage.tsx +14 -0
  246. package/src/backend/detail/NotesSection.tsx +1275 -0
  247. package/src/backend/detail/TabEmptyState.tsx +48 -0
  248. package/src/backend/detail/TagsSection.tsx +314 -0
  249. package/src/backend/detail/addressFormat.tsx +121 -0
  250. package/src/backend/detail/index.ts +44 -0
  251. package/src/backend/fields/registry.generated.ts +8 -0
  252. package/src/backend/fields/registry.ts +38 -0
  253. package/src/backend/indexes/PartialIndexBanner.tsx +68 -0
  254. package/src/backend/indexes/store.ts +88 -0
  255. package/src/backend/injection/InjectionSpot.tsx +236 -0
  256. package/src/backend/injection/PageInjectionBoundary.tsx +31 -0
  257. package/src/backend/injection/helpers.ts +35 -0
  258. package/src/backend/injection/widgetRegistry.ts +68 -0
  259. package/src/backend/inputs/ComboboxInput.tsx +269 -0
  260. package/src/backend/inputs/LookupSelect.tsx +247 -0
  261. package/src/backend/inputs/PhoneNumberField.tsx +129 -0
  262. package/src/backend/inputs/SwitchableMarkdownInput.tsx +128 -0
  263. package/src/backend/inputs/TagsInput.tsx +259 -0
  264. package/src/backend/inputs/index.ts +5 -0
  265. package/src/backend/operations/LastOperationBanner.tsx +85 -0
  266. package/src/backend/operations/__tests__/LastOperationBanner.test.tsx +99 -0
  267. package/src/backend/operations/store.ts +230 -0
  268. package/src/backend/schedule/ScheduleAgenda.tsx +136 -0
  269. package/src/backend/schedule/ScheduleGrid.tsx +136 -0
  270. package/src/backend/schedule/ScheduleToolbar.tsx +178 -0
  271. package/src/backend/schedule/ScheduleView.tsx +198 -0
  272. package/src/backend/schedule/index.ts +5 -0
  273. package/src/backend/schedule/recurrence.ts +99 -0
  274. package/src/backend/schedule/types.ts +26 -0
  275. package/src/backend/upgrades/UpgradeActionBanner.tsx +128 -0
  276. package/src/backend/utils/__tests__/apiCall.test.ts +109 -0
  277. package/src/backend/utils/__tests__/crud.test.ts +87 -0
  278. package/src/backend/utils/__tests__/customFieldDefs.test.ts +25 -0
  279. package/src/backend/utils/__tests__/customFieldValues.test.ts +35 -0
  280. package/src/backend/utils/api.ts +149 -0
  281. package/src/backend/utils/apiCall.ts +96 -0
  282. package/src/backend/utils/crud.ts +174 -0
  283. package/src/backend/utils/customFieldColumns.ts +71 -0
  284. package/src/backend/utils/customFieldDefs.ts +245 -0
  285. package/src/backend/utils/customFieldFilters.ts +145 -0
  286. package/src/backend/utils/customFieldForms.ts +196 -0
  287. package/src/backend/utils/customFieldValues.ts +41 -0
  288. package/src/backend/utils/flash.ts +17 -0
  289. package/src/backend/utils/nav.ts +238 -0
  290. package/src/backend/utils/serverErrors.ts +302 -0
  291. package/src/frontend/AuthFooter.tsx +29 -0
  292. package/src/frontend/LanguageSwitcher.tsx +66 -0
  293. package/src/frontend/Layout.tsx +13 -0
  294. package/src/index.ts +32 -0
  295. package/src/primitives/DataLoader.tsx +92 -0
  296. package/src/primitives/ErrorNotice.tsx +26 -0
  297. package/src/primitives/alert.tsx +52 -0
  298. package/src/primitives/badge.tsx +31 -0
  299. package/src/primitives/button.tsx +47 -0
  300. package/src/primitives/card.tsx +92 -0
  301. package/src/primitives/checkbox.tsx +28 -0
  302. package/src/primitives/dialog.tsx +110 -0
  303. package/src/primitives/input.tsx +20 -0
  304. package/src/primitives/label.tsx +18 -0
  305. package/src/primitives/separator.tsx +7 -0
  306. package/src/primitives/spinner.tsx +27 -0
  307. package/src/primitives/switch.tsx +86 -0
  308. package/src/primitives/table.tsx +27 -0
  309. package/src/primitives/tabs.tsx +128 -0
  310. package/src/primitives/textarea.tsx +20 -0
  311. package/src/primitives/tooltip.tsx +85 -0
  312. package/src/theme/QueryProvider.tsx +46 -0
  313. package/src/theme/ThemeProvider.tsx +120 -0
  314. package/src/theme/ThemeToggle.tsx +88 -0
  315. package/src/theme/index.ts +3 -0
  316. package/src/types/react-big-calendar.d.ts +16 -0
  317. package/tsconfig.build.json +11 -0
  318. package/tsconfig.json +9 -0
  319. package/watch.mjs +6 -0
@@ -0,0 +1,1264 @@
1
+ "use client";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { Cog, GripVertical, Pencil, Plus, Trash2 } from "lucide-react";
5
+ import { CUSTOM_FIELD_KINDS } from "@open-mercato/shared/modules/entities/kinds";
6
+ import { FieldRegistry } from "../fields/registry.js";
7
+ import { slugify } from "@open-mercato/shared/lib/slugify";
8
+ import {
9
+ Dialog,
10
+ DialogContent,
11
+ DialogDescription,
12
+ DialogFooter,
13
+ DialogHeader,
14
+ DialogTitle
15
+ } from "../../primitives/dialog.js";
16
+ import {
17
+ normalizeCustomFieldOptions
18
+ } from "@open-mercato/shared/modules/entities/options";
19
+ const DEFAULT_KIND_OPTIONS = CUSTOM_FIELD_KINDS.map((k) => ({
20
+ value: k,
21
+ label: k.charAt(0).toUpperCase() + k.slice(1)
22
+ }));
23
+ const FIELDSET_ICON_OPTIONS = [
24
+ { value: "layers", label: "Layers" },
25
+ { value: "tag", label: "Tag" },
26
+ { value: "sparkles", label: "Sparkles" },
27
+ { value: "package", label: "Package" },
28
+ { value: "shirt", label: "Shirt" },
29
+ { value: "grid", label: "Grid" },
30
+ { value: "shoppingBag", label: "Shopping bag" },
31
+ { value: "shoppingCart", label: "Shopping cart" },
32
+ { value: "store", label: "Store" },
33
+ { value: "users", label: "Users" },
34
+ { value: "briefcase", label: "Briefcase" },
35
+ { value: "building", label: "Building" },
36
+ { value: "bookOpen", label: "Book open" },
37
+ { value: "bookmark", label: "Bookmark" },
38
+ { value: "camera", label: "Camera" },
39
+ { value: "car", label: "Car" },
40
+ { value: "clock", label: "Clock" },
41
+ { value: "cloud", label: "Cloud" },
42
+ { value: "compass", label: "Compass" },
43
+ { value: "creditCard", label: "Credit card" },
44
+ { value: "database", label: "Database" },
45
+ { value: "flame", label: "Flame" },
46
+ { value: "gift", label: "Gift" },
47
+ { value: "globe", label: "Globe" },
48
+ { value: "heart", label: "Heart" },
49
+ { value: "key", label: "Key" },
50
+ { value: "map", label: "Map" },
51
+ { value: "palette", label: "Palette" },
52
+ { value: "shield", label: "Shield" },
53
+ { value: "star", label: "Star" },
54
+ { value: "truck", label: "Truck" },
55
+ { value: "zap", label: "Zap" },
56
+ { value: "coins", label: "Coins" }
57
+ ];
58
+ function slugifyFieldsetCode(value) {
59
+ return slugify(value, { replacement: "", allowedChars: "_-" });
60
+ }
61
+ function ensureUniqueFieldsetCode(base, existing) {
62
+ const sanitizedBase = slugifyFieldsetCode(base) || "fieldset";
63
+ let candidate = sanitizedBase;
64
+ let counter = 1;
65
+ const existingCodes = new Set(existing.map((fs) => fs.code));
66
+ while (existingCodes.has(candidate)) {
67
+ counter += 1;
68
+ candidate = `${sanitizedBase}_${counter}`;
69
+ }
70
+ return candidate;
71
+ }
72
+ function normalizeGroupValue(raw) {
73
+ if (!raw) return null;
74
+ if (typeof raw === "string") {
75
+ const code2 = raw.trim();
76
+ return code2 ? { code: code2 } : null;
77
+ }
78
+ if (typeof raw !== "object") return null;
79
+ const entry = raw;
80
+ const code = typeof entry.code === "string" ? entry.code.trim() : "";
81
+ if (!code) return null;
82
+ const group = { code };
83
+ if (typeof entry.title === "string" && entry.title.trim()) group.title = entry.title.trim();
84
+ if (typeof entry.hint === "string" && entry.hint.trim()) group.hint = entry.hint.trim();
85
+ return group;
86
+ }
87
+ function FieldDefinitionsEditor({
88
+ definitions,
89
+ errors,
90
+ deletedKeys,
91
+ kindOptions = DEFAULT_KIND_OPTIONS,
92
+ orderNotice,
93
+ infoNote = /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground mt-2", children: "Supported kinds: text, multiline, integer, float, boolean, select (with options/optionsUrl), currency (fixed currencies list), relation (with related entity and options URL)." }),
94
+ addButtonLabel = "Add Field",
95
+ fieldsets = [],
96
+ activeFieldset = null,
97
+ onActiveFieldsetChange,
98
+ onFieldsetsChange,
99
+ onFieldsetCodeChange,
100
+ onFieldsetRemoved,
101
+ singleFieldsetPerRecord,
102
+ onSingleFieldsetPerRecordChange,
103
+ onAddField,
104
+ onRemoveField,
105
+ onDefinitionChange,
106
+ onRestoreField,
107
+ onReorder,
108
+ listRef,
109
+ listProps,
110
+ translate
111
+ }) {
112
+ const dragIndex = React.useRef(null);
113
+ const hasFieldsets = fieldsets.length > 0;
114
+ const t = React.useCallback((key, fallback) => translate ? translate(key, fallback) : fallback, [translate]);
115
+ const resolvedActiveFieldset = React.useMemo(() => {
116
+ if (!hasFieldsets) return activeFieldset ?? null;
117
+ if (activeFieldset === null) return null;
118
+ return fieldsets.some((fs) => fs.code === activeFieldset) ? activeFieldset : fieldsets[0]?.code ?? null;
119
+ }, [activeFieldset, fieldsets, hasFieldsets]);
120
+ const filteredDefinitions = React.useMemo(
121
+ () => definitions.map((definition, index) => ({ definition, index })).filter(({ definition }) => {
122
+ if (!hasFieldsets) return true;
123
+ const assigned = typeof definition.configJson?.fieldset === "string" ? definition.configJson.fieldset : void 0;
124
+ if (!resolvedActiveFieldset) return !assigned;
125
+ return assigned === resolvedActiveFieldset;
126
+ }),
127
+ [definitions, hasFieldsets, resolvedActiveFieldset]
128
+ );
129
+ const activeFieldsetConfig = hasFieldsets && resolvedActiveFieldset ? fieldsets.find((fs) => fs.code === resolvedActiveFieldset) ?? null : null;
130
+ const handleActiveFieldsetChange = (value) => {
131
+ if (!onActiveFieldsetChange) return;
132
+ onActiveFieldsetChange(value ? value : null);
133
+ };
134
+ const handleFieldsetPatch = (code, patch) => {
135
+ if (!onFieldsetsChange) return;
136
+ const next = fieldsets.map((fs) => fs.code === code ? { ...fs, ...patch } : fs);
137
+ onFieldsetsChange(next);
138
+ };
139
+ const handleFieldsetCodeInput = (code, nextValue) => {
140
+ if (!onFieldsetsChange) return;
141
+ const target = fieldsets.find((fs) => fs.code === code);
142
+ if (!target) return;
143
+ const sanitized = slugifyFieldsetCode(nextValue);
144
+ if (!sanitized) return;
145
+ const next = fieldsets.map((fs) => fs.code === code ? { ...fs, code: sanitized } : fs);
146
+ onFieldsetsChange(next);
147
+ onFieldsetCodeChange?.(code, sanitized);
148
+ onActiveFieldsetChange?.(sanitized);
149
+ };
150
+ const handleAddFieldset = () => {
151
+ if (!onFieldsetsChange) return;
152
+ const code = ensureUniqueFieldsetCode(`fieldset_${fieldsets.length + 1}`, fieldsets);
153
+ const nextFieldsets = [...fieldsets, { code, label: "New fieldset", icon: "layers" }];
154
+ onFieldsetsChange(nextFieldsets);
155
+ onActiveFieldsetChange?.(code);
156
+ };
157
+ const handleRemoveFieldset = () => {
158
+ if (!onFieldsetsChange) return;
159
+ if (!resolvedActiveFieldset) return;
160
+ if (!window.confirm(`Delete fieldset "${resolvedActiveFieldset}"? This will move its fields to Unassigned.`)) return;
161
+ const next = fieldsets.filter((fs) => fs.code !== resolvedActiveFieldset);
162
+ onFieldsetsChange(next);
163
+ onFieldsetRemoved?.(resolvedActiveFieldset);
164
+ const fallback = next[0]?.code ?? null;
165
+ onActiveFieldsetChange?.(fallback);
166
+ };
167
+ const registerGroup = React.useCallback(
168
+ (fieldsetCode, group) => {
169
+ if (!onFieldsetsChange || !fieldsetCode) return;
170
+ const next = fieldsets.map((fs) => {
171
+ if (fs.code !== fieldsetCode) return fs;
172
+ const list = Array.isArray(fs.groups) ? fs.groups : [];
173
+ const existingIndex = list.findIndex((entry) => entry.code === group.code);
174
+ if (existingIndex >= 0) {
175
+ const updated = [...list];
176
+ updated[existingIndex] = { ...list[existingIndex], ...group };
177
+ return { ...fs, groups: updated };
178
+ }
179
+ return { ...fs, groups: [...list, group] };
180
+ });
181
+ onFieldsetsChange(next);
182
+ },
183
+ [fieldsets, onFieldsetsChange]
184
+ );
185
+ const removeGroup = React.useCallback(
186
+ (fieldsetCode, groupCode) => {
187
+ if (!onFieldsetsChange || !fieldsetCode || !groupCode) return;
188
+ const next = fieldsets.map((fs) => {
189
+ if (fs.code !== fieldsetCode) return fs;
190
+ const list = Array.isArray(fs.groups) ? fs.groups : [];
191
+ return { ...fs, groups: list.filter((entry) => entry.code !== groupCode) };
192
+ });
193
+ onFieldsetsChange(next);
194
+ },
195
+ [fieldsets, onFieldsetsChange]
196
+ );
197
+ const availableGroups = activeFieldsetConfig?.groups ?? [];
198
+ const canToggleSingleFieldset = hasFieldsets && fieldsets.length > 1;
199
+ const singleFieldsetChecked = singleFieldsetPerRecord !== false;
200
+ const handleReorder = React.useCallback(
201
+ (from, to) => {
202
+ if (from === to) return;
203
+ onReorder?.(from, to);
204
+ },
205
+ [onReorder]
206
+ );
207
+ return /* @__PURE__ */ jsxs(
208
+ "div",
209
+ {
210
+ ref: listRef,
211
+ className: "space-y-3",
212
+ ...listProps,
213
+ children: [
214
+ hasFieldsets ? /* @__PURE__ */ jsxs("div", { className: "rounded border bg-card p-3 space-y-3", children: [
215
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
216
+ /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: "Fieldset" }),
217
+ /* @__PURE__ */ jsxs(
218
+ "select",
219
+ {
220
+ className: "border rounded px-2 py-1 text-sm",
221
+ value: resolvedActiveFieldset ?? "",
222
+ onChange: (event) => handleActiveFieldsetChange(event.target.value),
223
+ children: [
224
+ /* @__PURE__ */ jsx("option", { value: "", children: "Unassigned fields" }),
225
+ fieldsets.map((fs) => /* @__PURE__ */ jsx("option", { value: fs.code, children: fs.label || fs.code }, fs.code))
226
+ ]
227
+ }
228
+ ),
229
+ /* @__PURE__ */ jsxs(
230
+ "button",
231
+ {
232
+ type: "button",
233
+ onClick: handleAddFieldset,
234
+ className: "px-2 py-1 border rounded hover:bg-muted inline-flex items-center gap-1 text-xs",
235
+ children: [
236
+ /* @__PURE__ */ jsx(Plus, { className: "h-3.5 w-3.5" }),
237
+ " Add"
238
+ ]
239
+ }
240
+ ),
241
+ /* @__PURE__ */ jsxs(
242
+ "button",
243
+ {
244
+ type: "button",
245
+ onClick: handleRemoveFieldset,
246
+ disabled: !resolvedActiveFieldset,
247
+ className: "px-2 py-1 border rounded hover:bg-muted inline-flex items-center gap-1 text-xs disabled:opacity-50",
248
+ children: [
249
+ /* @__PURE__ */ jsx(Trash2, { className: "h-3.5 w-3.5" }),
250
+ " Delete"
251
+ ]
252
+ }
253
+ )
254
+ ] }),
255
+ resolvedActiveFieldset && activeFieldsetConfig ? /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 md:grid-cols-4 gap-3", children: [
256
+ /* @__PURE__ */ jsxs("div", { children: [
257
+ /* @__PURE__ */ jsx("label", { className: "text-xs", children: "Code" }),
258
+ /* @__PURE__ */ jsx(
259
+ "input",
260
+ {
261
+ className: "border rounded w-full px-2 py-1 text-sm font-mono",
262
+ value: activeFieldsetConfig.code,
263
+ onChange: (event) => handleFieldsetCodeInput(activeFieldsetConfig.code, event.target.value)
264
+ }
265
+ )
266
+ ] }),
267
+ /* @__PURE__ */ jsxs("div", { children: [
268
+ /* @__PURE__ */ jsx("label", { className: "text-xs", children: "Label" }),
269
+ /* @__PURE__ */ jsx(
270
+ "input",
271
+ {
272
+ className: "border rounded w-full px-2 py-1 text-sm",
273
+ value: activeFieldsetConfig.label,
274
+ onChange: (event) => handleFieldsetPatch(activeFieldsetConfig.code, { label: event.target.value })
275
+ }
276
+ )
277
+ ] }),
278
+ /* @__PURE__ */ jsxs("div", { children: [
279
+ /* @__PURE__ */ jsx("label", { className: "text-xs", children: "Icon" }),
280
+ /* @__PURE__ */ jsxs(
281
+ "select",
282
+ {
283
+ className: "border rounded w-full px-2 py-1 text-sm",
284
+ value: activeFieldsetConfig.icon ?? "",
285
+ onChange: (event) => handleFieldsetPatch(activeFieldsetConfig.code, {
286
+ icon: event.target.value || void 0
287
+ }),
288
+ children: [
289
+ /* @__PURE__ */ jsx("option", { value: "", children: "Default" }),
290
+ FIELDSET_ICON_OPTIONS.map((option) => /* @__PURE__ */ jsx("option", { value: option.value, children: option.label }, option.value))
291
+ ]
292
+ }
293
+ )
294
+ ] }),
295
+ /* @__PURE__ */ jsxs("div", { children: [
296
+ /* @__PURE__ */ jsx("label", { className: "text-xs", children: "Description" }),
297
+ /* @__PURE__ */ jsx(
298
+ "input",
299
+ {
300
+ className: "border rounded w-full px-2 py-1 text-sm",
301
+ value: activeFieldsetConfig.description ?? "",
302
+ onChange: (event) => handleFieldsetPatch(activeFieldsetConfig.code, { description: event.target.value })
303
+ }
304
+ )
305
+ ] })
306
+ ] }) : null,
307
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-xs", children: [
308
+ /* @__PURE__ */ jsxs("label", { className: "inline-flex items-center gap-2", children: [
309
+ /* @__PURE__ */ jsx(
310
+ "input",
311
+ {
312
+ type: "checkbox",
313
+ disabled: !canToggleSingleFieldset,
314
+ checked: singleFieldsetChecked,
315
+ onChange: (event) => onSingleFieldsetPerRecordChange?.(event.target.checked)
316
+ }
317
+ ),
318
+ "Single fieldset per entity"
319
+ ] }),
320
+ !canToggleSingleFieldset ? /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "(add at least two fieldsets to toggle)" }) : null
321
+ ] })
322
+ ] }) : /* @__PURE__ */ jsxs("div", { className: "rounded border border-dashed bg-muted/30 p-4 text-sm text-muted-foreground flex flex-col gap-3", children: [
323
+ /* @__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)." }),
324
+ /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsxs(
325
+ "button",
326
+ {
327
+ type: "button",
328
+ onClick: handleAddFieldset,
329
+ className: "px-3 py-1.5 border rounded bg-card text-sm font-medium inline-flex items-center gap-2",
330
+ children: [
331
+ /* @__PURE__ */ jsx(Plus, { className: "h-4 w-4" }),
332
+ "Add first fieldset"
333
+ ]
334
+ }
335
+ ) })
336
+ ] }),
337
+ orderNotice?.dirty && /* @__PURE__ */ jsx("div", { className: "sticky top-0 z-10 -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" }) }),
338
+ filteredDefinitions.map(({ definition, index }) => {
339
+ const assignedFieldset = typeof definition.configJson?.fieldset === "string" ? definition.configJson.fieldset : null;
340
+ const groupOptions = assignedFieldset ? fieldsets.find((fs) => fs.code === assignedFieldset)?.groups ?? [] : availableGroups;
341
+ return /* @__PURE__ */ jsx(
342
+ "div",
343
+ {
344
+ className: "group",
345
+ draggable: true,
346
+ onDragStart: () => {
347
+ dragIndex.current = index;
348
+ },
349
+ onDragOver: (event) => {
350
+ event.preventDefault();
351
+ },
352
+ onDrop: () => {
353
+ const from = dragIndex.current;
354
+ if (from == null) return;
355
+ dragIndex.current = null;
356
+ handleReorder(from, index);
357
+ },
358
+ onDragEnd: () => {
359
+ dragIndex.current = null;
360
+ },
361
+ tabIndex: 0,
362
+ onKeyDown: (event) => {
363
+ if (!event.altKey) return;
364
+ if (event.key === "ArrowUp" || event.key === "Up") {
365
+ event.preventDefault();
366
+ handleReorder(index, Math.max(0, index - 1));
367
+ }
368
+ if (event.key === "ArrowDown" || event.key === "Down") {
369
+ event.preventDefault();
370
+ handleReorder(index, Math.min(definitions.length - 1, index + 1));
371
+ }
372
+ },
373
+ children: /* @__PURE__ */ jsx(
374
+ FieldDefinitionCard,
375
+ {
376
+ definition,
377
+ error: errors?.[index],
378
+ kindOptions,
379
+ onChange: (next) => onDefinitionChange(index, next),
380
+ onRemove: () => onRemoveField(index),
381
+ allowFieldsetSelection: hasFieldsets,
382
+ fieldsets,
383
+ activeFieldset: resolvedActiveFieldset,
384
+ availableGroups: groupOptions,
385
+ onRegisterGroup: registerGroup,
386
+ onRemoveGroup: removeGroup,
387
+ translate: t
388
+ }
389
+ )
390
+ },
391
+ definition.key || `def-${index}`
392
+ );
393
+ }),
394
+ /* @__PURE__ */ jsxs("div", { children: [
395
+ /* @__PURE__ */ jsxs(
396
+ "button",
397
+ {
398
+ type: "button",
399
+ onClick: onAddField,
400
+ className: "px-3 py-1.5 text-sm border rounded hover:bg-muted inline-flex items-center gap-1",
401
+ children: [
402
+ /* @__PURE__ */ jsx(Plus, { className: "h-4 w-4" }),
403
+ " ",
404
+ addButtonLabel
405
+ ]
406
+ }
407
+ ),
408
+ infoNote,
409
+ deletedKeys && deletedKeys.length > 0 && onRestoreField ? /* @__PURE__ */ jsxs("div", { className: "text-xs text-muted-foreground mt-2", children: [
410
+ "Restore deleted fields:",
411
+ " ",
412
+ deletedKeys.map((key, idx) => /* @__PURE__ */ jsxs("span", { children: [
413
+ /* @__PURE__ */ jsx(
414
+ "button",
415
+ {
416
+ type: "button",
417
+ className: "underline hover:no-underline text-blue-600 disabled:opacity-50",
418
+ onClick: () => onRestoreField(key),
419
+ children: key
420
+ }
421
+ ),
422
+ idx < deletedKeys.length - 1 ? ", " : ""
423
+ ] }, key))
424
+ ] }) : null
425
+ ] })
426
+ ]
427
+ }
428
+ );
429
+ }
430
+ const FieldDefinitionCard = React.memo(function FieldDefinitionCard2({
431
+ definition,
432
+ error,
433
+ kindOptions,
434
+ onChange,
435
+ onRemove,
436
+ allowFieldsetSelection = false,
437
+ fieldsets = [],
438
+ activeFieldset,
439
+ availableGroups = [],
440
+ onRegisterGroup,
441
+ onRemoveGroup,
442
+ translate
443
+ }) {
444
+ const [local, setLocal] = React.useState(definition);
445
+ const [optionValueDraft, setOptionValueDraft] = React.useState("");
446
+ const [optionLabelDraft, setOptionLabelDraft] = React.useState("");
447
+ const [optionDialogOpen, setOptionDialogOpen] = React.useState(false);
448
+ const [optionFormError, setOptionFormError] = React.useState(null);
449
+ const [groupDialogOpen, setGroupDialogOpen] = React.useState(false);
450
+ const [groupDraft, setGroupDraft] = React.useState({ code: "", title: "", hint: "" });
451
+ const [editingGroupCode, setEditingGroupCode] = React.useState(null);
452
+ const [groupError, setGroupError] = React.useState(null);
453
+ const currentFieldsetValue = React.useMemo(
454
+ () => typeof local.configJson?.fieldset === "string" ? local.configJson.fieldset : "",
455
+ [local.configJson?.fieldset]
456
+ );
457
+ React.useEffect(() => {
458
+ setLocal(definition);
459
+ }, [definition.key]);
460
+ React.useEffect(() => {
461
+ setOptionValueDraft("");
462
+ setOptionLabelDraft("");
463
+ setGroupDialogOpen(false);
464
+ setGroupDraft({ code: "", title: "", hint: "" });
465
+ setEditingGroupCode(null);
466
+ setGroupError(null);
467
+ }, [definition.key]);
468
+ React.useEffect(() => {
469
+ if (!currentFieldsetValue) {
470
+ setGroupDialogOpen(false);
471
+ setGroupDraft({ code: "", title: "", hint: "" });
472
+ setEditingGroupCode(null);
473
+ setGroupError(null);
474
+ }
475
+ }, [currentFieldsetValue]);
476
+ const currentGroup = React.useMemo(() => normalizeGroupValue(local.configJson?.group), [local]);
477
+ const groupOptions = React.useMemo(() => {
478
+ const list = Array.isArray(availableGroups) ? [...availableGroups] : [];
479
+ if (currentGroup && !list.some((entry) => entry.code === currentGroup.code)) {
480
+ list.push(currentGroup);
481
+ }
482
+ return list;
483
+ }, [availableGroups, currentGroup]);
484
+ const resolvedOptions = React.useMemo(
485
+ () => normalizeCustomFieldOptions(local.configJson?.options),
486
+ [local.configJson?.options]
487
+ );
488
+ const sanitize = (def) => {
489
+ if (!def.configJson || !Array.isArray(def.configJson.options)) return def;
490
+ const normalizedOptions = normalizeCustomFieldOptions(def.configJson.options);
491
+ return {
492
+ ...def,
493
+ configJson: {
494
+ ...def.configJson,
495
+ options: normalizedOptions
496
+ }
497
+ };
498
+ };
499
+ const apply = (patch, propagateNow = false) => {
500
+ setLocal((prev) => {
501
+ const resolvedPatch = typeof patch === "function" ? patch(prev) : patch;
502
+ const next = { ...prev, ...resolvedPatch };
503
+ if (!propagateNow) return next;
504
+ const sanitized = sanitize(next);
505
+ onChange(sanitized);
506
+ return sanitized;
507
+ });
508
+ };
509
+ const commit = () => {
510
+ setLocal((prev) => {
511
+ const sanitized = sanitize(prev);
512
+ onChange(sanitized);
513
+ return sanitized;
514
+ });
515
+ };
516
+ const handleFieldsetSelect = (value) => {
517
+ setLocal((prev) => {
518
+ const nextConfig = { ...prev.configJson || {} };
519
+ if (value) nextConfig.fieldset = value;
520
+ else delete nextConfig.fieldset;
521
+ delete nextConfig.group;
522
+ const next = { ...prev, configJson: nextConfig };
523
+ onChange(next);
524
+ return next;
525
+ });
526
+ };
527
+ const handleGroupSelect = (value) => {
528
+ if (!currentFieldsetValue) return;
529
+ if (!value) {
530
+ const nextConfig2 = { ...local.configJson || {} };
531
+ delete nextConfig2.group;
532
+ apply({ configJson: nextConfig2 }, true);
533
+ return;
534
+ }
535
+ const match = groupOptions.find((group) => group.code === value);
536
+ const nextGroup = match ?? { code: value };
537
+ const nextConfig = { ...local.configJson || {} };
538
+ nextConfig.group = nextGroup;
539
+ apply({ configJson: nextConfig }, true);
540
+ onRegisterGroup?.(currentFieldsetValue, nextGroup);
541
+ };
542
+ const handleOpenGroupDialog = (group) => {
543
+ if (!currentFieldsetValue) return;
544
+ if (group) {
545
+ setGroupDraft({
546
+ code: group.code,
547
+ title: group.title ?? "",
548
+ hint: group.hint ?? ""
549
+ });
550
+ setEditingGroupCode(group.code);
551
+ } else {
552
+ setGroupDraft({ code: "", title: "", hint: "" });
553
+ setEditingGroupCode(null);
554
+ }
555
+ setGroupError(null);
556
+ setGroupDialogOpen(true);
557
+ };
558
+ const handleGroupDialogSubmit = () => {
559
+ if (!currentFieldsetValue) return;
560
+ const code = slugifyFieldsetCode(groupDraft.code || "");
561
+ if (!code) {
562
+ setGroupError("Group code is required.");
563
+ return;
564
+ }
565
+ const group = {
566
+ code,
567
+ title: groupDraft.title.trim() || void 0,
568
+ hint: groupDraft.hint.trim() || void 0
569
+ };
570
+ onRegisterGroup?.(currentFieldsetValue, group);
571
+ const shouldAttachToField = !editingGroupCode || currentGroup?.code === editingGroupCode;
572
+ if (shouldAttachToField) {
573
+ const nextConfig = { ...local.configJson || {} };
574
+ nextConfig.group = group;
575
+ apply({ configJson: nextConfig }, true);
576
+ }
577
+ setGroupDraft({ code: "", title: "", hint: "" });
578
+ setEditingGroupCode(null);
579
+ setGroupDialogOpen(false);
580
+ };
581
+ const handleRemoveGroupEntry = (code) => {
582
+ if (!currentFieldsetValue) return;
583
+ onRemoveGroup?.(currentFieldsetValue, code);
584
+ if (currentGroup?.code === code) {
585
+ handleGroupSelect("");
586
+ }
587
+ if (editingGroupCode === code) {
588
+ setGroupDraft({ code: "", title: "", hint: "" });
589
+ setEditingGroupCode(null);
590
+ }
591
+ };
592
+ const handleEditGroupEntry = (group) => {
593
+ handleOpenGroupDialog(group);
594
+ };
595
+ const resetOptionDialog = () => {
596
+ setOptionValueDraft("");
597
+ setOptionLabelDraft("");
598
+ setOptionFormError(null);
599
+ };
600
+ const handleOpenOptionDialog = () => {
601
+ resetOptionDialog();
602
+ setOptionDialogOpen(true);
603
+ };
604
+ const handleCloseOptionDialog = () => {
605
+ resetOptionDialog();
606
+ setOptionDialogOpen(false);
607
+ };
608
+ const handleAddOption = () => {
609
+ const value = optionValueDraft.trim();
610
+ const label = optionLabelDraft.trim();
611
+ if (!value) {
612
+ setOptionFormError("Value is required");
613
+ return;
614
+ }
615
+ setOptionFormError(null);
616
+ const nextOptions = Array.isArray(local.configJson?.options) ? [...local.configJson.options] : [];
617
+ nextOptions.push({ value, label: label || value });
618
+ apply({ configJson: { ...local.configJson || {}, options: nextOptions } }, true);
619
+ handleCloseOptionDialog();
620
+ };
621
+ const handleRemoveOption = (index) => {
622
+ const nextOptions = Array.isArray(local.configJson?.options) ? [...local.configJson.options] : [];
623
+ nextOptions.splice(index, 1);
624
+ apply({ configJson: { ...local.configJson || {}, options: nextOptions } }, true);
625
+ };
626
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
627
+ /* @__PURE__ */ jsxs("div", { className: "rounded border p-3 bg-card transition-colors hover:border-muted-foreground/60", children: [
628
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
629
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-xs text-muted-foreground", children: [
630
+ /* @__PURE__ */ jsx("span", { className: "inline-flex items-center justify-center h-5 w-5 rounded hover:bg-muted cursor-grab active:cursor-grabbing", children: /* @__PURE__ */ jsx(GripVertical, { className: "h-4 w-4 opacity-70" }) }),
631
+ "Drag to reorder"
632
+ ] }),
633
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
634
+ /* @__PURE__ */ jsxs("label", { className: "inline-flex items-center gap-2 text-sm", children: [
635
+ /* @__PURE__ */ jsx("input", { type: "checkbox", checked: local.isActive !== false, onChange: (event) => {
636
+ apply({ isActive: event.target.checked }, true);
637
+ } }),
638
+ " Active"
639
+ ] }),
640
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: onRemove, className: "px-2 py-1 border rounded hover:bg-muted", "aria-label": "Remove field", children: /* @__PURE__ */ jsx(Trash2, { className: "h-4 w-4" }) })
641
+ ] })
642
+ ] }),
643
+ /* @__PURE__ */ jsxs("div", { className: "mt-3 grid grid-cols-1 md:grid-cols-12 gap-3 items-center", children: [
644
+ /* @__PURE__ */ jsxs("div", { className: "md:col-span-6", children: [
645
+ /* @__PURE__ */ jsx("label", { className: "text-xs", children: "Key" }),
646
+ /* @__PURE__ */ jsx(
647
+ "input",
648
+ {
649
+ className: `rounded w-full px-2 py-1 text-sm font-mono ${error?.key ? "border-red-500 border" : "border"}`,
650
+ placeholder: "snake_case",
651
+ value: local.key,
652
+ onChange: (event) => apply({ key: event.target.value }),
653
+ onBlur: commit
654
+ }
655
+ ),
656
+ error?.key ? /* @__PURE__ */ jsx("div", { className: "text-xs text-red-600 mt-1", children: error.key }) : null
657
+ ] }),
658
+ /* @__PURE__ */ jsxs("div", { className: "md:col-span-6", children: [
659
+ /* @__PURE__ */ jsx("label", { className: "text-xs", children: "Kind" }),
660
+ /* @__PURE__ */ jsx(
661
+ "select",
662
+ {
663
+ className: `rounded w-full px-2 py-1 text-sm ${error?.kind ? "border-red-500 border" : "border"}`,
664
+ value: local.kind,
665
+ onChange: (event) => {
666
+ apply({ kind: event.target.value }, true);
667
+ },
668
+ children: kindOptions.map((option) => /* @__PURE__ */ jsx("option", { value: option.value, children: option.label }, option.value))
669
+ }
670
+ ),
671
+ error?.kind ? /* @__PURE__ */ jsx("div", { className: "text-xs text-red-600 mt-1", children: error.kind }) : null
672
+ ] })
673
+ ] }),
674
+ allowFieldsetSelection ? /* @__PURE__ */ jsxs("div", { className: "mt-3 grid grid-cols-1 md:grid-cols-2 gap-3", children: [
675
+ /* @__PURE__ */ jsxs("div", { children: [
676
+ /* @__PURE__ */ jsx("label", { className: "text-xs", children: "Assign to fieldset" }),
677
+ /* @__PURE__ */ jsxs(
678
+ "select",
679
+ {
680
+ className: "border rounded w-full px-2 py-1 text-sm",
681
+ value: currentFieldsetValue,
682
+ onChange: (event) => handleFieldsetSelect(event.target.value),
683
+ children: [
684
+ /* @__PURE__ */ jsx("option", { value: "", children: "Unassigned" }),
685
+ (fieldsets || []).map((fs) => /* @__PURE__ */ jsx("option", { value: fs.code, children: fs.label || fs.code }, fs.code))
686
+ ]
687
+ }
688
+ )
689
+ ] }),
690
+ currentFieldsetValue ? /* @__PURE__ */ jsxs("div", { children: [
691
+ /* @__PURE__ */ jsx("div", { className: "flex items-center justify-between", children: /* @__PURE__ */ jsx("label", { className: "text-xs", children: "Group" }) }),
692
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mt-1", children: [
693
+ /* @__PURE__ */ jsxs(
694
+ "select",
695
+ {
696
+ className: "flex-1 border rounded px-2 py-1 text-sm",
697
+ value: currentGroup?.code ?? "",
698
+ onChange: (event) => handleGroupSelect(event.target.value),
699
+ children: [
700
+ /* @__PURE__ */ jsx("option", { value: "", children: "No group" }),
701
+ groupOptions.map((group) => /* @__PURE__ */ jsx("option", { value: group.code, children: group.title || group.code }, group.code))
702
+ ]
703
+ }
704
+ ),
705
+ /* @__PURE__ */ jsx(
706
+ "button",
707
+ {
708
+ type: "button",
709
+ className: "h-8 w-8 inline-flex items-center justify-center rounded border text-muted-foreground hover:bg-muted/40",
710
+ onClick: () => handleOpenGroupDialog(),
711
+ "aria-label": "Create group",
712
+ children: /* @__PURE__ */ jsx(Plus, { className: "h-4 w-4" })
713
+ }
714
+ ),
715
+ /* @__PURE__ */ jsxs(
716
+ "button",
717
+ {
718
+ type: "button",
719
+ className: "h-8 w-8 inline-flex items-center justify-center rounded border text-muted-foreground hover:bg-muted/40",
720
+ onClick: () => handleOpenGroupDialog(),
721
+ "aria-label": "Edit groups",
722
+ children: [
723
+ /* @__PURE__ */ jsx(Cog, { className: "h-4 w-4" }),
724
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: "Edit groups" })
725
+ ]
726
+ }
727
+ )
728
+ ] })
729
+ ] }) : null
730
+ ] }) : null,
731
+ /* @__PURE__ */ jsxs("div", { className: "mt-3 grid grid-cols-1 md:grid-cols-2 gap-3", children: [
732
+ /* @__PURE__ */ jsxs("div", { children: [
733
+ /* @__PURE__ */ jsx("label", { className: "text-xs", children: "Label" }),
734
+ /* @__PURE__ */ jsx(
735
+ "input",
736
+ {
737
+ className: "border rounded w-full px-2 py-1 text-sm",
738
+ value: typeof local.configJson?.label === "string" ? local.configJson.label : "",
739
+ onChange: (event) => apply({ configJson: { ...local.configJson || {}, label: event.target.value } }),
740
+ onBlur: commit
741
+ }
742
+ )
743
+ ] }),
744
+ /* @__PURE__ */ jsxs("div", { children: [
745
+ /* @__PURE__ */ jsx("label", { className: "text-xs", children: "Description" }),
746
+ /* @__PURE__ */ jsx(
747
+ "input",
748
+ {
749
+ className: "border rounded w-full px-2 py-1 text-sm",
750
+ value: typeof local.configJson?.description === "string" ? local.configJson.description : "",
751
+ onChange: (event) => apply({ configJson: { ...local.configJson || {}, description: event.target.value } }),
752
+ onBlur: commit
753
+ }
754
+ )
755
+ ] }),
756
+ (local.kind === "text" || local.kind === "multiline") && /* @__PURE__ */ jsxs(Fragment, { children: [
757
+ /* @__PURE__ */ jsxs("div", { children: [
758
+ /* @__PURE__ */ jsx("label", { className: "text-xs", children: "Editor" }),
759
+ /* @__PURE__ */ jsxs(
760
+ "select",
761
+ {
762
+ className: "border rounded w-full px-2 py-1 text-sm",
763
+ value: typeof local.configJson?.editor === "string" ? local.configJson.editor : "",
764
+ onChange: (event) => {
765
+ apply({ configJson: { ...local.configJson || {}, editor: event.target.value || void 0 } }, true);
766
+ },
767
+ children: [
768
+ /* @__PURE__ */ jsx("option", { value: "", children: "Default" }),
769
+ /* @__PURE__ */ jsx("option", { value: "markdown", children: "Markdown (UIW)" }),
770
+ /* @__PURE__ */ jsx("option", { value: "simpleMarkdown", children: "Simple Markdown" }),
771
+ /* @__PURE__ */ jsx("option", { value: "htmlRichText", children: "HTML Rich Text" })
772
+ ]
773
+ }
774
+ )
775
+ ] }),
776
+ local.kind === "text" && /* @__PURE__ */ jsxs(Fragment, { children: [
777
+ /* @__PURE__ */ jsx("div", { className: "md:col-span-2", children: /* @__PURE__ */ jsxs("label", { className: "inline-flex items-center gap-2 text-xs", children: [
778
+ /* @__PURE__ */ jsx("input", { type: "checkbox", checked: !!local.configJson?.multi, onChange: (event) => {
779
+ apply({ configJson: { ...local.configJson || {}, multi: event.target.checked } }, true);
780
+ } }),
781
+ " Multiple"
782
+ ] }) }),
783
+ !!local.configJson?.multi && /* @__PURE__ */ jsxs("div", { className: "md:col-span-2", children: [
784
+ /* @__PURE__ */ jsx("label", { className: "text-xs", children: "Multi-select input style" }),
785
+ /* @__PURE__ */ jsxs(
786
+ "select",
787
+ {
788
+ className: "border rounded w-full px-2 py-1 text-sm",
789
+ value: local.configJson?.input === "listbox" ? "listbox" : "default",
790
+ onChange: (event) => {
791
+ const { value } = event.target;
792
+ const nextConfig = { ...local.configJson || {} };
793
+ if (value === "listbox") nextConfig.input = "listbox";
794
+ else delete nextConfig.input;
795
+ apply({ configJson: nextConfig }, true);
796
+ },
797
+ children: [
798
+ /* @__PURE__ */ jsx("option", { value: "default", children: "Default" }),
799
+ /* @__PURE__ */ jsx("option", { value: "listbox", children: "Listbox (searchable)" })
800
+ ]
801
+ }
802
+ )
803
+ ] })
804
+ ] })
805
+ ] }),
806
+ local.kind === "select" && /* @__PURE__ */ jsxs("div", { className: "md:col-span-6 space-y-3", children: [
807
+ /* @__PURE__ */ jsx("label", { className: "text-xs", children: "Options" }),
808
+ /* @__PURE__ */ jsx("div", { className: "space-y-2", children: resolvedOptions.length > 0 ? resolvedOptions.map((option, idx) => /* @__PURE__ */ jsxs(
809
+ "div",
810
+ {
811
+ className: "flex items-center justify-between rounded border px-3 py-2 text-xs bg-muted",
812
+ children: [
813
+ /* @__PURE__ */ jsxs("div", { children: [
814
+ /* @__PURE__ */ jsx("div", { className: "font-medium text-foreground", children: option.label }),
815
+ /* @__PURE__ */ jsx("div", { className: "text-muted-foreground font-mono text-[11px]", children: option.value })
816
+ ] }),
817
+ /* @__PURE__ */ jsx(
818
+ "button",
819
+ {
820
+ type: "button",
821
+ onClick: () => handleRemoveOption(idx),
822
+ className: "text-red-500 hover:text-red-700",
823
+ "aria-label": "Remove option",
824
+ children: /* @__PURE__ */ jsx(Trash2, { className: "h-3.5 w-3.5" })
825
+ }
826
+ )
827
+ ]
828
+ },
829
+ `${option.value}-${idx}`
830
+ )) : /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "No options defined." }) }),
831
+ /* @__PURE__ */ jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxs(
832
+ "button",
833
+ {
834
+ type: "button",
835
+ className: "text-xs px-2 py-1 border rounded hover:bg-muted inline-flex items-center gap-1",
836
+ onClick: handleOpenOptionDialog,
837
+ children: [
838
+ /* @__PURE__ */ jsx(Plus, { className: "h-3.5 w-3.5" }),
839
+ "Add option"
840
+ ]
841
+ }
842
+ ) })
843
+ ] }),
844
+ (local.kind === "select" || local.kind === "relation") && /* @__PURE__ */ jsxs(Fragment, { children: [
845
+ /* @__PURE__ */ jsxs("div", { children: [
846
+ /* @__PURE__ */ jsx("label", { className: "text-xs", children: "Options URL" }),
847
+ /* @__PURE__ */ jsx(
848
+ "input",
849
+ {
850
+ className: "border rounded w-full px-2 py-1 text-sm",
851
+ placeholder: "/api/...",
852
+ value: typeof local.configJson?.optionsUrl === "string" ? local.configJson.optionsUrl : "",
853
+ onChange: (event) => apply({ configJson: { ...local.configJson || {}, optionsUrl: event.target.value } }),
854
+ onBlur: commit
855
+ }
856
+ )
857
+ ] }),
858
+ local.kind === "relation" && /* @__PURE__ */ jsxs("div", { children: [
859
+ /* @__PURE__ */ jsx("label", { className: "text-xs", children: "Related Entity ID" }),
860
+ /* @__PURE__ */ jsx(
861
+ "input",
862
+ {
863
+ className: "border rounded w-full px-2 py-1 text-sm font-mono",
864
+ placeholder: "module:entity",
865
+ value: typeof local.configJson?.relatedEntityId === "string" ? local.configJson.relatedEntityId : "",
866
+ onChange: (event) => {
867
+ const relatedEntityId = event.target.value;
868
+ const defOptionsUrl = relatedEntityId ? `/api/entities/relations/options?entityId=${encodeURIComponent(relatedEntityId)}` : "";
869
+ apply({
870
+ configJson: {
871
+ ...local.configJson || {},
872
+ relatedEntityId,
873
+ optionsUrl: local.configJson?.optionsUrl || defOptionsUrl
874
+ }
875
+ });
876
+ },
877
+ onBlur: commit
878
+ }
879
+ )
880
+ ] })
881
+ ] }),
882
+ local.kind === "currency" && /* @__PURE__ */ jsxs("div", { className: "md:col-span-2", children: [
883
+ /* @__PURE__ */ jsx("label", { className: "text-xs", children: "Options source" }),
884
+ /* @__PURE__ */ jsx("div", { className: "rounded border bg-muted px-2 py-1 text-xs text-muted-foreground", children: "/api/currencies/options" })
885
+ ] }),
886
+ (local.kind === "integer" || local.kind === "float") && /* @__PURE__ */ jsxs("div", { className: "md:col-span-2", children: [
887
+ /* @__PURE__ */ jsx("label", { className: "text-xs", children: "Units (optional)" }),
888
+ /* @__PURE__ */ jsx(
889
+ "input",
890
+ {
891
+ className: "border rounded w-full px-2 py-1 text-sm",
892
+ placeholder: "kg, cm, etc.",
893
+ value: typeof local.configJson?.unit === "string" ? local.configJson.unit : "",
894
+ onChange: (event) => apply({ configJson: { ...local.configJson || {}, unit: event.target.value } }),
895
+ onBlur: commit
896
+ }
897
+ )
898
+ ] })
899
+ ] }),
900
+ /* @__PURE__ */ jsxs("div", { className: "mt-3 pt-3 border-t", children: [
901
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-2", children: [
902
+ /* @__PURE__ */ jsx("label", { className: "text-sm font-medium", children: "Validation rules" }),
903
+ /* @__PURE__ */ jsxs(
904
+ "button",
905
+ {
906
+ type: "button",
907
+ className: "text-xs px-2 py-1 border rounded hover:bg-muted inline-flex items-center gap-1",
908
+ onClick: () => {
909
+ apply((current) => {
910
+ const list = Array.isArray(current.configJson?.validation) ? [...current.configJson.validation] : [];
911
+ list.push({ rule: "required", message: "This field is required" });
912
+ return { configJson: { ...current.configJson || {}, validation: list } };
913
+ }, true);
914
+ },
915
+ children: [
916
+ /* @__PURE__ */ jsx(Plus, { className: "h-3.5 w-3.5" }),
917
+ "Add rule"
918
+ ]
919
+ }
920
+ )
921
+ ] }),
922
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
923
+ (Array.isArray(local.configJson?.validation) ? local.configJson.validation : []).map((rule, index) => /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 md:grid-cols-12 gap-2 items-center", children: [
924
+ /* @__PURE__ */ jsx("div", { className: "md:col-span-3", children: /* @__PURE__ */ jsxs(
925
+ "select",
926
+ {
927
+ className: "border rounded w-full px-2 py-1 text-sm",
928
+ value: rule?.rule || "required",
929
+ onChange: (event) => {
930
+ const nextRule = event.target.value;
931
+ apply((current) => {
932
+ const list = Array.isArray(current.configJson?.validation) ? [...current.configJson.validation] : [];
933
+ const existing = list[index] || {};
934
+ list[index] = { ...existing, rule: nextRule, message: existing.message || rule?.message || "" };
935
+ return { configJson: { ...current.configJson || {}, validation: list } };
936
+ }, true);
937
+ },
938
+ children: [
939
+ /* @__PURE__ */ jsx("option", { value: "required", children: "required" }),
940
+ /* @__PURE__ */ jsx("option", { value: "date", children: "date" }),
941
+ /* @__PURE__ */ jsx("option", { value: "integer", children: "integer" }),
942
+ /* @__PURE__ */ jsx("option", { value: "float", children: "float" }),
943
+ /* @__PURE__ */ jsx("option", { value: "lt", children: "lt" }),
944
+ /* @__PURE__ */ jsx("option", { value: "lte", children: "lte" }),
945
+ /* @__PURE__ */ jsx("option", { value: "gt", children: "gt" }),
946
+ /* @__PURE__ */ jsx("option", { value: "gte", children: "gte" }),
947
+ /* @__PURE__ */ jsx("option", { value: "eq", children: "eq" }),
948
+ /* @__PURE__ */ jsx("option", { value: "ne", children: "ne" }),
949
+ /* @__PURE__ */ jsx("option", { value: "regex", children: "regex" })
950
+ ]
951
+ }
952
+ ) }),
953
+ /* @__PURE__ */ jsx("div", { className: "md:col-span-4", children: /* @__PURE__ */ jsx(
954
+ "input",
955
+ {
956
+ className: "border rounded w-full px-2 py-1 text-sm",
957
+ placeholder: rule?.rule === "regex" ? "Pattern (e.g. ^[a-z]+$)" : ["lt", "lte", "gt", "gte"].includes(rule?.rule) ? "Number" : "\u2014",
958
+ value: rule?.param ?? "",
959
+ onChange: (event) => {
960
+ const value = ["lt", "lte", "gt", "gte"].includes(rule?.rule) ? Number(event.target.value) : event.target.value;
961
+ apply((current) => {
962
+ const list = Array.isArray(current.configJson?.validation) ? [...current.configJson.validation] : [];
963
+ const existing = list[index] || {};
964
+ list[index] = { ...existing, ...rule, param: value };
965
+ return { configJson: { ...current.configJson || {}, validation: list } };
966
+ });
967
+ },
968
+ onBlur: commit,
969
+ disabled: rule?.rule === "required" || rule?.rule === "date" || rule?.rule === "integer" || rule?.rule === "float"
970
+ }
971
+ ) }),
972
+ /* @__PURE__ */ jsx("div", { className: "md:col-span-4", children: /* @__PURE__ */ jsx(
973
+ "input",
974
+ {
975
+ className: "border rounded w-full px-2 py-1 text-sm",
976
+ placeholder: "Error message",
977
+ value: rule?.message || "",
978
+ onChange: (event) => {
979
+ const message = event.target.value;
980
+ apply((current) => {
981
+ const list = Array.isArray(current.configJson?.validation) ? [...current.configJson.validation] : [];
982
+ const existing = list[index] || {};
983
+ list[index] = { ...existing, ...rule, message };
984
+ return { configJson: { ...current.configJson || {}, validation: list } };
985
+ });
986
+ },
987
+ onBlur: commit
988
+ }
989
+ ) }),
990
+ /* @__PURE__ */ jsx("div", { className: "md:col-span-1 flex justify-end", children: /* @__PURE__ */ jsx(
991
+ "button",
992
+ {
993
+ type: "button",
994
+ className: "px-2 py-1 border rounded hover:bg-muted",
995
+ "aria-label": "Remove rule",
996
+ onClick: () => {
997
+ apply((current) => {
998
+ const list = Array.isArray(current.configJson?.validation) ? [...current.configJson.validation] : [];
999
+ list.splice(index, 1);
1000
+ return { configJson: { ...current.configJson || {}, validation: list } };
1001
+ }, true);
1002
+ },
1003
+ children: /* @__PURE__ */ jsx(Trash2, { className: "h-4 w-4" })
1004
+ }
1005
+ ) })
1006
+ ] }, index)),
1007
+ (!Array.isArray(local.configJson?.validation) || local.configJson.validation.length === 0) && /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground", children: "No validation rules defined." })
1008
+ ] })
1009
+ ] }),
1010
+ /* @__PURE__ */ jsx("div", { className: "mt-3", children: (() => {
1011
+ const Editor = FieldRegistry.getDefEditor(local.kind);
1012
+ if (!Editor) return null;
1013
+ return /* @__PURE__ */ jsx(
1014
+ Editor,
1015
+ {
1016
+ def: { key: local.key, kind: local.kind, configJson: local.configJson },
1017
+ onChange: (patch) => apply({ configJson: { ...local.configJson || {}, ...patch || {} } }, true)
1018
+ }
1019
+ );
1020
+ })() }),
1021
+ /* @__PURE__ */ jsxs("div", { className: "mt-3 pt-2 border-t flex flex-wrap items-center gap-4", children: [
1022
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Visibility:" }),
1023
+ /* @__PURE__ */ jsxs("label", { className: "inline-flex items-center gap-2 text-xs", children: [
1024
+ /* @__PURE__ */ jsx(
1025
+ "input",
1026
+ {
1027
+ type: "checkbox",
1028
+ checked: local.configJson?.listVisible !== false,
1029
+ onChange: (event) => {
1030
+ apply({ configJson: { ...local.configJson || {}, listVisible: event.target.checked } }, true);
1031
+ }
1032
+ }
1033
+ ),
1034
+ "List"
1035
+ ] }),
1036
+ /* @__PURE__ */ jsxs("label", { className: "inline-flex items-center gap-2 text-xs", children: [
1037
+ /* @__PURE__ */ jsx(
1038
+ "input",
1039
+ {
1040
+ type: "checkbox",
1041
+ checked: !!local.configJson?.filterable,
1042
+ onChange: (event) => {
1043
+ apply({ configJson: { ...local.configJson || {}, filterable: event.target.checked } }, true);
1044
+ }
1045
+ }
1046
+ ),
1047
+ "Filter"
1048
+ ] }),
1049
+ /* @__PURE__ */ jsxs("label", { className: "inline-flex items-center gap-2 text-xs", children: [
1050
+ /* @__PURE__ */ jsx(
1051
+ "input",
1052
+ {
1053
+ type: "checkbox",
1054
+ checked: local.configJson?.formEditable !== false,
1055
+ onChange: (event) => {
1056
+ apply({ configJson: { ...local.configJson || {}, formEditable: event.target.checked } }, true);
1057
+ }
1058
+ }
1059
+ ),
1060
+ "Form"
1061
+ ] }),
1062
+ /* @__PURE__ */ jsxs("label", { className: "inline-flex items-center gap-2 text-xs", children: [
1063
+ /* @__PURE__ */ jsx(
1064
+ "input",
1065
+ {
1066
+ type: "checkbox",
1067
+ checked: !!local.configJson?.encrypted,
1068
+ onChange: (event) => {
1069
+ apply({ configJson: { ...local.configJson || {}, encrypted: event.target.checked } }, true);
1070
+ }
1071
+ }
1072
+ ),
1073
+ translate?.("entities.customFields.fields.encrypted", "Encrypted") ?? "Encrypted"
1074
+ ] })
1075
+ ] })
1076
+ ] }),
1077
+ /* @__PURE__ */ jsx(
1078
+ Dialog,
1079
+ {
1080
+ open: optionDialogOpen,
1081
+ onOpenChange: (open) => {
1082
+ setOptionDialogOpen(open);
1083
+ if (!open) resetOptionDialog();
1084
+ },
1085
+ children: /* @__PURE__ */ jsxs(DialogContent, { className: "max-w-sm", children: [
1086
+ /* @__PURE__ */ jsxs(DialogHeader, { children: [
1087
+ /* @__PURE__ */ jsx(DialogTitle, { children: "Add option" }),
1088
+ /* @__PURE__ */ jsx(DialogDescription, { children: "Provide the stored value and optional label shown to users." })
1089
+ ] }),
1090
+ /* @__PURE__ */ jsxs("div", { className: "space-y-4 py-2", children: [
1091
+ /* @__PURE__ */ jsxs("div", { children: [
1092
+ /* @__PURE__ */ jsx("label", { className: "text-xs", children: "Value" }),
1093
+ /* @__PURE__ */ jsx(
1094
+ "input",
1095
+ {
1096
+ className: "mt-1 w-full rounded border px-2 py-1 text-sm font-mono",
1097
+ placeholder: "unique_value",
1098
+ value: optionValueDraft,
1099
+ onChange: (event) => {
1100
+ setOptionFormError(null);
1101
+ setOptionValueDraft(event.target.value);
1102
+ }
1103
+ }
1104
+ ),
1105
+ optionFormError ? /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-red-600", children: optionFormError }) : null
1106
+ ] }),
1107
+ /* @__PURE__ */ jsxs("div", { children: [
1108
+ /* @__PURE__ */ jsx("label", { className: "text-xs", children: "Label" }),
1109
+ /* @__PURE__ */ jsx(
1110
+ "input",
1111
+ {
1112
+ className: "mt-1 w-full rounded border px-2 py-1 text-sm",
1113
+ placeholder: "Label shown to users (optional)",
1114
+ value: optionLabelDraft,
1115
+ onChange: (event) => setOptionLabelDraft(event.target.value)
1116
+ }
1117
+ )
1118
+ ] })
1119
+ ] }),
1120
+ /* @__PURE__ */ jsxs(DialogFooter, { children: [
1121
+ /* @__PURE__ */ jsx(
1122
+ "button",
1123
+ {
1124
+ type: "button",
1125
+ className: "h-8 rounded border px-3 text-sm",
1126
+ onClick: handleCloseOptionDialog,
1127
+ children: "Cancel"
1128
+ }
1129
+ ),
1130
+ /* @__PURE__ */ jsx(
1131
+ "button",
1132
+ {
1133
+ type: "button",
1134
+ className: "h-8 rounded bg-primary px-3 text-sm text-primary-foreground",
1135
+ onClick: handleAddOption,
1136
+ children: "Add option"
1137
+ }
1138
+ )
1139
+ ] })
1140
+ ] })
1141
+ }
1142
+ ),
1143
+ /* @__PURE__ */ jsx(
1144
+ Dialog,
1145
+ {
1146
+ open: groupDialogOpen,
1147
+ onOpenChange: (open) => {
1148
+ setGroupDialogOpen(open);
1149
+ if (!open) {
1150
+ setGroupError(null);
1151
+ setEditingGroupCode(null);
1152
+ setGroupDraft({ code: "", title: "", hint: "" });
1153
+ }
1154
+ },
1155
+ children: /* @__PURE__ */ jsxs(DialogContent, { className: "max-w-md", children: [
1156
+ /* @__PURE__ */ jsxs(DialogHeader, { children: [
1157
+ /* @__PURE__ */ jsx(DialogTitle, { children: editingGroupCode ? "Edit group" : "New group" }),
1158
+ /* @__PURE__ */ jsx(DialogDescription, { children: editingGroupCode ? "Update the selected group for this fieldset." : "Add a reusable group for this fieldset." })
1159
+ ] }),
1160
+ /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
1161
+ /* @__PURE__ */ jsxs("div", { children: [
1162
+ /* @__PURE__ */ jsx("label", { className: "text-xs font-medium", children: "Group code" }),
1163
+ /* @__PURE__ */ jsx(
1164
+ "input",
1165
+ {
1166
+ className: "mt-1 w-full rounded border px-2 py-1 text-sm font-mono disabled:cursor-not-allowed disabled:bg-muted/40 disabled:text-muted-foreground",
1167
+ value: groupDraft.code,
1168
+ onChange: (event) => {
1169
+ setGroupDraft((prev) => ({ ...prev, code: event.target.value }));
1170
+ if (groupError) setGroupError(null);
1171
+ },
1172
+ disabled: !!editingGroupCode,
1173
+ placeholder: "e.g. buying_committee"
1174
+ }
1175
+ ),
1176
+ groupError ? /* @__PURE__ */ jsx("div", { className: "text-xs text-red-600 mt-1", children: groupError }) : null
1177
+ ] }),
1178
+ /* @__PURE__ */ jsxs("div", { children: [
1179
+ /* @__PURE__ */ jsx("label", { className: "text-xs font-medium", children: "Label" }),
1180
+ /* @__PURE__ */ jsx(
1181
+ "input",
1182
+ {
1183
+ className: "mt-1 w-full rounded border px-2 py-1 text-sm",
1184
+ value: groupDraft.title,
1185
+ onChange: (event) => setGroupDraft((prev) => ({ ...prev, title: event.target.value })),
1186
+ placeholder: "Buying committee"
1187
+ }
1188
+ )
1189
+ ] }),
1190
+ /* @__PURE__ */ jsxs("div", { children: [
1191
+ /* @__PURE__ */ jsx("label", { className: "text-xs font-medium", children: "Hint" }),
1192
+ /* @__PURE__ */ jsx(
1193
+ "input",
1194
+ {
1195
+ className: "mt-1 w-full rounded border px-2 py-1 text-sm",
1196
+ value: groupDraft.hint,
1197
+ onChange: (event) => setGroupDraft((prev) => ({ ...prev, hint: event.target.value })),
1198
+ placeholder: "Visible to merchandisers"
1199
+ }
1200
+ )
1201
+ ] }),
1202
+ currentFieldsetValue && groupOptions.length > 0 ? /* @__PURE__ */ jsxs("div", { children: [
1203
+ /* @__PURE__ */ jsx("div", { className: "text-xs font-medium mb-1", children: "Existing groups" }),
1204
+ /* @__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: [
1205
+ /* @__PURE__ */ jsxs("div", { children: [
1206
+ /* @__PURE__ */ jsx("div", { className: "font-medium", children: group.title || group.code }),
1207
+ /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground font-mono", children: group.code }),
1208
+ group.hint ? /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground", children: group.hint }) : null
1209
+ ] }),
1210
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
1211
+ /* @__PURE__ */ jsx(
1212
+ "button",
1213
+ {
1214
+ type: "button",
1215
+ className: "text-muted-foreground hover:text-foreground",
1216
+ onClick: () => handleEditGroupEntry(group),
1217
+ "aria-label": `Edit ${group.code}`,
1218
+ children: /* @__PURE__ */ jsx(Pencil, { className: "h-4 w-4" })
1219
+ }
1220
+ ),
1221
+ /* @__PURE__ */ jsx(
1222
+ "button",
1223
+ {
1224
+ type: "button",
1225
+ className: "text-red-500 hover:text-red-600",
1226
+ onClick: () => handleRemoveGroupEntry(group.code),
1227
+ "aria-label": `Delete ${group.code}`,
1228
+ children: /* @__PURE__ */ jsx(Trash2, { className: "h-4 w-4" })
1229
+ }
1230
+ )
1231
+ ] })
1232
+ ] }, group.code)) })
1233
+ ] }) : null
1234
+ ] }),
1235
+ /* @__PURE__ */ jsxs(DialogFooter, { children: [
1236
+ /* @__PURE__ */ jsx(
1237
+ "button",
1238
+ {
1239
+ type: "button",
1240
+ className: "h-8 rounded border px-3 text-sm",
1241
+ onClick: () => setGroupDialogOpen(false),
1242
+ children: "Cancel"
1243
+ }
1244
+ ),
1245
+ /* @__PURE__ */ jsx(
1246
+ "button",
1247
+ {
1248
+ type: "button",
1249
+ className: "h-8 rounded bg-primary px-3 text-sm text-primary-foreground",
1250
+ onClick: handleGroupDialogSubmit,
1251
+ children: "Save group"
1252
+ }
1253
+ )
1254
+ ] })
1255
+ ] })
1256
+ }
1257
+ )
1258
+ ] });
1259
+ });
1260
+ FieldDefinitionCard.displayName = "FieldDefinitionCard";
1261
+ export {
1262
+ FieldDefinitionsEditor
1263
+ };
1264
+ //# sourceMappingURL=FieldDefinitionsEditor.js.map