@open-mercato/core 0.6.6-develop.6094.1.28b081ea16 → 0.6.6-develop.6126.1.0b9392fdae

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 (317) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/generated/entities/catalog_product/index.js +52 -0
  3. package/dist/generated/entities/catalog_product/index.js.map +2 -2
  4. package/dist/generated/entities/catalog_product_variant/index.js +4 -0
  5. package/dist/generated/entities/catalog_product_variant/index.js.map +2 -2
  6. package/dist/generated/entity-fields-registry.js +28 -0
  7. package/dist/generated/entity-fields-registry.js.map +2 -2
  8. package/dist/modules/catalog/api/products/route.js +103 -33
  9. package/dist/modules/catalog/api/products/route.js.map +2 -2
  10. package/dist/modules/catalog/api/variants/route.js +4 -0
  11. package/dist/modules/catalog/api/variants/route.js.map +2 -2
  12. package/dist/modules/catalog/backend/catalog/products/[id]/page.js +46 -2
  13. package/dist/modules/catalog/backend/catalog/products/[id]/page.js.map +2 -2
  14. package/dist/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js +4 -0
  15. package/dist/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js.map +2 -2
  16. package/dist/modules/catalog/backend/catalog/products/[productId]/variants/create/page.js +2 -0
  17. package/dist/modules/catalog/backend/catalog/products/[productId]/variants/create/page.js.map +2 -2
  18. package/dist/modules/catalog/backend/catalog/products/create/page.js +42 -2
  19. package/dist/modules/catalog/backend/catalog/products/create/page.js.map +2 -2
  20. package/dist/modules/catalog/commands/products.js +251 -14
  21. package/dist/modules/catalog/commands/products.js.map +2 -2
  22. package/dist/modules/catalog/commands/variants.js +72 -1
  23. package/dist/modules/catalog/commands/variants.js.map +2 -2
  24. package/dist/modules/catalog/components/products/ProductComplianceSection.js +500 -0
  25. package/dist/modules/catalog/components/products/ProductComplianceSection.js.map +7 -0
  26. package/dist/modules/catalog/components/products/VariantBuilder.js +39 -1
  27. package/dist/modules/catalog/components/products/VariantBuilder.js.map +2 -2
  28. package/dist/modules/catalog/components/products/productForm.js +164 -2
  29. package/dist/modules/catalog/components/products/productForm.js.map +2 -2
  30. package/dist/modules/catalog/components/products/variantForm.js +2 -0
  31. package/dist/modules/catalog/components/products/variantForm.js.map +2 -2
  32. package/dist/modules/catalog/data/entities.js +93 -0
  33. package/dist/modules/catalog/data/entities.js.map +2 -2
  34. package/dist/modules/catalog/data/types.js +28 -0
  35. package/dist/modules/catalog/data/types.js.map +2 -2
  36. package/dist/modules/catalog/data/validators.js +100 -5
  37. package/dist/modules/catalog/data/validators.js.map +2 -2
  38. package/dist/modules/catalog/lib/gtin.js +61 -0
  39. package/dist/modules/catalog/lib/gtin.js.map +7 -0
  40. package/dist/modules/catalog/migrations/Migration20260611090000.js +17 -0
  41. package/dist/modules/catalog/migrations/Migration20260611090000.js.map +7 -0
  42. package/dist/modules/catalog/translations.js +1 -1
  43. package/dist/modules/catalog/translations.js.map +2 -2
  44. package/dist/modules/configs/api/cache/route.js +60 -10
  45. package/dist/modules/configs/api/cache/route.js.map +3 -3
  46. package/dist/modules/configs/components/CachePanel.js +52 -29
  47. package/dist/modules/configs/components/CachePanel.js.map +2 -2
  48. package/dist/modules/currencies/api/currencies/options/route.js +18 -4
  49. package/dist/modules/currencies/api/currencies/options/route.js.map +2 -2
  50. package/dist/modules/currencies/services/rateFetchingService.js +35 -18
  51. package/dist/modules/currencies/services/rateFetchingService.js.map +2 -2
  52. package/dist/modules/customer_accounts/backend/customer_accounts/users/[id]/page.js +16 -16
  53. package/dist/modules/customer_accounts/backend/customer_accounts/users/[id]/page.js.map +2 -2
  54. package/dist/modules/customer_accounts/backend/customer_accounts/users/page.js +1 -1
  55. package/dist/modules/customer_accounts/backend/customer_accounts/users/page.js.map +2 -2
  56. package/dist/modules/customers/backend/calendar/page.js +10 -0
  57. package/dist/modules/customers/backend/calendar/page.js.map +7 -0
  58. package/dist/modules/customers/backend/calendar/page.meta.js +25 -0
  59. package/dist/modules/customers/backend/calendar/page.meta.js.map +7 -0
  60. package/dist/modules/customers/cli.js +1 -0
  61. package/dist/modules/customers/cli.js.map +2 -2
  62. package/dist/modules/customers/components/calendar/AgendaList.js +182 -0
  63. package/dist/modules/customers/components/calendar/AgendaList.js.map +7 -0
  64. package/dist/modules/customers/components/calendar/CalendarEventEditor.js +331 -0
  65. package/dist/modules/customers/components/calendar/CalendarEventEditor.js.map +7 -0
  66. package/dist/modules/customers/components/calendar/CalendarFooter.js +48 -0
  67. package/dist/modules/customers/components/calendar/CalendarFooter.js.map +7 -0
  68. package/dist/modules/customers/components/calendar/CalendarHeader.js +36 -0
  69. package/dist/modules/customers/components/calendar/CalendarHeader.js.map +7 -0
  70. package/dist/modules/customers/components/calendar/CalendarScreen.js +516 -0
  71. package/dist/modules/customers/components/calendar/CalendarScreen.js.map +7 -0
  72. package/dist/modules/customers/components/calendar/CalendarSettingsModal.js +163 -0
  73. package/dist/modules/customers/components/calendar/CalendarSettingsModal.js.map +7 -0
  74. package/dist/modules/customers/components/calendar/CalendarTabs.js +60 -0
  75. package/dist/modules/customers/components/calendar/CalendarTabs.js.map +7 -0
  76. package/dist/modules/customers/components/calendar/CalendarToolbar.js +262 -0
  77. package/dist/modules/customers/components/calendar/CalendarToolbar.js.map +7 -0
  78. package/dist/modules/customers/components/calendar/EventBlock.js +149 -0
  79. package/dist/modules/customers/components/calendar/EventBlock.js.map +7 -0
  80. package/dist/modules/customers/components/calendar/EventPeekPopover.js +72 -0
  81. package/dist/modules/customers/components/calendar/EventPeekPopover.js.map +7 -0
  82. package/dist/modules/customers/components/calendar/MonthGrid.js +214 -0
  83. package/dist/modules/customers/components/calendar/MonthGrid.js.map +7 -0
  84. package/dist/modules/customers/components/calendar/ShortcutsDialog.js +44 -0
  85. package/dist/modules/customers/components/calendar/ShortcutsDialog.js.map +7 -0
  86. package/dist/modules/customers/components/calendar/TimeGrid.js +410 -0
  87. package/dist/modules/customers/components/calendar/TimeGrid.js.map +7 -0
  88. package/dist/modules/customers/components/calendar/UpcomingCards.js +175 -0
  89. package/dist/modules/customers/components/calendar/UpcomingCards.js.map +7 -0
  90. package/dist/modules/customers/components/calendar/editor/CategoryField.js +75 -0
  91. package/dist/modules/customers/components/calendar/editor/CategoryField.js.map +7 -0
  92. package/dist/modules/customers/components/calendar/editor/LocationField.js +30 -0
  93. package/dist/modules/customers/components/calendar/editor/LocationField.js.map +7 -0
  94. package/dist/modules/customers/components/calendar/editor/PeopleField.js +135 -0
  95. package/dist/modules/customers/components/calendar/editor/PeopleField.js.map +7 -0
  96. package/dist/modules/customers/components/calendar/editor/RelatedToField.js +218 -0
  97. package/dist/modules/customers/components/calendar/editor/RelatedToField.js.map +7 -0
  98. package/dist/modules/customers/components/calendar/editor/RepeatField.js +119 -0
  99. package/dist/modules/customers/components/calendar/editor/RepeatField.js.map +7 -0
  100. package/dist/modules/customers/components/calendar/editor/ScheduleSection.js +83 -0
  101. package/dist/modules/customers/components/calendar/editor/ScheduleSection.js.map +7 -0
  102. package/dist/modules/customers/components/calendar/editor/SegmentGroup.js +44 -0
  103. package/dist/modules/customers/components/calendar/editor/SegmentGroup.js.map +7 -0
  104. package/dist/modules/customers/components/calendar/editor/hooks.js +132 -0
  105. package/dist/modules/customers/components/calendar/editor/hooks.js.map +7 -0
  106. package/dist/modules/customers/components/calendar/editor/inputs.js +160 -0
  107. package/dist/modules/customers/components/calendar/editor/inputs.js.map +7 -0
  108. package/dist/modules/customers/components/calendar/editor/lookups.js +107 -0
  109. package/dist/modules/customers/components/calendar/editor/lookups.js.map +7 -0
  110. package/dist/modules/customers/components/calendar/types.js +30 -0
  111. package/dist/modules/customers/components/calendar/types.js.map +7 -0
  112. package/dist/modules/customers/components/calendar/useCalendarItems.js +131 -0
  113. package/dist/modules/customers/components/calendar/useCalendarItems.js.map +7 -0
  114. package/dist/modules/customers/components/calendar/useCalendarPreferences.js +41 -0
  115. package/dist/modules/customers/components/calendar/useCalendarPreferences.js.map +7 -0
  116. package/dist/modules/customers/lib/calendar/categories.js +28 -0
  117. package/dist/modules/customers/lib/calendar/categories.js.map +7 -0
  118. package/dist/modules/customers/lib/calendar/conflicts.js +72 -0
  119. package/dist/modules/customers/lib/calendar/conflicts.js.map +7 -0
  120. package/dist/modules/customers/lib/calendar/editorPayload.js +271 -0
  121. package/dist/modules/customers/lib/calendar/editorPayload.js.map +7 -0
  122. package/dist/modules/customers/lib/calendar/grid.js +48 -0
  123. package/dist/modules/customers/lib/calendar/grid.js.map +7 -0
  124. package/dist/modules/customers/lib/calendar/layout.js +39 -0
  125. package/dist/modules/customers/lib/calendar/layout.js.map +7 -0
  126. package/dist/modules/customers/lib/calendar/mapItem.js +79 -0
  127. package/dist/modules/customers/lib/calendar/mapItem.js.map +7 -0
  128. package/dist/modules/customers/lib/calendar/preferences.js +71 -0
  129. package/dist/modules/customers/lib/calendar/preferences.js.map +7 -0
  130. package/dist/modules/customers/lib/calendar/range.js +46 -0
  131. package/dist/modules/customers/lib/calendar/range.js.map +7 -0
  132. package/dist/modules/customers/lib/calendar/recurrence.js +113 -0
  133. package/dist/modules/customers/lib/calendar/recurrence.js.map +7 -0
  134. package/dist/modules/dashboards/services/widgetDataService.js +5 -5
  135. package/dist/modules/dashboards/services/widgetDataService.js.map +2 -2
  136. package/dist/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.js +61 -1
  137. package/dist/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.js.map +2 -2
  138. package/dist/modules/dictionaries/api/[dictionaryId]/entries/route.js +33 -1
  139. package/dist/modules/dictionaries/api/[dictionaryId]/entries/route.js.map +2 -2
  140. package/dist/modules/dictionaries/api/[dictionaryId]/route.js +61 -1
  141. package/dist/modules/dictionaries/api/[dictionaryId]/route.js.map +2 -2
  142. package/dist/modules/dictionaries/api/route.js +33 -1
  143. package/dist/modules/dictionaries/api/route.js.map +2 -2
  144. package/dist/modules/feature_toggles/lib/queries.js +36 -52
  145. package/dist/modules/feature_toggles/lib/queries.js.map +2 -2
  146. package/dist/modules/perspectives/api/[tableId]/[perspectiveId]/route.js +32 -1
  147. package/dist/modules/perspectives/api/[tableId]/[perspectiveId]/route.js.map +2 -2
  148. package/dist/modules/perspectives/api/[tableId]/roles/[roleId]/route.js +32 -1
  149. package/dist/modules/perspectives/api/[tableId]/roles/[roleId]/route.js.map +2 -2
  150. package/dist/modules/perspectives/api/[tableId]/route.js +73 -2
  151. package/dist/modules/perspectives/api/[tableId]/route.js.map +2 -2
  152. package/dist/modules/perspectives/services/perspectiveService.js +5 -3
  153. package/dist/modules/perspectives/services/perspectiveService.js.map +2 -2
  154. package/dist/modules/planner/api/access.js +9 -0
  155. package/dist/modules/planner/api/access.js.map +2 -2
  156. package/dist/modules/planner/api/availability-date-specific.js +22 -1
  157. package/dist/modules/planner/api/availability-date-specific.js.map +2 -2
  158. package/dist/modules/planner/api/availability-weekly.js +22 -1
  159. package/dist/modules/planner/api/availability-weekly.js.map +2 -2
  160. package/dist/modules/resources/api/resources/tags/assign/route.js +39 -0
  161. package/dist/modules/resources/api/resources/tags/assign/route.js.map +2 -2
  162. package/dist/modules/resources/api/resources/tags/unassign/route.js +39 -0
  163. package/dist/modules/resources/api/resources/tags/unassign/route.js.map +2 -2
  164. package/dist/modules/sales/api/settings/document-numbers/route.js +33 -1
  165. package/dist/modules/sales/api/settings/document-numbers/route.js.map +2 -2
  166. package/dist/modules/sales/api/settings/order-editing/route.js +35 -3
  167. package/dist/modules/sales/api/settings/order-editing/route.js.map +2 -2
  168. package/dist/modules/staff/api/leave-requests/accept/route.js +41 -0
  169. package/dist/modules/staff/api/leave-requests/accept/route.js.map +2 -2
  170. package/dist/modules/staff/api/leave-requests/reject/route.js +41 -0
  171. package/dist/modules/staff/api/leave-requests/reject/route.js.map +2 -2
  172. package/dist/modules/staff/api/team-members/self/route.js +40 -0
  173. package/dist/modules/staff/api/team-members/self/route.js.map +2 -2
  174. package/dist/modules/staff/api/team-members/tags/assign/route.js +41 -0
  175. package/dist/modules/staff/api/team-members/tags/assign/route.js.map +2 -2
  176. package/dist/modules/staff/api/team-members/tags/unassign/route.js +41 -0
  177. package/dist/modules/staff/api/team-members/tags/unassign/route.js.map +2 -2
  178. package/dist/modules/translations/api/[entityType]/[entityId]/route.js +61 -1
  179. package/dist/modules/translations/api/[entityType]/[entityId]/route.js.map +2 -2
  180. package/dist/modules/translations/api/context.js +7 -0
  181. package/dist/modules/translations/api/context.js.map +2 -2
  182. package/dist/modules/translations/api/put/locales.js +33 -1
  183. package/dist/modules/translations/api/put/locales.js.map +2 -2
  184. package/dist/modules/workflows/backend/definitions/visual-editor/page.js +6 -7
  185. package/dist/modules/workflows/backend/definitions/visual-editor/page.js.map +2 -2
  186. package/dist/modules/workflows/backend/instances/[id]/page.js.map +1 -1
  187. package/dist/modules/workflows/components/EdgeEditDialog.js.map +1 -1
  188. package/dist/modules/workflows/components/EdgeEditDialogCrudForm.js.map +1 -1
  189. package/dist/modules/workflows/components/NodeEditDialog.js.map +1 -1
  190. package/dist/modules/workflows/components/NodeEditDialogCrudForm.js.map +1 -1
  191. package/dist/modules/workflows/components/WorkflowGraph.js.map +1 -1
  192. package/dist/modules/workflows/components/WorkflowGraphImpl.js +17 -9
  193. package/dist/modules/workflows/components/WorkflowGraphImpl.js.map +2 -2
  194. package/dist/modules/workflows/components/mobile/MobileVisualEditor.js.map +1 -1
  195. package/dist/modules/workflows/frontend/checkout-demo/deriveCurrentStep.js +8 -0
  196. package/dist/modules/workflows/frontend/checkout-demo/deriveCurrentStep.js.map +7 -0
  197. package/dist/modules/workflows/frontend/checkout-demo/page.js +2 -20
  198. package/dist/modules/workflows/frontend/checkout-demo/page.js.map +2 -2
  199. package/dist/modules/workflows/lib/graph-utils.js +8 -0
  200. package/dist/modules/workflows/lib/graph-utils.js.map +2 -2
  201. package/generated/entities/catalog_product/index.ts +26 -0
  202. package/generated/entities/catalog_product_variant/index.ts +2 -0
  203. package/generated/entity-fields-registry.ts +28 -0
  204. package/package.json +7 -7
  205. package/src/modules/catalog/api/products/route.ts +123 -39
  206. package/src/modules/catalog/api/variants/route.ts +4 -0
  207. package/src/modules/catalog/backend/catalog/products/[id]/page.tsx +43 -0
  208. package/src/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.tsx +14 -0
  209. package/src/modules/catalog/backend/catalog/products/[productId]/variants/create/page.tsx +2 -0
  210. package/src/modules/catalog/backend/catalog/products/create/page.tsx +41 -0
  211. package/src/modules/catalog/commands/products.ts +307 -2
  212. package/src/modules/catalog/commands/variants.ts +90 -1
  213. package/src/modules/catalog/components/products/ProductComplianceSection.tsx +497 -0
  214. package/src/modules/catalog/components/products/VariantBuilder.tsx +43 -0
  215. package/src/modules/catalog/components/products/productForm.ts +262 -0
  216. package/src/modules/catalog/components/products/variantForm.ts +4 -0
  217. package/src/modules/catalog/data/entities.ts +97 -0
  218. package/src/modules/catalog/data/types.ts +36 -0
  219. package/src/modules/catalog/data/validators.ts +159 -3
  220. package/src/modules/catalog/i18n/de.json +64 -0
  221. package/src/modules/catalog/i18n/en.json +64 -0
  222. package/src/modules/catalog/i18n/es.json +64 -0
  223. package/src/modules/catalog/i18n/pl.json +64 -0
  224. package/src/modules/catalog/lib/gtin.ts +66 -0
  225. package/src/modules/catalog/migrations/.snapshot-open-mercato.json +457 -0
  226. package/src/modules/catalog/migrations/Migration20260611090000.ts +17 -0
  227. package/src/modules/catalog/translations.ts +1 -1
  228. package/src/modules/configs/api/cache/route.ts +72 -10
  229. package/src/modules/configs/components/CachePanel.tsx +59 -29
  230. package/src/modules/currencies/api/currencies/options/route.ts +22 -4
  231. package/src/modules/currencies/services/rateFetchingService.ts +63 -26
  232. package/src/modules/customer_accounts/backend/customer_accounts/users/[id]/page.tsx +18 -18
  233. package/src/modules/customer_accounts/backend/customer_accounts/users/page.tsx +1 -1
  234. package/src/modules/customer_accounts/i18n/de.json +1 -1
  235. package/src/modules/customer_accounts/i18n/en.json +1 -1
  236. package/src/modules/customer_accounts/i18n/es.json +1 -1
  237. package/src/modules/customer_accounts/i18n/pl.json +1 -1
  238. package/src/modules/customers/backend/calendar/page.meta.ts +23 -0
  239. package/src/modules/customers/backend/calendar/page.tsx +12 -0
  240. package/src/modules/customers/cli.ts +1 -0
  241. package/src/modules/customers/components/calendar/AgendaList.tsx +219 -0
  242. package/src/modules/customers/components/calendar/CalendarEventEditor.tsx +386 -0
  243. package/src/modules/customers/components/calendar/CalendarFooter.tsx +61 -0
  244. package/src/modules/customers/components/calendar/CalendarHeader.tsx +40 -0
  245. package/src/modules/customers/components/calendar/CalendarScreen.tsx +590 -0
  246. package/src/modules/customers/components/calendar/CalendarSettingsModal.tsx +207 -0
  247. package/src/modules/customers/components/calendar/CalendarTabs.tsx +63 -0
  248. package/src/modules/customers/components/calendar/CalendarToolbar.tsx +295 -0
  249. package/src/modules/customers/components/calendar/EventBlock.tsx +205 -0
  250. package/src/modules/customers/components/calendar/EventPeekPopover.tsx +100 -0
  251. package/src/modules/customers/components/calendar/MonthGrid.tsx +236 -0
  252. package/src/modules/customers/components/calendar/ShortcutsDialog.tsx +57 -0
  253. package/src/modules/customers/components/calendar/TimeGrid.tsx +510 -0
  254. package/src/modules/customers/components/calendar/UpcomingCards.tsx +220 -0
  255. package/src/modules/customers/components/calendar/editor/CategoryField.tsx +79 -0
  256. package/src/modules/customers/components/calendar/editor/LocationField.tsx +37 -0
  257. package/src/modules/customers/components/calendar/editor/PeopleField.tsx +148 -0
  258. package/src/modules/customers/components/calendar/editor/RelatedToField.tsx +242 -0
  259. package/src/modules/customers/components/calendar/editor/RepeatField.tsx +132 -0
  260. package/src/modules/customers/components/calendar/editor/ScheduleSection.tsx +110 -0
  261. package/src/modules/customers/components/calendar/editor/SegmentGroup.tsx +50 -0
  262. package/src/modules/customers/components/calendar/editor/hooks.ts +162 -0
  263. package/src/modules/customers/components/calendar/editor/inputs.tsx +158 -0
  264. package/src/modules/customers/components/calendar/editor/lookups.ts +160 -0
  265. package/src/modules/customers/components/calendar/types.ts +163 -0
  266. package/src/modules/customers/components/calendar/useCalendarItems.ts +162 -0
  267. package/src/modules/customers/components/calendar/useCalendarPreferences.ts +52 -0
  268. package/src/modules/customers/i18n/de.json +195 -0
  269. package/src/modules/customers/i18n/en.json +195 -0
  270. package/src/modules/customers/i18n/es.json +195 -0
  271. package/src/modules/customers/i18n/pl.json +195 -0
  272. package/src/modules/customers/lib/calendar/categories.ts +26 -0
  273. package/src/modules/customers/lib/calendar/conflicts.ts +117 -0
  274. package/src/modules/customers/lib/calendar/editorPayload.ts +388 -0
  275. package/src/modules/customers/lib/calendar/grid.ts +50 -0
  276. package/src/modules/customers/lib/calendar/layout.ts +47 -0
  277. package/src/modules/customers/lib/calendar/mapItem.ts +94 -0
  278. package/src/modules/customers/lib/calendar/preferences.ts +95 -0
  279. package/src/modules/customers/lib/calendar/range.ts +45 -0
  280. package/src/modules/customers/lib/calendar/recurrence.ts +137 -0
  281. package/src/modules/dashboards/services/widgetDataService.ts +8 -6
  282. package/src/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.ts +61 -1
  283. package/src/modules/dictionaries/api/[dictionaryId]/entries/route.ts +33 -1
  284. package/src/modules/dictionaries/api/[dictionaryId]/route.ts +65 -1
  285. package/src/modules/dictionaries/api/route.ts +36 -1
  286. package/src/modules/feature_toggles/lib/queries.ts +41 -60
  287. package/src/modules/perspectives/api/[tableId]/[perspectiveId]/route.ts +34 -1
  288. package/src/modules/perspectives/api/[tableId]/roles/[roleId]/route.ts +34 -1
  289. package/src/modules/perspectives/api/[tableId]/route.ts +107 -32
  290. package/src/modules/perspectives/services/perspectiveService.ts +9 -5
  291. package/src/modules/planner/api/access.ts +9 -0
  292. package/src/modules/planner/api/availability-date-specific.ts +22 -1
  293. package/src/modules/planner/api/availability-weekly.ts +22 -1
  294. package/src/modules/resources/api/resources/tags/assign/route.ts +42 -0
  295. package/src/modules/resources/api/resources/tags/unassign/route.ts +42 -0
  296. package/src/modules/sales/api/settings/document-numbers/route.ts +35 -1
  297. package/src/modules/sales/api/settings/order-editing/route.ts +37 -3
  298. package/src/modules/staff/api/leave-requests/accept/route.ts +45 -0
  299. package/src/modules/staff/api/leave-requests/reject/route.ts +45 -0
  300. package/src/modules/staff/api/team-members/self/route.ts +43 -0
  301. package/src/modules/staff/api/team-members/tags/assign/route.ts +45 -0
  302. package/src/modules/staff/api/team-members/tags/unassign/route.ts +45 -0
  303. package/src/modules/translations/api/[entityType]/[entityId]/route.ts +65 -1
  304. package/src/modules/translations/api/context.ts +7 -0
  305. package/src/modules/translations/api/put/locales.ts +35 -1
  306. package/src/modules/workflows/backend/definitions/visual-editor/page.tsx +11 -9
  307. package/src/modules/workflows/backend/instances/[id]/page.tsx +1 -1
  308. package/src/modules/workflows/components/EdgeEditDialog.tsx +1 -1
  309. package/src/modules/workflows/components/EdgeEditDialogCrudForm.tsx +1 -1
  310. package/src/modules/workflows/components/NodeEditDialog.tsx +1 -1
  311. package/src/modules/workflows/components/NodeEditDialogCrudForm.tsx +1 -1
  312. package/src/modules/workflows/components/WorkflowGraph.tsx +2 -2
  313. package/src/modules/workflows/components/WorkflowGraphImpl.tsx +27 -13
  314. package/src/modules/workflows/components/mobile/MobileVisualEditor.tsx +3 -3
  315. package/src/modules/workflows/frontend/checkout-demo/deriveCurrentStep.ts +12 -0
  316. package/src/modules/workflows/frontend/checkout-demo/page.tsx +3 -23
  317. package/src/modules/workflows/lib/graph-utils.ts +21 -1
