@open-mercato/core 0.10.1-develop.7709.1.046124599c → 0.10.1-develop.7714.1.9dbb8200d8

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 (276) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/dist/generated/entities/asn/index.js +19 -0
  3. package/dist/generated/entities/asn/index.js.map +7 -0
  4. package/dist/generated/entities/putaway_task/index.js +25 -0
  5. package/dist/generated/entities/putaway_task/index.js.map +7 -0
  6. package/dist/generated/entities/receiving_line/index.js +21 -0
  7. package/dist/generated/entities/receiving_line/index.js.map +7 -0
  8. package/dist/generated/entities.ids.generated.js +4 -1
  9. package/dist/generated/entities.ids.generated.js.map +2 -2
  10. package/dist/generated/entity-fields-registry.js +34 -0
  11. package/dist/generated/entity-fields-registry.js.map +2 -2
  12. package/dist/modules/communication_channels/lib/sanitize-channel-html.js +22 -3
  13. package/dist/modules/communication_channels/lib/sanitize-channel-html.js.map +2 -2
  14. package/dist/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.client.js +40 -11
  15. package/dist/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.client.js.map +2 -2
  16. package/dist/modules/customers/components/calendar/CalendarEventEditor.js +13 -2
  17. package/dist/modules/customers/components/calendar/CalendarEventEditor.js.map +2 -2
  18. package/dist/modules/wms/acl.js +2 -0
  19. package/dist/modules/wms/acl.js.map +2 -2
  20. package/dist/modules/wms/api/asns/[id]/complete/route.js +61 -0
  21. package/dist/modules/wms/api/asns/[id]/complete/route.js.map +7 -0
  22. package/dist/modules/wms/api/asns/[id]/receive/route.js +68 -0
  23. package/dist/modules/wms/api/asns/[id]/receive/route.js.map +7 -0
  24. package/dist/modules/wms/api/asns/route.js +197 -0
  25. package/dist/modules/wms/api/asns/route.js.map +7 -0
  26. package/dist/modules/wms/api/inventory/helpers.js +42 -34
  27. package/dist/modules/wms/api/inventory/helpers.js.map +2 -2
  28. package/dist/modules/wms/api/inventory/receive/route.js +1 -1
  29. package/dist/modules/wms/api/inventory/receive/route.js.map +2 -2
  30. package/dist/modules/wms/api/listEnrichers.js +23 -0
  31. package/dist/modules/wms/api/listEnrichers.js.map +2 -2
  32. package/dist/modules/wms/api/putaway-tasks/[id]/assign/route.js +56 -0
  33. package/dist/modules/wms/api/putaway-tasks/[id]/assign/route.js.map +7 -0
  34. package/dist/modules/wms/api/putaway-tasks/[id]/cancel/route.js +56 -0
  35. package/dist/modules/wms/api/putaway-tasks/[id]/cancel/route.js.map +7 -0
  36. package/dist/modules/wms/api/putaway-tasks/[id]/complete/route.js +65 -0
  37. package/dist/modules/wms/api/putaway-tasks/[id]/complete/route.js.map +7 -0
  38. package/dist/modules/wms/api/putaway-tasks/[id]/start/route.js +56 -0
  39. package/dist/modules/wms/api/putaway-tasks/[id]/start/route.js.map +7 -0
  40. package/dist/modules/wms/api/putaway-tasks/create-from-balance/route.js +51 -0
  41. package/dist/modules/wms/api/putaway-tasks/create-from-balance/route.js.map +7 -0
  42. package/dist/modules/wms/api/putaway-tasks/route.js +192 -0
  43. package/dist/modules/wms/api/putaway-tasks/route.js.map +7 -0
  44. package/dist/modules/wms/api/receiving-lines/route.js +180 -0
  45. package/dist/modules/wms/api/receiving-lines/route.js.map +7 -0
  46. package/dist/modules/wms/api/scan/putaway/route.js +104 -0
  47. package/dist/modules/wms/api/scan/putaway/route.js.map +7 -0
  48. package/dist/modules/wms/api/scan/receive/route.js +109 -0
  49. package/dist/modules/wms/api/scan/receive/route.js.map +7 -0
  50. package/dist/modules/wms/api/scan/resolve-location/route.js +89 -0
  51. package/dist/modules/wms/api/scan/resolve-location/route.js.map +7 -0
  52. package/dist/modules/wms/api/scan/resolve-lot/route.js +89 -0
  53. package/dist/modules/wms/api/scan/resolve-lot/route.js.map +7 -0
  54. package/dist/modules/wms/backend/wms/asns/[id]/page.js +9 -0
  55. package/dist/modules/wms/backend/wms/asns/[id]/page.js.map +7 -0
  56. package/dist/modules/wms/backend/wms/asns/[id]/page.meta.js +18 -0
  57. package/dist/modules/wms/backend/wms/asns/[id]/page.meta.js.map +7 -0
  58. package/dist/modules/wms/backend/wms/asns/page.js +9 -0
  59. package/dist/modules/wms/backend/wms/asns/page.js.map +7 -0
  60. package/dist/modules/wms/backend/wms/asns/page.meta.js +18 -0
  61. package/dist/modules/wms/backend/wms/asns/page.meta.js.map +7 -0
  62. package/dist/modules/wms/backend/wms/putaway/page.js +9 -0
  63. package/dist/modules/wms/backend/wms/putaway/page.js.map +7 -0
  64. package/dist/modules/wms/backend/wms/putaway/page.meta.js +18 -0
  65. package/dist/modules/wms/backend/wms/putaway/page.meta.js.map +7 -0
  66. package/dist/modules/wms/backend/wms/receiving/page.js +8 -0
  67. package/dist/modules/wms/backend/wms/receiving/page.js.map +7 -0
  68. package/dist/modules/wms/backend/wms/receiving/page.meta.js +18 -0
  69. package/dist/modules/wms/backend/wms/receiving/page.meta.js.map +7 -0
  70. package/dist/modules/wms/ce.js +27 -0
  71. package/dist/modules/wms/ce.js.map +2 -2
  72. package/dist/modules/wms/commands/asn.js +1746 -0
  73. package/dist/modules/wms/commands/asn.js.map +7 -0
  74. package/dist/modules/wms/commands/index.js +2 -0
  75. package/dist/modules/wms/commands/index.js.map +2 -2
  76. package/dist/modules/wms/commands/inventory-actions.js +347 -243
  77. package/dist/modules/wms/commands/inventory-actions.js.map +2 -2
  78. package/dist/modules/wms/commands/putaway.js +945 -0
  79. package/dist/modules/wms/commands/putaway.js.map +7 -0
  80. package/dist/modules/wms/commands/shared.js +51 -0
  81. package/dist/modules/wms/commands/shared.js.map +2 -2
  82. package/dist/modules/wms/components/backend/AssignPutawayTaskDialog.js +278 -0
  83. package/dist/modules/wms/components/backend/AssignPutawayTaskDialog.js.map +7 -0
  84. package/dist/modules/wms/components/backend/CompletePutawayTaskDialog.js +327 -0
  85. package/dist/modules/wms/components/backend/CompletePutawayTaskDialog.js.map +7 -0
  86. package/dist/modules/wms/components/backend/CreateAsnDialog.js +404 -0
  87. package/dist/modules/wms/components/backend/CreateAsnDialog.js.map +7 -0
  88. package/dist/modules/wms/components/backend/InventoryOperationsSection.js +16 -3
  89. package/dist/modules/wms/components/backend/InventoryOperationsSection.js.map +2 -2
  90. package/dist/modules/wms/components/backend/ReceiveAsnLineDialog.js +476 -0
  91. package/dist/modules/wms/components/backend/ReceiveAsnLineDialog.js.map +7 -0
  92. package/dist/modules/wms/components/backend/ReceiveInventoryDialog.js +2 -2
  93. package/dist/modules/wms/components/backend/ReceiveInventoryDialog.js.map +2 -2
  94. package/dist/modules/wms/components/backend/WmsAsnDetailPage.js +524 -0
  95. package/dist/modules/wms/components/backend/WmsAsnDetailPage.js.map +7 -0
  96. package/dist/modules/wms/components/backend/WmsAsnsListPage.js +339 -0
  97. package/dist/modules/wms/components/backend/WmsAsnsListPage.js.map +7 -0
  98. package/dist/modules/wms/components/backend/WmsLocationDetailPage.js +5 -6
  99. package/dist/modules/wms/components/backend/WmsLocationDetailPage.js.map +2 -2
  100. package/dist/modules/wms/components/backend/WmsLotDetailPage.js +5 -6
  101. package/dist/modules/wms/components/backend/WmsLotDetailPage.js.map +2 -2
  102. package/dist/modules/wms/components/backend/WmsOperationalDashboardPage.js +14 -10
  103. package/dist/modules/wms/components/backend/WmsOperationalDashboardPage.js.map +2 -2
  104. package/dist/modules/wms/components/backend/WmsPutawayQueuePage.js +598 -0
  105. package/dist/modules/wms/components/backend/WmsPutawayQueuePage.js.map +7 -0
  106. package/dist/modules/wms/components/backend/WmsSkuDetailPage.js +5 -6
  107. package/dist/modules/wms/components/backend/WmsSkuDetailPage.js.map +2 -2
  108. package/dist/modules/wms/components/backend/asnCompleteOptimisticLock.js +32 -0
  109. package/dist/modules/wms/components/backend/asnCompleteOptimisticLock.js.map +7 -0
  110. package/dist/modules/wms/components/backend/asnReceivingLinesLoader.js +36 -0
  111. package/dist/modules/wms/components/backend/asnReceivingLinesLoader.js.map +7 -0
  112. package/dist/modules/wms/components/backend/inboundStatusUi.js +106 -0
  113. package/dist/modules/wms/components/backend/inboundStatusUi.js.map +7 -0
  114. package/dist/modules/wms/components/backend/inventoryMutationLoaders.js +17 -0
  115. package/dist/modules/wms/components/backend/inventoryMutationLoaders.js.map +2 -2
  116. package/dist/modules/wms/components/backend/putawayQueueOptimisticLock.js +14 -0
  117. package/dist/modules/wms/components/backend/putawayQueueOptimisticLock.js.map +7 -0
  118. package/dist/modules/wms/components/backend/useWmsInventoryMutationAccess.js +4 -0
  119. package/dist/modules/wms/components/backend/useWmsInventoryMutationAccess.js.map +2 -2
  120. package/dist/modules/wms/data/enrichers.js +117 -12
  121. package/dist/modules/wms/data/enrichers.js.map +2 -2
  122. package/dist/modules/wms/data/entities.js +150 -1
  123. package/dist/modules/wms/data/entities.js.map +2 -2
  124. package/dist/modules/wms/data/validators.js +214 -0
  125. package/dist/modules/wms/data/validators.js.map +2 -2
  126. package/dist/modules/wms/events.js +12 -1
  127. package/dist/modules/wms/events.js.map +2 -2
  128. package/dist/modules/wms/extension-points.js +3 -1
  129. package/dist/modules/wms/extension-points.js.map +2 -2
  130. package/dist/modules/wms/lib/asnReceiving.js +124 -0
  131. package/dist/modules/wms/lib/asnReceiving.js.map +7 -0
  132. package/dist/modules/wms/lib/asnVendor.js +52 -0
  133. package/dist/modules/wms/lib/asnVendor.js.map +7 -0
  134. package/dist/modules/wms/lib/flashMutationError.js +4 -0
  135. package/dist/modules/wms/lib/flashMutationError.js.map +2 -2
  136. package/dist/modules/wms/lib/inventoryDisplayUi.js +24 -0
  137. package/dist/modules/wms/lib/inventoryDisplayUi.js.map +2 -2
  138. package/dist/modules/wms/lib/inventoryPolicy.js +17 -0
  139. package/dist/modules/wms/lib/inventoryPolicy.js.map +2 -2
  140. package/dist/modules/wms/lib/loadOperationalDashboard.js +67 -1
  141. package/dist/modules/wms/lib/loadOperationalDashboard.js.map +2 -2
  142. package/dist/modules/wms/lib/putaway.js +141 -0
  143. package/dist/modules/wms/lib/putaway.js.map +7 -0
  144. package/dist/modules/wms/lib/roleFeatures.js +3 -1
  145. package/dist/modules/wms/lib/roleFeatures.js.map +2 -2
  146. package/dist/modules/wms/lib/salesOrderInventoryAutomation.js +99 -1
  147. package/dist/modules/wms/lib/salesOrderInventoryAutomation.js.map +2 -2
  148. package/dist/modules/wms/lib/scanResolve.js +147 -0
  149. package/dist/modules/wms/lib/scanResolve.js.map +7 -0
  150. package/dist/modules/wms/lib/wmsIntegrationToggles.js +1 -1
  151. package/dist/modules/wms/lib/wmsIntegrationToggles.js.map +2 -2
  152. package/dist/modules/wms/migrations/Migration20260812120000.js +43 -0
  153. package/dist/modules/wms/migrations/Migration20260812120000.js.map +7 -0
  154. package/dist/modules/wms/migrations/Migration20260812130000.js +30 -0
  155. package/dist/modules/wms/migrations/Migration20260812130000.js.map +7 -0
  156. package/dist/modules/wms/migrations/Migration20260822120000.js +17 -0
  157. package/dist/modules/wms/migrations/Migration20260822120000.js.map +7 -0
  158. package/dist/modules/wms/migrations/Migration20260822130000.js +17 -0
  159. package/dist/modules/wms/migrations/Migration20260822130000.js.map +7 -0
  160. package/dist/modules/wms/migrations/Migration20261029120000.js +13 -0
  161. package/dist/modules/wms/migrations/Migration20261029120000.js.map +7 -0
  162. package/dist/modules/wms/search.js +85 -0
  163. package/dist/modules/wms/search.js.map +2 -2
  164. package/dist/modules/wms/subscribers/asn-line-received-reevaluate-reservations.js +14 -0
  165. package/dist/modules/wms/subscribers/asn-line-received-reevaluate-reservations.js.map +7 -0
  166. package/dist/modules/wms/subscribers/invalidate-enricher-cache-asn.js +12 -0
  167. package/dist/modules/wms/subscribers/invalidate-enricher-cache-asn.js.map +7 -0
  168. package/dist/modules/wms/subscribers/procurement-goods-receipt-created.js +163 -0
  169. package/dist/modules/wms/subscribers/procurement-goods-receipt-created.js.map +7 -0
  170. package/dist/modules/wms/subscribers/putaway-completed-reevaluate-reservations.js +14 -0
  171. package/dist/modules/wms/subscribers/putaway-completed-reevaluate-reservations.js.map +7 -0
  172. package/dist/modules/wms/widgets/injection/sales-order-stock-context/widget.client.js +37 -8
  173. package/dist/modules/wms/widgets/injection/sales-order-stock-context/widget.client.js.map +2 -2
  174. package/generated/entities/asn/index.ts +8 -0
  175. package/generated/entities/putaway_task/index.ts +11 -0
  176. package/generated/entities/receiving_line/index.ts +9 -0
  177. package/generated/entities.ids.generated.ts +4 -1
  178. package/generated/entity-fields-registry.ts +34 -0
  179. package/package.json +7 -7
  180. package/src/modules/auth/i18n/de.json +2 -0
  181. package/src/modules/auth/i18n/en.json +2 -0
  182. package/src/modules/auth/i18n/es.json +2 -0
  183. package/src/modules/auth/i18n/ko.json +2 -0
  184. package/src/modules/auth/i18n/pl.json +2 -0
  185. package/src/modules/communication_channels/i18n/de.json +2 -0
  186. package/src/modules/communication_channels/i18n/en.json +2 -0
  187. package/src/modules/communication_channels/i18n/es.json +2 -0
  188. package/src/modules/communication_channels/i18n/ko.json +2 -0
  189. package/src/modules/communication_channels/i18n/pl.json +2 -0
  190. package/src/modules/communication_channels/lib/sanitize-channel-html.ts +23 -3
  191. package/src/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.client.tsx +44 -12
  192. package/src/modules/customers/components/calendar/CalendarEventEditor.tsx +16 -2
  193. package/src/modules/wms/acl.ts +2 -0
  194. package/src/modules/wms/api/asns/[id]/complete/route.ts +62 -0
  195. package/src/modules/wms/api/asns/[id]/receive/route.ts +74 -0
  196. package/src/modules/wms/api/asns/route.ts +200 -0
  197. package/src/modules/wms/api/inventory/helpers.ts +57 -34
  198. package/src/modules/wms/api/inventory/receive/route.ts +2 -1
  199. package/src/modules/wms/api/listEnrichers.ts +38 -0
  200. package/src/modules/wms/api/putaway-tasks/[id]/assign/route.ts +56 -0
  201. package/src/modules/wms/api/putaway-tasks/[id]/cancel/route.ts +56 -0
  202. package/src/modules/wms/api/putaway-tasks/[id]/complete/route.ts +66 -0
  203. package/src/modules/wms/api/putaway-tasks/[id]/start/route.ts +56 -0
  204. package/src/modules/wms/api/putaway-tasks/create-from-balance/route.ts +50 -0
  205. package/src/modules/wms/api/putaway-tasks/route.ts +199 -0
  206. package/src/modules/wms/api/receiving-lines/route.ts +180 -0
  207. package/src/modules/wms/api/scan/putaway/route.ts +108 -0
  208. package/src/modules/wms/api/scan/receive/route.ts +118 -0
  209. package/src/modules/wms/api/scan/resolve-location/route.ts +92 -0
  210. package/src/modules/wms/api/scan/resolve-lot/route.ts +92 -0
  211. package/src/modules/wms/backend/wms/asns/[id]/page.meta.ts +14 -0
  212. package/src/modules/wms/backend/wms/asns/[id]/page.tsx +5 -0
  213. package/src/modules/wms/backend/wms/asns/page.meta.ts +14 -0
  214. package/src/modules/wms/backend/wms/asns/page.tsx +5 -0
  215. package/src/modules/wms/backend/wms/putaway/page.meta.ts +14 -0
  216. package/src/modules/wms/backend/wms/putaway/page.tsx +5 -0
  217. package/src/modules/wms/backend/wms/receiving/page.meta.ts +14 -0
  218. package/src/modules/wms/backend/wms/receiving/page.tsx +5 -0
  219. package/src/modules/wms/ce.ts +27 -0
  220. package/src/modules/wms/commands/asn.ts +2122 -0
  221. package/src/modules/wms/commands/index.ts +2 -0
  222. package/src/modules/wms/commands/inventory-actions.ts +413 -243
  223. package/src/modules/wms/commands/putaway.ts +1169 -0
  224. package/src/modules/wms/commands/shared.ts +51 -0
  225. package/src/modules/wms/components/backend/AssignPutawayTaskDialog.tsx +328 -0
  226. package/src/modules/wms/components/backend/CompletePutawayTaskDialog.tsx +366 -0
  227. package/src/modules/wms/components/backend/CreateAsnDialog.tsx +427 -0
  228. package/src/modules/wms/components/backend/InventoryOperationsSection.tsx +20 -2
  229. package/src/modules/wms/components/backend/ReceiveAsnLineDialog.tsx +535 -0
  230. package/src/modules/wms/components/backend/ReceiveInventoryDialog.tsx +2 -2
  231. package/src/modules/wms/components/backend/WmsAsnDetailPage.tsx +647 -0
  232. package/src/modules/wms/components/backend/WmsAsnsListPage.tsx +407 -0
  233. package/src/modules/wms/components/backend/WmsLocationDetailPage.tsx +4 -5
  234. package/src/modules/wms/components/backend/WmsLotDetailPage.tsx +4 -5
  235. package/src/modules/wms/components/backend/WmsOperationalDashboardPage.tsx +12 -8
  236. package/src/modules/wms/components/backend/WmsPutawayQueuePage.tsx +677 -0
  237. package/src/modules/wms/components/backend/WmsSkuDetailPage.tsx +4 -5
  238. package/src/modules/wms/components/backend/asnCompleteOptimisticLock.ts +52 -0
  239. package/src/modules/wms/components/backend/asnReceivingLinesLoader.ts +83 -0
  240. package/src/modules/wms/components/backend/inboundStatusUi.ts +154 -0
  241. package/src/modules/wms/components/backend/inventoryMutationLoaders.ts +20 -0
  242. package/src/modules/wms/components/backend/putawayQueueOptimisticLock.ts +20 -0
  243. package/src/modules/wms/components/backend/useWmsInventoryMutationAccess.ts +4 -0
  244. package/src/modules/wms/data/enrichers.ts +175 -13
  245. package/src/modules/wms/data/entities.ts +155 -0
  246. package/src/modules/wms/data/validators.ts +256 -0
  247. package/src/modules/wms/events.ts +11 -0
  248. package/src/modules/wms/extension-points.ts +2 -0
  249. package/src/modules/wms/i18n/de.json +205 -3
  250. package/src/modules/wms/i18n/en.json +205 -3
  251. package/src/modules/wms/i18n/es.json +205 -3
  252. package/src/modules/wms/i18n/ko.json +205 -3
  253. package/src/modules/wms/i18n/pl.json +205 -3
  254. package/src/modules/wms/lib/asnReceiving.ts +226 -0
  255. package/src/modules/wms/lib/asnVendor.ts +80 -0
  256. package/src/modules/wms/lib/flashMutationError.ts +4 -0
  257. package/src/modules/wms/lib/inventoryDisplayUi.ts +46 -0
  258. package/src/modules/wms/lib/inventoryPolicy.ts +31 -1
  259. package/src/modules/wms/lib/loadOperationalDashboard.ts +89 -1
  260. package/src/modules/wms/lib/putaway.ts +210 -0
  261. package/src/modules/wms/lib/roleFeatures.ts +2 -0
  262. package/src/modules/wms/lib/salesOrderInventoryAutomation.ts +150 -1
  263. package/src/modules/wms/lib/scanResolve.ts +185 -0
  264. package/src/modules/wms/lib/wmsIntegrationToggles.ts +2 -1
  265. package/src/modules/wms/migrations/.snapshot-open-mercato.json +1000 -0
  266. package/src/modules/wms/migrations/Migration20260812120000.ts +43 -0
  267. package/src/modules/wms/migrations/Migration20260812130000.ts +28 -0
  268. package/src/modules/wms/migrations/Migration20260822120000.ts +17 -0
  269. package/src/modules/wms/migrations/Migration20260822130000.ts +15 -0
  270. package/src/modules/wms/migrations/Migration20261029120000.ts +11 -0
  271. package/src/modules/wms/search.ts +95 -0
  272. package/src/modules/wms/subscribers/asn-line-received-reevaluate-reservations.ts +21 -0
  273. package/src/modules/wms/subscribers/invalidate-enricher-cache-asn.ts +12 -0
  274. package/src/modules/wms/subscribers/procurement-goods-receipt-created.ts +238 -0
  275. package/src/modules/wms/subscribers/putaway-completed-reevaluate-reservations.ts +21 -0
  276. package/src/modules/wms/widgets/injection/sales-order-stock-context/widget.client.tsx +63 -13
