@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,433 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import Link from "next/link";
5
+ import dynamic from "next/dynamic";
6
+ import { Pencil, X } from "lucide-react";
7
+ import { useQuery, useQueryClient } from "@tanstack/react-query";
8
+ import { Button } from "@open-mercato/ui/primitives/button";
9
+ import { DataLoader } from "@open-mercato/ui/primitives/DataLoader";
10
+ import { CrudForm } from "@open-mercato/ui/backend/CrudForm";
11
+ import { fetchCustomFieldFormFieldsWithDefinitions } from "@open-mercato/ui/backend/utils/customFieldForms";
12
+ import {
13
+ DictionaryValue
14
+ } from "@open-mercato/core/modules/dictionaries/components/dictionaryAppearance";
15
+ import { ensureDictionaryEntries } from "@open-mercato/core/modules/dictionaries/components/hooks/useDictionaryEntries";
16
+ import { useOrganizationScopeVersion } from "@open-mercato/shared/lib/frontend/useOrganizationScope";
17
+ import { cn } from "@open-mercato/shared/lib/utils";
18
+ const isTestEnv = typeof process !== "undefined" && process.env.NODE_ENV === "test";
19
+ const MarkdownPreview = isTestEnv ? ({ children, className }) => /* @__PURE__ */ jsx("div", { className, children }) : dynamic(() => import("react-markdown").then((mod) => mod.default), {
20
+ ssr: false,
21
+ loading: () => null
22
+ });
23
+ let markdownPluginsPromise = null;
24
+ async function loadMarkdownPlugins() {
25
+ if (isTestEnv) return [];
26
+ if (!markdownPluginsPromise) {
27
+ markdownPluginsPromise = import("remark-gfm").then((mod) => [mod.default ?? mod]).catch(() => []);
28
+ }
29
+ return markdownPluginsPromise;
30
+ }
31
+ const MARKDOWN_FIELD_TYPES = /* @__PURE__ */ new Set(["text", "textarea", "richtext"]);
32
+ const MARKDOWN_CLASSNAME = "text-sm text-foreground break-words [&>*]:mb-2 [&>*:last-child]:mb-0 [&_ul]:ml-4 [&_ul]:list-disc [&_ol]:ml-4 [&_ol]:list-decimal [&_code]:rounded [&_code]:bg-muted [&_code]:px-1 [&_code]:py-0.5 [&_pre]:rounded-md [&_pre]:bg-muted [&_pre]:p-3 [&_pre]:text-xs";
33
+ function renderMarkdownValue(content, remarkPlugins) {
34
+ return /* @__PURE__ */ jsx(MarkdownPreview, { remarkPlugins, className: MARKDOWN_CLASSNAME, children: content });
35
+ }
36
+ function extractDictionaryValue(entry) {
37
+ if (typeof entry === "string") {
38
+ const trimmed = entry.trim();
39
+ return trimmed.length ? trimmed : null;
40
+ }
41
+ if (!entry || typeof entry !== "object") return null;
42
+ const record = entry;
43
+ const candidate = record.value ?? record.name ?? record.id ?? record.key ?? record.label;
44
+ if (typeof candidate === "string") {
45
+ const trimmed = candidate.trim();
46
+ return trimmed.length ? trimmed : null;
47
+ }
48
+ return null;
49
+ }
50
+ function formatFieldValue(field, value, emptyLabel, dictionaryMap, remarkPlugins = []) {
51
+ if (dictionaryMap) {
52
+ if (value === void 0 || value === null || value === "") {
53
+ return /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: emptyLabel });
54
+ }
55
+ if (Array.isArray(value)) {
56
+ const normalizedValues = value.map((entry) => extractDictionaryValue(entry)).filter((entry) => typeof entry === "string" && entry.length > 0);
57
+ if (!normalizedValues.length) {
58
+ return /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: emptyLabel });
59
+ }
60
+ return /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5", children: normalizedValues.map((entry, index) => /* @__PURE__ */ jsx(
61
+ DictionaryValue,
62
+ {
63
+ value: entry,
64
+ map: dictionaryMap,
65
+ className: "inline-flex items-center gap-1 rounded-full border border-border bg-card px-2 py-1 text-xs",
66
+ iconWrapperClassName: "inline-flex h-4 w-4 items-center justify-center rounded-full border border-border bg-background",
67
+ iconClassName: "h-3 w-3",
68
+ colorClassName: "h-2.5 w-2.5 rounded-full"
69
+ },
70
+ `${field.id}-${entry}-${index}`
71
+ )) });
72
+ }
73
+ const resolved2 = extractDictionaryValue(value);
74
+ if (!resolved2) {
75
+ return /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: emptyLabel });
76
+ }
77
+ return /* @__PURE__ */ jsx(
78
+ DictionaryValue,
79
+ {
80
+ value: resolved2,
81
+ map: dictionaryMap,
82
+ className: "inline-flex items-center gap-2 text-sm",
83
+ fallback: /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: emptyLabel }),
84
+ iconWrapperClassName: "inline-flex h-6 w-6 items-center justify-center rounded border border-border bg-card",
85
+ iconClassName: "h-4 w-4",
86
+ colorClassName: "h-3 w-3 rounded-full"
87
+ }
88
+ );
89
+ }
90
+ const optionMap = "options" in field && Array.isArray(field.options) ? field.options.reduce((acc, option) => {
91
+ acc.set(option.value, option.label);
92
+ return acc;
93
+ }, /* @__PURE__ */ new Map()) : null;
94
+ const resolveOptionLabel = (entry) => {
95
+ if (entry && typeof entry === "object") {
96
+ const record = entry;
97
+ const candidateLabel = record.label;
98
+ if (typeof candidateLabel === "string" && candidateLabel.trim().length) {
99
+ return candidateLabel.trim();
100
+ }
101
+ const candidateValue = record.value ?? record.name;
102
+ if (typeof candidateValue === "string" && candidateValue.trim().length) {
103
+ const normalized2 = candidateValue.trim();
104
+ return optionMap?.get(normalized2) ?? normalized2;
105
+ }
106
+ }
107
+ if (entry === void 0 || entry === null) return "";
108
+ const normalized = String(entry);
109
+ if (!normalized.length) return "";
110
+ return optionMap?.get(normalized) ?? normalized;
111
+ };
112
+ if (value === void 0 || value === null || value === "") {
113
+ return /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: emptyLabel });
114
+ }
115
+ if (Array.isArray(value)) {
116
+ if (!value.length) return /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: emptyLabel });
117
+ return value.map((entry, index) => /* @__PURE__ */ jsx(
118
+ "span",
119
+ {
120
+ className: "mr-1 inline-flex items-center rounded-full bg-muted px-2 py-0.5 text-xs",
121
+ children: resolveOptionLabel(entry) || emptyLabel
122
+ },
123
+ `${field.id}-${index}`
124
+ ));
125
+ }
126
+ if (typeof value === "boolean") {
127
+ return value ? "Yes" : "No";
128
+ }
129
+ if (typeof value === "object") {
130
+ try {
131
+ return JSON.stringify(value);
132
+ } catch {
133
+ return String(value);
134
+ }
135
+ }
136
+ const resolved = resolveOptionLabel(value);
137
+ if (typeof value === "string" && MARKDOWN_FIELD_TYPES.has(field.type)) {
138
+ if (!resolved.trim().length) {
139
+ return /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: emptyLabel });
140
+ }
141
+ return renderMarkdownValue(value, remarkPlugins);
142
+ }
143
+ if (!resolved.length) return /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: emptyLabel });
144
+ return resolved;
145
+ }
146
+ function CustomDataSection({
147
+ entityId,
148
+ entityIds,
149
+ values,
150
+ onSubmit,
151
+ title,
152
+ scopeVersion: scopeVersionProp,
153
+ loadFields,
154
+ labels,
155
+ definitionHref: explicitDefinitionHref
156
+ }) {
157
+ const queryClient = useQueryClient();
158
+ const defaultScopeVersion = useOrganizationScopeVersion();
159
+ const scopeVersion = scopeVersionProp ?? defaultScopeVersion;
160
+ const resolvedScopeVersion = React.useMemo(
161
+ () => typeof scopeVersion === "number" ? scopeVersion : Number(scopeVersion) || 0,
162
+ [scopeVersion]
163
+ );
164
+ const [dictionaryMapsByField, setDictionaryMapsByField] = React.useState({});
165
+ const [editing, setEditing] = React.useState(false);
166
+ const sectionRef = React.useRef(null);
167
+ const [markdownPlugins, setMarkdownPlugins] = React.useState([]);
168
+ React.useEffect(() => {
169
+ if (isTestEnv) return;
170
+ let mounted = true;
171
+ void loadMarkdownPlugins().then((plugins) => {
172
+ if (!mounted) return;
173
+ setMarkdownPlugins(plugins);
174
+ });
175
+ return () => {
176
+ mounted = false;
177
+ };
178
+ }, []);
179
+ const resolvedEntityIds = React.useMemo(() => {
180
+ if (Array.isArray(entityIds) && entityIds.length) {
181
+ const dedup = /* @__PURE__ */ new Set();
182
+ const list = [];
183
+ entityIds.forEach((id) => {
184
+ const trimmed = typeof id === "string" ? id.trim() : "";
185
+ if (!trimmed || dedup.has(trimmed)) return;
186
+ dedup.add(trimmed);
187
+ list.push(trimmed);
188
+ });
189
+ return list;
190
+ }
191
+ if (typeof entityId === "string" && entityId.trim().length > 0) {
192
+ return [entityId.trim()];
193
+ }
194
+ return [];
195
+ }, [entityId, entityIds]);
196
+ const primaryEntityId = resolvedEntityIds.length ? resolvedEntityIds[0] : void 0;
197
+ const customFieldFormsQuery = useQuery({
198
+ queryKey: ["customFieldForms", resolvedScopeVersion, ...resolvedEntityIds],
199
+ enabled: resolvedEntityIds.length > 0,
200
+ staleTime: 5 * 60 * 1e3,
201
+ gcTime: 30 * 60 * 1e3,
202
+ queryFn: async () => {
203
+ const loader = loadFields ?? fetchCustomFieldFormFieldsWithDefinitions;
204
+ return loader(resolvedEntityIds);
205
+ }
206
+ });
207
+ const fields = React.useMemo(() => customFieldFormsQuery.data?.fields ?? [], [customFieldFormsQuery.data]);
208
+ const definitions = React.useMemo(
209
+ () => customFieldFormsQuery.data?.definitions ?? [],
210
+ [customFieldFormsQuery.data]
211
+ );
212
+ const [dictionaryLoading, setDictionaryLoading] = React.useState(false);
213
+ const loading = customFieldFormsQuery.isLoading || dictionaryLoading;
214
+ const hasFields = fields.length > 0;
215
+ const definitionHref = explicitDefinitionHref ?? (primaryEntityId ? `/backend/entities/system/${encodeURIComponent(primaryEntityId)}` : void 0);
216
+ React.useEffect(() => {
217
+ if (!hasFields && editing) {
218
+ setEditing(false);
219
+ }
220
+ }, [editing, hasFields]);
221
+ const submitActiveForm = React.useCallback(() => {
222
+ const node = sectionRef.current?.querySelector("form");
223
+ if (!node) return;
224
+ const form = node;
225
+ if (typeof form.requestSubmit === "function") {
226
+ form.requestSubmit();
227
+ return;
228
+ }
229
+ form.dispatchEvent(new Event("submit", { bubbles: true, cancelable: true }));
230
+ }, []);
231
+ const handleEditingKeyDown = React.useCallback(
232
+ (event) => {
233
+ if (!editing) return;
234
+ if (event.key === "Escape") {
235
+ event.preventDefault();
236
+ setEditing(false);
237
+ return;
238
+ }
239
+ if (event.key === "Enter" && (event.metaKey || event.ctrlKey)) {
240
+ event.preventDefault();
241
+ submitActiveForm();
242
+ }
243
+ },
244
+ [editing, submitActiveForm]
245
+ );
246
+ const handleActivate = React.useCallback(() => {
247
+ if (loading || editing || !hasFields) return;
248
+ setEditing(true);
249
+ }, [editing, hasFields, loading]);
250
+ const handleReadOnlyKeyDown = React.useCallback(
251
+ (event) => {
252
+ if (loading || editing || !hasFields) return;
253
+ if (event.key === "Enter" || event.key === " ") {
254
+ event.preventDefault();
255
+ setEditing(true);
256
+ }
257
+ },
258
+ [editing, hasFields, loading]
259
+ );
260
+ React.useEffect(() => {
261
+ if (!resolvedEntityIds.length || !definitions.length) {
262
+ setDictionaryLoading((prev) => prev ? false : prev);
263
+ setDictionaryMapsByField((prev) => Object.keys(prev).length ? {} : prev);
264
+ return;
265
+ }
266
+ let cancelled = false;
267
+ const load = async () => {
268
+ setDictionaryLoading(true);
269
+ try {
270
+ const dictionaryDefs = definitions.map((def) => {
271
+ const rawId = typeof def.dictionaryId === "string" ? def.dictionaryId.trim() : "";
272
+ if (!rawId) return null;
273
+ return { keyLower: def.key.toLowerCase(), dictionaryId: rawId };
274
+ }).filter((entry) => !!entry);
275
+ if (!dictionaryDefs.length) {
276
+ if (!cancelled) {
277
+ setDictionaryMapsByField((prev) => Object.keys(prev).length ? {} : prev);
278
+ }
279
+ return;
280
+ }
281
+ const uniqueDictionaryIds = Array.from(new Set(dictionaryDefs.map((entry) => entry.dictionaryId)));
282
+ const mapsByDictionaryId = {};
283
+ await Promise.all(
284
+ uniqueDictionaryIds.map(async (dictionaryId) => {
285
+ try {
286
+ const data = await ensureDictionaryEntries(queryClient, dictionaryId, resolvedScopeVersion);
287
+ mapsByDictionaryId[dictionaryId] = data.map;
288
+ } catch {
289
+ mapsByDictionaryId[dictionaryId] = {};
290
+ }
291
+ })
292
+ );
293
+ const dictionaryByKey = dictionaryDefs.reduce((acc, entry) => {
294
+ acc.set(entry.keyLower, entry.dictionaryId);
295
+ return acc;
296
+ }, /* @__PURE__ */ new Map());
297
+ const nextMaps = {};
298
+ fields.forEach((field) => {
299
+ const id = typeof field.id === "string" ? field.id : "";
300
+ if (!id) return;
301
+ const normalizedKey = id.startsWith("cf_") ? id.slice(3) : id;
302
+ const keyLower = normalizedKey.toLowerCase();
303
+ if (!keyLower) return;
304
+ const dictionaryId = dictionaryByKey.get(keyLower);
305
+ if (!dictionaryId) return;
306
+ nextMaps[id] = mapsByDictionaryId[dictionaryId] ?? {};
307
+ });
308
+ if (!cancelled) {
309
+ setDictionaryMapsByField((prev) => {
310
+ const prevKeys = Object.keys(prev);
311
+ const nextKeys = Object.keys(nextMaps);
312
+ if (prevKeys.length === nextKeys.length && prevKeys.every((key) => prev[key] === nextMaps[key])) {
313
+ return prev;
314
+ }
315
+ return nextMaps;
316
+ });
317
+ }
318
+ } catch {
319
+ if (!cancelled) {
320
+ setDictionaryMapsByField((prev) => Object.keys(prev).length ? {} : prev);
321
+ }
322
+ } finally {
323
+ if (!cancelled) {
324
+ setDictionaryLoading(false);
325
+ }
326
+ }
327
+ };
328
+ load().catch(() => {
329
+ });
330
+ return () => {
331
+ cancelled = true;
332
+ };
333
+ }, [definitions, fields, queryClient, resolvedEntityIds, resolvedScopeVersion]);
334
+ const handleSubmit = React.useCallback(
335
+ async (input) => {
336
+ await onSubmit(input);
337
+ setEditing(false);
338
+ },
339
+ [onSubmit]
340
+ );
341
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
342
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between group", children: [
343
+ /* @__PURE__ */ jsx("h2", { className: "text-sm font-semibold", children: title }),
344
+ /* @__PURE__ */ jsxs(
345
+ Button,
346
+ {
347
+ type: "button",
348
+ variant: "ghost",
349
+ size: "icon",
350
+ onClick: () => {
351
+ if (!hasFields || loading) return;
352
+ setEditing((prev) => !prev);
353
+ },
354
+ disabled: loading || !hasFields,
355
+ className: editing ? "opacity-100 transition-opacity duration-150" : "opacity-0 transition-opacity duration-150 group-hover:opacity-100 focus-visible:opacity-100",
356
+ children: [
357
+ editing ? /* @__PURE__ */ jsx(X, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(Pencil, { className: "h-4 w-4" }),
358
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: editing ? labels.cancel ?? "Cancel" : labels.edit ?? "Edit" })
359
+ ]
360
+ }
361
+ )
362
+ ] }),
363
+ /* @__PURE__ */ jsx(
364
+ DataLoader,
365
+ {
366
+ isLoading: loading,
367
+ loadingMessage: labels.loading,
368
+ spinnerSize: "md",
369
+ className: "min-h-[120px]",
370
+ children: editing ? /* @__PURE__ */ jsx(
371
+ "div",
372
+ {
373
+ ref: sectionRef,
374
+ className: "rounded-lg border bg-card p-4",
375
+ onKeyDown: handleEditingKeyDown,
376
+ children: /* @__PURE__ */ jsx(
377
+ CrudForm,
378
+ {
379
+ embedded: true,
380
+ entityId: primaryEntityId,
381
+ entityIds: resolvedEntityIds,
382
+ fields,
383
+ initialValues: values,
384
+ onSubmit: handleSubmit,
385
+ submitLabel: labels.saveShortcut,
386
+ isLoading: loading
387
+ }
388
+ )
389
+ }
390
+ ) : /* @__PURE__ */ jsx(
391
+ "div",
392
+ {
393
+ className: cn(
394
+ "rounded-lg border bg-muted/20 p-4 space-y-3 transition hover:border-border/80 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary",
395
+ hasFields && !loading ? "cursor-pointer" : "cursor-default"
396
+ ),
397
+ role: hasFields && !loading ? "button" : void 0,
398
+ tabIndex: hasFields && !loading ? 0 : -1,
399
+ onClick: hasFields && !loading ? handleActivate : void 0,
400
+ onKeyDown: hasFields && !loading ? handleReadOnlyKeyDown : void 0,
401
+ children: !hasFields ? /* @__PURE__ */ jsxs("p", { className: "text-sm text-muted-foreground", children: [
402
+ labels.noFields,
403
+ " ",
404
+ definitionHref && labels.defineFields ? /* @__PURE__ */ jsx(
405
+ Link,
406
+ {
407
+ href: definitionHref,
408
+ className: "font-medium text-primary underline-offset-2 hover:underline focus-visible:underline",
409
+ children: labels.defineFields
410
+ }
411
+ ) : null
412
+ ] }) : fields.map((field) => /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
413
+ /* @__PURE__ */ jsx("p", { className: "text-xs uppercase tracking-wide text-muted-foreground", children: field.label }),
414
+ /* @__PURE__ */ jsx("div", { className: "text-sm break-words", children: formatFieldValue(
415
+ field,
416
+ values?.[field.id],
417
+ labels.emptyValue,
418
+ dictionaryMapsByField[field.id],
419
+ markdownPlugins
420
+ ) })
421
+ ] }, field.id))
422
+ }
423
+ )
424
+ }
425
+ )
426
+ ] });
427
+ }
428
+ var CustomDataSection_default = CustomDataSection;
429
+ export {
430
+ CustomDataSection,
431
+ CustomDataSection_default as default
432
+ };
433
+ //# sourceMappingURL=CustomDataSection.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/backend/detail/CustomDataSection.tsx"],
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport dynamic from 'next/dynamic'\nimport type { PluggableList } from 'unified'\nimport { Pencil, X } from 'lucide-react'\nimport { useQuery, useQueryClient } from '@tanstack/react-query'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { DataLoader } from '@open-mercato/ui/primitives/DataLoader'\nimport type { CrudField } from '@open-mercato/ui/backend/CrudForm'\nimport { CrudForm } from '@open-mercato/ui/backend/CrudForm'\nimport { fetchCustomFieldFormFieldsWithDefinitions } from '@open-mercato/ui/backend/utils/customFieldForms'\nimport type { CustomFieldDefDto } from '@open-mercato/ui/backend/utils/customFieldDefs'\nimport {\n DictionaryValue,\n type DictionaryMap,\n} from '@open-mercato/core/modules/dictionaries/components/dictionaryAppearance'\nimport { ensureDictionaryEntries } from '@open-mercato/core/modules/dictionaries/components/hooks/useDictionaryEntries'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { cn } from '@open-mercato/shared/lib/utils'\n\ntype MarkdownPreviewProps = { children: string; className?: string; remarkPlugins?: PluggableList }\n\nconst isTestEnv = typeof process !== 'undefined' && process.env.NODE_ENV === 'test'\n\nconst MarkdownPreview: React.ComponentType<MarkdownPreviewProps> = isTestEnv\n ? ({ children, className }) => <div className={className}>{children}</div>\n : (dynamic(() => import('react-markdown').then((mod) => mod.default as React.ComponentType<MarkdownPreviewProps>), {\n ssr: false,\n loading: () => null,\n }) as unknown as React.ComponentType<MarkdownPreviewProps>)\n\nlet markdownPluginsPromise: Promise<PluggableList> | null = null\n\nasync function loadMarkdownPlugins(): Promise<PluggableList> {\n if (isTestEnv) return []\n if (!markdownPluginsPromise) {\n markdownPluginsPromise = import('remark-gfm')\n .then((mod) => [mod.default ?? mod] as PluggableList)\n .catch(() => [])\n }\n return markdownPluginsPromise\n}\n\nconst MARKDOWN_FIELD_TYPES = new Set<CrudField['type']>(['text', 'textarea', 'richtext'])\nconst MARKDOWN_CLASSNAME =\n 'text-sm text-foreground break-words [&>*]:mb-2 [&>*:last-child]:mb-0 [&_ul]:ml-4 [&_ul]:list-disc [&_ol]:ml-4 [&_ol]:list-decimal [&_code]:rounded [&_code]:bg-muted [&_code]:px-1 [&_code]:py-0.5 [&_pre]:rounded-md [&_pre]:bg-muted [&_pre]:p-3 [&_pre]:text-xs'\n\nfunction renderMarkdownValue(content: string, remarkPlugins: PluggableList) {\n return (\n <MarkdownPreview remarkPlugins={remarkPlugins} className={MARKDOWN_CLASSNAME}>\n {content}\n </MarkdownPreview>\n )\n}\n\nfunction extractDictionaryValue(entry: unknown): string | null {\n if (typeof entry === 'string') {\n const trimmed = entry.trim()\n return trimmed.length ? trimmed : null\n }\n if (!entry || typeof entry !== 'object') return null\n const record = entry as Record<string, unknown>\n const candidate = record.value ?? record.name ?? record.id ?? record.key ?? record.label\n if (typeof candidate === 'string') {\n const trimmed = candidate.trim()\n return trimmed.length ? trimmed : null\n }\n return null\n}\n\nexport type CustomDataLabels = {\n loading: string\n emptyValue: string\n noFields: string\n defineFields?: string\n saveShortcut: string\n edit?: string\n cancel?: string\n}\n\nexport type CustomDataSectionProps = {\n entityId?: string\n entityIds?: string[]\n values: Record<string, unknown>\n onSubmit: (values: Record<string, unknown>) => Promise<void>\n title: string\n scopeVersion?: string | number | null\n loadFields?: (\n entityIds: string[],\n ) => Promise<{ fields: CrudField[]; definitions: CustomFieldDefDto[] }>\n labels: CustomDataLabels\n definitionHref?: string\n}\n\nfunction formatFieldValue(\n field: CrudField,\n value: unknown,\n emptyLabel: string,\n dictionaryMap?: DictionaryMap,\n remarkPlugins: PluggableList = [],\n): React.ReactNode {\n if (dictionaryMap) {\n if (value === undefined || value === null || value === '') {\n return <span className=\"text-muted-foreground\">{emptyLabel}</span>\n }\n\n if (Array.isArray(value)) {\n const normalizedValues = value\n .map((entry) => extractDictionaryValue(entry))\n .filter((entry): entry is string => typeof entry === 'string' && entry.length > 0)\n\n if (!normalizedValues.length) {\n return <span className=\"text-muted-foreground\">{emptyLabel}</span>\n }\n\n return (\n <div className=\"flex flex-wrap gap-1.5\">\n {normalizedValues.map((entry, index) => (\n <DictionaryValue\n key={`${field.id}-${entry}-${index}`}\n value={entry}\n map={dictionaryMap}\n className=\"inline-flex items-center gap-1 rounded-full border border-border bg-card px-2 py-1 text-xs\"\n iconWrapperClassName=\"inline-flex h-4 w-4 items-center justify-center rounded-full border border-border bg-background\"\n iconClassName=\"h-3 w-3\"\n colorClassName=\"h-2.5 w-2.5 rounded-full\"\n />\n ))}\n </div>\n )\n }\n\n const resolved = extractDictionaryValue(value)\n if (!resolved) {\n return <span className=\"text-muted-foreground\">{emptyLabel}</span>\n }\n\n return (\n <DictionaryValue\n value={resolved}\n map={dictionaryMap}\n className=\"inline-flex items-center gap-2 text-sm\"\n fallback={<span className=\"text-muted-foreground\">{emptyLabel}</span>}\n iconWrapperClassName=\"inline-flex h-6 w-6 items-center justify-center rounded border border-border bg-card\"\n iconClassName=\"h-4 w-4\"\n colorClassName=\"h-3 w-3 rounded-full\"\n />\n )\n }\n\n const optionMap =\n 'options' in field && Array.isArray(field.options)\n ? field.options.reduce<Map<string, string>>((acc, option) => {\n acc.set(option.value, option.label)\n return acc\n }, new Map())\n : null\n\n const resolveOptionLabel = (entry: unknown): string => {\n if (entry && typeof entry === 'object') {\n const record = entry as { label?: unknown; value?: unknown; name?: unknown }\n const candidateLabel = record.label\n if (typeof candidateLabel === 'string' && candidateLabel.trim().length) {\n return candidateLabel.trim()\n }\n const candidateValue = record.value ?? record.name\n if (typeof candidateValue === 'string' && candidateValue.trim().length) {\n const normalized = candidateValue.trim()\n return optionMap?.get(normalized) ?? normalized\n }\n }\n if (entry === undefined || entry === null) return ''\n const normalized = String(entry)\n if (!normalized.length) return ''\n return optionMap?.get(normalized) ?? normalized\n }\n\n if (value === undefined || value === null || value === '') {\n return <span className=\"text-muted-foreground\">{emptyLabel}</span>\n }\n\n if (Array.isArray(value)) {\n if (!value.length) return <span className=\"text-muted-foreground\">{emptyLabel}</span>\n return value.map((entry, index) => (\n <span\n key={`${field.id}-${index}`}\n className=\"mr-1 inline-flex items-center rounded-full bg-muted px-2 py-0.5 text-xs\"\n >\n {resolveOptionLabel(entry) || emptyLabel}\n </span>\n ))\n }\n\n if (typeof value === 'boolean') {\n return value ? 'Yes' : 'No'\n }\n\n if (typeof value === 'object') {\n try {\n return JSON.stringify(value)\n } catch {\n return String(value)\n }\n }\n\n const resolved = resolveOptionLabel(value)\n if (typeof value === 'string' && MARKDOWN_FIELD_TYPES.has(field.type)) {\n if (!resolved.trim().length) {\n return <span className=\"text-muted-foreground\">{emptyLabel}</span>\n }\n return renderMarkdownValue(value, remarkPlugins)\n }\n if (!resolved.length) return <span className=\"text-muted-foreground\">{emptyLabel}</span>\n return resolved\n}\n\nexport function CustomDataSection({\n entityId,\n entityIds,\n values,\n onSubmit,\n title,\n scopeVersion: scopeVersionProp,\n loadFields,\n labels,\n definitionHref: explicitDefinitionHref,\n}: CustomDataSectionProps) {\n const queryClient = useQueryClient()\n const defaultScopeVersion = useOrganizationScopeVersion()\n const scopeVersion = scopeVersionProp ?? defaultScopeVersion\n const resolvedScopeVersion = React.useMemo(\n () => (typeof scopeVersion === 'number' ? scopeVersion : Number(scopeVersion) || 0),\n [scopeVersion],\n )\n const [dictionaryMapsByField, setDictionaryMapsByField] = React.useState<Record<string, DictionaryMap>>({})\n const [editing, setEditing] = React.useState(false)\n const sectionRef = React.useRef<HTMLDivElement | null>(null)\n const [markdownPlugins, setMarkdownPlugins] = React.useState<PluggableList>([])\n React.useEffect(() => {\n if (isTestEnv) return\n let mounted = true\n void loadMarkdownPlugins().then((plugins) => {\n if (!mounted) return\n setMarkdownPlugins(plugins)\n })\n return () => {\n mounted = false\n }\n }, [])\n const resolvedEntityIds = React.useMemo(() => {\n if (Array.isArray(entityIds) && entityIds.length) {\n const dedup = new Set<string>()\n const list: string[] = []\n entityIds.forEach((id) => {\n const trimmed = typeof id === 'string' ? id.trim() : ''\n if (!trimmed || dedup.has(trimmed)) return\n dedup.add(trimmed)\n list.push(trimmed)\n })\n return list\n }\n if (typeof entityId === 'string' && entityId.trim().length > 0) {\n return [entityId.trim()]\n }\n return []\n }, [entityId, entityIds])\n const primaryEntityId = resolvedEntityIds.length ? resolvedEntityIds[0] : undefined\n const customFieldFormsQuery = useQuery({\n queryKey: ['customFieldForms', resolvedScopeVersion, ...resolvedEntityIds],\n enabled: resolvedEntityIds.length > 0,\n staleTime: 5 * 60 * 1000,\n gcTime: 30 * 60 * 1000,\n queryFn: async () => {\n const loader = loadFields ?? fetchCustomFieldFormFieldsWithDefinitions\n return loader(resolvedEntityIds)\n },\n })\n const fields = React.useMemo(() => customFieldFormsQuery.data?.fields ?? [], [customFieldFormsQuery.data])\n const definitions = React.useMemo(\n () => customFieldFormsQuery.data?.definitions ?? [],\n [customFieldFormsQuery.data],\n )\n const [dictionaryLoading, setDictionaryLoading] = React.useState(false)\n const loading = customFieldFormsQuery.isLoading || dictionaryLoading\n const hasFields = fields.length > 0\n const definitionHref = explicitDefinitionHref ?? (primaryEntityId\n ? `/backend/entities/system/${encodeURIComponent(primaryEntityId)}`\n : undefined)\n\n React.useEffect(() => {\n if (!hasFields && editing) {\n setEditing(false)\n }\n }, [editing, hasFields])\n\n const submitActiveForm = React.useCallback(() => {\n const node = sectionRef.current?.querySelector('form')\n if (!node) return\n const form = node as HTMLFormElement\n if (typeof form.requestSubmit === 'function') {\n form.requestSubmit()\n return\n }\n form.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true }))\n }, [])\n\n const handleEditingKeyDown = React.useCallback(\n (event: React.KeyboardEvent<HTMLDivElement>) => {\n if (!editing) return\n if (event.key === 'Escape') {\n event.preventDefault()\n setEditing(false)\n return\n }\n if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) {\n event.preventDefault()\n submitActiveForm()\n }\n },\n [editing, submitActiveForm],\n )\n\n const handleActivate = React.useCallback(() => {\n if (loading || editing || !hasFields) return\n setEditing(true)\n }, [editing, hasFields, loading])\n\n const handleReadOnlyKeyDown = React.useCallback(\n (event: React.KeyboardEvent<HTMLDivElement>) => {\n if (loading || editing || !hasFields) return\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault()\n setEditing(true)\n }\n },\n [editing, hasFields, loading],\n )\n\n React.useEffect(() => {\n if (!resolvedEntityIds.length || !definitions.length) {\n setDictionaryLoading((prev) => (prev ? false : prev))\n setDictionaryMapsByField((prev) => (Object.keys(prev).length ? {} : prev))\n return\n }\n\n let cancelled = false\n const load = async () => {\n setDictionaryLoading(true)\n try {\n const dictionaryDefs = definitions\n .map((def) => {\n const rawId = typeof def.dictionaryId === 'string' ? def.dictionaryId.trim() : ''\n if (!rawId) return null\n return { keyLower: def.key.toLowerCase(), dictionaryId: rawId }\n })\n .filter((entry): entry is { keyLower: string; dictionaryId: string } => !!entry)\n\n if (!dictionaryDefs.length) {\n if (!cancelled) {\n setDictionaryMapsByField((prev) => (Object.keys(prev).length ? {} : prev))\n }\n return\n }\n\n const uniqueDictionaryIds = Array.from(new Set(dictionaryDefs.map((entry) => entry.dictionaryId)))\n const mapsByDictionaryId: Record<string, DictionaryMap> = {}\n\n await Promise.all(\n uniqueDictionaryIds.map(async (dictionaryId) => {\n try {\n const data = await ensureDictionaryEntries(queryClient, dictionaryId, resolvedScopeVersion)\n mapsByDictionaryId[dictionaryId] = data.map\n } catch {\n mapsByDictionaryId[dictionaryId] = {}\n }\n }),\n )\n\n const dictionaryByKey = dictionaryDefs.reduce<Map<string, string>>((acc, entry) => {\n acc.set(entry.keyLower, entry.dictionaryId)\n return acc\n }, new Map())\n\n const nextMaps: Record<string, DictionaryMap> = {}\n fields.forEach((field) => {\n const id = typeof field.id === 'string' ? field.id : ''\n if (!id) return\n const normalizedKey = id.startsWith('cf_') ? id.slice(3) : id\n const keyLower = normalizedKey.toLowerCase()\n if (!keyLower) return\n const dictionaryId = dictionaryByKey.get(keyLower)\n if (!dictionaryId) return\n nextMaps[id] = mapsByDictionaryId[dictionaryId] ?? {}\n })\n\n if (!cancelled) {\n setDictionaryMapsByField((prev) => {\n const prevKeys = Object.keys(prev)\n const nextKeys = Object.keys(nextMaps)\n if (\n prevKeys.length === nextKeys.length &&\n prevKeys.every((key) => prev[key] === nextMaps[key])\n ) {\n return prev\n }\n return nextMaps\n })\n }\n } catch {\n if (!cancelled) {\n setDictionaryMapsByField((prev) => (Object.keys(prev).length ? {} : prev))\n }\n } finally {\n if (!cancelled) {\n setDictionaryLoading(false)\n }\n }\n }\n\n load().catch(() => {})\n return () => {\n cancelled = true\n }\n }, [definitions, fields, queryClient, resolvedEntityIds, resolvedScopeVersion])\n\n const handleSubmit = React.useCallback(\n async (input: Record<string, unknown>) => {\n await onSubmit(input)\n setEditing(false)\n },\n [onSubmit],\n )\n\n return (\n <div className=\"space-y-3\">\n <div className=\"flex items-center justify-between group\">\n <h2 className=\"text-sm font-semibold\">{title}</h2>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n onClick={() => {\n if (!hasFields || loading) return\n setEditing((prev) => !prev)\n }}\n disabled={loading || !hasFields}\n className={\n editing\n ? 'opacity-100 transition-opacity duration-150'\n : 'opacity-0 transition-opacity duration-150 group-hover:opacity-100 focus-visible:opacity-100'\n }\n >\n {editing ? <X className=\"h-4 w-4\" /> : <Pencil className=\"h-4 w-4\" />}\n <span className=\"sr-only\">{editing ? labels.cancel ?? 'Cancel' : labels.edit ?? 'Edit'}</span>\n </Button>\n </div>\n <DataLoader\n isLoading={loading}\n loadingMessage={labels.loading}\n spinnerSize=\"md\"\n className=\"min-h-[120px]\"\n >\n {editing ? (\n <div\n ref={sectionRef}\n className=\"rounded-lg border bg-card p-4\"\n onKeyDown={handleEditingKeyDown}\n >\n <CrudForm<Record<string, unknown>>\n embedded\n entityId={primaryEntityId}\n entityIds={resolvedEntityIds}\n fields={fields}\n initialValues={values}\n onSubmit={handleSubmit}\n submitLabel={labels.saveShortcut}\n isLoading={loading}\n />\n </div>\n ) : (\n <div\n className={cn(\n 'rounded-lg border bg-muted/20 p-4 space-y-3 transition hover:border-border/80 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary',\n hasFields && !loading ? 'cursor-pointer' : 'cursor-default',\n )}\n role={hasFields && !loading ? 'button' : undefined}\n tabIndex={hasFields && !loading ? 0 : -1}\n onClick={hasFields && !loading ? handleActivate : undefined}\n onKeyDown={hasFields && !loading ? handleReadOnlyKeyDown : undefined}\n >\n {!hasFields ? (\n <p className=\"text-sm text-muted-foreground\">\n {labels.noFields}{' '}\n {definitionHref && labels.defineFields ? (\n <Link\n href={definitionHref}\n className=\"font-medium text-primary underline-offset-2 hover:underline focus-visible:underline\"\n >\n {labels.defineFields}\n </Link>\n ) : null}\n </p>\n ) : (\n fields.map((field) => (\n <div key={field.id} className=\"space-y-1\">\n <p className=\"text-xs uppercase tracking-wide text-muted-foreground\">\n {field.label}\n </p>\n <div className=\"text-sm break-words\">\n {formatFieldValue(\n field,\n values?.[field.id],\n labels.emptyValue,\n dictionaryMapsByField[field.id],\n markdownPlugins,\n )}\n </div>\n </div>\n ))\n )}\n </div>\n )}\n </DataLoader>\n </div>\n )\n}\n\nexport default CustomDataSection\n"],
5
+ "mappings": "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",
6
+ "names": ["resolved", "normalized"]
7
+ }
@@ -0,0 +1,75 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import {
4
+ InlineMultilineEditor,
5
+ InlineSelectEditor,
6
+ InlineTextEditor
7
+ } from "./InlineEditors.js";
8
+ function DetailFieldsSection({ fields, className }) {
9
+ return /* @__PURE__ */ jsx("div", { className: ["grid gap-4 sm:grid-cols-2 xl:grid-cols-3", className].filter(Boolean).join(" "), children: fields.map((field) => {
10
+ const variant = field.editorVariant ?? "muted";
11
+ const activateOnClick = field.activateOnClick ?? true;
12
+ const containerClassName = field.containerClassName ?? void 0;
13
+ const triggerClassName = field.triggerClassName ?? void 0;
14
+ const wrapperClassName = field.gridClassName ?? void 0;
15
+ if (field.kind === "text") {
16
+ return /* @__PURE__ */ jsx("div", { className: wrapperClassName, children: /* @__PURE__ */ jsx(
17
+ InlineTextEditor,
18
+ {
19
+ label: field.label,
20
+ value: field.value,
21
+ placeholder: field.placeholder,
22
+ emptyLabel: field.emptyLabel,
23
+ onSave: field.onSave,
24
+ inputType: field.inputType,
25
+ validator: field.validator,
26
+ variant,
27
+ activateOnClick,
28
+ containerClassName,
29
+ triggerClassName,
30
+ hideLabel: field.hideLabel,
31
+ renderDisplay: field.renderDisplay
32
+ }
33
+ ) }, field.key);
34
+ }
35
+ if (field.kind === "multiline") {
36
+ return /* @__PURE__ */ jsx("div", { className: wrapperClassName, children: /* @__PURE__ */ jsx(
37
+ InlineMultilineEditor,
38
+ {
39
+ label: field.label,
40
+ value: field.value,
41
+ placeholder: field.placeholder,
42
+ emptyLabel: field.emptyLabel,
43
+ onSave: field.onSave,
44
+ validator: field.validator,
45
+ variant: variant === "plain" ? "default" : variant,
46
+ activateOnClick,
47
+ containerClassName,
48
+ triggerClassName,
49
+ renderDisplay: field.renderDisplay
50
+ }
51
+ ) }, field.key);
52
+ }
53
+ if (field.kind === "select") {
54
+ return /* @__PURE__ */ jsx("div", { className: wrapperClassName, children: /* @__PURE__ */ jsx(
55
+ InlineSelectEditor,
56
+ {
57
+ label: field.label,
58
+ value: field.value,
59
+ emptyLabel: field.emptyLabel,
60
+ onSave: field.onSave,
61
+ options: field.options,
62
+ variant,
63
+ activateOnClick,
64
+ containerClassName,
65
+ triggerClassName
66
+ }
67
+ ) }, field.key);
68
+ }
69
+ return /* @__PURE__ */ jsx("div", { className: wrapperClassName, children: field.render() }, field.key);
70
+ }) });
71
+ }
72
+ export {
73
+ DetailFieldsSection
74
+ };
75
+ //# sourceMappingURL=DetailFieldsSection.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/backend/detail/DetailFieldsSection.tsx"],
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport {\n InlineMultilineEditor,\n InlineSelectEditor,\n InlineTextEditor,\n type InlineSelectOption,\n type InlineTextEditorProps,\n type InlineMultilineEditorProps,\n} from './InlineEditors'\n\ntype EditorVariant = 'default' | 'muted' | 'plain'\n\ntype DetailFieldCommon = {\n key: string\n label: string\n emptyLabel: string\n gridClassName?: string\n editorVariant?: EditorVariant\n activateOnClick?: boolean\n containerClassName?: string\n triggerClassName?: string\n}\n\nexport type DetailTextFieldConfig = DetailFieldCommon & {\n kind: 'text'\n value: string | null | undefined\n placeholder?: string\n onSave: (value: string | null) => Promise<void>\n inputType?: React.HTMLInputTypeAttribute\n validator?: (value: string) => string | null\n hideLabel?: boolean\n renderDisplay?: InlineTextEditorProps['renderDisplay']\n}\n\nexport type DetailMultilineFieldConfig = DetailFieldCommon & {\n kind: 'multiline'\n value: string | null | undefined\n placeholder?: string\n onSave: (value: string | null) => Promise<void>\n validator?: (value: string) => string | null\n renderDisplay?: InlineMultilineEditorProps['renderDisplay']\n}\n\nexport type DetailSelectFieldConfig = DetailFieldCommon & {\n kind: 'select'\n value: string | null | undefined\n onSave: (value: string | null) => Promise<void>\n options: InlineSelectOption[]\n}\n\nexport type DetailCustomFieldConfig = DetailFieldCommon & {\n kind: 'custom'\n render: () => React.ReactNode\n}\n\nexport type DetailFieldConfig =\n | DetailTextFieldConfig\n | DetailMultilineFieldConfig\n | DetailSelectFieldConfig\n | DetailCustomFieldConfig\n\nexport type DetailFieldsSectionProps = {\n fields: DetailFieldConfig[]\n className?: string\n}\n\nexport function DetailFieldsSection({ fields, className }: DetailFieldsSectionProps) {\n return (\n <div className={['grid gap-4 sm:grid-cols-2 xl:grid-cols-3', className].filter(Boolean).join(' ')}>\n {fields.map((field) => {\n const variant = field.editorVariant ?? 'muted'\n const activateOnClick = field.activateOnClick ?? true\n const containerClassName = field.containerClassName ?? undefined\n const triggerClassName = field.triggerClassName ?? undefined\n const wrapperClassName = field.gridClassName ?? undefined\n\n if (field.kind === 'text') {\n return (\n <div key={field.key} className={wrapperClassName}>\n <InlineTextEditor\n label={field.label}\n value={field.value}\n placeholder={field.placeholder}\n emptyLabel={field.emptyLabel}\n onSave={field.onSave}\n inputType={field.inputType}\n validator={field.validator}\n variant={variant}\n activateOnClick={activateOnClick}\n containerClassName={containerClassName}\n triggerClassName={triggerClassName}\n hideLabel={field.hideLabel}\n renderDisplay={field.renderDisplay}\n />\n </div>\n )\n }\n\n if (field.kind === 'multiline') {\n return (\n <div key={field.key} className={wrapperClassName}>\n <InlineMultilineEditor\n label={field.label}\n value={field.value}\n placeholder={field.placeholder}\n emptyLabel={field.emptyLabel}\n onSave={field.onSave}\n validator={field.validator}\n variant={variant === 'plain' ? 'default' : variant}\n activateOnClick={activateOnClick}\n containerClassName={containerClassName}\n triggerClassName={triggerClassName}\n renderDisplay={field.renderDisplay}\n />\n </div>\n )\n }\n\n if (field.kind === 'select') {\n return (\n <div key={field.key} className={wrapperClassName}>\n <InlineSelectEditor\n label={field.label}\n value={field.value}\n emptyLabel={field.emptyLabel}\n onSave={field.onSave}\n options={field.options}\n variant={variant}\n activateOnClick={activateOnClick}\n containerClassName={containerClassName}\n triggerClassName={triggerClassName}\n />\n </div>\n )\n }\n\n return (\n <div key={field.key} className={wrapperClassName}>\n {field.render()}\n </div>\n )\n })}\n </div>\n )\n}\n"],
5
+ "mappings": ";AAiFc;AA9Ed;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAIK;AA0DA,SAAS,oBAAoB,EAAE,QAAQ,UAAU,GAA6B;AACnF,SACE,oBAAC,SAAI,WAAW,CAAC,4CAA4C,SAAS,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG,GAC7F,iBAAO,IAAI,CAAC,UAAU;AACrB,UAAM,UAAU,MAAM,iBAAiB;AACvC,UAAM,kBAAkB,MAAM,mBAAmB;AACjD,UAAM,qBAAqB,MAAM,sBAAsB;AACvD,UAAM,mBAAmB,MAAM,oBAAoB;AACnD,UAAM,mBAAmB,MAAM,iBAAiB;AAEhD,QAAI,MAAM,SAAS,QAAQ;AACzB,aACE,oBAAC,SAAoB,WAAW,kBAC9B;AAAA,QAAC;AAAA;AAAA,UACC,OAAO,MAAM;AAAA,UACb,OAAO,MAAM;AAAA,UACb,aAAa,MAAM;AAAA,UACnB,YAAY,MAAM;AAAA,UAClB,QAAQ,MAAM;AAAA,UACd,WAAW,MAAM;AAAA,UACjB,WAAW,MAAM;AAAA,UACjB;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA,WAAW,MAAM;AAAA,UACjB,eAAe,MAAM;AAAA;AAAA,MACvB,KAfQ,MAAM,GAgBhB;AAAA,IAEJ;AAEA,QAAI,MAAM,SAAS,aAAa;AAC9B,aACE,oBAAC,SAAoB,WAAW,kBAC9B;AAAA,QAAC;AAAA;AAAA,UACC,OAAO,MAAM;AAAA,UACb,OAAO,MAAM;AAAA,UACb,aAAa,MAAM;AAAA,UACnB,YAAY,MAAM;AAAA,UAClB,QAAQ,MAAM;AAAA,UACd,WAAW,MAAM;AAAA,UACjB,SAAS,YAAY,UAAU,YAAY;AAAA,UAC3C;AAAA,UACA;AAAA,UACA;AAAA,UACA,eAAe,MAAM;AAAA;AAAA,MACvB,KAbQ,MAAM,GAchB;AAAA,IAEJ;AAEA,QAAI,MAAM,SAAS,UAAU;AAC3B,aACE,oBAAC,SAAoB,WAAW,kBAC9B;AAAA,QAAC;AAAA;AAAA,UACC,OAAO,MAAM;AAAA,UACb,OAAO,MAAM;AAAA,UACb,YAAY,MAAM;AAAA,UAClB,QAAQ,MAAM;AAAA,UACd,SAAS,MAAM;AAAA,UACf;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA;AAAA,MACF,KAXQ,MAAM,GAYhB;AAAA,IAEJ;AAEA,WACE,oBAAC,SAAoB,WAAW,kBAC7B,gBAAM,OAAO,KADN,MAAM,GAEhB;AAAA,EAEJ,CAAC,GACH;AAEJ;",
6
+ "names": []
7
+ }
@@ -0,0 +1,28 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { AlertCircle } from "lucide-react";
4
+ import { cn } from "@open-mercato/shared/lib/utils";
5
+ function ErrorMessage({ label, description, action, className, iconClassName }) {
6
+ return /* @__PURE__ */ jsxs(
7
+ "div",
8
+ {
9
+ className: cn(
10
+ "flex items-start gap-3 rounded border border-destructive/50 bg-destructive/5 px-3 py-2 text-sm text-destructive",
11
+ className
12
+ ),
13
+ role: "alert",
14
+ children: [
15
+ /* @__PURE__ */ jsx(AlertCircle, { className: cn("h-4 w-4 flex-none", iconClassName), "aria-hidden": true }),
16
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
17
+ /* @__PURE__ */ jsx("p", { className: "leading-tight", children: label }),
18
+ description ? /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: description }) : null,
19
+ action ? /* @__PURE__ */ jsx("div", { className: "pt-1", children: action }) : null
20
+ ] })
21
+ ]
22
+ }
23
+ );
24
+ }
25
+ export {
26
+ ErrorMessage
27
+ };
28
+ //# sourceMappingURL=ErrorMessage.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/backend/detail/ErrorMessage.tsx"],
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { AlertCircle } from 'lucide-react'\nimport { cn } from '@open-mercato/shared/lib/utils'\n\ntype ErrorMessageProps = {\n label: string\n description?: string\n action?: React.ReactNode\n className?: string\n iconClassName?: string\n}\n\nexport function ErrorMessage({ label, description, action, className, iconClassName }: ErrorMessageProps) {\n return (\n <div\n className={cn(\n 'flex items-start gap-3 rounded border border-destructive/50 bg-destructive/5 px-3 py-2 text-sm text-destructive',\n className\n )}\n role=\"alert\"\n >\n <AlertCircle className={cn('h-4 w-4 flex-none', iconClassName)} aria-hidden />\n <div className=\"space-y-1\">\n <p className=\"leading-tight\">{label}</p>\n {description ? <p className=\"text-muted-foreground\">{description}</p> : null}\n {action ? <div className=\"pt-1\">{action}</div> : null}\n </div>\n </div>\n )\n}\n"],
5
+ "mappings": ";AAuBM,cACA,YADA;AApBN,SAAS,mBAAmB;AAC5B,SAAS,UAAU;AAUZ,SAAS,aAAa,EAAE,OAAO,aAAa,QAAQ,WAAW,cAAc,GAAsB;AACxG,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACA,MAAK;AAAA,MAEL;AAAA,4BAAC,eAAY,WAAW,GAAG,qBAAqB,aAAa,GAAG,eAAW,MAAC;AAAA,QAC5E,qBAAC,SAAI,WAAU,aACb;AAAA,8BAAC,OAAE,WAAU,iBAAiB,iBAAM;AAAA,UACnC,cAAc,oBAAC,OAAE,WAAU,yBAAyB,uBAAY,IAAO;AAAA,UACvE,SAAS,oBAAC,SAAI,WAAU,QAAQ,kBAAO,IAAS;AAAA,WACnD;AAAA;AAAA;AAAA,EACF;AAEJ;",
6
+ "names": []
7
+ }