@@ -0,0 +1,388 @@
1
+ import type { CalendarItem } from '../../components/calendar/types'
2
+ import { parseRecurrenceRule } from './recurrence'
3
+
4
+ export type EditorKind = 'meeting' | 'call' | 'email' | 'note' | 'event' | 'task'
5
+
6
+ export const EDITOR_KINDS: EditorKind[] = ['meeting', 'call', 'email', 'note', 'event', 'task']
7
+
8
+ export type EditorDateLabel = 'starts' | 'when' | 'sent' | 'logged' | 'due'
9
+
10
+ export type EditorPeopleMode = 'attendees' | 'participants' | 'to' | 'assignee'
11
+
12
+ export type EditorKindConfig = {
13
+ dateLabel: EditorDateLabel
14
+ hasEnd: boolean
15
+ hasAllDay: boolean
16
+ hasRepeat: boolean
17
+ location: 'location' | 'phoneLink' | null
18
+ people: EditorPeopleMode | null
19
+ hasPriority: boolean
20
+ }
21
+
22
+ export const KIND_CONFIG: Record<EditorKind, EditorKindConfig> = {
23
+ meeting: { dateLabel: 'starts', hasEnd: true, hasAllDay: true, hasRepeat: true, location: 'location', people: 'attendees', hasPriority: false },
24
+ call: { dateLabel: 'when', hasEnd: false, hasAllDay: true, hasRepeat: true, location: 'phoneLink', people: 'participants', hasPriority: false },
25
+ email: { dateLabel: 'sent', hasEnd: false, hasAllDay: true, hasRepeat: true, location: null, people: 'to', hasPriority: false },
26
+ note: { dateLabel: 'logged', hasEnd: false, hasAllDay: false, hasRepeat: false, location: null, people: null, hasPriority: false },
27
+ event: { dateLabel: 'starts', hasEnd: true, hasAllDay: true, hasRepeat: true, location: 'location', people: 'attendees', hasPriority: false },
28
+ task: { dateLabel: 'due', hasEnd: false, hasAllDay: true, hasRepeat: true, location: null, people: 'assignee', hasPriority: true },
29
+ }
30
+
31
+ const KIND_BY_INTERACTION_TYPE: Record<string, EditorKind> = {
32
+ meeting: 'meeting',
33
+ call: 'call',
34
+ 'video-call': 'call',
35
+ email: 'email',
36
+ note: 'note',
37
+ event: 'event',
38
+ webinar: 'event',
39
+ task: 'task',
40
+ todo: 'task',
41
+ deadline: 'task',
42
+ }
43
+
44
+ export function editorKindOfInteractionType(interactionType: string): EditorKind {
45
+ return KIND_BY_INTERACTION_TYPE[interactionType] ?? 'meeting'
46
+ }
47
+
48
+ export type EditorCategoryOption = { value: string; label: string }
49
+
50
+ /**
51
+ * Builds the editor's Category quick-pick options from the tenant dictionary and
52
+ * the user's calendar preferences:
53
+ * - `surfacedTypes` (settings "Activity Types") filters which dictionary types are
54
+ * offered — a non-empty list keeps only matching labels; empty means "show all".
55
+ * - `eventCategories` (settings "Event Categories") add custom quick-pick labels.
56
+ * Dictionary entries keep their canonical key as the value (so category→tab/tint
57
+ * mapping is preserved); custom labels use the label as the value.
58
+ */
59
+ export function buildEditorCategoryOptions(params: {
60
+ typeLabels: Record<string, string>
61
+ surfacedTypes: string[]
62
+ eventCategories: string[]
63
+ selectedValue: string
64
+ selectedFallbackLabel: string
65
+ }): EditorCategoryOption[] {
66
+ const { typeLabels, surfacedTypes, eventCategories, selectedValue, selectedFallbackLabel } = params
67
+ const dictionaryOptions: EditorCategoryOption[] = Object.entries(typeLabels).map(([value, label]) => ({ value, label }))
68
+ const surfacedSet = new Set(surfacedTypes)
69
+ const filtered =
70
+ surfacedTypes.length > 0
71
+ ? dictionaryOptions.filter((option) => surfacedSet.has(option.label))
72
+ : dictionaryOptions
73
+ const options = [...filtered]
74
+ const knownLabels = new Set(options.map((option) => option.label))
75
+ const knownValues = new Set(options.map((option) => option.value))
76
+ const customLabels: string[] = []
77
+ for (const label of [...eventCategories, ...surfacedTypes]) {
78
+ const trimmed = label.trim()
79
+ if (!trimmed || knownLabels.has(trimmed) || customLabels.includes(trimmed)) continue
80
+ customLabels.push(trimmed)
81
+ }
82
+ for (const label of customLabels) {
83
+ options.push({ value: label, label })
84
+ knownValues.add(label)
85
+ }
86
+ if (!knownValues.has(selectedValue)) {
87
+ options.unshift({ value: selectedValue, label: typeLabels[selectedValue] ?? selectedFallbackLabel })
88
+ }
89
+ return options
90
+ }
91
+
92
+ export type EditorRepeatFreq = 'none' | 'daily' | 'weekly'
93
+
94
+ export type EditorRepeatEndType = 'never' | 'date' | 'count'
95
+
96
+ export type EditorPriority = 'low' | 'medium' | 'high'
97
+
98
+ export const PRIORITY_NUMBER: Record<EditorPriority, number> = { low: 10, medium: 50, high: 90 }
99
+
100
+ export function priorityFromNumber(value: number | null | undefined): EditorPriority {
101
+ if (typeof value !== 'number' || Number.isNaN(value)) return 'medium'
102
+ if (value <= 33) return 'low'
103
+ if (value <= 66) return 'medium'
104
+ return 'high'
105
+ }
106
+
107
+ export type EditorRelatedTo = {
108
+ id: string
109
+ kind: 'person' | 'company' | 'unknown'
110
+ label: string
111
+ }
112
+
113
+ export type EditorParticipant = {
114
+ userId: string
115
+ name: string
116
+ email?: string
117
+ isCustomer: boolean
118
+ }
119
+
120
+ export type EditorFormState = {
121
+ kind: EditorKind
122
+ title: string
123
+ relatedTo: EditorRelatedTo | null
124
+ dealId: string | null
125
+ dealLabel: string | null
126
+ allDay: boolean
127
+ date: string
128
+ startTime: string
129
+ endDate: string
130
+ endTime: string
131
+ repeatFreq: EditorRepeatFreq
132
+ repeatDays: boolean[]
133
+ repeatEndType: EditorRepeatEndType
134
+ repeatCount: number
135
+ repeatUntilDate: string
136
+ category: string | null
137
+ location: string
138
+ participants: EditorParticipant[]
139
+ assigneeUserId: string | null
140
+ assigneeName: string | null
141
+ priority: EditorPriority
142
+ description: string
143
+ status: 'planned' | 'done' | 'canceled'
144
+ }
145
+
146
+ const MO_FIRST_DAY_TOKENS = ['MO', 'TU', 'WE', 'TH', 'FR', 'SA', 'SU'] as const
147
+
148
+ function padDatePart(value: number): string {
149
+ return String(value).padStart(2, '0')
150
+ }
151
+
152
+ export function formatLocalDateInput(date: Date): string {
153
+ return `${date.getFullYear()}-${padDatePart(date.getMonth() + 1)}-${padDatePart(date.getDate())}`
154
+ }
155
+
156
+ export function formatLocalTimeInput(date: Date): string {
157
+ return `${padDatePart(date.getHours())}:${padDatePart(date.getMinutes())}`
158
+ }
159
+
160
+ function moFirstIndexOfDate(date: Date): number {
161
+ return (date.getDay() + 6) % 7
162
+ }
163
+
164
+ export function defaultRepeatDaysForDateInput(dateInput: string): boolean[] {
165
+ const parsed = new Date(`${dateInput}T00:00:00`)
166
+ return defaultRepeatDays(Number.isNaN(parsed.getTime()) ? new Date() : parsed)
167
+ }
168
+
169
+ function defaultRepeatDays(start: Date): boolean[] {
170
+ const days = [false, false, false, false, false, false, false]
171
+ days[moFirstIndexOfDate(start)] = true
172
+ return days
173
+ }
174
+
175
+ /**
176
+ * Resolves the `ownerUserId` an interaction will actually be SAVED with, so the
177
+ * editor's conflict probe keys on the same owner the grid sees after save (see
178
+ * buildInteractionPayload + the create/update command):
179
+ * - Task (people === 'assignee'): owner = the assignee.
180
+ * - Other kinds: the payload omits owner, so edit preserves the existing owner
181
+ * and create is ownerless (null) — conflicts then come from participants only.
182
+ */
183
+ export function resolveSavedOwnerUserId(
184
+ config: EditorKindConfig,
185
+ form: EditorFormState,
186
+ isEdit: boolean,
187
+ existingOwnerUserId: string | null,
188
+ ): string | null {
189
+ if (config.people === 'assignee') return form.assigneeUserId ?? null
190
+ return isEdit ? existingOwnerUserId : null
191
+ }
192
+
193
+ export function createDefaultFormState(
194
+ defaultDate?: Date | null,
195
+ now: Date = new Date(),
196
+ range?: { start: Date; end: Date } | null,
197
+ ): EditorFormState {
198
+ let base: Date
199
+ let end: Date
200
+ if (range) {
201
+ base = new Date(range.start)
202
+ end = new Date(range.end)
203
+ } else {
204
+ base = defaultDate ? new Date(defaultDate) : new Date(now)
205
+ base.setHours(now.getHours(), 0, 0, 0)
206
+ base.setHours(base.getHours() + 1)
207
+ end = new Date(base.getTime() + 90 * 60_000)
208
+ }
209
+ return {
210
+ kind: 'meeting',
211
+ title: '',
212
+ relatedTo: null,
213
+ dealId: null,
214
+ dealLabel: null,
215
+ allDay: false,
216
+ date: formatLocalDateInput(base),
217
+ startTime: formatLocalTimeInput(base),
218
+ endDate: formatLocalDateInput(end),
219
+ endTime: formatLocalTimeInput(end),
220
+ repeatFreq: 'none',
221
+ repeatDays: defaultRepeatDays(base),
222
+ repeatEndType: 'never',
223
+ repeatCount: 8,
224
+ repeatUntilDate: '',
225
+ category: null,
226
+ location: '',
227
+ participants: [],
228
+ assigneeUserId: null,
229
+ assigneeName: null,
230
+ priority: 'medium',
231
+ description: '',
232
+ status: 'planned',
233
+ }
234
+ }
235
+
236
+ export function buildRecurrenceRule(state: EditorFormState): string | null {
237
+ if (state.repeatFreq === 'none') return null
238
+ let rule: string
239
+ if (state.repeatFreq === 'daily') {
240
+ rule = 'FREQ=DAILY'
241
+ } else {
242
+ const selected = MO_FIRST_DAY_TOKENS.filter((_, index) => state.repeatDays[index])
243
+ const tokens = selected.length > 0
244
+ ? selected
245
+ : [MO_FIRST_DAY_TOKENS[moFirstIndexOfDate(new Date(`${state.date}T00:00:00`))]]
246
+ rule = `FREQ=WEEKLY;BYDAY=${tokens.join(',')}`
247
+ }
248
+ if (state.repeatEndType === 'count') rule += `;COUNT=${state.repeatCount}`
249
+ if (state.repeatEndType === 'date' && state.repeatUntilDate) {
250
+ rule += `;UNTIL=${state.repeatUntilDate.replace(/-/g, '')}T235959Z`
251
+ }
252
+ return rule
253
+ }
254
+
255
+ export function computeDurationMinutes(state: EditorFormState): number | null {
256
+ const start = new Date(`${state.date}T${state.startTime}:00`)
257
+ const end = new Date(`${state.endDate}T${state.endTime}:00`)
258
+ if (Number.isNaN(start.getTime()) || Number.isNaN(end.getTime())) return null
259
+ const minutes = Math.round((end.getTime() - start.getTime()) / 60_000)
260
+ return minutes > 0 ? minutes : null
261
+ }
262
+
263
+ export type BuildPayloadOptions = { mode: 'create' | 'edit'; id?: string }
264
+
265
+ export function buildInteractionPayload(state: EditorFormState, options: BuildPayloadOptions): Record<string, unknown> {
266
+ const config = KIND_CONFIG[state.kind]
267
+ const time = state.allDay && config.hasAllDay ? '00:00' : state.startTime
268
+ const scheduledAt = new Date(`${state.date}T${time}:00`).toISOString()
269
+ const recurrenceRule = config.hasRepeat ? buildRecurrenceRule(state) : null
270
+ const payload: Record<string, unknown> = {
271
+ ...(options.mode === 'edit' && options.id ? { id: options.id } : {}),
272
+ entityId: state.relatedTo?.id ?? null,
273
+ dealId: state.dealId ?? null,
274
+ interactionType: state.category ?? state.kind,
275
+ title: state.title.trim(),
276
+ body: state.description.trim() || null,
277
+ status: options.mode === 'create' ? 'planned' : state.status,
278
+ date: state.date,
279
+ time,
280
+ scheduledAt,
281
+ durationMinutes: config.hasEnd && !(state.allDay && config.hasAllDay) ? computeDurationMinutes(state) : null,
282
+ allDay: config.hasAllDay ? state.allDay : null,
283
+ location: config.location ? state.location.trim() || null : null,
284
+ recurrenceRule,
285
+ recurrenceEnd:
286
+ recurrenceRule && state.repeatEndType === 'date' && state.repeatUntilDate
287
+ ? new Date(state.repeatUntilDate).toISOString()
288
+ : null,
289
+ participants:
290
+ config.people && config.people !== 'assignee' && state.participants.length > 0
291
+ ? state.participants.map((participant) => ({
292
+ userId: participant.userId,
293
+ name: participant.name,
294
+ email: participant.email,
295
+ status: participant.isCustomer ? 'customer' : 'pending',
296
+ }))
297
+ : null,
298
+ }
299
+ if (config.people === 'assignee') payload.ownerUserId = state.assigneeUserId ?? null
300
+ if (config.hasPriority) payload.priority = PRIORITY_NUMBER[state.priority]
301
+ return payload
302
+ }
303
+
304
+ function readUnknownString(value: unknown): string | null {
305
+ return typeof value === 'string' && value.length > 0 ? value : null
306
+ }
307
+
308
+ function readUnknownNumber(value: unknown): number | null {
309
+ return typeof value === 'number' && Number.isFinite(value) ? value : null
310
+ }
311
+
312
+ type ParsedRepeat = Pick<EditorFormState, 'repeatFreq' | 'repeatDays' | 'repeatEndType' | 'repeatCount' | 'repeatUntilDate'>
313
+
314
+ function parseRepeatFromRule(rawRule: unknown, start: Date): ParsedRepeat {
315
+ const fallback: ParsedRepeat = {
316
+ repeatFreq: 'none',
317
+ repeatDays: defaultRepeatDays(start),
318
+ repeatEndType: 'never',
319
+ repeatCount: 8,
320
+ repeatUntilDate: '',
321
+ }
322
+ const ruleText = readUnknownString(rawRule)
323
+ if (!ruleText) return fallback
324
+ const parsed = parseRecurrenceRule(ruleText)
325
+ if (!parsed) return fallback
326
+ const repeatDays = defaultRepeatDays(start)
327
+ if (parsed.byDay) {
328
+ repeatDays.fill(false)
329
+ for (const jsWeekday of parsed.byDay) repeatDays[(jsWeekday + 6) % 7] = true
330
+ }
331
+ let repeatEndType: EditorRepeatEndType = 'never'
332
+ let repeatCount = 8
333
+ let repeatUntilDate = ''
334
+ if (parsed.count !== null) {
335
+ repeatEndType = 'count'
336
+ repeatCount = parsed.count
337
+ } else if (parsed.until) {
338
+ repeatEndType = 'date'
339
+ repeatUntilDate = `${parsed.until.getUTCFullYear()}-${padDatePart(parsed.until.getUTCMonth() + 1)}-${padDatePart(parsed.until.getUTCDate())}`
340
+ }
341
+ return {
342
+ repeatFreq: parsed.freq === 'DAILY' ? 'daily' : 'weekly',
343
+ repeatDays,
344
+ repeatEndType,
345
+ repeatCount,
346
+ repeatUntilDate,
347
+ }
348
+ }
349
+
350
+ function parseParticipants(item: CalendarItem): EditorParticipant[] {
351
+ const rawParticipants = Array.isArray(item.raw.participants) ? item.raw.participants : []
352
+ const statusByUserId = new Map<string, string | null>()
353
+ for (const raw of rawParticipants) {
354
+ statusByUserId.set(raw.userId, readUnknownString((raw as Record<string, unknown>).status))
355
+ }
356
+ return item.participants.map((participant) => ({
357
+ userId: participant.userId,
358
+ name: participant.name ?? participant.email ?? participant.userId,
359
+ email: participant.email,
360
+ isCustomer: statusByUserId.get(participant.userId) === 'customer',
361
+ }))
362
+ }
363
+
364
+ export function parseItemToFormState(item: CalendarItem): EditorFormState {
365
+ const kind = editorKindOfInteractionType(item.interactionType)
366
+ const raw = item.raw as Record<string, unknown>
367
+ return {
368
+ kind,
369
+ title: item.title,
370
+ relatedTo: item.entityId ? { id: item.entityId, kind: 'unknown', label: '' } : null,
371
+ dealId: item.dealId,
372
+ dealLabel: null,
373
+ allDay: item.allDay,
374
+ date: formatLocalDateInput(item.start),
375
+ startTime: formatLocalTimeInput(item.start),
376
+ endDate: formatLocalDateInput(item.end),
377
+ endTime: formatLocalTimeInput(item.end),
378
+ ...parseRepeatFromRule(item.raw.recurrenceRule, item.start),
379
+ category: item.interactionType,
380
+ location: item.location ?? '',
381
+ participants: parseParticipants(item),
382
+ assigneeUserId: item.ownerUserId,
383
+ assigneeName: null,
384
+ priority: priorityFromNumber(readUnknownNumber(raw.priority)),
385
+ description: readUnknownString(raw.body) ?? '',
386
+ status: item.status,
387
+ }
388
+ }
@@ -0,0 +1,50 @@
1
+ export const DRAG_SNAP_MINUTES = 15
2
+ export const MIN_DRAG_DURATION_MINUTES = 30
3
+ const MINUTES_PER_DAY = 24 * 60
4
+
5
+ export function isWeekendDay(date: Date): boolean {
6
+ const weekday = date.getDay()
7
+ return weekday === 0 || weekday === 6
8
+ }
9
+
10
+ export function applyWeekendVisibility(days: Date[], showWeekends: boolean): Date[] {
11
+ if (showWeekends) return days
12
+ const workingDays = days.filter((day) => !isWeekendDay(day))
13
+ return workingDays.length > 0 ? workingDays : days
14
+ }
15
+
16
+ function clampMinutes(value: number): number {
17
+ if (value < 0) return 0
18
+ if (value > MINUTES_PER_DAY) return MINUTES_PER_DAY
19
+ return value
20
+ }
21
+
22
+ export function offsetYToMinutes(
23
+ offsetY: number,
24
+ hourHeightPx: number,
25
+ snapMinutes: number = DRAG_SNAP_MINUTES,
26
+ ): number {
27
+ if (hourHeightPx <= 0) return 0
28
+ const rawMinutes = (offsetY / hourHeightPx) * 60
29
+ const snapped = Math.round(rawMinutes / snapMinutes) * snapMinutes
30
+ return clampMinutes(snapped)
31
+ }
32
+
33
+ export type DragRange = { start: Date; end: Date }
34
+
35
+ export function buildDragRange(dayStart: Date, startMinutes: number, endMinutes: number): DragRange {
36
+ let lower = clampMinutes(Math.min(startMinutes, endMinutes))
37
+ let upper = clampMinutes(Math.max(startMinutes, endMinutes))
38
+ if (upper - lower < MIN_DRAG_DURATION_MINUTES) upper = lower + MIN_DRAG_DURATION_MINUTES
39
+ if (upper > MINUTES_PER_DAY) {
40
+ upper = MINUTES_PER_DAY
41
+ lower = Math.max(0, upper - MIN_DRAG_DURATION_MINUTES)
42
+ }
43
+ const start = new Date(dayStart)
44
+ start.setHours(0, 0, 0, 0)
45
+ start.setMinutes(lower)
46
+ const end = new Date(dayStart)
47
+ end.setHours(0, 0, 0, 0)
48
+ end.setMinutes(upper)
49
+ return { start, end }
50
+ }
@@ -0,0 +1,47 @@
1
+ import type { CalendarItem } from '../../components/calendar/types'
2
+
3
+ export type PackedCalendarItem = {
4
+ item: CalendarItem
5
+ column: number
6
+ columns: number
7
+ }
8
+
9
+ export function packOverlaps(dayItems: CalendarItem[]): PackedCalendarItem[] {
10
+ const sorted = [...dayItems].sort((first, second) => {
11
+ const startDelta = first.start.getTime() - second.start.getTime()
12
+ if (startDelta !== 0) return startDelta
13
+ return second.end.getTime() - first.end.getTime()
14
+ })
15
+
16
+ const result: PackedCalendarItem[] = []
17
+ let cluster: Array<{ item: CalendarItem; column: number }> = []
18
+ let columnEnds: number[] = []
19
+ let clusterEnd = Number.NEGATIVE_INFINITY
20
+
21
+ const flushCluster = () => {
22
+ const columns = columnEnds.length
23
+ for (const entry of cluster) {
24
+ result.push({ item: entry.item, column: entry.column, columns })
25
+ }
26
+ cluster = []
27
+ columnEnds = []
28
+ clusterEnd = Number.NEGATIVE_INFINITY
29
+ }
30
+
31
+ for (const item of sorted) {
32
+ const startTime = item.start.getTime()
33
+ if (cluster.length > 0 && startTime >= clusterEnd) flushCluster()
34
+ let column = columnEnds.findIndex((columnEnd) => columnEnd <= startTime)
35
+ if (column === -1) {
36
+ column = columnEnds.length
37
+ columnEnds.push(item.end.getTime())
38
+ } else {
39
+ columnEnds[column] = item.end.getTime()
40
+ }
41
+ cluster.push({ item, column })
42
+ clusterEnd = Math.max(clusterEnd, item.end.getTime())
43
+ }
44
+ flushCluster()
45
+
46
+ return result
47
+ }
@@ -0,0 +1,94 @@
1
+ import { addMinutes } from 'date-fns/addMinutes'
2
+ import { endOfDay } from 'date-fns/endOfDay'
3
+ import { startOfDay } from 'date-fns/startOfDay'
4
+ import type {
5
+ CalendarInteractionPayload,
6
+ CalendarItem,
7
+ CalendarItemStatus,
8
+ CalendarLocationKind,
9
+ CalendarParticipant,
10
+ CalendarPlatform,
11
+ } from '../../components/calendar/types'
12
+ import { categoryOf } from './categories'
13
+
14
+ const DEFAULT_DURATION_MINUTES = 30
15
+
16
+ function narrowStatus(status: string): CalendarItemStatus {
17
+ if (status === 'done') return 'done'
18
+ if (status === 'canceled') return 'canceled'
19
+ return 'planned'
20
+ }
21
+
22
+ export function detectPlatform(location: string | null): CalendarPlatform | null {
23
+ if (!location) return null
24
+ const normalized = location.toLowerCase()
25
+ if (normalized.includes('zoom.us') || normalized.includes('zoom')) return 'zoom'
26
+ if (normalized.includes('meet.google') || normalized.includes('on meet')) return 'meet'
27
+ if (normalized.includes('slack')) return 'slack'
28
+ if (normalized.includes('teams')) return 'teams'
29
+ return null
30
+ }
31
+
32
+ function detectLocationKind(location: string | null, platform: CalendarPlatform | null): CalendarLocationKind | null {
33
+ if (!location) return null
34
+ const normalized = location.trim().toLowerCase()
35
+ if (normalized.startsWith('http') || normalized.startsWith('www')) return 'url'
36
+ if (platform) return 'platform'
37
+ return 'venue'
38
+ }
39
+
40
+ function mapParticipants(payload: CalendarInteractionPayload): CalendarParticipant[] {
41
+ const participants = payload.participants
42
+ if (!Array.isArray(participants)) return []
43
+ const seen = new Set<string>()
44
+ const mapped: CalendarParticipant[] = []
45
+ for (const participant of participants) {
46
+ if (seen.has(participant.userId)) continue
47
+ seen.add(participant.userId)
48
+ const entry: CalendarParticipant = { userId: participant.userId }
49
+ if (typeof participant.name === 'string') entry.name = participant.name
50
+ if (typeof participant.email === 'string') entry.email = participant.email
51
+ mapped.push(entry)
52
+ }
53
+ return mapped
54
+ }
55
+
56
+ export function mapInteractionToCalendarItem(
57
+ payload: CalendarInteractionPayload,
58
+ typeColorByType: Record<string, string | null>,
59
+ ): CalendarItem | null {
60
+ const effectiveStartRaw = payload.occurredAt ?? payload.scheduledAt ?? null
61
+ if (!effectiveStartRaw) return null
62
+ const parsedStart = new Date(effectiveStartRaw)
63
+ if (Number.isNaN(parsedStart.getTime())) return null
64
+
65
+ const allDay = payload.allDay === true
66
+ const durationMinutes = payload.durationMinutes ?? DEFAULT_DURATION_MINUTES
67
+ const start = allDay ? startOfDay(parsedStart) : parsedStart
68
+ const end = allDay ? endOfDay(parsedStart) : addMinutes(parsedStart, durationMinutes)
69
+
70
+ const location = payload.location ?? null
71
+ const platform = detectPlatform(location)
72
+
73
+ return {
74
+ id: payload.id,
75
+ title: payload.title ?? '',
76
+ interactionType: payload.interactionType,
77
+ category: categoryOf(payload.interactionType),
78
+ status: narrowStatus(payload.status),
79
+ start,
80
+ end,
81
+ allDay,
82
+ location,
83
+ platform,
84
+ locationKind: detectLocationKind(location, platform),
85
+ participants: mapParticipants(payload),
86
+ ownerUserId: payload.ownerUserId ?? null,
87
+ entityId: payload.entityId ?? null,
88
+ dealId: payload.dealId ?? null,
89
+ color: payload.appearanceColor ?? typeColorByType[payload.interactionType] ?? null,
90
+ isRecurringOccurrence: false,
91
+ updatedAt: payload.updatedAt ?? null,
92
+ raw: payload,
93
+ }
94
+ }
@@ -0,0 +1,95 @@
1
+ // Whose overlaps the calendar flags as conflicts:
2
+ // - 'mine': only when the current user is an actor (owner/participant) in BOTH
3
+ // overlapping events — i.e. the current user is personally double-booked.
4
+ // - 'all': any two visible org events that share an actor, even when the current
5
+ // user is in neither (team-wide scheduling awareness).
6
+ export type ConflictScope = 'mine' | 'all'
7
+
8
+ export const CONFLICT_SCOPES: ConflictScope[] = ['mine', 'all']
9
+
10
+ export type CalendarPreferences = {
11
+ showWeekends: boolean
12
+ conflictWarnings: boolean
13
+ conflictScope: ConflictScope
14
+ showCrmActivities: boolean
15
+ aiSummaries: boolean
16
+ eventCategories: string[]
17
+ activityTypes: string[]
18
+ }
19
+
20
+ export const MAX_EVENT_CATEGORIES = 8
21
+ export const MAX_ACTIVITY_TYPES = 6
22
+
23
+ export const DEFAULT_CALENDAR_PREFERENCES: CalendarPreferences = {
24
+ showWeekends: false,
25
+ conflictWarnings: true,
26
+ conflictScope: 'mine',
27
+ showCrmActivities: true,
28
+ aiSummaries: true,
29
+ eventCategories: [],
30
+ activityTypes: [],
31
+ }
32
+
33
+ export const CALENDAR_PREFERENCES_STORAGE_PREFIX = 'om.customers.calendar.preferences.v1'
34
+
35
+ export function calendarPreferencesStorageKey(scopeKey: string): string {
36
+ return `${CALENDAR_PREFERENCES_STORAGE_PREFIX}:${scopeKey}`
37
+ }
38
+
39
+ export function normalizeCalendarTagList(value: unknown, max: number): string[] {
40
+ if (!Array.isArray(value)) return []
41
+ const seen = new Set<string>()
42
+ const result: string[] = []
43
+ for (const entry of value) {
44
+ if (typeof entry !== 'string') continue
45
+ const trimmed = entry.trim()
46
+ if (!trimmed || seen.has(trimmed)) continue
47
+ seen.add(trimmed)
48
+ result.push(trimmed)
49
+ if (result.length >= max) break
50
+ }
51
+ return result
52
+ }
53
+
54
+ function readConflictScope(value: unknown, fallback: ConflictScope): ConflictScope {
55
+ return value === 'mine' || value === 'all' ? value : fallback
56
+ }
57
+
58
+ export function mergeCalendarPreferences(stored: unknown): CalendarPreferences {
59
+ if (!stored || typeof stored !== 'object') return { ...DEFAULT_CALENDAR_PREFERENCES }
60
+ const record = stored as Record<string, unknown>
61
+ const readBoolean = (key: keyof CalendarPreferences, fallback: boolean): boolean =>
62
+ typeof record[key] === 'boolean' ? (record[key] as boolean) : fallback
63
+ return {
64
+ showWeekends: readBoolean('showWeekends', DEFAULT_CALENDAR_PREFERENCES.showWeekends),
65
+ conflictWarnings: readBoolean('conflictWarnings', DEFAULT_CALENDAR_PREFERENCES.conflictWarnings),
66
+ conflictScope: readConflictScope(record.conflictScope, DEFAULT_CALENDAR_PREFERENCES.conflictScope),
67
+ showCrmActivities: readBoolean('showCrmActivities', DEFAULT_CALENDAR_PREFERENCES.showCrmActivities),
68
+ aiSummaries: readBoolean('aiSummaries', DEFAULT_CALENDAR_PREFERENCES.aiSummaries),
69
+ eventCategories: normalizeCalendarTagList(record.eventCategories, MAX_EVENT_CATEGORIES),
70
+ activityTypes: normalizeCalendarTagList(record.activityTypes, MAX_ACTIVITY_TYPES),
71
+ }
72
+ }
73
+
74
+ export function parseStoredCalendarPreferences(raw: string | null): CalendarPreferences {
75
+ if (!raw) return { ...DEFAULT_CALENDAR_PREFERENCES }
76
+ try {
77
+ return mergeCalendarPreferences(JSON.parse(raw))
78
+ } catch {
79
+ return { ...DEFAULT_CALENDAR_PREFERENCES }
80
+ }
81
+ }
82
+
83
+ export function calendarPreferencesEqual(first: CalendarPreferences, second: CalendarPreferences): boolean {
84
+ return (
85
+ first.showWeekends === second.showWeekends &&
86
+ first.conflictWarnings === second.conflictWarnings &&
87
+ first.conflictScope === second.conflictScope &&
88
+ first.showCrmActivities === second.showCrmActivities &&
89
+ first.aiSummaries === second.aiSummaries &&
90
+ first.eventCategories.length === second.eventCategories.length &&
91
+ first.eventCategories.every((value, index) => value === second.eventCategories[index]) &&
92
+ first.activityTypes.length === second.activityTypes.length &&
93
+ first.activityTypes.every((value, index) => value === second.activityTypes[index])
94
+ )
95
+ }