@@ -35,20 +35,39 @@ const ALLOWED_TAGS = [
35
35
  ];
36
36
  const ALLOWED_SCHEMES = ["http", "https", "mailto", "tel"];
37
37
  const ALLOWED_SCHEMES_BY_TAG = {
38
- img: ["http", "https", "data"]
38
+ img: ["data"]
39
39
  };
40
40
  const SAFE_CSS_COLOR = /^#(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$|^rgba?\(\s*[\d.,\s%]+\)$|^hsla?\(\s*[\d.,\s%]+\)$|^[a-z]+$/i;
41
41
  const SANITIZE_OPTIONS = {
42
42
  allowedTags: ALLOWED_TAGS,
43
43
  allowedAttributes: {
44
44
  a: ["href", "title", "class"],
45
- img: ["src", "alt", "title", "class", "width", "height"],
45
+ img: ["src", "alt", "title", "class", "width", "height", "data-channel-remote-src"],
46
46
  "*": ["class", "style", "colspan", "rowspan", "width", "height"]
47
47
  },
48
48
  allowedSchemes: ALLOWED_SCHEMES,
49
49
  allowedSchemesByTag: ALLOWED_SCHEMES_BY_TAG,
50
50
  allowedSchemesAppliedToAttributes: ["href", "src"],
51
51
  allowProtocolRelative: false,
52
+ transformTags: {
53
+ img: (tagName, attributes) => {
54
+ const { src } = attributes;
55
+ const safeAttributes = { ...attributes };
56
+ delete safeAttributes.src;
57
+ delete safeAttributes["data-channel-remote-src"];
58
+ if (!src) return { tagName, attribs: safeAttributes };
59
+ if (/^data:/i.test(src)) return { tagName, attribs: { ...safeAttributes, src } };
60
+ try {
61
+ const remoteUrl = new URL(src.startsWith("//") ? `https:${src}` : src);
62
+ if (remoteUrl.protocol === "http:" || remoteUrl.protocol === "https:") {
63
+ safeAttributes["data-channel-remote-src"] = remoteUrl.href;
64
+ }
65
+ } catch {
66
+ return { tagName, attribs: safeAttributes };
67
+ }
68
+ return { tagName, attribs: safeAttributes };
69
+ }
70
+ },
52
71
  // disable inline scripts in style attributes
53
72
  allowedStyles: {
54
73
  "*": {
@@ -77,7 +96,7 @@ const SANITIZE_OPTIONS = {
77
96
  exclusiveFilter: (frame) => {
78
97
  if (frame.tag === "img" && frame.attribs?.src) {
79
98
  const src = frame.attribs.src;
80
- if (src.startsWith("data:") && !/^data:image\/(?:png|jpe?g|gif|webp);base64,/i.test(src)) {
99
+ if (/^data:/i.test(src) && !/^data:image\/(?:png|jpe?g|gif|webp);base64,/i.test(src)) {
81
100
  return true;
82
101
  }
83
102
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/communication_channels/lib/sanitize-channel-html.ts"],
4
- "sourcesContent": ["import sanitizeHtml from 'sanitize-html'\n\n/**\n * HTML sanitizer for channel-supplied payloads (email MIME, Slack rich-text, future channel types).\n *\n * The hub owns this helper; every widget that renders channel HTML \u2014 in the hub itself or in\n * downstream provider packages \u2014 imports it. The Messages module's `channel-payload-renderer`\n * widget calls this before any `dangerouslySetInnerHTML`-style render.\n *\n * Implementation note: backed by `sanitize-html` (CommonJS, server-friendly) rather than\n * DOMPurify so the same code runs in Jest without ESM transform gymnastics. SPEC-045d \u00A74.6\n * specifies \"DOMPurify or equivalent\" \u2014 `sanitize-html` is an allowlist-based equivalent\n * widely used for the same sanitization shape.\n *\n * Allowlist is tuned for email + chat HTML:\n * - Preserves typical email layout primitives (table-based layouts, inline images, basic typography).\n * - Strips `<script>`, `<style>`, `<iframe>`, all event-handler attributes (`on*`).\n * - Blocks `javascript:` and `data:` URLs except `data:image/*;base64,\u2026` (inline base64 images, common in email).\n */\n\nconst ALLOWED_TAGS = [\n 'a',\n 'img',\n 'table',\n 'thead',\n 'tbody',\n 'tfoot',\n 'tr',\n 'td',\n 'th',\n 'p',\n 'br',\n 'hr',\n 'ul',\n 'ol',\n 'li',\n 'h1',\n 'h2',\n 'h3',\n 'h4',\n 'h5',\n 'h6',\n 'strong',\n 'em',\n 'b',\n 'i',\n 'u',\n 's',\n 'blockquote',\n 'code',\n 'pre',\n 'span',\n 'div',\n]\n\nconst ALLOWED_SCHEMES = ['http', 'https', 'mailto', 'tel']\nconst ALLOWED_SCHEMES_BY_TAG = {\n img: ['http', 'https', 'data'],\n}\n\n/**\n * Safe CSS color value shapes only: hex (#rgb / #rgba / #rrggbb / #rrggbbaa),\n * rgb()/rgba(), hsl()/hsla(), and bare named colors (`red`, `transparent`,\n * `currentcolor`, \u2026). Deliberately rejects any value containing `url(` or\n * `expression(` so a `color`/`background-color` declaration can never smuggle a\n * CSS-based tracking beacon or legacy-IE script expression past the sanitizer.\n */\nconst SAFE_CSS_COLOR =\n /^#(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$|^rgba?\\(\\s*[\\d.,\\s%]+\\)$|^hsla?\\(\\s*[\\d.,\\s%]+\\)$|^[a-z]+$/i\n\nconst SANITIZE_OPTIONS: sanitizeHtml.IOptions = {\n allowedTags: ALLOWED_TAGS,\n allowedAttributes: {\n a: ['href', 'title', 'class'],\n img: ['src', 'alt', 'title', 'class', 'width', 'height'],\n '*': ['class', 'style', 'colspan', 'rowspan', 'width', 'height'],\n },\n allowedSchemes: ALLOWED_SCHEMES,\n allowedSchemesByTag: ALLOWED_SCHEMES_BY_TAG,\n allowedSchemesAppliedToAttributes: ['href', 'src'],\n allowProtocolRelative: false,\n // disable inline scripts in style attributes\n allowedStyles: {\n '*': {\n 'background-color': [SAFE_CSS_COLOR],\n color: [SAFE_CSS_COLOR],\n 'text-align': [/^left$|^right$|^center$|^justify$/i],\n 'font-size': [/^\\d+(?:\\.\\d+)?(?:px|em|rem|%)$/],\n 'font-weight': [/^\\d{3}$|^bold$|^normal$/],\n 'font-style': [/^italic$|^normal$/],\n 'text-decoration': [/^underline$|^line-through$|^none$/],\n padding: [/^[\\d\\s.]+(?:px|em|rem|%)?$/],\n margin: [/^[\\d\\s.]+(?:px|em|rem|%)?$/],\n border: [/^[\\d\\s\\w.]+$/],\n 'border-radius': [/^\\d+(?:\\.\\d+)?(?:px|em|rem|%)$/],\n // Permit only `display:none` so the hidden thread-token footer span\n // (`buildBodyFooter`) stays hidden when a sent email body is re-rendered.\n // Every other display value is stripped.\n display: [/^none$/],\n },\n },\n /**\n * data:image/<mime>;base64,... URLs in <img src=...> are explicitly allowed so inline\n * email images render. sanitize-html validates the base64 payload shape; if a future\n * payload tries to smuggle a non-image MIME type via data URL we strip it.\n */\n exclusiveFilter: (frame) => {\n if (frame.tag === 'img' && frame.attribs?.src) {\n const src = frame.attribs.src\n if (src.startsWith('data:') && !/^data:image\\/(?:png|jpe?g|gif|webp);base64,/i.test(src)) {\n return true\n }\n }\n return false\n },\n}\n\n/**\n * Sanitize an HTML string for safe rendering.\n *\n * @param html Raw HTML \u2014 may originate from an external channel (email body, Slack mrkdwn rendered to HTML, \u2026).\n * @returns Sanitized HTML safe for `dangerouslySetInnerHTML`.\n */\nexport function sanitizeChannelHtml(html: string): string {\n if (!html) return ''\n return sanitizeHtml(html, SANITIZE_OPTIONS)\n}\n\nexport default sanitizeChannelHtml\n"],
5
- "mappings": "AAAA,OAAO,kBAAkB;AAoBzB,MAAM,eAAe;AAAA,EACnB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,MAAM,kBAAkB,CAAC,QAAQ,SAAS,UAAU,KAAK;AACzD,MAAM,yBAAyB;AAAA,EAC7B,KAAK,CAAC,QAAQ,SAAS,MAAM;AAC/B;AASA,MAAM,iBACJ;AAEF,MAAM,mBAA0C;AAAA,EAC9C,aAAa;AAAA,EACb,mBAAmB;AAAA,IACjB,GAAG,CAAC,QAAQ,SAAS,OAAO;AAAA,IAC5B,KAAK,CAAC,OAAO,OAAO,SAAS,SAAS,SAAS,QAAQ;AAAA,IACvD,KAAK,CAAC,SAAS,SAAS,WAAW,WAAW,SAAS,QAAQ;AAAA,EACjE;AAAA,EACA,gBAAgB;AAAA,EAChB,qBAAqB;AAAA,EACrB,mCAAmC,CAAC,QAAQ,KAAK;AAAA,EACjD,uBAAuB;AAAA;AAAA,EAEvB,eAAe;AAAA,IACb,KAAK;AAAA,MACH,oBAAoB,CAAC,cAAc;AAAA,MACnC,OAAO,CAAC,cAAc;AAAA,MACtB,cAAc,CAAC,oCAAoC;AAAA,MACnD,aAAa,CAAC,gCAAgC;AAAA,MAC9C,eAAe,CAAC,yBAAyB;AAAA,MACzC,cAAc,CAAC,mBAAmB;AAAA,MAClC,mBAAmB,CAAC,mCAAmC;AAAA,MACvD,SAAS,CAAC,4BAA4B;AAAA,MACtC,QAAQ,CAAC,4BAA4B;AAAA,MACrC,QAAQ,CAAC,cAAc;AAAA,MACvB,iBAAiB,CAAC,gCAAgC;AAAA;AAAA;AAAA;AAAA,MAIlD,SAAS,CAAC,QAAQ;AAAA,IACpB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,iBAAiB,CAAC,UAAU;AAC1B,QAAI,MAAM,QAAQ,SAAS,MAAM,SAAS,KAAK;AAC7C,YAAM,MAAM,MAAM,QAAQ;AAC1B,UAAI,IAAI,WAAW,OAAO,KAAK,CAAC,+CAA+C,KAAK,GAAG,GAAG;AACxF,eAAO;AAAA,MACT;AAAA,IACF;AACA,WAAO;AAAA,EACT;AACF;AAQO,SAAS,oBAAoB,MAAsB;AACxD,MAAI,CAAC,KAAM,QAAO;AAClB,SAAO,aAAa,MAAM,gBAAgB;AAC5C;AAEA,IAAO,gCAAQ;",
4
+ "sourcesContent": ["import sanitizeHtml from 'sanitize-html'\n\n/**\n * HTML sanitizer for channel-supplied payloads (email MIME, Slack rich-text, future channel types).\n *\n * The hub owns this helper; every widget that renders channel HTML \u2014 in the hub itself or in\n * downstream provider packages \u2014 imports it. The Messages module's `channel-payload-renderer`\n * widget calls this before any `dangerouslySetInnerHTML`-style render.\n *\n * Implementation note: backed by `sanitize-html` (CommonJS, server-friendly) rather than\n * DOMPurify so the same code runs in Jest without ESM transform gymnastics. SPEC-045d \u00A74.6\n * specifies \"DOMPurify or equivalent\" \u2014 `sanitize-html` is an allowlist-based equivalent\n * widely used for the same sanitization shape.\n *\n * Allowlist is tuned for email + chat HTML:\n * - Preserves typical email layout primitives (table-based layouts, inline images, basic typography).\n * - Defers network images until the reader explicitly opts in for that message.\n * - Strips `<script>`, `<style>`, `<iframe>`, all event-handler attributes (`on*`).\n * - Blocks `javascript:` and `data:` URLs except `data:image/*;base64,\u2026` (inline base64 images, common in email).\n */\n\nconst ALLOWED_TAGS = [\n 'a',\n 'img',\n 'table',\n 'thead',\n 'tbody',\n 'tfoot',\n 'tr',\n 'td',\n 'th',\n 'p',\n 'br',\n 'hr',\n 'ul',\n 'ol',\n 'li',\n 'h1',\n 'h2',\n 'h3',\n 'h4',\n 'h5',\n 'h6',\n 'strong',\n 'em',\n 'b',\n 'i',\n 'u',\n 's',\n 'blockquote',\n 'code',\n 'pre',\n 'span',\n 'div',\n]\n\nconst ALLOWED_SCHEMES = ['http', 'https', 'mailto', 'tel']\nconst ALLOWED_SCHEMES_BY_TAG = {\n img: ['data'],\n}\n\n/**\n * Safe CSS color value shapes only: hex (#rgb / #rgba / #rrggbb / #rrggbbaa),\n * rgb()/rgba(), hsl()/hsla(), and bare named colors (`red`, `transparent`,\n * `currentcolor`, \u2026). Deliberately rejects any value containing `url(` or\n * `expression(` so a `color`/`background-color` declaration can never smuggle a\n * CSS-based tracking beacon or legacy-IE script expression past the sanitizer.\n */\nconst SAFE_CSS_COLOR =\n /^#(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$|^rgba?\\(\\s*[\\d.,\\s%]+\\)$|^hsla?\\(\\s*[\\d.,\\s%]+\\)$|^[a-z]+$/i\n\nconst SANITIZE_OPTIONS: sanitizeHtml.IOptions = {\n allowedTags: ALLOWED_TAGS,\n allowedAttributes: {\n a: ['href', 'title', 'class'],\n img: ['src', 'alt', 'title', 'class', 'width', 'height', 'data-channel-remote-src'],\n '*': ['class', 'style', 'colspan', 'rowspan', 'width', 'height'],\n },\n allowedSchemes: ALLOWED_SCHEMES,\n allowedSchemesByTag: ALLOWED_SCHEMES_BY_TAG,\n allowedSchemesAppliedToAttributes: ['href', 'src'],\n allowProtocolRelative: false,\n transformTags: {\n img: (tagName, attributes) => {\n const { src } = attributes\n const safeAttributes = { ...attributes }\n delete safeAttributes.src\n delete safeAttributes['data-channel-remote-src']\n if (!src) return { tagName, attribs: safeAttributes }\n if (/^data:/i.test(src)) return { tagName, attribs: { ...safeAttributes, src } }\n try {\n const remoteUrl = new URL(src.startsWith('//') ? `https:${src}` : src)\n if (remoteUrl.protocol === 'http:' || remoteUrl.protocol === 'https:') {\n safeAttributes['data-channel-remote-src'] = remoteUrl.href\n }\n } catch {\n return { tagName, attribs: safeAttributes }\n }\n return { tagName, attribs: safeAttributes }\n },\n },\n // disable inline scripts in style attributes\n allowedStyles: {\n '*': {\n 'background-color': [SAFE_CSS_COLOR],\n color: [SAFE_CSS_COLOR],\n 'text-align': [/^left$|^right$|^center$|^justify$/i],\n 'font-size': [/^\\d+(?:\\.\\d+)?(?:px|em|rem|%)$/],\n 'font-weight': [/^\\d{3}$|^bold$|^normal$/],\n 'font-style': [/^italic$|^normal$/],\n 'text-decoration': [/^underline$|^line-through$|^none$/],\n padding: [/^[\\d\\s.]+(?:px|em|rem|%)?$/],\n margin: [/^[\\d\\s.]+(?:px|em|rem|%)?$/],\n border: [/^[\\d\\s\\w.]+$/],\n 'border-radius': [/^\\d+(?:\\.\\d+)?(?:px|em|rem|%)$/],\n // Permit only `display:none` so the hidden thread-token footer span\n // (`buildBodyFooter`) stays hidden when a sent email body is re-rendered.\n // Every other display value is stripped.\n display: [/^none$/],\n },\n },\n /**\n * data:image/<mime>;base64,... URLs in <img src=...> are explicitly allowed so inline\n * email images render. sanitize-html validates the base64 payload shape; if a future\n * payload tries to smuggle a non-image MIME type via data URL we strip it.\n */\n exclusiveFilter: (frame) => {\n if (frame.tag === 'img' && frame.attribs?.src) {\n const src = frame.attribs.src\n if (/^data:/i.test(src) && !/^data:image\\/(?:png|jpe?g|gif|webp);base64,/i.test(src)) {\n return true\n }\n }\n return false\n },\n}\n\n/**\n * Sanitize an HTML string for safe rendering.\n *\n * @param html Raw HTML \u2014 may originate from an external channel (email body, Slack mrkdwn rendered to HTML, \u2026).\n * @returns Sanitized HTML safe for `dangerouslySetInnerHTML`.\n */\nexport function sanitizeChannelHtml(html: string): string {\n if (!html) return ''\n return sanitizeHtml(html, SANITIZE_OPTIONS)\n}\n\nexport default sanitizeChannelHtml\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,6 +1,45 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
3
4
  import { useT } from "@open-mercato/shared/lib/i18n/context";
5
+ import { Alert } from "@open-mercato/ui/primitives/alert";
6
+ import { Button } from "@open-mercato/ui/primitives/button";
7
+ function EmailPayload({ html }) {
8
+ const t = useT();
9
+ const bodyRef = React.useRef(null);
10
+ const [remoteImagesAllowed, setRemoteImagesAllowed] = React.useState(false);
11
+ const hasRemoteImages = html.includes("data-channel-remote-src=");
12
+ React.useEffect(() => {
13
+ if (!remoteImagesAllowed) return;
14
+ for (const image of bodyRef.current?.querySelectorAll("img[data-channel-remote-src]") ?? []) {
15
+ const source = image.getAttribute("data-channel-remote-src");
16
+ if (source && /^https?:\/\//i.test(source)) {
17
+ image.referrerPolicy = "no-referrer";
18
+ image.src = source.replace(/[<>"']/g, (character) => `%${character.charCodeAt(0).toString(16)}`);
19
+ }
20
+ }
21
+ }, [remoteImagesAllowed]);
22
+ return /* @__PURE__ */ jsxs(
23
+ "section",
24
+ {
25
+ className: "rounded-md border border-border bg-card p-4 text-sm",
26
+ "aria-label": t("communication_channels.channelPayload.email.aria"),
27
+ children: [
28
+ hasRemoteImages && !remoteImagesAllowed ? /* @__PURE__ */ jsx(
29
+ Alert,
30
+ {
31
+ status: "information",
32
+ style: "lighter",
33
+ className: "mb-4",
34
+ action: /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: () => setRemoteImagesAllowed(true), children: t("communication_channels.channelPayload.email.loadRemoteImages") }),
35
+ children: t("communication_channels.channelPayload.email.remoteImagesBlocked")
36
+ }
37
+ ) : null,
38
+ /* @__PURE__ */ jsx("div", { ref: bodyRef, dangerouslySetInnerHTML: { __html: html } })
39
+ ]
40
+ }
41
+ );
42
+ }
4
43
  function ChannelPayloadRendererWidget({
5
44
  data
6
45
  }) {
@@ -9,17 +48,7 @@ function ChannelPayloadRendererWidget({
9
48
  if (!payload || !payload.channelContentType) return null;
10
49
  const { channelContentType, channelPayload, sanitizedHtml } = payload;
11
50
  if (channelContentType.startsWith("email/") && typeof sanitizedHtml === "string") {
12
- return /* @__PURE__ */ jsx(
13
- "section",
14
- {
15
- className: "rounded-md border bg-card p-4 text-sm",
16
- "aria-label": t(
17
- "communication_channels.channelPayload.email.aria",
18
- "Channel payload \u2014 email"
19
- ),
20
- children: /* @__PURE__ */ jsx("div", { dangerouslySetInnerHTML: { __html: sanitizedHtml } })
21
- }
22
- );
51
+ return /* @__PURE__ */ jsx(EmailPayload, { html: sanitizedHtml }, `${data?.id ?? ""}:${sanitizedHtml}`);
23
52
  }
24
53
  return /* @__PURE__ */ jsxs(
25
54
  "section",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.client.tsx"],
4
- "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport type { InjectionWidgetComponentProps } from '@open-mercato/shared/modules/widgets/injection'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\n\ntype ChannelPayloadEnrichment = {\n channelContentType: string | null\n channelPayload: Record<string, unknown> | null\n interactiveState: Record<string, unknown> | null\n channelMetadata: Record<string, unknown> | null\n // Email HTML is sanitized server-side by the channel-payload enricher and\n // delivered here ready to render \u2014 the client never imports `sanitize-html`.\n sanitizedHtml: string | null\n}\n\ntype MessageWithPayload = Record<string, unknown> & {\n id?: string\n _channelPayload?: ChannelPayloadEnrichment | null\n}\n\nexport default function ChannelPayloadRendererWidget({\n data,\n}: InjectionWidgetComponentProps<Record<string, unknown>, MessageWithPayload>) {\n const t = useT()\n const payload = data?._channelPayload ?? null\n if (!payload || !payload.channelContentType) return null\n\n const { channelContentType, channelPayload, sanitizedHtml } = payload\n\n // Email / HTML \u2014 render the HTML the server already sanitized.\n if (channelContentType.startsWith('email/') && typeof sanitizedHtml === 'string') {\n return (\n <section\n className=\"rounded-md border bg-card p-4 text-sm\"\n aria-label={t(\n 'communication_channels.channelPayload.email.aria',\n 'Channel payload \u2014 email',\n )}\n >\n <div dangerouslySetInnerHTML={{ __html: sanitizedHtml }} />\n </section>\n )\n }\n\n // Provider-specific payloads (Slack Block Kit, WhatsApp interactive) \u2014\n // hub-level fallback shows raw JSON; provider packages override this widget.\n return (\n <section\n className=\"rounded-md border bg-muted p-4 text-xs\"\n aria-label={t('communication_channels.channelPayload.aria', 'Channel payload')}\n >\n <header className=\"mb-2 text-overline text-muted-foreground\">\n {t(\n `communication_channels.channelPayload.types.${channelContentType}`,\n channelContentType,\n )}\n </header>\n <pre className=\"overflow-auto whitespace-pre-wrap break-words\">\n {JSON.stringify(channelPayload, null, 2)}\n </pre>\n </section>\n )\n}\n"],
5
- "mappings": ";AAwCQ,cAQJ,YARI;AApCR,SAAS,YAAY;AAiBN,SAAR,6BAA8C;AAAA,EACnD;AACF,GAA+E;AAC7E,QAAM,IAAI,KAAK;AACf,QAAM,UAAU,MAAM,mBAAmB;AACzC,MAAI,CAAC,WAAW,CAAC,QAAQ,mBAAoB,QAAO;AAEpD,QAAM,EAAE,oBAAoB,gBAAgB,cAAc,IAAI;AAG9D,MAAI,mBAAmB,WAAW,QAAQ,KAAK,OAAO,kBAAkB,UAAU;AAChF,WACE;AAAA,MAAC;AAAA;AAAA,QACC,WAAU;AAAA,QACV,cAAY;AAAA,UACV;AAAA,UACA;AAAA,QACF;AAAA,QAEA,8BAAC,SAAI,yBAAyB,EAAE,QAAQ,cAAc,GAAG;AAAA;AAAA,IAC3D;AAAA,EAEJ;AAIA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAU;AAAA,MACV,cAAY,EAAE,8CAA8C,iBAAiB;AAAA,MAE7E;AAAA,4BAAC,YAAO,WAAU,4CACf;AAAA,UACC,+CAA+C,kBAAkB;AAAA,UACjE;AAAA,QACF,GACF;AAAA,QACA,oBAAC,SAAI,WAAU,iDACZ,eAAK,UAAU,gBAAgB,MAAM,CAAC,GACzC;AAAA;AAAA;AAAA,EACF;AAEJ;",
4
+ "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport type { InjectionWidgetComponentProps } from '@open-mercato/shared/modules/widgets/injection'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Alert } from '@open-mercato/ui/primitives/alert'\nimport { Button } from '@open-mercato/ui/primitives/button'\n\ntype ChannelPayloadEnrichment = {\n channelContentType: string | null\n channelPayload: Record<string, unknown> | null\n interactiveState: Record<string, unknown> | null\n channelMetadata: Record<string, unknown> | null\n // Email HTML is sanitized server-side by the channel-payload enricher and\n // delivered here ready to render \u2014 the client never imports `sanitize-html`.\n sanitizedHtml: string | null\n}\n\ntype MessageWithPayload = Record<string, unknown> & {\n id?: string\n _channelPayload?: ChannelPayloadEnrichment | null\n}\n\nfunction EmailPayload({ html }: { html: string }) {\n const t = useT()\n const bodyRef = React.useRef<HTMLDivElement>(null)\n const [remoteImagesAllowed, setRemoteImagesAllowed] = React.useState(false)\n const hasRemoteImages = html.includes('data-channel-remote-src=')\n\n React.useEffect(() => {\n if (!remoteImagesAllowed) return\n for (const image of bodyRef.current?.querySelectorAll<HTMLImageElement>('img[data-channel-remote-src]') ?? []) {\n const source = image.getAttribute('data-channel-remote-src')\n if (source && /^https?:\\/\\//i.test(source)) {\n image.referrerPolicy = 'no-referrer'\n image.src = source.replace(/[<>\"']/g, (character) => `%${character.charCodeAt(0).toString(16)}`)\n }\n }\n }, [remoteImagesAllowed])\n\n return (\n <section\n className=\"rounded-md border border-border bg-card p-4 text-sm\"\n aria-label={t('communication_channels.channelPayload.email.aria')}\n >\n {hasRemoteImages && !remoteImagesAllowed ? (\n <Alert\n status=\"information\"\n style=\"lighter\"\n className=\"mb-4\"\n action={(\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => setRemoteImagesAllowed(true)}>\n {t('communication_channels.channelPayload.email.loadRemoteImages')}\n </Button>\n )}\n >\n {t('communication_channels.channelPayload.email.remoteImagesBlocked')}\n </Alert>\n ) : null}\n <div ref={bodyRef} dangerouslySetInnerHTML={{ __html: html }} />\n </section>\n )\n}\n\nexport default function ChannelPayloadRendererWidget({\n data,\n}: InjectionWidgetComponentProps<Record<string, unknown>, MessageWithPayload>) {\n const t = useT()\n const payload = data?._channelPayload ?? null\n if (!payload || !payload.channelContentType) return null\n\n const { channelContentType, channelPayload, sanitizedHtml } = payload\n\n if (channelContentType.startsWith('email/') && typeof sanitizedHtml === 'string') {\n return <EmailPayload key={`${data?.id ?? ''}:${sanitizedHtml}`} html={sanitizedHtml} />\n }\n\n // Provider-specific payloads (Slack Block Kit, WhatsApp interactive) \u2014\n // hub-level fallback shows raw JSON; provider packages override this widget.\n return (\n <section\n className=\"rounded-md border bg-muted p-4 text-xs\"\n aria-label={t('communication_channels.channelPayload.aria', 'Channel payload')}\n >\n <header className=\"mb-2 text-overline text-muted-foreground\">\n {t(\n `communication_channels.channelPayload.types.${channelContentType}`,\n channelContentType,\n )}\n </header>\n <pre className=\"overflow-auto whitespace-pre-wrap break-words\">\n {JSON.stringify(channelPayload, null, 2)}\n </pre>\n </section>\n )\n}\n"],
5
+ "mappings": ";AAyCI,SAUQ,KAVR;AAvCJ,YAAY,WAAW;AAEvB,SAAS,YAAY;AACrB,SAAS,aAAa;AACtB,SAAS,cAAc;AAiBvB,SAAS,aAAa,EAAE,KAAK,GAAqB;AAChD,QAAM,IAAI,KAAK;AACf,QAAM,UAAU,MAAM,OAAuB,IAAI;AACjD,QAAM,CAAC,qBAAqB,sBAAsB,IAAI,MAAM,SAAS,KAAK;AAC1E,QAAM,kBAAkB,KAAK,SAAS,0BAA0B;AAEhE,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,oBAAqB;AAC1B,eAAW,SAAS,QAAQ,SAAS,iBAAmC,8BAA8B,KAAK,CAAC,GAAG;AAC7G,YAAM,SAAS,MAAM,aAAa,yBAAyB;AAC3D,UAAI,UAAU,gBAAgB,KAAK,MAAM,GAAG;AAC1C,cAAM,iBAAiB;AACvB,cAAM,MAAM,OAAO,QAAQ,WAAW,CAAC,cAAc,IAAI,UAAU,WAAW,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE;AAAA,MACjG;AAAA,IACF;AAAA,EACF,GAAG,CAAC,mBAAmB,CAAC;AAExB,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAU;AAAA,MACV,cAAY,EAAE,kDAAkD;AAAA,MAE/D;AAAA,2BAAmB,CAAC,sBACnB;AAAA,UAAC;AAAA;AAAA,YACC,QAAO;AAAA,YACP,OAAM;AAAA,YACN,WAAU;AAAA,YACV,QACE,oBAAC,UAAO,MAAK,UAAS,SAAQ,WAAU,MAAK,MAAK,SAAS,MAAM,uBAAuB,IAAI,GACzF,YAAE,8DAA8D,GACnE;AAAA,YAGD,YAAE,iEAAiE;AAAA;AAAA,QACtE,IACE;AAAA,QACJ,oBAAC,SAAI,KAAK,SAAS,yBAAyB,EAAE,QAAQ,KAAK,GAAG;AAAA;AAAA;AAAA,EAChE;AAEJ;AAEe,SAAR,6BAA8C;AAAA,EACnD;AACF,GAA+E;AAC7E,QAAM,IAAI,KAAK;AACf,QAAM,UAAU,MAAM,mBAAmB;AACzC,MAAI,CAAC,WAAW,CAAC,QAAQ,mBAAoB,QAAO;AAEpD,QAAM,EAAE,oBAAoB,gBAAgB,cAAc,IAAI;AAE9D,MAAI,mBAAmB,WAAW,QAAQ,KAAK,OAAO,kBAAkB,UAAU;AAChF,WAAO,oBAAC,gBAAwD,MAAM,iBAA5C,GAAG,MAAM,MAAM,EAAE,IAAI,aAAa,EAAyB;AAAA,EACvF;AAIA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAU;AAAA,MACV,cAAY,EAAE,8CAA8C,iBAAiB;AAAA,MAE7E;AAAA,4BAAC,YAAO,WAAU,4CACf;AAAA,UACC,+CAA+C,kBAAkB;AAAA,UACjE;AAAA,QACF,GACF;AAAA,QACA,oBAAC,SAAI,WAAU,iDACZ,eAAK,UAAU,gBAAgB,MAAM,CAAC,GACzC;AAAA;AAAA;AAAA,EACF;AAEJ;",
6
6
  "names": []
7
7
  }
@@ -9,6 +9,7 @@ import { extractOptimisticLockConflict } from "@open-mercato/ui/backend/utils/op
9
9
  import { surfaceRecordConflict } from "@open-mercato/ui/backend/conflicts";
10
10
  import { createCrudFormError } from "@open-mercato/ui/backend/utils/serverErrors";
11
11
  import { useConfirmDialog } from "@open-mercato/ui/backend/confirm-dialog";
12
+ import { useCurrentOrganization } from "@open-mercato/ui/backend/BackendChromeProvider";
12
13
  import { flash } from "@open-mercato/ui/backend/FlashMessages";
13
14
  import { CrudForm } from "@open-mercato/ui/backend/CrudForm";
14
15
  import { useInjectionWidgets } from "@open-mercato/ui/backend/injection/InjectionSpot";
@@ -133,7 +134,6 @@ const EditorBody = React.memo(function EditorBody2({
133
134
  [form.dealId, form.dealLabel]
134
135
  );
135
136
  const capabilities = React.useMemo(() => ({ resourcesEnabled, staffEnabled }), [resourcesEnabled, staffEnabled]);
136
- useEditorLabelResolution(open, form, update);
137
137
  const draftOwnerUserId = resolveSavedOwnerUserId(config, form, isEdit, item?.ownerUserId ?? null);
138
138
  const draftResources = resourcesEnabled && definition.behavior.fields.resources ? form.resources : NO_RESOURCES;
139
139
  const conflict = useConflictProbe(open, form, config, isEdit && item ? item.raw.id : null, draftOwnerUserId, conflictScope, currentUserId, draftResources);
@@ -230,6 +230,7 @@ function CalendarEventEditor({
230
230
  onSaved
231
231
  }) {
232
232
  const t = useT();
233
+ const activeOrgId = useCurrentOrganization()?.id ?? null;
233
234
  const [saving, setSaving] = React.useState(false);
234
235
  const { confirm, ConfirmDialogElement } = useConfirmDialog();
235
236
  const dirtyRef = React.useRef(false);
@@ -283,11 +284,20 @@ function CalendarEventEditor({
283
284
  wasOpenRef.current = open;
284
285
  }, [open]);
285
286
  const formKey = `${mode}:${item?.id ?? "new"}:${openSeq}:${autoSelectedCreateType?.key ?? "default"}`;
287
+ const labelScopeKey = JSON.stringify([formKey, activeOrgId, item?.entityId, item?.dealId, item?.ownerUserId]);
288
+ const [resolvedLabels, setResolvedLabels] = React.useState(null);
289
+ const updateInitialLabels = React.useCallback((patch) => {
290
+ setResolvedLabels((previous) => ({
291
+ scopeKey: labelScopeKey,
292
+ values: { ...previous?.scopeKey === labelScopeKey ? previous.values : {}, ...patch }
293
+ }));
294
+ }, [labelScopeKey]);
286
295
  const initialValues = React.useMemo(() => {
287
296
  if (isEdit && item) {
288
297
  return {
289
298
  ...parseItemToFormState(item),
290
299
  ...customFieldInitialValues(item),
300
+ ...resolvedLabels?.scopeKey === labelScopeKey ? resolvedLabels.values : {},
291
301
  id: item.id,
292
302
  updatedAt: item.updatedAt ?? void 0
293
303
  };
@@ -299,7 +309,8 @@ function CalendarEventEditor({
299
309
  kind: autoSelectedCreateType.behavior.baseKind,
300
310
  category: autoSelectedCreateType.key
301
311
  };
302
- }, [isEdit, item, defaultDate, defaultRange, openSeq, autoSelectedCreateType]);
312
+ }, [isEdit, item, defaultDate, defaultRange, openSeq, autoSelectedCreateType, labelScopeKey, resolvedLabels]);
313
+ useEditorLabelResolution(open, formStateOfValues(initialValues), updateInitialLabels);
303
314
  const initialTypeKey = String(initialValues.category ?? initialValues.kind ?? "meeting");
304
315
  React.useEffect(() => {
305
316
  setSelectedTypeKey(initialTypeKey);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/customers/components/calendar/CalendarEventEditor.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Calendar, X } from 'lucide-react'\nimport { VisuallyHidden } from '@radix-ui/react-visually-hidden'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { apiCallOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { extractOptimisticLockConflict } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { surfaceRecordConflict } from '@open-mercato/ui/backend/conflicts'\nimport { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { CrudForm, type CrudFormGroup, type CrudFormGroupComponentProps } from '@open-mercato/ui/backend/CrudForm'\nimport { useInjectionWidgets } from '@open-mercato/ui/backend/injection/InjectionSpot'\nimport { projectCalendarCustomValues, useCalendarCustomFields, validateCalendarCustomValues } from './editor/useCalendarCustomFields'\nimport { collectCustomFieldValues } from '@open-mercato/ui/backend/utils/customFieldValues'\nimport { Alert, AlertDescription, AlertTitle } from '@open-mercato/ui/primitives/alert'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Dialog, DialogContent, DialogTitle } from '@open-mercato/ui/primitives/dialog'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@open-mercato/ui/primitives/select'\nimport { calendarDayEndInstant, calendarDayStartInstant, calendarWallTimeToInstant, calendarTimezoneOptions, defaultCalendarTimezone, isCalendarTimezone } from '../../lib/calendar/timezone'\nimport { Textarea } from '@open-mercato/ui/primitives/textarea'\nimport { useDialogKeyHandler } from '@open-mercato/ui/hooks/useDialogKeyHandler'\nimport { E } from '#generated/entities.ids.generated'\nimport {\n buildRecurrenceRule,\n buildInteractionPayload,\n computeDurationMinutes,\n createDefaultFormState,\n parseItemToFormState,\n resolveSavedOwnerUserId,\n type EditorFormState,\n} from '../../lib/calendar/editorPayload'\nimport type { ConflictScope } from '../../lib/calendar/preferences'\nimport { renderDictionaryIcon } from '@open-mercato/core/modules/dictionaries/components/dictionaryAppearance'\nimport { normalizeCustomFieldSubmitValue } from '../detail/customFieldUtils'\nimport type { CalendarItem } from './types'\nimport { EDITOR_SCROLL_EVENT, Field } from './editor/inputs'\nimport { SegmentGroup } from './editor/SegmentGroup'\nimport { RelatedToField } from './editor/RelatedToField'\nimport { useConflictProbe, useEditorLabelResolution } from './editor/hooks'\nimport { EventTypePanel } from './editor/EventTypePanel'\nimport { eventTypeConfig, eventTypeOptions, isSelectableEventType, selectedEventType, useEventTypeCatalog } from './editor/useEventTypeCatalog'\nimport type { ScopedCalendarEventType } from '../../lib/calendar/eventTypeResolver'\n\nexport interface CalendarEventEditorProps {\n open: boolean\n mode: 'create' | 'edit'\n item?: CalendarItem | null\n defaultDate?: Date\n defaultRange?: { start: Date; end: Date } | null\n /** @deprecated Event-type labels are loaded from the scoped catalog. */\n typeLabels?: Record<string, string>\n typeIcons?: Record<string, string | null>\n conflictScope?: ConflictScope\n currentUserId?: string | null\n resourcesEnabled?: boolean\n staffEnabled?: boolean\n onOpenChange(open: boolean): void\n onSaved(): void\n}\n\nconst FORM_ID = 'customers-calendar-event-editor'\nconst INTERACTION_INJECTION_SPOT = `crud-form:${E.customers.customer_interaction.replace(/:/g, '.')}`\n\n// CrudForm values carry the flattened EditorFormState keys (seeded via\n// initialValues) plus cf_* custom-field keys managed by CrudForm itself.\nfunction formStateOfValues(values: Record<string, unknown>): EditorFormState {\n return values as unknown as EditorFormState\n}\n\nfunction customFieldInitialValues(item: CalendarItem): Record<string, unknown> {\n const customValues = (item.raw as Record<string, unknown>).customValues\n if (!customValues || typeof customValues !== 'object' || Array.isArray(customValues)) return {}\n return Object.fromEntries(\n Object.entries(customValues as Record<string, unknown>).map(([key, value]) => [`cf_${key}`, value]),\n )\n}\n\nfunction typeChangeDiscardFields(error: unknown): string[] | null {\n if (!error || typeof error !== 'object') return null\n const details = error as { status?: unknown; code?: unknown; fields?: unknown }\n if (details.status !== 409 || details.code !== 'calendar_type_change_confirmation_required') return null\n if (!Array.isArray(details.fields) || details.fields.length === 0 ||\n !details.fields.every((field) => typeof field === 'string' && field.length > 0)) return null\n return details.fields\n}\n\ntype EditorBodyProps = Pick<CrudFormGroupComponentProps, 'values' | 'errors' | 'setValue'> & {\n open: boolean\n isEdit: boolean\n item?: CalendarItem | null\n conflictScope: ConflictScope\n currentUserId: string | null\n resourcesEnabled: boolean\n staffEnabled: boolean\n saving: boolean\n catalogItems: readonly ScopedCalendarEventType[]\n catalogReady: boolean\n catalogError: boolean\n onRetryCatalog(): void\n onSelectedTypeChange(key: string): void\n}\n\nfunction CatalogStatusAlert({ error, onRetry }: { error: boolean; onRetry(): void }) {\n const t = useT()\n return (\n <Alert status={error ? 'error' : 'information'} className=\"rounded-lg lg:col-span-2\">\n <AlertDescription>\n {error\n ? t('customers.calendar.editor.catalogLoadFailed')\n : t('customers.calendar.editor.catalogLoading')}\n </AlertDescription>\n {error ? (\n <Button type=\"button\" variant=\"outline\" onClick={onRetry}>\n {t('customers.calendar.errors.retry')}\n </Button>\n ) : null}\n </Alert>\n )\n}\n\nconst TimezoneField = React.memo(function TimezoneField({\n value,\n error,\n disabled,\n onChange,\n}: {\n value: string | null | undefined\n error?: string\n disabled: boolean\n onChange(timezone: string): void\n}) {\n const t = useT()\n const [open, setOpen] = React.useState(false)\n const timezone = value ?? defaultCalendarTimezone()\n const options = React.useMemo(() => open ? calendarTimezoneOptions(timezone) : [timezone], [open, timezone])\n const items = React.useMemo(\n () => options.map((timezone) => <SelectItem key={timezone} value={timezone}>{timezone}</SelectItem>),\n [options],\n )\n return (\n <Field label={t('customers.calendar.editor.timezone', 'Time zone')} error={error}>\n <Select value={timezone} onValueChange={onChange} disabled={disabled} open={open} onOpenChange={setOpen}>\n <SelectTrigger aria-label={t('customers.calendar.editor.timezone', 'Time zone')}><SelectValue /></SelectTrigger>\n <SelectContent>{items}</SelectContent>\n </Select>\n </Field>\n )\n})\n\nconst TypeSwitcher = React.memo(SegmentGroup) as typeof SegmentGroup\nconst NO_RESOURCES: EditorFormState['resources'] = []\nconst MemoRelatedToField = React.memo(RelatedToField)\n\n// CrudForm re-invokes the group renderer on every one of its own renders;\n// memoizing on the form state identities skips the ones that change neither\n// the values nor the errors.\nconst EditorBody = React.memo(function EditorBody({\n values,\n errors,\n setValue,\n open,\n isEdit,\n item,\n conflictScope,\n currentUserId,\n resourcesEnabled,\n staffEnabled,\n saving,\n catalogItems,\n catalogReady,\n catalogError,\n onRetryCatalog,\n onSelectedTypeChange,\n}: EditorBodyProps) {\n const t = useT()\n const form = formStateOfValues(values)\n const selectedType = form.category ?? form.kind\n const definition = selectedEventType(catalogItems, selectedType)\n const config = eventTypeConfig(definition)\n\n const update = React.useCallback(\n (patch: Partial<EditorFormState>) => {\n for (const [key, value] of Object.entries(patch)) setValue(key, value)\n },\n [setValue],\n )\n\n const updateTimezone = React.useCallback((timezone: string) => setValue('timezone', timezone), [setValue])\n const selectType = React.useCallback((type: string) => {\n const nextDefinition = selectedEventType(catalogItems, type)\n update({ kind: nextDefinition.behavior.baseKind, category: type })\n onSelectedTypeChange(type)\n }, [catalogItems, update, onSelectedTypeChange])\n const updateRelatedTo = React.useCallback((relatedTo: EditorFormState['relatedTo']) => update({ relatedTo }), [update])\n const updateDeal = React.useCallback(\n (deal: { id: string; label: string } | null) => update({ dealId: deal?.id ?? null, dealLabel: deal?.label ?? null }),\n [update],\n )\n const deal = React.useMemo(\n () => form.dealId && form.dealLabel ? { id: form.dealId, label: form.dealLabel } : null,\n [form.dealId, form.dealLabel],\n )\n const capabilities = React.useMemo(() => ({ resourcesEnabled, staffEnabled }), [resourcesEnabled, staffEnabled])\n\n useEditorLabelResolution(open, form, update)\n // Probe against the owner the interaction will actually be SAVED with so the\n // editor warning matches the grid's post-save conflict rings: tasks own via\n // their assignee, other kinds stay ownerless on create / keep the existing\n // owner on edit (conflicts then come from shared participants).\n const draftOwnerUserId = resolveSavedOwnerUserId(config, form, isEdit, item?.ownerUserId ?? null)\n // Self-exclude by the underlying interaction id (raw.id): findEditorConflictItems\n // drops candidates by raw.id, and every expanded occurrence of a recurring series\n // shares it \u2014 so the edited record never conflicts with itself.\n // Resources count toward a conflict only when the selected type can book them.\n const draftResources = resourcesEnabled && definition.behavior.fields.resources ? form.resources : NO_RESOURCES\n const conflict = useConflictProbe(open, form, config, isEdit && item ? item.raw.id : null, draftOwnerUserId, conflictScope, currentUserId, draftResources)\n\n const typeOptions = React.useMemo(\n () => eventTypeOptions(catalogItems, selectedType, t),\n [catalogItems, selectedType, t],\n )\n const typeSwitcherOptions = React.useMemo(\n () =>\n typeOptions.map((option) => ({\n value: option.value,\n label: option.label,\n icon: renderDictionaryIcon(option.icon, 'h-4 w-4'),\n })),\n [typeOptions],\n )\n\n const titleLabel = form.kind === 'email'\n ? t('customers.calendar.editor.titleLabel.email', 'Subject')\n : form.kind === 'note'\n ? t('customers.calendar.editor.titleLabel.note', 'Note')\n : t('customers.calendar.editor.titleLabel.generic', 'Title')\n\n return (\n <div className=\"grid w-full grid-cols-1 items-start gap-4 lg:grid-cols-2 lg:gap-x-6\">\n {conflict ? (\n <Alert status=\"warning\" className=\"rounded-lg lg:col-span-2\">\n <AlertTitle>{t('customers.calendar.editor.conflictTitle', 'Calendar conflict')}</AlertTitle>\n <AlertDescription>{conflict}</AlertDescription>\n </Alert>\n ) : null}\n {!catalogReady ? (\n <CatalogStatusAlert error={catalogError} onRetry={onRetryCatalog} />\n ) : null}\n {catalogReady && (definition.historical || catalogItems.some((item) => item.key === selectedType && !item.selectable)) ? (\n <Alert status=\"warning\" className=\"rounded-lg lg:col-span-2\">\n <AlertDescription>{t('customers.calendar.editor.historicalType')}</AlertDescription>\n </Alert>\n ) : null}\n <div className=\"w-full lg:col-span-2\">\n <TypeSwitcher<string>\n ariaLabel={t('customers.calendar.editor.typeSwitcher', 'Event type')}\n value={selectedType}\n onChange={selectType}\n disabled={!catalogReady}\n options={typeSwitcherOptions}\n />\n </div>\n <Field label={titleLabel} error={errors.title} className=\"lg:col-span-2\">\n <Input\n type=\"text\"\n value={form.title}\n onChange={(event) => update({ title: event.target.value })}\n placeholder={t('customers.calendar.editor.titlePlaceholder', 'Add a title\u2026')}\n aria-label={titleLabel}\n autoFocus\n size=\"lg\"\n />\n </Field>\n <TimezoneField value={form.timezone} error={errors.timezone} disabled={saving} onChange={updateTimezone} />\n <div className=\"lg:col-span-2\">\n <Field label={t('customers.calendar.editor.relatedTo', 'Related to')} error={errors.relatedTo}>\n <MemoRelatedToField\n label={t('customers.calendar.editor.relatedTo', 'Related to')}\n value={form.relatedTo}\n deal={deal}\n onChange={updateRelatedTo}\n onDealChange={updateDeal}\n error={errors.relatedTo}\n />\n </Field>\n </div>\n {catalogReady ? (\n <div className=\"lg:col-span-2\">\n <EventTypePanel\n definition={definition}\n panelKey={definition.panelKey}\n mode={isEdit ? 'edit' : 'create'}\n values={values}\n errors={errors}\n disabled={saving}\n capabilities={capabilities}\n setValue={setValue}\n />\n </div>\n ) : null}\n <Field label={t('customers.calendar.editor.description', 'Description')} className=\"lg:col-span-2\">\n <Textarea\n value={form.description}\n onChange={(event) => update({ description: event.target.value })}\n placeholder={t('customers.calendar.editor.descriptionPlaceholder', 'Add details\u2026')}\n aria-label={t('customers.calendar.editor.description', 'Description')}\n className=\"h-20 resize-none\"\n />\n </Field>\n </div>\n )\n})\n\nexport function CalendarEventEditor({\n open,\n mode,\n item,\n defaultDate,\n defaultRange,\n conflictScope,\n currentUserId,\n resourcesEnabled,\n staffEnabled,\n onOpenChange,\n onSaved,\n}: CalendarEventEditorProps) {\n const t = useT()\n const [saving, setSaving] = React.useState(false)\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const dirtyRef = React.useRef(false)\n const closeConfirmPendingRef = React.useRef(false)\n const [closePromptOpen, setClosePromptOpen] = React.useState(false)\n // The shared confirm answers Cmd/Ctrl+Enter with its confirm action, which\n // here discards the draft \u2014 on the very shortcut that saves the form. The\n // shortcut is swallowed while the prompt is up; the buttons and Escape (keep\n // editing) still answer it. Owned by an effect so the listener is removed\n // even when the editor unmounts with the prompt still open.\n React.useEffect(() => {\n if (!closePromptOpen) return\n const swallowSubmitShortcut = (event: KeyboardEvent) => {\n if (event.key !== 'Enter' || !(event.metaKey || event.ctrlKey)) return\n event.preventDefault()\n event.stopImmediatePropagation()\n }\n window.addEventListener('keydown', swallowSubmitShortcut, true)\n return () => window.removeEventListener('keydown', swallowSubmitShortcut, true)\n }, [closePromptOpen])\n const requestClose = React.useCallback(async () => {\n if (saving || closeConfirmPendingRef.current) return\n if (dirtyRef.current) {\n closeConfirmPendingRef.current = true\n setClosePromptOpen(true)\n try {\n const confirmed = await confirm({\n title: t('ui.forms.confirmUnsavedChanges', 'You have unsaved changes. Are you sure you want to leave?'),\n confirmText: t('customers.deals.detail.unsavedConfirm', 'Discard changes'),\n cancelText: t('customers.deals.detail.unsavedCancel', 'Keep editing'),\n variant: 'destructive',\n })\n if (!confirmed) return\n } finally {\n closeConfirmPendingRef.current = false\n setClosePromptOpen(false)\n }\n }\n onOpenChange(false)\n }, [confirm, onOpenChange, saving, t])\n const handleDirtyChange = React.useCallback((dirty: boolean) => { dirtyRef.current = dirty }, [])\n React.useEffect(() => { if (!open) dirtyRef.current = false }, [open])\n const isEdit = mode === 'edit' && Boolean(item?.id)\n const catalog = useEventTypeCatalog(open)\n const { items: catalogItems, status: catalogStatus, retry: retryCatalog } = catalog\n const [selectedTypeKey, setSelectedTypeKey] = React.useState(() => isEdit && item ? item.interactionType : 'meeting')\n const autoSelectedCreateType = !isEdit && catalog.status === 'ready' && !isSelectableEventType(catalog.items, 'meeting')\n ? catalog.items.find((entry) => entry.selectable && !entry.historical) ?? null\n : null\n\n // CrudForm reads initialValues once, so every dialog open gets a fresh form\n // instance keyed by the open sequence + edited record.\n const [openSeq, setOpenSeq] = React.useState(0)\n const wasOpenRef = React.useRef(false)\n React.useEffect(() => {\n if (open && !wasOpenRef.current) setOpenSeq((seq) => seq + 1)\n wasOpenRef.current = open\n }, [open])\n const formKey = `${mode}:${item?.id ?? 'new'}:${openSeq}:${autoSelectedCreateType?.key ?? 'default'}`\n\n const initialValues = React.useMemo<Record<string, unknown>>(() => {\n if (isEdit && item) {\n return {\n ...parseItemToFormState(item),\n ...customFieldInitialValues(item),\n id: item.id,\n updatedAt: item.updatedAt ?? undefined,\n }\n }\n const defaults = createDefaultFormState(defaultDate ?? null, undefined, defaultRange ?? null)\n if (!autoSelectedCreateType) return { ...defaults }\n return {\n ...defaults,\n kind: autoSelectedCreateType.behavior.baseKind,\n category: autoSelectedCreateType.key,\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps -- openSeq re-seeds defaults per dialog open\n }, [isEdit, item, defaultDate, defaultRange, openSeq, autoSelectedCreateType])\n const initialTypeKey = String(initialValues.category ?? initialValues.kind ?? 'meeting')\n React.useEffect(() => { setSelectedTypeKey(initialTypeKey) }, [initialTypeKey, formKey])\n const selectedDefinition = selectedEventType(catalog.items, selectedTypeKey)\n const customFields = useCalendarCustomFields(open, E.customers.customer_interaction, selectedDefinition.behavior.customFieldsetIds)\n const injectionWidgets = useInjectionWidgets(INTERACTION_INJECTION_SPOT)\n const canCreateType = isSelectableEventType(catalog.items, selectedTypeKey)\n const formReady = isEdit || catalog.status === 'ready'\n\n const handleSubmit = React.useCallback(\n async (values: Record<string, unknown>) => {\n const form = formStateOfValues(values)\n if (catalogStatus !== 'ready' || customFields.status !== 'ready' || injectionWidgets.loading || injectionWidgets.error) {\n throw createCrudFormError(t('customers.calendar.editor.catalogLoadFailed'))\n }\n const definition = selectedEventType(catalogItems, form.category ?? form.kind)\n const selectedKey = form.category ?? form.kind\n if ((mode === 'create' || selectedKey !== initialTypeKey) &&\n !isSelectableEventType(catalogItems, selectedKey)) {\n throw createCrudFormError(t('customers.calendar.editor.typeUnavailable'))\n }\n const config = eventTypeConfig(definition)\n const fieldErrors: Record<string, string> = {}\n const timezone = form.timezone ?? defaultCalendarTimezone()\n const startTime = form.allDay && config.hasAllDay ? '00:00' : form.startTime\n if (!isCalendarTimezone(timezone)) fieldErrors.timezone = t('customers.calendar.editor.validation.timezoneInvalid', 'Choose a valid time zone')\n else if (!(form.allDay && config.hasAllDay ? calendarDayStartInstant(form.date, timezone) : calendarWallTimeToInstant(form.date, startTime, timezone))) {\n fieldErrors[form.allDay && config.hasAllDay ? 'date' : 'startTime'] = t('customers.calendar.editor.validation.timezoneGap', 'This local time does not exist in the selected time zone')\n }\n if (config.hasEnd && !form.allDay && isCalendarTimezone(timezone) && !calendarWallTimeToInstant(form.endDate, form.endTime, timezone)) {\n fieldErrors.ends = t('customers.calendar.editor.validation.timezoneGap', 'This local time does not exist in the selected time zone')\n }\n if (config.hasRepeat && form.repeatFreq !== 'none' && isCalendarTimezone(timezone) && form.repeatEndType === 'date' && form.repeatUntilDate && !calendarDayEndInstant(form.repeatUntilDate, timezone)) {\n fieldErrors.repeatUntilDate = t('customers.calendar.editor.validation.timezoneGap', 'This local time does not exist in the selected time zone')\n }\n if (!form.title.trim()) {\n fieldErrors.title = t('customers.calendar.editor.validation.titleRequired', 'Title is required')\n }\n if (!form.relatedTo) {\n fieldErrors.relatedTo = t('customers.calendar.editor.validation.relatedToRequired', 'Select a person or company to link this event')\n }\n if (!fieldErrors.ends && !fieldErrors.startTime && !fieldErrors.date && config.hasEnd && !form.allDay && computeDurationMinutes(form) === null) {\n fieldErrors.ends = t('customers.calendar.editor.validation.endsBeforeStarts', 'End must be after start')\n }\n // A task must be assigned to a team member \u2014 surface the requirement inline\n // instead of letting the save fail with no visible reason (#3747 feedback).\n if (config.people === 'assignee' && staffEnabled !== false && !form.assigneeUserId) {\n fieldErrors.assignee = t('customers.calendar.editor.validation.assigneeRequired', 'Assign this task to a team member')\n }\n if (Object.keys(fieldErrors).length > 0) {\n throw createCrudFormError(Object.values(fieldErrors)[0], fieldErrors)\n }\n const customValidation = validateCalendarCustomValues(values, customFields.definitions)\n if (!customValidation.ok) {\n const translatedErrors = Object.fromEntries(Object.entries(customValidation.fieldErrors).map(([key, message]) => [key, t(message, message)]))\n throw createCrudFormError(Object.values(translatedErrors)[0], translatedErrors)\n }\n setSaving(true)\n try {\n const payload = buildInteractionPayload({ ...form, timezone }, {\n config,\n mode,\n id: item?.id,\n resourcesEnabled: resourcesEnabled === true,\n staffEnabled: staffEnabled !== false,\n })\n payload.enforceSelectableType = true\n const applicable = definition.behavior.fields\n const time = form.allDay && applicable.allDay ? '00:00' : form.startTime\n payload.time = time\n payload.scheduledAt = (form.allDay && applicable.allDay ? calendarDayStartInstant(form.date, timezone) : calendarWallTimeToInstant(form.date, time, timezone))!.toISOString()\n payload.timezone = timezone\n payload.allDay = applicable.allDay ? form.allDay : null\n payload.durationMinutes = applicable.endTime && !(form.allDay && applicable.allDay) ? computeDurationMinutes(form) : null\n payload.location = applicable.location === 'none' ? null : form.location.trim() || null\n const recurrenceRule = applicable.recurrence ? buildRecurrenceRule(form) : null\n payload.recurrenceRule = recurrenceRule\n payload.recurrenceEnd = recurrenceRule && form.repeatEndType === 'date' && form.repeatUntilDate\n ? calendarDayEndInstant(form.repeatUntilDate, timezone)!.toISOString()\n : null\n payload.participants = applicable.people !== 'none' && applicable.people !== 'assignee' && form.participants.length\n ? form.participants.map((participant) => ({\n userId: participant.userId,\n name: participant.name,\n email: participant.email,\n status: participant.isCustomer ? 'customer' : 'pending',\n }))\n : null\n if (applicable.people === 'assignee' && staffEnabled !== false) payload.ownerUserId = form.assigneeUserId ?? null\n else delete payload.ownerUserId\n if (applicable.priority) payload.priority = form.priority === 'low' ? 10 : form.priority === 'high' ? 90 : 50\n else delete payload.priority\n if (!applicable.resources) delete payload.linkedEntities\n const custom = collectCustomFieldValues(projectCalendarCustomValues(values, customFields.definitions), {\n transform: (value) => normalizeCustomFieldSubmitValue(value),\n })\n for (const [key, value] of Object.entries(custom)) payload[`cf_${key}`] = value\n // CrudForm supplies the optimistic-lock header (auto-derived from\n // initialValues.updatedAt) via scoped request headers around onSubmit.\n const submitInteraction = (confirmDiscardInapplicableValues: boolean) => apiCallOrThrow('/api/customers/interactions', {\n method: isEdit ? 'PUT' : 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(confirmDiscardInapplicableValues\n ? { ...payload, confirmDiscardInapplicableValues: true }\n : payload),\n })\n try {\n await submitInteraction(false)\n } catch (error) {\n const fields = typeChangeDiscardFields(error)\n if (!fields) throw error\n const approved = await confirm({\n title: t('customers.calendar.editor.discardTitle'),\n text: `${t('customers.calendar.editor.discardDescription')} ${t('customers.calendar.editor.discardFields')} ${fields.map((field) => {\n const definition = customFields.allDefinitions.find((entry) => `cf_${entry.key}` === field)\n if (definition?.label) return definition.label\n const labelKeys: Record<string, string> = {\n location: 'customers.calendar.editor.location',\n participants: 'customers.calendar.editor.attendees',\n linkedEntities: 'customers.calendar.editor.resources',\n allDay: 'customers.calendar.editor.allDay',\n durationMinutes: 'customers.calendar.editor.dates.ends',\n priority: 'customers.calendar.editor.priority.label',\n recurrenceRule: 'customers.calendar.editor.repeat.label',\n recurrenceEnd: 'customers.calendar.editor.repeat.ends',\n }\n return t(labelKeys[field] ?? 'customers.calendar.editor.discardUnknownField')\n }).join(', ')}`,\n confirmText: t('customers.calendar.editor.discardConfirm'),\n cancelText: t('customers.calendar.editor.cancel'),\n variant: 'destructive',\n })\n if (!approved) return\n await submitInteraction(true)\n }\n flash(t('customers.calendar.editor.saved', 'Event saved'), 'success')\n onOpenChange(false)\n requestAnimationFrame(() => { onSaved() })\n } catch (err) {\n // Surface an optimistic-lock 409 as the persistent conflict bar and\n // close \u2014 the bar renders at page level, behind the dialog overlay.\n if (extractOptimisticLockConflict(err)) {\n surfaceRecordConflict(err, t)\n onOpenChange(false)\n return\n }\n throw err\n } finally {\n setSaving(false)\n }\n },\n [catalogItems, catalogStatus, customFields.status, customFields.definitions, customFields.allDefinitions, injectionWidgets.loading, injectionWidgets.error, confirm, initialTypeKey, isEdit, item?.id, mode, onOpenChange, onSaved, resourcesEnabled, staffEnabled, t],\n )\n\n const groups = React.useMemo<CrudFormGroup[]>(\n () => [\n {\n id: 'details',\n bare: true,\n component: (ctx) => (\n <EditorBody\n values={ctx.values}\n errors={ctx.errors}\n setValue={ctx.setValue}\n open={open}\n isEdit={isEdit}\n item={item}\n conflictScope={conflictScope ?? 'all'}\n currentUserId={currentUserId ?? null}\n resourcesEnabled={resourcesEnabled === true}\n staffEnabled={staffEnabled !== false}\n saving={saving}\n catalogItems={catalogItems}\n catalogReady={catalogStatus === 'ready'}\n catalogError={catalogStatus === 'error'}\n onRetryCatalog={retryCatalog}\n onSelectedTypeChange={setSelectedTypeKey}\n />\n ),\n },\n ...(customFields.fields.length ? [{ id: 'customFields', title: t('entities.customFields.title'), fields: customFields.fields.map((field) => field.id) }] : []),\n ],\n [open, isEdit, item, conflictScope, currentUserId, resourcesEnabled, staffEnabled, saving, catalogItems, catalogStatus, retryCatalog, customFields.fields, t],\n )\n\n const handleKeyDown = useDialogKeyHandler({\n onCancel: () => { void requestClose() },\n onConfirm: () => {\n const formElement = document.getElementById(FORM_ID)\n if (formElement instanceof HTMLFormElement) formElement.requestSubmit()\n },\n disabled: saving || customFields.status !== 'ready' || injectionWidgets.loading || !!injectionWidgets.error || catalog.status !== 'ready' || (mode === 'create' && !canCreateType),\n })\n\n const dialogTitle = isEdit ? t('customers.calendar.editor.title.edit', 'Edit event') : t('customers.calendar.editor.title.create', 'New event')\n\n return (\n <Dialog open={open} onOpenChange={(nextOpen) => { if (nextOpen) onOpenChange(true); else void requestClose() }}>\n <DialogContent\n onKeyDown={handleKeyDown}\n // Only dismiss on a genuine click OUTSIDE the whole editor. Radix\n // relays a click inside the dialog (but outside an open DS\n // DatePicker/Select popover) up to the dialog as an \"interact\n // outside\" event when it dismisses that popover \u2014 without this guard\n // it would tear down the entire editor. Keep it open when the target\n // is inside the dialog body or inside any portalled popover. The\n // shared confirm renders as a native modal `<dialog>` on top of the\n // editor; answering it is not an outside click either, otherwise it\n // would re-request the close it is asking about.\n onInteractOutside={(event) => {\n const target = event.detail.originalEvent.target as HTMLElement | null\n if (target?.closest('[data-dialog-content]') || target?.closest('[data-radix-popper-content-wrapper]') || target?.closest('dialog')) {\n event.preventDefault()\n }\n }}\n aria-describedby={undefined}\n dismissible={false}\n className=\"flex h-dvh max-h-dvh w-screen max-w-none flex-col gap-0 overflow-hidden rounded-none border-0 bg-background p-0 shadow-xl sm:h-auto sm:max-h-[calc(100dvh-4rem)] sm:w-full sm:max-w-lg sm:rounded-2xl sm:border-0 lg:max-w-3xl\"\n >\n <VisuallyHidden>\n <DialogTitle>{dialogTitle}</DialogTitle>\n </VisuallyHidden>\n <div className=\"flex shrink-0 items-center gap-3 border-b border-border bg-background py-4 pl-5 pr-4\">\n <Calendar aria-hidden className=\"size-6 shrink-0 text-foreground\" strokeWidth={1.75} />\n <p className=\"min-w-0 flex-1 text-sm font-medium leading-5 text-foreground\">{dialogTitle}</p>\n <IconButton\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => { void requestClose() }}\n aria-label={t('customers.calendar.editor.close', 'Close')}\n className=\"shrink-0 text-muted-foreground\"\n >\n <X aria-hidden className=\"size-5\" />\n </IconButton>\n </div>\n <div\n className=\"flex-1 overflow-y-auto\"\n onScroll={() => {\n // Tell the DS date/time fields to close their (controlled) popover\n // so a portalled popover doesn't float over the form or drift away\n // from its field while scrolling (#3747 feedback). Radix ignores\n // synthetic dismiss events, so the fields drive their own `open`.\n document.dispatchEvent(new CustomEvent(EDITOR_SCROLL_EVENT))\n }}\n >\n <div className=\"px-4 py-4 sm:px-6 sm:py-5\">\n {formReady ? (\n <CrudForm<Record<string, unknown>>\n key={formKey}\n formId={FORM_ID}\n embedded\n trackDirtyWhenEmbedded\n onDirtyChange={handleDirtyChange}\n hideFooterActions\n customFieldsManageMode=\"page\"\n fields={customFields.fields}\n groups={groups}\n initialValues={initialValues}\n injectionSpotId={INTERACTION_INJECTION_SPOT}\n onSubmit={handleSubmit}\n />\n ) : (\n <CatalogStatusAlert error={catalog.status === 'error'} onRetry={catalog.retry} />\n )}\n </div>\n </div>\n <div className=\"flex shrink-0 items-center justify-end gap-3 border-t border-border bg-background px-5 py-4\">\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => onOpenChange(false)}\n >\n {t('customers.calendar.editor.cancel', 'Cancel')}\n </Button>\n <Button\n type=\"submit\"\n form={FORM_ID}\n disabled={saving || customFields.status !== 'ready' || injectionWidgets.loading || !!injectionWidgets.error || catalog.status !== 'ready' || (mode === 'create' && !canCreateType)}\n >\n {saving ? t('customers.calendar.editor.saving', 'Saving\u2026') : t('customers.calendar.editor.save', 'Save event')}\n </Button>\n </div>\n </DialogContent>\n {ConfirmDialogElement}\n </Dialog>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Calendar, X } from 'lucide-react'\nimport { VisuallyHidden } from '@radix-ui/react-visually-hidden'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { apiCallOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { extractOptimisticLockConflict } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { surfaceRecordConflict } from '@open-mercato/ui/backend/conflicts'\nimport { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { useCurrentOrganization } from '@open-mercato/ui/backend/BackendChromeProvider'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { CrudForm, type CrudFormGroup, type CrudFormGroupComponentProps } from '@open-mercato/ui/backend/CrudForm'\nimport { useInjectionWidgets } from '@open-mercato/ui/backend/injection/InjectionSpot'\nimport { projectCalendarCustomValues, useCalendarCustomFields, validateCalendarCustomValues } from './editor/useCalendarCustomFields'\nimport { collectCustomFieldValues } from '@open-mercato/ui/backend/utils/customFieldValues'\nimport { Alert, AlertDescription, AlertTitle } from '@open-mercato/ui/primitives/alert'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Dialog, DialogContent, DialogTitle } from '@open-mercato/ui/primitives/dialog'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@open-mercato/ui/primitives/select'\nimport { calendarDayEndInstant, calendarDayStartInstant, calendarWallTimeToInstant, calendarTimezoneOptions, defaultCalendarTimezone, isCalendarTimezone } from '../../lib/calendar/timezone'\nimport { Textarea } from '@open-mercato/ui/primitives/textarea'\nimport { useDialogKeyHandler } from '@open-mercato/ui/hooks/useDialogKeyHandler'\nimport { E } from '#generated/entities.ids.generated'\nimport {\n buildRecurrenceRule,\n buildInteractionPayload,\n computeDurationMinutes,\n createDefaultFormState,\n parseItemToFormState,\n resolveSavedOwnerUserId,\n type EditorFormState,\n} from '../../lib/calendar/editorPayload'\nimport type { ConflictScope } from '../../lib/calendar/preferences'\nimport { renderDictionaryIcon } from '@open-mercato/core/modules/dictionaries/components/dictionaryAppearance'\nimport { normalizeCustomFieldSubmitValue } from '../detail/customFieldUtils'\nimport type { CalendarItem } from './types'\nimport { EDITOR_SCROLL_EVENT, Field } from './editor/inputs'\nimport { SegmentGroup } from './editor/SegmentGroup'\nimport { RelatedToField } from './editor/RelatedToField'\nimport { useConflictProbe, useEditorLabelResolution } from './editor/hooks'\nimport { EventTypePanel } from './editor/EventTypePanel'\nimport { eventTypeConfig, eventTypeOptions, isSelectableEventType, selectedEventType, useEventTypeCatalog } from './editor/useEventTypeCatalog'\nimport type { ScopedCalendarEventType } from '../../lib/calendar/eventTypeResolver'\n\nexport interface CalendarEventEditorProps {\n open: boolean\n mode: 'create' | 'edit'\n item?: CalendarItem | null\n defaultDate?: Date\n defaultRange?: { start: Date; end: Date } | null\n /** @deprecated Event-type labels are loaded from the scoped catalog. */\n typeLabels?: Record<string, string>\n typeIcons?: Record<string, string | null>\n conflictScope?: ConflictScope\n currentUserId?: string | null\n resourcesEnabled?: boolean\n staffEnabled?: boolean\n onOpenChange(open: boolean): void\n onSaved(): void\n}\n\nconst FORM_ID = 'customers-calendar-event-editor'\nconst INTERACTION_INJECTION_SPOT = `crud-form:${E.customers.customer_interaction.replace(/:/g, '.')}`\n\n// CrudForm values carry the flattened EditorFormState keys (seeded via\n// initialValues) plus cf_* custom-field keys managed by CrudForm itself.\nfunction formStateOfValues(values: Record<string, unknown>): EditorFormState {\n return values as unknown as EditorFormState\n}\n\nfunction customFieldInitialValues(item: CalendarItem): Record<string, unknown> {\n const customValues = (item.raw as Record<string, unknown>).customValues\n if (!customValues || typeof customValues !== 'object' || Array.isArray(customValues)) return {}\n return Object.fromEntries(\n Object.entries(customValues as Record<string, unknown>).map(([key, value]) => [`cf_${key}`, value]),\n )\n}\n\nfunction typeChangeDiscardFields(error: unknown): string[] | null {\n if (!error || typeof error !== 'object') return null\n const details = error as { status?: unknown; code?: unknown; fields?: unknown }\n if (details.status !== 409 || details.code !== 'calendar_type_change_confirmation_required') return null\n if (!Array.isArray(details.fields) || details.fields.length === 0 ||\n !details.fields.every((field) => typeof field === 'string' && field.length > 0)) return null\n return details.fields\n}\n\ntype EditorBodyProps = Pick<CrudFormGroupComponentProps, 'values' | 'errors' | 'setValue'> & {\n open: boolean\n isEdit: boolean\n item?: CalendarItem | null\n conflictScope: ConflictScope\n currentUserId: string | null\n resourcesEnabled: boolean\n staffEnabled: boolean\n saving: boolean\n catalogItems: readonly ScopedCalendarEventType[]\n catalogReady: boolean\n catalogError: boolean\n onRetryCatalog(): void\n onSelectedTypeChange(key: string): void\n}\n\nfunction CatalogStatusAlert({ error, onRetry }: { error: boolean; onRetry(): void }) {\n const t = useT()\n return (\n <Alert status={error ? 'error' : 'information'} className=\"rounded-lg lg:col-span-2\">\n <AlertDescription>\n {error\n ? t('customers.calendar.editor.catalogLoadFailed')\n : t('customers.calendar.editor.catalogLoading')}\n </AlertDescription>\n {error ? (\n <Button type=\"button\" variant=\"outline\" onClick={onRetry}>\n {t('customers.calendar.errors.retry')}\n </Button>\n ) : null}\n </Alert>\n )\n}\n\nconst TimezoneField = React.memo(function TimezoneField({\n value,\n error,\n disabled,\n onChange,\n}: {\n value: string | null | undefined\n error?: string\n disabled: boolean\n onChange(timezone: string): void\n}) {\n const t = useT()\n const [open, setOpen] = React.useState(false)\n const timezone = value ?? defaultCalendarTimezone()\n const options = React.useMemo(() => open ? calendarTimezoneOptions(timezone) : [timezone], [open, timezone])\n const items = React.useMemo(\n () => options.map((timezone) => <SelectItem key={timezone} value={timezone}>{timezone}</SelectItem>),\n [options],\n )\n return (\n <Field label={t('customers.calendar.editor.timezone', 'Time zone')} error={error}>\n <Select value={timezone} onValueChange={onChange} disabled={disabled} open={open} onOpenChange={setOpen}>\n <SelectTrigger aria-label={t('customers.calendar.editor.timezone', 'Time zone')}><SelectValue /></SelectTrigger>\n <SelectContent>{items}</SelectContent>\n </Select>\n </Field>\n )\n})\n\nconst TypeSwitcher = React.memo(SegmentGroup) as typeof SegmentGroup\nconst NO_RESOURCES: EditorFormState['resources'] = []\nconst MemoRelatedToField = React.memo(RelatedToField)\n\n// CrudForm re-invokes the group renderer on every one of its own renders;\n// memoizing on the form state identities skips the ones that change neither\n// the values nor the errors.\nconst EditorBody = React.memo(function EditorBody({\n values,\n errors,\n setValue,\n open,\n isEdit,\n item,\n conflictScope,\n currentUserId,\n resourcesEnabled,\n staffEnabled,\n saving,\n catalogItems,\n catalogReady,\n catalogError,\n onRetryCatalog,\n onSelectedTypeChange,\n}: EditorBodyProps) {\n const t = useT()\n const form = formStateOfValues(values)\n const selectedType = form.category ?? form.kind\n const definition = selectedEventType(catalogItems, selectedType)\n const config = eventTypeConfig(definition)\n\n const update = React.useCallback(\n (patch: Partial<EditorFormState>) => {\n for (const [key, value] of Object.entries(patch)) setValue(key, value)\n },\n [setValue],\n )\n\n const updateTimezone = React.useCallback((timezone: string) => setValue('timezone', timezone), [setValue])\n const selectType = React.useCallback((type: string) => {\n const nextDefinition = selectedEventType(catalogItems, type)\n update({ kind: nextDefinition.behavior.baseKind, category: type })\n onSelectedTypeChange(type)\n }, [catalogItems, update, onSelectedTypeChange])\n const updateRelatedTo = React.useCallback((relatedTo: EditorFormState['relatedTo']) => update({ relatedTo }), [update])\n const updateDeal = React.useCallback(\n (deal: { id: string; label: string } | null) => update({ dealId: deal?.id ?? null, dealLabel: deal?.label ?? null }),\n [update],\n )\n const deal = React.useMemo(\n () => form.dealId && form.dealLabel ? { id: form.dealId, label: form.dealLabel } : null,\n [form.dealId, form.dealLabel],\n )\n const capabilities = React.useMemo(() => ({ resourcesEnabled, staffEnabled }), [resourcesEnabled, staffEnabled])\n\n // Probe against the owner the interaction will actually be SAVED with so the\n // editor warning matches the grid's post-save conflict rings: tasks own via\n // their assignee, other kinds stay ownerless on create / keep the existing\n // owner on edit (conflicts then come from shared participants).\n const draftOwnerUserId = resolveSavedOwnerUserId(config, form, isEdit, item?.ownerUserId ?? null)\n // Self-exclude by the underlying interaction id (raw.id): findEditorConflictItems\n // drops candidates by raw.id, and every expanded occurrence of a recurring series\n // shares it \u2014 so the edited record never conflicts with itself.\n // Resources count toward a conflict only when the selected type can book them.\n const draftResources = resourcesEnabled && definition.behavior.fields.resources ? form.resources : NO_RESOURCES\n const conflict = useConflictProbe(open, form, config, isEdit && item ? item.raw.id : null, draftOwnerUserId, conflictScope, currentUserId, draftResources)\n\n const typeOptions = React.useMemo(\n () => eventTypeOptions(catalogItems, selectedType, t),\n [catalogItems, selectedType, t],\n )\n const typeSwitcherOptions = React.useMemo(\n () =>\n typeOptions.map((option) => ({\n value: option.value,\n label: option.label,\n icon: renderDictionaryIcon(option.icon, 'h-4 w-4'),\n })),\n [typeOptions],\n )\n\n const titleLabel = form.kind === 'email'\n ? t('customers.calendar.editor.titleLabel.email', 'Subject')\n : form.kind === 'note'\n ? t('customers.calendar.editor.titleLabel.note', 'Note')\n : t('customers.calendar.editor.titleLabel.generic', 'Title')\n\n return (\n <div className=\"grid w-full grid-cols-1 items-start gap-4 lg:grid-cols-2 lg:gap-x-6\">\n {conflict ? (\n <Alert status=\"warning\" className=\"rounded-lg lg:col-span-2\">\n <AlertTitle>{t('customers.calendar.editor.conflictTitle', 'Calendar conflict')}</AlertTitle>\n <AlertDescription>{conflict}</AlertDescription>\n </Alert>\n ) : null}\n {!catalogReady ? (\n <CatalogStatusAlert error={catalogError} onRetry={onRetryCatalog} />\n ) : null}\n {catalogReady && (definition.historical || catalogItems.some((item) => item.key === selectedType && !item.selectable)) ? (\n <Alert status=\"warning\" className=\"rounded-lg lg:col-span-2\">\n <AlertDescription>{t('customers.calendar.editor.historicalType')}</AlertDescription>\n </Alert>\n ) : null}\n <div className=\"w-full lg:col-span-2\">\n <TypeSwitcher<string>\n ariaLabel={t('customers.calendar.editor.typeSwitcher', 'Event type')}\n value={selectedType}\n onChange={selectType}\n disabled={!catalogReady}\n options={typeSwitcherOptions}\n />\n </div>\n <Field label={titleLabel} error={errors.title} className=\"lg:col-span-2\">\n <Input\n type=\"text\"\n value={form.title}\n onChange={(event) => update({ title: event.target.value })}\n placeholder={t('customers.calendar.editor.titlePlaceholder', 'Add a title\u2026')}\n aria-label={titleLabel}\n autoFocus\n size=\"lg\"\n />\n </Field>\n <TimezoneField value={form.timezone} error={errors.timezone} disabled={saving} onChange={updateTimezone} />\n <div className=\"lg:col-span-2\">\n <Field label={t('customers.calendar.editor.relatedTo', 'Related to')} error={errors.relatedTo}>\n <MemoRelatedToField\n label={t('customers.calendar.editor.relatedTo', 'Related to')}\n value={form.relatedTo}\n deal={deal}\n onChange={updateRelatedTo}\n onDealChange={updateDeal}\n error={errors.relatedTo}\n />\n </Field>\n </div>\n {catalogReady ? (\n <div className=\"lg:col-span-2\">\n <EventTypePanel\n definition={definition}\n panelKey={definition.panelKey}\n mode={isEdit ? 'edit' : 'create'}\n values={values}\n errors={errors}\n disabled={saving}\n capabilities={capabilities}\n setValue={setValue}\n />\n </div>\n ) : null}\n <Field label={t('customers.calendar.editor.description', 'Description')} className=\"lg:col-span-2\">\n <Textarea\n value={form.description}\n onChange={(event) => update({ description: event.target.value })}\n placeholder={t('customers.calendar.editor.descriptionPlaceholder', 'Add details\u2026')}\n aria-label={t('customers.calendar.editor.description', 'Description')}\n className=\"h-20 resize-none\"\n />\n </Field>\n </div>\n )\n})\n\nexport function CalendarEventEditor({\n open,\n mode,\n item,\n defaultDate,\n defaultRange,\n conflictScope,\n currentUserId,\n resourcesEnabled,\n staffEnabled,\n onOpenChange,\n onSaved,\n}: CalendarEventEditorProps) {\n const t = useT()\n const activeOrgId = useCurrentOrganization()?.id ?? null\n const [saving, setSaving] = React.useState(false)\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const dirtyRef = React.useRef(false)\n const closeConfirmPendingRef = React.useRef(false)\n const [closePromptOpen, setClosePromptOpen] = React.useState(false)\n // The shared confirm answers Cmd/Ctrl+Enter with its confirm action, which\n // here discards the draft \u2014 on the very shortcut that saves the form. The\n // shortcut is swallowed while the prompt is up; the buttons and Escape (keep\n // editing) still answer it. Owned by an effect so the listener is removed\n // even when the editor unmounts with the prompt still open.\n React.useEffect(() => {\n if (!closePromptOpen) return\n const swallowSubmitShortcut = (event: KeyboardEvent) => {\n if (event.key !== 'Enter' || !(event.metaKey || event.ctrlKey)) return\n event.preventDefault()\n event.stopImmediatePropagation()\n }\n window.addEventListener('keydown', swallowSubmitShortcut, true)\n return () => window.removeEventListener('keydown', swallowSubmitShortcut, true)\n }, [closePromptOpen])\n const requestClose = React.useCallback(async () => {\n if (saving || closeConfirmPendingRef.current) return\n if (dirtyRef.current) {\n closeConfirmPendingRef.current = true\n setClosePromptOpen(true)\n try {\n const confirmed = await confirm({\n title: t('ui.forms.confirmUnsavedChanges', 'You have unsaved changes. Are you sure you want to leave?'),\n confirmText: t('customers.deals.detail.unsavedConfirm', 'Discard changes'),\n cancelText: t('customers.deals.detail.unsavedCancel', 'Keep editing'),\n variant: 'destructive',\n })\n if (!confirmed) return\n } finally {\n closeConfirmPendingRef.current = false\n setClosePromptOpen(false)\n }\n }\n onOpenChange(false)\n }, [confirm, onOpenChange, saving, t])\n const handleDirtyChange = React.useCallback((dirty: boolean) => { dirtyRef.current = dirty }, [])\n React.useEffect(() => { if (!open) dirtyRef.current = false }, [open])\n const isEdit = mode === 'edit' && Boolean(item?.id)\n const catalog = useEventTypeCatalog(open)\n const { items: catalogItems, status: catalogStatus, retry: retryCatalog } = catalog\n const [selectedTypeKey, setSelectedTypeKey] = React.useState(() => isEdit && item ? item.interactionType : 'meeting')\n const autoSelectedCreateType = !isEdit && catalog.status === 'ready' && !isSelectableEventType(catalog.items, 'meeting')\n ? catalog.items.find((entry) => entry.selectable && !entry.historical) ?? null\n : null\n\n // CrudForm reads initialValues once, so every dialog open gets a fresh form\n // instance keyed by the open sequence + edited record.\n const [openSeq, setOpenSeq] = React.useState(0)\n const wasOpenRef = React.useRef(false)\n React.useEffect(() => {\n if (open && !wasOpenRef.current) setOpenSeq((seq) => seq + 1)\n wasOpenRef.current = open\n }, [open])\n const formKey = `${mode}:${item?.id ?? 'new'}:${openSeq}:${autoSelectedCreateType?.key ?? 'default'}`\n const labelScopeKey = JSON.stringify([formKey, activeOrgId, item?.entityId, item?.dealId, item?.ownerUserId])\n const [resolvedLabels, setResolvedLabels] = React.useState<{\n scopeKey: string\n values: Partial<EditorFormState>\n } | null>(null)\n const updateInitialLabels = React.useCallback((patch: Partial<EditorFormState>) => {\n setResolvedLabels((previous) => ({\n scopeKey: labelScopeKey,\n values: { ...(previous?.scopeKey === labelScopeKey ? previous.values : {}), ...patch },\n }))\n }, [labelScopeKey])\n\n const initialValues = React.useMemo<Record<string, unknown>>(() => {\n if (isEdit && item) {\n return {\n ...parseItemToFormState(item),\n ...customFieldInitialValues(item),\n ...(resolvedLabels?.scopeKey === labelScopeKey ? resolvedLabels.values : {}),\n id: item.id,\n updatedAt: item.updatedAt ?? undefined,\n }\n }\n const defaults = createDefaultFormState(defaultDate ?? null, undefined, defaultRange ?? null)\n if (!autoSelectedCreateType) return { ...defaults }\n return {\n ...defaults,\n kind: autoSelectedCreateType.behavior.baseKind,\n category: autoSelectedCreateType.key,\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps -- openSeq re-seeds defaults per dialog open\n }, [isEdit, item, defaultDate, defaultRange, openSeq, autoSelectedCreateType, labelScopeKey, resolvedLabels])\n useEditorLabelResolution(open, formStateOfValues(initialValues), updateInitialLabels)\n const initialTypeKey = String(initialValues.category ?? initialValues.kind ?? 'meeting')\n React.useEffect(() => { setSelectedTypeKey(initialTypeKey) }, [initialTypeKey, formKey])\n const selectedDefinition = selectedEventType(catalog.items, selectedTypeKey)\n const customFields = useCalendarCustomFields(open, E.customers.customer_interaction, selectedDefinition.behavior.customFieldsetIds)\n const injectionWidgets = useInjectionWidgets(INTERACTION_INJECTION_SPOT)\n const canCreateType = isSelectableEventType(catalog.items, selectedTypeKey)\n const formReady = isEdit || catalog.status === 'ready'\n\n const handleSubmit = React.useCallback(\n async (values: Record<string, unknown>) => {\n const form = formStateOfValues(values)\n if (catalogStatus !== 'ready' || customFields.status !== 'ready' || injectionWidgets.loading || injectionWidgets.error) {\n throw createCrudFormError(t('customers.calendar.editor.catalogLoadFailed'))\n }\n const definition = selectedEventType(catalogItems, form.category ?? form.kind)\n const selectedKey = form.category ?? form.kind\n if ((mode === 'create' || selectedKey !== initialTypeKey) &&\n !isSelectableEventType(catalogItems, selectedKey)) {\n throw createCrudFormError(t('customers.calendar.editor.typeUnavailable'))\n }\n const config = eventTypeConfig(definition)\n const fieldErrors: Record<string, string> = {}\n const timezone = form.timezone ?? defaultCalendarTimezone()\n const startTime = form.allDay && config.hasAllDay ? '00:00' : form.startTime\n if (!isCalendarTimezone(timezone)) fieldErrors.timezone = t('customers.calendar.editor.validation.timezoneInvalid', 'Choose a valid time zone')\n else if (!(form.allDay && config.hasAllDay ? calendarDayStartInstant(form.date, timezone) : calendarWallTimeToInstant(form.date, startTime, timezone))) {\n fieldErrors[form.allDay && config.hasAllDay ? 'date' : 'startTime'] = t('customers.calendar.editor.validation.timezoneGap', 'This local time does not exist in the selected time zone')\n }\n if (config.hasEnd && !form.allDay && isCalendarTimezone(timezone) && !calendarWallTimeToInstant(form.endDate, form.endTime, timezone)) {\n fieldErrors.ends = t('customers.calendar.editor.validation.timezoneGap', 'This local time does not exist in the selected time zone')\n }\n if (config.hasRepeat && form.repeatFreq !== 'none' && isCalendarTimezone(timezone) && form.repeatEndType === 'date' && form.repeatUntilDate && !calendarDayEndInstant(form.repeatUntilDate, timezone)) {\n fieldErrors.repeatUntilDate = t('customers.calendar.editor.validation.timezoneGap', 'This local time does not exist in the selected time zone')\n }\n if (!form.title.trim()) {\n fieldErrors.title = t('customers.calendar.editor.validation.titleRequired', 'Title is required')\n }\n if (!form.relatedTo) {\n fieldErrors.relatedTo = t('customers.calendar.editor.validation.relatedToRequired', 'Select a person or company to link this event')\n }\n if (!fieldErrors.ends && !fieldErrors.startTime && !fieldErrors.date && config.hasEnd && !form.allDay && computeDurationMinutes(form) === null) {\n fieldErrors.ends = t('customers.calendar.editor.validation.endsBeforeStarts', 'End must be after start')\n }\n // A task must be assigned to a team member \u2014 surface the requirement inline\n // instead of letting the save fail with no visible reason (#3747 feedback).\n if (config.people === 'assignee' && staffEnabled !== false && !form.assigneeUserId) {\n fieldErrors.assignee = t('customers.calendar.editor.validation.assigneeRequired', 'Assign this task to a team member')\n }\n if (Object.keys(fieldErrors).length > 0) {\n throw createCrudFormError(Object.values(fieldErrors)[0], fieldErrors)\n }\n const customValidation = validateCalendarCustomValues(values, customFields.definitions)\n if (!customValidation.ok) {\n const translatedErrors = Object.fromEntries(Object.entries(customValidation.fieldErrors).map(([key, message]) => [key, t(message, message)]))\n throw createCrudFormError(Object.values(translatedErrors)[0], translatedErrors)\n }\n setSaving(true)\n try {\n const payload = buildInteractionPayload({ ...form, timezone }, {\n config,\n mode,\n id: item?.id,\n resourcesEnabled: resourcesEnabled === true,\n staffEnabled: staffEnabled !== false,\n })\n payload.enforceSelectableType = true\n const applicable = definition.behavior.fields\n const time = form.allDay && applicable.allDay ? '00:00' : form.startTime\n payload.time = time\n payload.scheduledAt = (form.allDay && applicable.allDay ? calendarDayStartInstant(form.date, timezone) : calendarWallTimeToInstant(form.date, time, timezone))!.toISOString()\n payload.timezone = timezone\n payload.allDay = applicable.allDay ? form.allDay : null\n payload.durationMinutes = applicable.endTime && !(form.allDay && applicable.allDay) ? computeDurationMinutes(form) : null\n payload.location = applicable.location === 'none' ? null : form.location.trim() || null\n const recurrenceRule = applicable.recurrence ? buildRecurrenceRule(form) : null\n payload.recurrenceRule = recurrenceRule\n payload.recurrenceEnd = recurrenceRule && form.repeatEndType === 'date' && form.repeatUntilDate\n ? calendarDayEndInstant(form.repeatUntilDate, timezone)!.toISOString()\n : null\n payload.participants = applicable.people !== 'none' && applicable.people !== 'assignee' && form.participants.length\n ? form.participants.map((participant) => ({\n userId: participant.userId,\n name: participant.name,\n email: participant.email,\n status: participant.isCustomer ? 'customer' : 'pending',\n }))\n : null\n if (applicable.people === 'assignee' && staffEnabled !== false) payload.ownerUserId = form.assigneeUserId ?? null\n else delete payload.ownerUserId\n if (applicable.priority) payload.priority = form.priority === 'low' ? 10 : form.priority === 'high' ? 90 : 50\n else delete payload.priority\n if (!applicable.resources) delete payload.linkedEntities\n const custom = collectCustomFieldValues(projectCalendarCustomValues(values, customFields.definitions), {\n transform: (value) => normalizeCustomFieldSubmitValue(value),\n })\n for (const [key, value] of Object.entries(custom)) payload[`cf_${key}`] = value\n // CrudForm supplies the optimistic-lock header (auto-derived from\n // initialValues.updatedAt) via scoped request headers around onSubmit.\n const submitInteraction = (confirmDiscardInapplicableValues: boolean) => apiCallOrThrow('/api/customers/interactions', {\n method: isEdit ? 'PUT' : 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(confirmDiscardInapplicableValues\n ? { ...payload, confirmDiscardInapplicableValues: true }\n : payload),\n })\n try {\n await submitInteraction(false)\n } catch (error) {\n const fields = typeChangeDiscardFields(error)\n if (!fields) throw error\n const approved = await confirm({\n title: t('customers.calendar.editor.discardTitle'),\n text: `${t('customers.calendar.editor.discardDescription')} ${t('customers.calendar.editor.discardFields')} ${fields.map((field) => {\n const definition = customFields.allDefinitions.find((entry) => `cf_${entry.key}` === field)\n if (definition?.label) return definition.label\n const labelKeys: Record<string, string> = {\n location: 'customers.calendar.editor.location',\n participants: 'customers.calendar.editor.attendees',\n linkedEntities: 'customers.calendar.editor.resources',\n allDay: 'customers.calendar.editor.allDay',\n durationMinutes: 'customers.calendar.editor.dates.ends',\n priority: 'customers.calendar.editor.priority.label',\n recurrenceRule: 'customers.calendar.editor.repeat.label',\n recurrenceEnd: 'customers.calendar.editor.repeat.ends',\n }\n return t(labelKeys[field] ?? 'customers.calendar.editor.discardUnknownField')\n }).join(', ')}`,\n confirmText: t('customers.calendar.editor.discardConfirm'),\n cancelText: t('customers.calendar.editor.cancel'),\n variant: 'destructive',\n })\n if (!approved) return\n await submitInteraction(true)\n }\n flash(t('customers.calendar.editor.saved', 'Event saved'), 'success')\n onOpenChange(false)\n requestAnimationFrame(() => { onSaved() })\n } catch (err) {\n // Surface an optimistic-lock 409 as the persistent conflict bar and\n // close \u2014 the bar renders at page level, behind the dialog overlay.\n if (extractOptimisticLockConflict(err)) {\n surfaceRecordConflict(err, t)\n onOpenChange(false)\n return\n }\n throw err\n } finally {\n setSaving(false)\n }\n },\n [catalogItems, catalogStatus, customFields.status, customFields.definitions, customFields.allDefinitions, injectionWidgets.loading, injectionWidgets.error, confirm, initialTypeKey, isEdit, item?.id, mode, onOpenChange, onSaved, resourcesEnabled, staffEnabled, t],\n )\n\n const groups = React.useMemo<CrudFormGroup[]>(\n () => [\n {\n id: 'details',\n bare: true,\n component: (ctx) => (\n <EditorBody\n values={ctx.values}\n errors={ctx.errors}\n setValue={ctx.setValue}\n open={open}\n isEdit={isEdit}\n item={item}\n conflictScope={conflictScope ?? 'all'}\n currentUserId={currentUserId ?? null}\n resourcesEnabled={resourcesEnabled === true}\n staffEnabled={staffEnabled !== false}\n saving={saving}\n catalogItems={catalogItems}\n catalogReady={catalogStatus === 'ready'}\n catalogError={catalogStatus === 'error'}\n onRetryCatalog={retryCatalog}\n onSelectedTypeChange={setSelectedTypeKey}\n />\n ),\n },\n ...(customFields.fields.length ? [{ id: 'customFields', title: t('entities.customFields.title'), fields: customFields.fields.map((field) => field.id) }] : []),\n ],\n [open, isEdit, item, conflictScope, currentUserId, resourcesEnabled, staffEnabled, saving, catalogItems, catalogStatus, retryCatalog, customFields.fields, t],\n )\n\n const handleKeyDown = useDialogKeyHandler({\n onCancel: () => { void requestClose() },\n onConfirm: () => {\n const formElement = document.getElementById(FORM_ID)\n if (formElement instanceof HTMLFormElement) formElement.requestSubmit()\n },\n disabled: saving || customFields.status !== 'ready' || injectionWidgets.loading || !!injectionWidgets.error || catalog.status !== 'ready' || (mode === 'create' && !canCreateType),\n })\n\n const dialogTitle = isEdit ? t('customers.calendar.editor.title.edit', 'Edit event') : t('customers.calendar.editor.title.create', 'New event')\n\n return (\n <Dialog open={open} onOpenChange={(nextOpen) => { if (nextOpen) onOpenChange(true); else void requestClose() }}>\n <DialogContent\n onKeyDown={handleKeyDown}\n // Only dismiss on a genuine click OUTSIDE the whole editor. Radix\n // relays a click inside the dialog (but outside an open DS\n // DatePicker/Select popover) up to the dialog as an \"interact\n // outside\" event when it dismisses that popover \u2014 without this guard\n // it would tear down the entire editor. Keep it open when the target\n // is inside the dialog body or inside any portalled popover. The\n // shared confirm renders as a native modal `<dialog>` on top of the\n // editor; answering it is not an outside click either, otherwise it\n // would re-request the close it is asking about.\n onInteractOutside={(event) => {\n const target = event.detail.originalEvent.target as HTMLElement | null\n if (target?.closest('[data-dialog-content]') || target?.closest('[data-radix-popper-content-wrapper]') || target?.closest('dialog')) {\n event.preventDefault()\n }\n }}\n aria-describedby={undefined}\n dismissible={false}\n className=\"flex h-dvh max-h-dvh w-screen max-w-none flex-col gap-0 overflow-hidden rounded-none border-0 bg-background p-0 shadow-xl sm:h-auto sm:max-h-[calc(100dvh-4rem)] sm:w-full sm:max-w-lg sm:rounded-2xl sm:border-0 lg:max-w-3xl\"\n >\n <VisuallyHidden>\n <DialogTitle>{dialogTitle}</DialogTitle>\n </VisuallyHidden>\n <div className=\"flex shrink-0 items-center gap-3 border-b border-border bg-background py-4 pl-5 pr-4\">\n <Calendar aria-hidden className=\"size-6 shrink-0 text-foreground\" strokeWidth={1.75} />\n <p className=\"min-w-0 flex-1 text-sm font-medium leading-5 text-foreground\">{dialogTitle}</p>\n <IconButton\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => { void requestClose() }}\n aria-label={t('customers.calendar.editor.close', 'Close')}\n className=\"shrink-0 text-muted-foreground\"\n >\n <X aria-hidden className=\"size-5\" />\n </IconButton>\n </div>\n <div\n className=\"flex-1 overflow-y-auto\"\n onScroll={() => {\n // Tell the DS date/time fields to close their (controlled) popover\n // so a portalled popover doesn't float over the form or drift away\n // from its field while scrolling (#3747 feedback). Radix ignores\n // synthetic dismiss events, so the fields drive their own `open`.\n document.dispatchEvent(new CustomEvent(EDITOR_SCROLL_EVENT))\n }}\n >\n <div className=\"px-4 py-4 sm:px-6 sm:py-5\">\n {formReady ? (\n <CrudForm<Record<string, unknown>>\n key={formKey}\n formId={FORM_ID}\n embedded\n trackDirtyWhenEmbedded\n onDirtyChange={handleDirtyChange}\n hideFooterActions\n customFieldsManageMode=\"page\"\n fields={customFields.fields}\n groups={groups}\n initialValues={initialValues}\n injectionSpotId={INTERACTION_INJECTION_SPOT}\n onSubmit={handleSubmit}\n />\n ) : (\n <CatalogStatusAlert error={catalog.status === 'error'} onRetry={catalog.retry} />\n )}\n </div>\n </div>\n <div className=\"flex shrink-0 items-center justify-end gap-3 border-t border-border bg-background px-5 py-4\">\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => onOpenChange(false)}\n >\n {t('customers.calendar.editor.cancel', 'Cancel')}\n </Button>\n <Button\n type=\"submit\"\n form={FORM_ID}\n disabled={saving || customFields.status !== 'ready' || injectionWidgets.loading || !!injectionWidgets.error || catalog.status !== 'ready' || (mode === 'create' && !canCreateType)}\n >\n {saving ? t('customers.calendar.editor.saving', 'Saving\u2026') : t('customers.calendar.editor.save', 'Save event')}\n </Button>\n </div>\n </DialogContent>\n {ConfirmDialogElement}\n </Dialog>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["TimezoneField", "timezone", "EditorBody", "deal", "item", "definition"]
7
7
  }
@@ -7,6 +7,8 @@ const features = [
7
7
  { id: "wms.manage_reservations", title: "Manage inventory reservations", module: "wms" },
8
8
  { id: "wms.adjust_inventory", title: "Adjust and move inventory", module: "wms" },
9
9
  { id: "wms.receive_inventory", title: "Receive inbound inventory", module: "wms" },
10
+ { id: "wms.manage_asn", title: "Manage ASNs and receiving lines", module: "wms" },
11
+ { id: "wms.manage_putaway", title: "Manage putaway tasks", module: "wms" },
10
12
  { id: "wms.cycle_count", title: "Run cycle counts", module: "wms" },
11
13
  { id: "wms.import", title: "Import inventory from CSV", module: "wms" }
12
14
  ];
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/modules/wms/acl.ts"],
4
- "sourcesContent": ["export const features = [\n { id: 'wms.view', title: 'View WMS data', module: 'wms' },\n { id: 'wms.manage_warehouses', title: 'Manage warehouses', module: 'wms' },\n { id: 'wms.manage_zones', title: 'Manage warehouse zones', module: 'wms' },\n { id: 'wms.manage_locations', title: 'Manage warehouse locations', module: 'wms' },\n { id: 'wms.manage_inventory', title: 'Manage inventory profiles and stock state', module: 'wms' },\n { id: 'wms.manage_reservations', title: 'Manage inventory reservations', module: 'wms' },\n { id: 'wms.adjust_inventory', title: 'Adjust and move inventory', module: 'wms' },\n { id: 'wms.receive_inventory', title: 'Receive inbound inventory', module: 'wms' },\n { id: 'wms.cycle_count', title: 'Run cycle counts', module: 'wms' },\n { id: 'wms.import', title: 'Import inventory from CSV', module: 'wms' },\n]\n\nexport default features\n"],
5
- "mappings": "AAAO,MAAM,WAAW;AAAA,EACtB,EAAE,IAAI,YAAY,OAAO,iBAAiB,QAAQ,MAAM;AAAA,EACxD,EAAE,IAAI,yBAAyB,OAAO,qBAAqB,QAAQ,MAAM;AAAA,EACzE,EAAE,IAAI,oBAAoB,OAAO,0BAA0B,QAAQ,MAAM;AAAA,EACzE,EAAE,IAAI,wBAAwB,OAAO,8BAA8B,QAAQ,MAAM;AAAA,EACjF,EAAE,IAAI,wBAAwB,OAAO,6CAA6C,QAAQ,MAAM;AAAA,EAChG,EAAE,IAAI,2BAA2B,OAAO,iCAAiC,QAAQ,MAAM;AAAA,EACvF,EAAE,IAAI,wBAAwB,OAAO,6BAA6B,QAAQ,MAAM;AAAA,EAChF,EAAE,IAAI,yBAAyB,OAAO,6BAA6B,QAAQ,MAAM;AAAA,EACjF,EAAE,IAAI,mBAAmB,OAAO,oBAAoB,QAAQ,MAAM;AAAA,EAClE,EAAE,IAAI,cAAc,OAAO,6BAA6B,QAAQ,MAAM;AACxE;AAEA,IAAO,cAAQ;",
4
+ "sourcesContent": ["export const features = [\n { id: 'wms.view', title: 'View WMS data', module: 'wms' },\n { id: 'wms.manage_warehouses', title: 'Manage warehouses', module: 'wms' },\n { id: 'wms.manage_zones', title: 'Manage warehouse zones', module: 'wms' },\n { id: 'wms.manage_locations', title: 'Manage warehouse locations', module: 'wms' },\n { id: 'wms.manage_inventory', title: 'Manage inventory profiles and stock state', module: 'wms' },\n { id: 'wms.manage_reservations', title: 'Manage inventory reservations', module: 'wms' },\n { id: 'wms.adjust_inventory', title: 'Adjust and move inventory', module: 'wms' },\n { id: 'wms.receive_inventory', title: 'Receive inbound inventory', module: 'wms' },\n { id: 'wms.manage_asn', title: 'Manage ASNs and receiving lines', module: 'wms' },\n { id: 'wms.manage_putaway', title: 'Manage putaway tasks', module: 'wms' },\n { id: 'wms.cycle_count', title: 'Run cycle counts', module: 'wms' },\n { id: 'wms.import', title: 'Import inventory from CSV', module: 'wms' },\n]\n\nexport default features\n"],
5
+ "mappings": "AAAO,MAAM,WAAW;AAAA,EACtB,EAAE,IAAI,YAAY,OAAO,iBAAiB,QAAQ,MAAM;AAAA,EACxD,EAAE,IAAI,yBAAyB,OAAO,qBAAqB,QAAQ,MAAM;AAAA,EACzE,EAAE,IAAI,oBAAoB,OAAO,0BAA0B,QAAQ,MAAM;AAAA,EACzE,EAAE,IAAI,wBAAwB,OAAO,8BAA8B,QAAQ,MAAM;AAAA,EACjF,EAAE,IAAI,wBAAwB,OAAO,6CAA6C,QAAQ,MAAM;AAAA,EAChG,EAAE,IAAI,2BAA2B,OAAO,iCAAiC,QAAQ,MAAM;AAAA,EACvF,EAAE,IAAI,wBAAwB,OAAO,6BAA6B,QAAQ,MAAM;AAAA,EAChF,EAAE,IAAI,yBAAyB,OAAO,6BAA6B,QAAQ,MAAM;AAAA,EACjF,EAAE,IAAI,kBAAkB,OAAO,mCAAmC,QAAQ,MAAM;AAAA,EAChF,EAAE,IAAI,sBAAsB,OAAO,wBAAwB,QAAQ,MAAM;AAAA,EACzE,EAAE,IAAI,mBAAmB,OAAO,oBAAoB,QAAQ,MAAM;AAAA,EAClE,EAAE,IAAI,cAAc,OAAO,6BAA6B,QAAQ,MAAM;AACxE;AAEA,IAAO,cAAQ;",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,61 @@
1
+ import { z } from "zod";
2
+ import { readJsonSafe } from "@open-mercato/shared/lib/http/readJsonSafe";
3
+ import { asnCloseSchema } from "../../../../data/validators.js";
4
+ import { executeWmsCustomPostRoute } from "../../../inventory/helpers.js";
5
+ const metadata = {
6
+ POST: { requireAuth: true, requireFeatures: ["wms.manage_asn"] }
7
+ };
8
+ const paramsSchema = z.object({
9
+ id: z.string().uuid()
10
+ });
11
+ async function POST(request, routeContext) {
12
+ const parsedParams = paramsSchema.parse(routeContext.params);
13
+ const body = await readJsonSafe(request, {});
14
+ const scopedRequest = new Request(request.url, {
15
+ method: "POST",
16
+ headers: request.headers,
17
+ body: JSON.stringify({ ...body, id: parsedParams.id })
18
+ });
19
+ return executeWmsCustomPostRoute({
20
+ request: scopedRequest,
21
+ routePath: `wms/asns/${parsedParams.id}/complete`,
22
+ inputSchema: asnCloseSchema,
23
+ commandId: "wms.asns.close",
24
+ describeResource: (input) => ({
25
+ resourceKind: "wms.asn",
26
+ resourceId: input.id
27
+ }),
28
+ mapSuccess: (result) => ({
29
+ ok: true,
30
+ status: result.status
31
+ })
32
+ });
33
+ }
34
+ const successSchema = z.object({
35
+ ok: z.literal(true),
36
+ status: z.enum(["draft", "in_transit", "received", "closed"])
37
+ });
38
+ const errorSchema = z.object({ error: z.string() });
39
+ const openApi = {
40
+ tag: "WMS",
41
+ summary: "Complete ASN",
42
+ methods: {
43
+ POST: {
44
+ summary: "Complete ASN",
45
+ description: "Marks an ASN as received when it has receipt activity (at least one line with receivedQty > 0) and lines are complete (QC-passed accepted qty meets expected), or when closeWhenShort is true for short/QC-fail receipts after some receipt activity. Header-only ASNs and zero-receipt lines are rejected even with closeWhenShort.",
46
+ requestBody: { contentType: "application/json", schema: asnCloseSchema },
47
+ responses: [{ status: 200, description: "ASN completed", schema: successSchema }],
48
+ errors: [
49
+ { status: 400, description: "Validation failed", schema: errorSchema },
50
+ { status: 401, description: "Unauthorized", schema: errorSchema },
51
+ { status: 409, description: "Invalid receipt state", schema: errorSchema }
52
+ ]
53
+ }
54
+ }
55
+ };
56
+ export {
57
+ POST,
58
+ metadata,
59
+ openApi
60
+ };
61
+ //# sourceMappingURL=route.js.map