@open-mercato/core 0.10.1-develop.7717.1.2befe9c634 → 0.10.1-develop.7721.1.b5c4a00a68

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 (384) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/dist/generated/entities/staff_support_routing_rule/index.js +21 -0
  3. package/dist/generated/entities/staff_support_routing_rule/index.js.map +7 -0
  4. package/dist/generated/entities/staff_support_ticket/index.js +37 -0
  5. package/dist/generated/entities/staff_support_ticket/index.js.map +7 -0
  6. package/dist/generated/entities/staff_time_project/index.js +2 -0
  7. package/dist/generated/entities/staff_time_project/index.js.map +2 -2
  8. package/dist/generated/entities/staff_time_project_member/index.js +2 -0
  9. package/dist/generated/entities/staff_time_project_member/index.js.map +2 -2
  10. package/dist/generated/entities/staff_time_task/index.js +4 -0
  11. package/dist/generated/entities/staff_time_task/index.js.map +2 -2
  12. package/dist/generated/entities/staff_time_task_comment/index.js +9 -1
  13. package/dist/generated/entities/staff_time_task_comment/index.js.map +2 -2
  14. package/dist/generated/entities.ids.generated.js +2 -0
  15. package/dist/generated/entities.ids.generated.js.map +2 -2
  16. package/dist/generated/entity-fields-registry.js +38 -0
  17. package/dist/generated/entity-fields-registry.js.map +2 -2
  18. package/dist/helpers/integration/auth.js +1 -0
  19. package/dist/helpers/integration/auth.js.map +2 -2
  20. package/dist/helpers/integration/communicationChannelsFixtures.js +20 -0
  21. package/dist/helpers/integration/communicationChannelsFixtures.js.map +2 -2
  22. package/dist/modules/catalog/api/bulk-delete/route.js +12 -2
  23. package/dist/modules/catalog/api/bulk-delete/route.js.map +2 -2
  24. package/dist/modules/communication_channels/api/post/test-seed/route.js +17 -3
  25. package/dist/modules/communication_channels/api/post/test-seed/route.js.map +2 -2
  26. package/dist/modules/communication_channels/lib/test-seed.js +38 -5
  27. package/dist/modules/communication_channels/lib/test-seed.js.map +2 -2
  28. package/dist/modules/messages/commands/messages.js +2 -3
  29. package/dist/modules/messages/commands/messages.js.map +2 -2
  30. package/dist/modules/portal/frontend/[orgSlug]/portal/dashboard/page.js +2 -2
  31. package/dist/modules/portal/frontend/[orgSlug]/portal/dashboard/page.js.map +2 -2
  32. package/dist/modules/staff/api/portal/support-tickets/[id]/attachments/route.js +299 -0
  33. package/dist/modules/staff/api/portal/support-tickets/[id]/attachments/route.js.map +7 -0
  34. package/dist/modules/staff/api/portal/support-tickets/[id]/messages/route.js +91 -0
  35. package/dist/modules/staff/api/portal/support-tickets/[id]/messages/route.js.map +7 -0
  36. package/dist/modules/staff/api/portal/support-tickets/[id]/route.js +146 -0
  37. package/dist/modules/staff/api/portal/support-tickets/[id]/route.js.map +7 -0
  38. package/dist/modules/staff/api/portal/support-tickets/options/route.js +60 -0
  39. package/dist/modules/staff/api/portal/support-tickets/options/route.js.map +7 -0
  40. package/dist/modules/staff/api/portal/support-tickets/route.js +220 -0
  41. package/dist/modules/staff/api/portal/support-tickets/route.js.map +7 -0
  42. package/dist/modules/staff/api/timesheets/settings/route.js +7 -1
  43. package/dist/modules/staff/api/timesheets/settings/route.js.map +2 -2
  44. package/dist/modules/staff/api/timesheets/support-routing-rules/fallback-project/route.js +122 -0
  45. package/dist/modules/staff/api/timesheets/support-routing-rules/fallback-project/route.js.map +7 -0
  46. package/dist/modules/staff/api/timesheets/support-routing-rules/preview/route.js +180 -0
  47. package/dist/modules/staff/api/timesheets/support-routing-rules/preview/route.js.map +7 -0
  48. package/dist/modules/staff/api/timesheets/support-routing-rules/route.js +147 -0
  49. package/dist/modules/staff/api/timesheets/support-routing-rules/route.js.map +7 -0
  50. package/dist/modules/staff/api/timesheets/support-tickets/options/route.js +133 -0
  51. package/dist/modules/staff/api/timesheets/support-tickets/options/route.js.map +7 -0
  52. package/dist/modules/staff/api/timesheets/tasks/[id]/claim/route.js +33 -0
  53. package/dist/modules/staff/api/timesheets/tasks/[id]/claim/route.js.map +7 -0
  54. package/dist/modules/staff/api/timesheets/tasks/[id]/comments/route.js +25 -7
  55. package/dist/modules/staff/api/timesheets/tasks/[id]/comments/route.js.map +2 -2
  56. package/dist/modules/staff/api/timesheets/tasks/[id]/release/route.js +32 -0
  57. package/dist/modules/staff/api/timesheets/tasks/[id]/release/route.js.map +7 -0
  58. package/dist/modules/staff/api/timesheets/tasks/route.js +94 -47
  59. package/dist/modules/staff/api/timesheets/tasks/route.js.map +2 -2
  60. package/dist/modules/staff/api/timesheets/time-projects/[id]/employees/route.js +3 -0
  61. package/dist/modules/staff/api/timesheets/time-projects/[id]/employees/route.js.map +2 -2
  62. package/dist/modules/staff/api/timesheets/time-projects/route.js +3 -0
  63. package/dist/modules/staff/api/timesheets/time-projects/route.js.map +2 -2
  64. package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/edit/page.js +1 -0
  65. package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/edit/page.js.map +2 -2
  66. package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js +54 -4
  67. package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js.map +2 -2
  68. package/dist/modules/staff/backend/staff/time-tracking/projects/projectFormConfig.js +21 -5
  69. package/dist/modules/staff/backend/staff/time-tracking/projects/projectFormConfig.js.map +2 -2
  70. package/dist/modules/staff/backend/staff/time-tracking/settings/page.js +319 -254
  71. package/dist/modules/staff/backend/staff/time-tracking/settings/page.js.map +2 -2
  72. package/dist/modules/staff/backend/staff/time-tracking/support/page.js +363 -0
  73. package/dist/modules/staff/backend/staff/time-tracking/support/page.js.map +7 -0
  74. package/dist/modules/staff/backend/staff/time-tracking/support/page.meta.js +18 -0
  75. package/dist/modules/staff/backend/staff/time-tracking/support/page.meta.js.map +7 -0
  76. package/dist/modules/staff/cli.js +27 -0
  77. package/dist/modules/staff/cli.js.map +2 -2
  78. package/dist/modules/staff/commands/index.js +3 -0
  79. package/dist/modules/staff/commands/index.js.map +2 -2
  80. package/dist/modules/staff/commands/shared.js +10 -1
  81. package/dist/modules/staff/commands/shared.js.map +2 -2
  82. package/dist/modules/staff/commands/support-routing-rules.js +505 -0
  83. package/dist/modules/staff/commands/support-routing-rules.js.map +7 -0
  84. package/dist/modules/staff/commands/support-tickets.js +513 -0
  85. package/dist/modules/staff/commands/support-tickets.js.map +7 -0
  86. package/dist/modules/staff/commands/timesheets-projects.js +72 -14
  87. package/dist/modules/staff/commands/timesheets-projects.js.map +2 -2
  88. package/dist/modules/staff/commands/timesheets-task-assignment.js +270 -0
  89. package/dist/modules/staff/commands/timesheets-task-assignment.js.map +7 -0
  90. package/dist/modules/staff/commands/timesheets-task-comments.js +58 -12
  91. package/dist/modules/staff/commands/timesheets-task-comments.js.map +2 -2
  92. package/dist/modules/staff/commands/timesheets-tasks.js +91 -9
  93. package/dist/modules/staff/commands/timesheets-tasks.js.map +3 -3
  94. package/dist/modules/staff/data/enrichers.js +141 -0
  95. package/dist/modules/staff/data/enrichers.js.map +2 -2
  96. package/dist/modules/staff/data/entities.js +143 -2
  97. package/dist/modules/staff/data/entities.js.map +2 -2
  98. package/dist/modules/staff/data/extensions.js +8 -0
  99. package/dist/modules/staff/data/extensions.js.map +2 -2
  100. package/dist/modules/staff/data/validators.js +34 -5
  101. package/dist/modules/staff/data/validators.js.map +2 -2
  102. package/dist/modules/staff/encryption.js +6 -0
  103. package/dist/modules/staff/encryption.js.map +2 -2
  104. package/dist/modules/staff/events.js +25 -0
  105. package/dist/modules/staff/events.js.map +2 -2
  106. package/dist/modules/staff/events.payloads.js +54 -0
  107. package/dist/modules/staff/events.payloads.js.map +2 -2
  108. package/dist/modules/staff/extension-points.js +1 -0
  109. package/dist/modules/staff/extension-points.js.map +2 -2
  110. package/dist/modules/staff/frontend/[orgSlug]/portal/support/[id]/page.js +468 -0
  111. package/dist/modules/staff/frontend/[orgSlug]/portal/support/[id]/page.js.map +7 -0
  112. package/dist/modules/staff/frontend/[orgSlug]/portal/support/[id]/page.meta.js +12 -0
  113. package/dist/modules/staff/frontend/[orgSlug]/portal/support/[id]/page.meta.js.map +7 -0
  114. package/dist/modules/staff/frontend/[orgSlug]/portal/support/new/page.js +316 -0
  115. package/dist/modules/staff/frontend/[orgSlug]/portal/support/new/page.js.map +7 -0
  116. package/dist/modules/staff/frontend/[orgSlug]/portal/support/new/page.meta.js +12 -0
  117. package/dist/modules/staff/frontend/[orgSlug]/portal/support/new/page.meta.js.map +7 -0
  118. package/dist/modules/staff/frontend/[orgSlug]/portal/support/page.js +190 -0
  119. package/dist/modules/staff/frontend/[orgSlug]/portal/support/page.js.map +7 -0
  120. package/dist/modules/staff/frontend/[orgSlug]/portal/support/page.meta.js +19 -0
  121. package/dist/modules/staff/frontend/[orgSlug]/portal/support/page.meta.js.map +7 -0
  122. package/dist/modules/staff/index.js +1 -1
  123. package/dist/modules/staff/index.js.map +2 -2
  124. package/dist/modules/staff/lib/crud.js +2 -0
  125. package/dist/modules/staff/lib/crud.js.map +2 -2
  126. package/dist/modules/staff/lib/support/attachments.js +36 -0
  127. package/dist/modules/staff/lib/support/attachments.js.map +7 -0
  128. package/dist/modules/staff/lib/support/dictionaries.js +87 -0
  129. package/dist/modules/staff/lib/support/dictionaries.js.map +7 -0
  130. package/dist/modules/staff/lib/support/notifications.js +111 -0
  131. package/dist/modules/staff/lib/support/notifications.js.map +7 -0
  132. package/dist/modules/staff/lib/support/portalAttachments.js +100 -0
  133. package/dist/modules/staff/lib/support/portalAttachments.js.map +7 -0
  134. package/dist/modules/staff/lib/support/portalBroadcast.js +117 -0
  135. package/dist/modules/staff/lib/support/portalBroadcast.js.map +7 -0
  136. package/dist/modules/staff/lib/support/portalRoute.js +99 -0
  137. package/dist/modules/staff/lib/support/portalRoute.js.map +7 -0
  138. package/dist/modules/staff/lib/support/portalStatus.js +12 -0
  139. package/dist/modules/staff/lib/support/portalStatus.js.map +7 -0
  140. package/dist/modules/staff/lib/support/portalTickets.js +148 -0
  141. package/dist/modules/staff/lib/support/portalTickets.js.map +7 -0
  142. package/dist/modules/staff/lib/support/routing.js +81 -0
  143. package/dist/modules/staff/lib/support/routing.js.map +7 -0
  144. package/dist/modules/staff/lib/support/routingRuleDraft.js +25 -0
  145. package/dist/modules/staff/lib/support/routingRuleDraft.js.map +7 -0
  146. package/dist/modules/staff/lib/support/seedDefaults.js +114 -0
  147. package/dist/modules/staff/lib/support/seedDefaults.js.map +7 -0
  148. package/dist/modules/staff/lib/support/supportActivity.js +26 -0
  149. package/dist/modules/staff/lib/support/supportActivity.js.map +7 -0
  150. package/dist/modules/staff/lib/support-ui/PortalSupportNotLinked.js +23 -0
  151. package/dist/modules/staff/lib/support-ui/PortalSupportNotLinked.js.map +7 -0
  152. package/dist/modules/staff/lib/support-ui/portalAttachmentValidation.js +112 -0
  153. package/dist/modules/staff/lib/support-ui/portalAttachmentValidation.js.map +7 -0
  154. package/dist/modules/staff/lib/support-ui/portalPendingUploads.js +45 -0
  155. package/dist/modules/staff/lib/support-ui/portalPendingUploads.js.map +7 -0
  156. package/dist/modules/staff/lib/support-ui/portalSupport.js +51 -0
  157. package/dist/modules/staff/lib/support-ui/portalSupport.js.map +7 -0
  158. package/dist/modules/staff/lib/time-tracking/access.js +3 -39
  159. package/dist/modules/staff/lib/time-tracking/access.js.map +2 -2
  160. package/dist/modules/staff/lib/time-tracking/listProjectAccess.js +54 -0
  161. package/dist/modules/staff/lib/time-tracking/listProjectAccess.js.map +7 -0
  162. package/dist/modules/staff/lib/time-tracking/portalRecipients.js +92 -2
  163. package/dist/modules/staff/lib/time-tracking/portalRecipients.js.map +2 -2
  164. package/dist/modules/staff/lib/time-tracking/projectAssignment.js +112 -0
  165. package/dist/modules/staff/lib/time-tracking/projectAssignment.js.map +7 -0
  166. package/dist/modules/staff/lib/time-tracking/settingKeys.js +14 -0
  167. package/dist/modules/staff/lib/time-tracking/settingKeys.js.map +2 -2
  168. package/dist/modules/staff/lib/time-tracking/settings.js +28 -1
  169. package/dist/modules/staff/lib/time-tracking/settings.js.map +2 -2
  170. package/dist/modules/staff/lib/time-tracking/taskAssignmentRoute.js +143 -0
  171. package/dist/modules/staff/lib/time-tracking/taskAssignmentRoute.js.map +7 -0
  172. package/dist/modules/staff/lib/time-tracking/workRecipients.js +92 -0
  173. package/dist/modules/staff/lib/time-tracking/workRecipients.js.map +7 -0
  174. package/dist/modules/staff/lib/time-tracking-ui/BoardFilterChips.js +32 -3
  175. package/dist/modules/staff/lib/time-tracking-ui/BoardFilterChips.js.map +2 -2
  176. package/dist/modules/staff/lib/time-tracking-ui/KanbanBoard.js +29 -0
  177. package/dist/modules/staff/lib/time-tracking-ui/KanbanBoard.js.map +2 -2
  178. package/dist/modules/staff/lib/time-tracking-ui/KanbanCard.js +4 -0
  179. package/dist/modules/staff/lib/time-tracking-ui/KanbanCard.js.map +2 -2
  180. package/dist/modules/staff/lib/time-tracking-ui/ProjectFormSections.js +141 -1
  181. package/dist/modules/staff/lib/time-tracking-ui/ProjectFormSections.js.map +2 -2
  182. package/dist/modules/staff/lib/time-tracking-ui/SupportAttachmentsSection.js +153 -0
  183. package/dist/modules/staff/lib/time-tracking-ui/SupportAttachmentsSection.js.map +7 -0
  184. package/dist/modules/staff/lib/time-tracking-ui/SupportRequestPanel.js +67 -0
  185. package/dist/modules/staff/lib/time-tracking-ui/SupportRequestPanel.js.map +7 -0
  186. package/dist/modules/staff/lib/time-tracking-ui/TaskBoardScreen.js +3 -1
  187. package/dist/modules/staff/lib/time-tracking-ui/TaskBoardScreen.js.map +2 -2
  188. package/dist/modules/staff/lib/time-tracking-ui/TaskCommentThread.js +80 -17
  189. package/dist/modules/staff/lib/time-tracking-ui/TaskCommentThread.js.map +2 -2
  190. package/dist/modules/staff/lib/time-tracking-ui/TaskDrawer.js +230 -41
  191. package/dist/modules/staff/lib/time-tracking-ui/TaskDrawer.js.map +2 -2
  192. package/dist/modules/staff/lib/time-tracking-ui/boardDirectory.js +21 -1
  193. package/dist/modules/staff/lib/time-tracking-ui/boardDirectory.js.map +2 -2
  194. package/dist/modules/staff/lib/time-tracking-ui/boardFilters.js +16 -4
  195. package/dist/modules/staff/lib/time-tracking-ui/boardFilters.js.map +2 -2
  196. package/dist/modules/staff/lib/time-tracking-ui/kanbanBoardData.js +3 -1
  197. package/dist/modules/staff/lib/time-tracking-ui/kanbanBoardData.js.map +2 -2
  198. package/dist/modules/staff/lib/time-tracking-ui/projectDefaultAssignee.js +68 -0
  199. package/dist/modules/staff/lib/time-tracking-ui/projectDefaultAssignee.js.map +7 -0
  200. package/dist/modules/staff/lib/time-tracking-ui/settingsTabs.js +7 -0
  201. package/dist/modules/staff/lib/time-tracking-ui/settingsTabs.js.map +7 -0
  202. package/dist/modules/staff/lib/time-tracking-ui/supportTicketData.js +50 -0
  203. package/dist/modules/staff/lib/time-tracking-ui/supportTicketData.js.map +7 -0
  204. package/dist/modules/staff/lib/time-tracking-ui/taskDrawerData.js +13 -1
  205. package/dist/modules/staff/lib/time-tracking-ui/taskDrawerData.js.map +2 -2
  206. package/dist/modules/staff/lib/time-tracking-ui/timeTrackingSettingsForm.js +36 -2
  207. package/dist/modules/staff/lib/time-tracking-ui/timeTrackingSettingsForm.js.map +2 -2
  208. package/dist/modules/staff/migrations/Migration20261009180543_staff.js +42 -0
  209. package/dist/modules/staff/migrations/Migration20261009180543_staff.js.map +7 -0
  210. package/dist/modules/staff/notifications.js +92 -0
  211. package/dist/modules/staff/notifications.js.map +2 -2
  212. package/dist/modules/staff/setup.js +6 -2
  213. package/dist/modules/staff/setup.js.map +2 -2
  214. package/dist/modules/staff/subscribers/support-portal-comment-created.js +26 -0
  215. package/dist/modules/staff/subscribers/support-portal-comment-created.js.map +7 -0
  216. package/dist/modules/staff/subscribers/support-portal-comment-deleted.js +26 -0
  217. package/dist/modules/staff/subscribers/support-portal-comment-deleted.js.map +7 -0
  218. package/dist/modules/staff/subscribers/support-portal-comment-updated.js +26 -0
  219. package/dist/modules/staff/subscribers/support-portal-comment-updated.js.map +7 -0
  220. package/dist/modules/staff/subscribers/support-portal-status-column-updated.js +24 -0
  221. package/dist/modules/staff/subscribers/support-portal-status-column-updated.js.map +7 -0
  222. package/dist/modules/staff/subscribers/support-portal-task-status-changed.js +25 -0
  223. package/dist/modules/staff/subscribers/support-portal-task-status-changed.js.map +7 -0
  224. package/dist/modules/staff/subscribers/support-portal-task-updated.js +25 -0
  225. package/dist/modules/staff/subscribers/support-portal-task-updated.js.map +7 -0
  226. package/dist/modules/staff/subscribers/support-ticket-created-notification.js +25 -0
  227. package/dist/modules/staff/subscribers/support-ticket-created-notification.js.map +7 -0
  228. package/dist/modules/staff/subscribers/support-ticket-customer-replied-notification.js +25 -0
  229. package/dist/modules/staff/subscribers/support-ticket-customer-replied-notification.js.map +7 -0
  230. package/dist/modules/staff/subscribers/support-ticket-unnotified-clear.js +26 -0
  231. package/dist/modules/staff/subscribers/support-ticket-unnotified-clear.js.map +7 -0
  232. package/dist/modules/staff/subscribers/time-task-assignee-notification.js +83 -0
  233. package/dist/modules/staff/subscribers/time-task-assignee-notification.js.map +7 -0
  234. package/dist/modules/staff/widgets/injection/portal-support-tickets/widget.client.js +112 -0
  235. package/dist/modules/staff/widgets/injection/portal-support-tickets/widget.client.js.map +7 -0
  236. package/dist/modules/staff/widgets/injection/portal-support-tickets/widget.js +17 -0
  237. package/dist/modules/staff/widgets/injection/portal-support-tickets/widget.js.map +7 -0
  238. package/dist/modules/staff/widgets/injection/time-tracking-settings-support/widget.client.js +870 -0
  239. package/dist/modules/staff/widgets/injection/time-tracking-settings-support/widget.client.js.map +7 -0
  240. package/dist/modules/staff/widgets/injection/time-tracking-settings-support/widget.js +17 -0
  241. package/dist/modules/staff/widgets/injection/time-tracking-settings-support/widget.js.map +7 -0
  242. package/dist/modules/staff/widgets/injection-table.js +16 -1
  243. package/dist/modules/staff/widgets/injection-table.js.map +2 -2
  244. package/dist/modules/wms/components/backend/WmsLocationDetailPage.js +4 -18
  245. package/dist/modules/wms/components/backend/WmsLocationDetailPage.js.map +2 -2
  246. package/dist/modules/wms/components/backend/WmsLotDetailPage.js +4 -18
  247. package/dist/modules/wms/components/backend/WmsLotDetailPage.js.map +2 -2
  248. package/dist/modules/wms/components/backend/WmsSkuDetailPage.js +4 -18
  249. package/dist/modules/wms/components/backend/WmsSkuDetailPage.js.map +2 -2
  250. package/dist/modules/wms/lib/inventoryCsvExport.js +19 -0
  251. package/dist/modules/wms/lib/inventoryCsvExport.js.map +7 -0
  252. package/generated/entities/staff_support_routing_rule/index.ts +9 -0
  253. package/generated/entities/staff_support_ticket/index.ts +17 -0
  254. package/generated/entities/staff_time_project/index.ts +1 -0
  255. package/generated/entities/staff_time_project_member/index.ts +1 -0
  256. package/generated/entities/staff_time_task/index.ts +2 -0
  257. package/generated/entities/staff_time_task_comment/index.ts +4 -0
  258. package/generated/entities.ids.generated.ts +2 -0
  259. package/generated/entity-fields-registry.ts +38 -0
  260. package/package.json +8 -8
  261. package/src/helpers/integration/auth.ts +1 -1
  262. package/src/helpers/integration/communicationChannelsFixtures.ts +60 -2
  263. package/src/modules/catalog/api/bulk-delete/route.ts +12 -2
  264. package/src/modules/communication_channels/api/post/test-seed/route.ts +29 -12
  265. package/src/modules/communication_channels/lib/test-seed.ts +49 -10
  266. package/src/modules/messages/commands/messages.ts +8 -10
  267. package/src/modules/portal/frontend/[orgSlug]/portal/dashboard/page.tsx +2 -2
  268. package/src/modules/staff/AGENTS.md +22 -6
  269. package/src/modules/staff/api/portal/support-tickets/[id]/attachments/route.ts +327 -0
  270. package/src/modules/staff/api/portal/support-tickets/[id]/messages/route.ts +94 -0
  271. package/src/modules/staff/api/portal/support-tickets/[id]/route.ts +160 -0
  272. package/src/modules/staff/api/portal/support-tickets/options/route.ts +60 -0
  273. package/src/modules/staff/api/portal/support-tickets/route.ts +253 -0
  274. package/src/modules/staff/api/timesheets/settings/route.ts +7 -1
  275. package/src/modules/staff/api/timesheets/support-routing-rules/fallback-project/route.ts +131 -0
  276. package/src/modules/staff/api/timesheets/support-routing-rules/preview/route.ts +248 -0
  277. package/src/modules/staff/api/timesheets/support-routing-rules/route.ts +150 -0
  278. package/src/modules/staff/api/timesheets/support-tickets/options/route.ts +181 -0
  279. package/src/modules/staff/api/timesheets/tasks/[id]/claim/route.ts +31 -0
  280. package/src/modules/staff/api/timesheets/tasks/[id]/comments/route.ts +25 -7
  281. package/src/modules/staff/api/timesheets/tasks/[id]/release/route.ts +30 -0
  282. package/src/modules/staff/api/timesheets/tasks/route.ts +119 -67
  283. package/src/modules/staff/api/timesheets/time-projects/[id]/employees/route.ts +3 -0
  284. package/src/modules/staff/api/timesheets/time-projects/route.ts +3 -0
  285. package/src/modules/staff/backend/staff/time-tracking/projects/[id]/edit/page.tsx +1 -0
  286. package/src/modules/staff/backend/staff/time-tracking/projects/[id]/page.tsx +67 -2
  287. package/src/modules/staff/backend/staff/time-tracking/projects/projectFormConfig.ts +22 -3
  288. package/src/modules/staff/backend/staff/time-tracking/settings/page.tsx +405 -308
  289. package/src/modules/staff/backend/staff/time-tracking/support/page.meta.ts +14 -0
  290. package/src/modules/staff/backend/staff/time-tracking/support/page.tsx +428 -0
  291. package/src/modules/staff/cli.ts +28 -0
  292. package/src/modules/staff/commands/index.ts +3 -0
  293. package/src/modules/staff/commands/shared.ts +22 -1
  294. package/src/modules/staff/commands/support-routing-rules.ts +595 -0
  295. package/src/modules/staff/commands/support-tickets.ts +580 -0
  296. package/src/modules/staff/commands/timesheets-projects.ts +84 -13
  297. package/src/modules/staff/commands/timesheets-task-assignment.ts +319 -0
  298. package/src/modules/staff/commands/timesheets-task-comments.ts +70 -15
  299. package/src/modules/staff/commands/timesheets-tasks.ts +106 -15
  300. package/src/modules/staff/data/enrichers.ts +203 -0
  301. package/src/modules/staff/data/entities.ts +133 -2
  302. package/src/modules/staff/data/extensions.ts +9 -1
  303. package/src/modules/staff/data/validators.ts +36 -0
  304. package/src/modules/staff/encryption.ts +6 -0
  305. package/src/modules/staff/events.payloads.ts +87 -1
  306. package/src/modules/staff/events.ts +26 -0
  307. package/src/modules/staff/extension-points.ts +1 -0
  308. package/src/modules/staff/frontend/[orgSlug]/portal/support/[id]/page.meta.ts +10 -0
  309. package/src/modules/staff/frontend/[orgSlug]/portal/support/[id]/page.tsx +553 -0
  310. package/src/modules/staff/frontend/[orgSlug]/portal/support/new/page.meta.ts +10 -0
  311. package/src/modules/staff/frontend/[orgSlug]/portal/support/new/page.tsx +346 -0
  312. package/src/modules/staff/frontend/[orgSlug]/portal/support/page.meta.ts +17 -0
  313. package/src/modules/staff/frontend/[orgSlug]/portal/support/page.tsx +253 -0
  314. package/src/modules/staff/i18n/de.json +291 -0
  315. package/src/modules/staff/i18n/en.json +291 -0
  316. package/src/modules/staff/i18n/es.json +291 -0
  317. package/src/modules/staff/i18n/ko.json +291 -0
  318. package/src/modules/staff/i18n/pl.json +291 -0
  319. package/src/modules/staff/index.ts +1 -1
  320. package/src/modules/staff/lib/crud.ts +2 -1
  321. package/src/modules/staff/lib/support/attachments.ts +43 -0
  322. package/src/modules/staff/lib/support/dictionaries.ts +120 -0
  323. package/src/modules/staff/lib/support/notifications.ts +138 -0
  324. package/src/modules/staff/lib/support/portalAttachments.ts +140 -0
  325. package/src/modules/staff/lib/support/portalBroadcast.ts +161 -0
  326. package/src/modules/staff/lib/support/portalRoute.ts +108 -0
  327. package/src/modules/staff/lib/support/portalStatus.ts +15 -0
  328. package/src/modules/staff/lib/support/portalTickets.ts +233 -0
  329. package/src/modules/staff/lib/support/routing.ts +117 -0
  330. package/src/modules/staff/lib/support/routingRuleDraft.ts +45 -0
  331. package/src/modules/staff/lib/support/seedDefaults.ts +151 -0
  332. package/src/modules/staff/lib/support/supportActivity.ts +39 -0
  333. package/src/modules/staff/lib/support-ui/PortalSupportNotLinked.tsx +20 -0
  334. package/src/modules/staff/lib/support-ui/portalAttachmentValidation.ts +125 -0
  335. package/src/modules/staff/lib/support-ui/portalPendingUploads.ts +49 -0
  336. package/src/modules/staff/lib/support-ui/portalSupport.ts +116 -0
  337. package/src/modules/staff/lib/time-tracking/access.ts +2 -62
  338. package/src/modules/staff/lib/time-tracking/listProjectAccess.ts +78 -0
  339. package/src/modules/staff/lib/time-tracking/portalRecipients.ts +173 -5
  340. package/src/modules/staff/lib/time-tracking/projectAssignment.ts +172 -0
  341. package/src/modules/staff/lib/time-tracking/settingKeys.ts +11 -0
  342. package/src/modules/staff/lib/time-tracking/settings.ts +46 -1
  343. package/src/modules/staff/lib/time-tracking/taskAssignmentRoute.ts +165 -0
  344. package/src/modules/staff/lib/time-tracking/workRecipients.ts +183 -0
  345. package/src/modules/staff/lib/time-tracking-ui/BoardFilterChips.tsx +36 -1
  346. package/src/modules/staff/lib/time-tracking-ui/KanbanBoard.tsx +30 -0
  347. package/src/modules/staff/lib/time-tracking-ui/KanbanCard.tsx +9 -0
  348. package/src/modules/staff/lib/time-tracking-ui/ProjectFormSections.tsx +185 -6
  349. package/src/modules/staff/lib/time-tracking-ui/SupportAttachmentsSection.tsx +188 -0
  350. package/src/modules/staff/lib/time-tracking-ui/SupportRequestPanel.tsx +102 -0
  351. package/src/modules/staff/lib/time-tracking-ui/TaskBoardScreen.tsx +2 -0
  352. package/src/modules/staff/lib/time-tracking-ui/TaskCommentThread.tsx +105 -22
  353. package/src/modules/staff/lib/time-tracking-ui/TaskDrawer.tsx +278 -43
  354. package/src/modules/staff/lib/time-tracking-ui/boardDirectory.ts +21 -0
  355. package/src/modules/staff/lib/time-tracking-ui/boardFilters.ts +16 -2
  356. package/src/modules/staff/lib/time-tracking-ui/kanbanBoardData.ts +4 -0
  357. package/src/modules/staff/lib/time-tracking-ui/projectDefaultAssignee.ts +117 -0
  358. package/src/modules/staff/lib/time-tracking-ui/settingsTabs.ts +17 -0
  359. package/src/modules/staff/lib/time-tracking-ui/supportTicketData.ts +71 -0
  360. package/src/modules/staff/lib/time-tracking-ui/taskDrawerData.ts +15 -0
  361. package/src/modules/staff/lib/time-tracking-ui/timeTrackingSettingsForm.ts +48 -1
  362. package/src/modules/staff/migrations/.snapshot-open-mercato.json +714 -0
  363. package/src/modules/staff/migrations/Migration20261009180543_staff.ts +50 -0
  364. package/src/modules/staff/notifications.ts +92 -0
  365. package/src/modules/staff/setup.ts +6 -2
  366. package/src/modules/staff/subscribers/support-portal-comment-created.ts +35 -0
  367. package/src/modules/staff/subscribers/support-portal-comment-deleted.ts +35 -0
  368. package/src/modules/staff/subscribers/support-portal-comment-updated.ts +35 -0
  369. package/src/modules/staff/subscribers/support-portal-status-column-updated.ts +36 -0
  370. package/src/modules/staff/subscribers/support-portal-task-status-changed.ts +34 -0
  371. package/src/modules/staff/subscribers/support-portal-task-updated.ts +34 -0
  372. package/src/modules/staff/subscribers/support-ticket-created-notification.ts +27 -0
  373. package/src/modules/staff/subscribers/support-ticket-customer-replied-notification.ts +27 -0
  374. package/src/modules/staff/subscribers/support-ticket-unnotified-clear.ts +29 -0
  375. package/src/modules/staff/subscribers/time-task-assignee-notification.ts +102 -0
  376. package/src/modules/staff/widgets/injection/portal-support-tickets/widget.client.tsx +151 -0
  377. package/src/modules/staff/widgets/injection/portal-support-tickets/widget.ts +16 -0
  378. package/src/modules/staff/widgets/injection/time-tracking-settings-support/widget.client.tsx +1058 -0
  379. package/src/modules/staff/widgets/injection/time-tracking-settings-support/widget.ts +17 -0
  380. package/src/modules/staff/widgets/injection-table.ts +15 -0
  381. package/src/modules/wms/components/backend/WmsLocationDetailPage.tsx +4 -20
  382. package/src/modules/wms/components/backend/WmsLotDetailPage.tsx +4 -20
  383. package/src/modules/wms/components/backend/WmsSkuDetailPage.tsx +4 -19
  384. package/src/modules/wms/lib/inventoryCsvExport.ts +18 -0
@@ -0,0 +1,81 @@
1
+ import {
2
+ StaffSupportRoutingRule,
3
+ StaffTimeProject,
4
+ StaffTimeTaskStatus
5
+ } from "../../data/entities.js";
6
+ function resolveSupportRouteFromRules(rules, categoryValue, usableProjectIds) {
7
+ const categoryRule = rules.find((rule) => rule.categoryValue === categoryValue);
8
+ if (categoryRule && categoryRule.isActive && usableProjectIds.has(categoryRule.timeProjectId)) {
9
+ return { timeProjectId: categoryRule.timeProjectId, via: "category" };
10
+ }
11
+ const fallbackRule = rules.find((rule) => rule.categoryValue === null);
12
+ if (fallbackRule && usableProjectIds.has(fallbackRule.timeProjectId)) {
13
+ return { timeProjectId: fallbackRule.timeProjectId, via: "fallback" };
14
+ }
15
+ return null;
16
+ }
17
+ async function loadSupportRoutingRules(em, scope) {
18
+ const rows = await em.find(StaffSupportRoutingRule, {
19
+ tenantId: scope.tenantId,
20
+ organizationId: scope.organizationId,
21
+ deletedAt: null
22
+ });
23
+ return rows.map((row) => ({
24
+ id: row.id,
25
+ categoryValue: row.categoryValue ?? null,
26
+ timeProjectId: row.timeProjectId,
27
+ isActive: row.isActive,
28
+ updatedAt: row.updatedAt ?? null
29
+ }));
30
+ }
31
+ async function loadUsableSupportProjectIds(em, scope, projectIds) {
32
+ const unique = Array.from(new Set(projectIds));
33
+ if (unique.length === 0) return /* @__PURE__ */ new Set();
34
+ const projects = await em.find(
35
+ StaffTimeProject,
36
+ {
37
+ id: { $in: unique },
38
+ tenantId: scope.tenantId,
39
+ organizationId: scope.organizationId,
40
+ deletedAt: null,
41
+ status: { $in: ["active", "on_hold"] }
42
+ },
43
+ { fields: ["id"] }
44
+ );
45
+ if (projects.length === 0) return /* @__PURE__ */ new Set();
46
+ const defaults = await em.find(
47
+ StaffTimeTaskStatus,
48
+ {
49
+ timeProjectId: { $in: projects.map((project) => project.id) },
50
+ tenantId: scope.tenantId,
51
+ organizationId: scope.organizationId,
52
+ isDefault: true,
53
+ deletedAt: null
54
+ },
55
+ { fields: ["timeProjectId"] }
56
+ );
57
+ return new Set(defaults.map((status) => status.timeProjectId));
58
+ }
59
+ async function resolveSupportRoute(em, scope, categoryValue) {
60
+ const rules = await loadSupportRoutingRules(em, scope);
61
+ const usable = await loadUsableSupportProjectIds(em, scope, rules.map((rule) => rule.timeProjectId));
62
+ return resolveSupportRouteFromRules(rules, categoryValue, usable);
63
+ }
64
+ async function resolveRoutableCategoryValues(em, scope, categoryValues) {
65
+ const rules = await loadSupportRoutingRules(em, scope);
66
+ const usable = await loadUsableSupportProjectIds(em, scope, rules.map((rule) => rule.timeProjectId));
67
+ const routes = /* @__PURE__ */ new Map();
68
+ for (const value of categoryValues) {
69
+ const route = resolveSupportRouteFromRules(rules, value, usable);
70
+ if (route) routes.set(value, route);
71
+ }
72
+ return routes;
73
+ }
74
+ export {
75
+ loadSupportRoutingRules,
76
+ loadUsableSupportProjectIds,
77
+ resolveRoutableCategoryValues,
78
+ resolveSupportRoute,
79
+ resolveSupportRouteFromRules
80
+ };
81
+ //# sourceMappingURL=routing.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/modules/staff/lib/support/routing.ts"],
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport {\n StaffSupportRoutingRule,\n StaffTimeProject,\n StaffTimeTaskStatus,\n type StaffSupportRoutedVia,\n} from '../../data/entities'\n\nexport type SupportScope = {\n tenantId: string\n organizationId: string\n}\n\nexport type SupportRoutingRuleView = {\n id: string\n categoryValue: string | null\n timeProjectId: string\n isActive: boolean\n updatedAt?: Date | null\n}\n\nexport type SupportRoute = {\n timeProjectId: string\n via: StaffSupportRoutedVia\n}\n\nexport function resolveSupportRouteFromRules(\n rules: SupportRoutingRuleView[],\n categoryValue: string,\n usableProjectIds: ReadonlySet<string>,\n): SupportRoute | null {\n const categoryRule = rules.find((rule) => rule.categoryValue === categoryValue)\n if (categoryRule && categoryRule.isActive && usableProjectIds.has(categoryRule.timeProjectId)) {\n return { timeProjectId: categoryRule.timeProjectId, via: 'category' }\n }\n const fallbackRule = rules.find((rule) => rule.categoryValue === null)\n if (fallbackRule && usableProjectIds.has(fallbackRule.timeProjectId)) {\n return { timeProjectId: fallbackRule.timeProjectId, via: 'fallback' }\n }\n return null\n}\n\nexport async function loadSupportRoutingRules(\n em: EntityManager,\n scope: SupportScope,\n): Promise<SupportRoutingRuleView[]> {\n const rows = await em.find(StaffSupportRoutingRule, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n deletedAt: null,\n })\n return rows.map((row) => ({\n id: row.id,\n categoryValue: row.categoryValue ?? null,\n timeProjectId: row.timeProjectId,\n isActive: row.isActive,\n updatedAt: row.updatedAt ?? null,\n }))\n}\n\nexport async function loadUsableSupportProjectIds(\n em: EntityManager,\n scope: SupportScope,\n projectIds: string[],\n): Promise<Set<string>> {\n const unique = Array.from(new Set(projectIds))\n if (unique.length === 0) return new Set()\n const projects = await em.find(\n StaffTimeProject,\n {\n id: { $in: unique },\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n deletedAt: null,\n status: { $in: ['active', 'on_hold'] },\n },\n { fields: ['id'] },\n )\n if (projects.length === 0) return new Set()\n const defaults = await em.find(\n StaffTimeTaskStatus,\n {\n timeProjectId: { $in: projects.map((project) => project.id) },\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n isDefault: true,\n deletedAt: null,\n },\n { fields: ['timeProjectId'] },\n )\n return new Set(defaults.map((status) => status.timeProjectId))\n}\n\nexport async function resolveSupportRoute(\n em: EntityManager,\n scope: SupportScope,\n categoryValue: string,\n): Promise<SupportRoute | null> {\n const rules = await loadSupportRoutingRules(em, scope)\n const usable = await loadUsableSupportProjectIds(em, scope, rules.map((rule) => rule.timeProjectId))\n return resolveSupportRouteFromRules(rules, categoryValue, usable)\n}\n\nexport async function resolveRoutableCategoryValues(\n em: EntityManager,\n scope: SupportScope,\n categoryValues: string[],\n): Promise<Map<string, SupportRoute>> {\n const rules = await loadSupportRoutingRules(em, scope)\n const usable = await loadUsableSupportProjectIds(em, scope, rules.map((rule) => rule.timeProjectId))\n const routes = new Map<string, SupportRoute>()\n for (const value of categoryValues) {\n const route = resolveSupportRouteFromRules(rules, value, usable)\n if (route) routes.set(value, route)\n }\n return routes\n}\n"],
5
+ "mappings": "AACA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AAoBA,SAAS,6BACd,OACA,eACA,kBACqB;AACrB,QAAM,eAAe,MAAM,KAAK,CAAC,SAAS,KAAK,kBAAkB,aAAa;AAC9E,MAAI,gBAAgB,aAAa,YAAY,iBAAiB,IAAI,aAAa,aAAa,GAAG;AAC7F,WAAO,EAAE,eAAe,aAAa,eAAe,KAAK,WAAW;AAAA,EACtE;AACA,QAAM,eAAe,MAAM,KAAK,CAAC,SAAS,KAAK,kBAAkB,IAAI;AACrE,MAAI,gBAAgB,iBAAiB,IAAI,aAAa,aAAa,GAAG;AACpE,WAAO,EAAE,eAAe,aAAa,eAAe,KAAK,WAAW;AAAA,EACtE;AACA,SAAO;AACT;AAEA,eAAsB,wBACpB,IACA,OACmC;AACnC,QAAM,OAAO,MAAM,GAAG,KAAK,yBAAyB;AAAA,IAClD,UAAU,MAAM;AAAA,IAChB,gBAAgB,MAAM;AAAA,IACtB,WAAW;AAAA,EACb,CAAC;AACD,SAAO,KAAK,IAAI,CAAC,SAAS;AAAA,IACxB,IAAI,IAAI;AAAA,IACR,eAAe,IAAI,iBAAiB;AAAA,IACpC,eAAe,IAAI;AAAA,IACnB,UAAU,IAAI;AAAA,IACd,WAAW,IAAI,aAAa;AAAA,EAC9B,EAAE;AACJ;AAEA,eAAsB,4BACpB,IACA,OACA,YACsB;AACtB,QAAM,SAAS,MAAM,KAAK,IAAI,IAAI,UAAU,CAAC;AAC7C,MAAI,OAAO,WAAW,EAAG,QAAO,oBAAI,IAAI;AACxC,QAAM,WAAW,MAAM,GAAG;AAAA,IACxB;AAAA,IACA;AAAA,MACE,IAAI,EAAE,KAAK,OAAO;AAAA,MAClB,UAAU,MAAM;AAAA,MAChB,gBAAgB,MAAM;AAAA,MACtB,WAAW;AAAA,MACX,QAAQ,EAAE,KAAK,CAAC,UAAU,SAAS,EAAE;AAAA,IACvC;AAAA,IACA,EAAE,QAAQ,CAAC,IAAI,EAAE;AAAA,EACnB;AACA,MAAI,SAAS,WAAW,EAAG,QAAO,oBAAI,IAAI;AAC1C,QAAM,WAAW,MAAM,GAAG;AAAA,IACxB;AAAA,IACA;AAAA,MACE,eAAe,EAAE,KAAK,SAAS,IAAI,CAAC,YAAY,QAAQ,EAAE,EAAE;AAAA,MAC5D,UAAU,MAAM;AAAA,MAChB,gBAAgB,MAAM;AAAA,MACtB,WAAW;AAAA,MACX,WAAW;AAAA,IACb;AAAA,IACA,EAAE,QAAQ,CAAC,eAAe,EAAE;AAAA,EAC9B;AACA,SAAO,IAAI,IAAI,SAAS,IAAI,CAAC,WAAW,OAAO,aAAa,CAAC;AAC/D;AAEA,eAAsB,oBACpB,IACA,OACA,eAC8B;AAC9B,QAAM,QAAQ,MAAM,wBAAwB,IAAI,KAAK;AACrD,QAAM,SAAS,MAAM,4BAA4B,IAAI,OAAO,MAAM,IAAI,CAAC,SAAS,KAAK,aAAa,CAAC;AACnG,SAAO,6BAA6B,OAAO,eAAe,MAAM;AAClE;AAEA,eAAsB,8BACpB,IACA,OACA,gBACoC;AACpC,QAAM,QAAQ,MAAM,wBAAwB,IAAI,KAAK;AACrD,QAAM,SAAS,MAAM,4BAA4B,IAAI,OAAO,MAAM,IAAI,CAAC,SAAS,KAAK,aAAa,CAAC;AACnG,QAAM,SAAS,oBAAI,IAA0B;AAC7C,aAAW,SAAS,gBAAgB;AAClC,UAAM,QAAQ,6BAA6B,OAAO,OAAO,MAAM;AAC/D,QAAI,MAAO,QAAO,IAAI,OAAO,KAAK;AAAA,EACpC;AACA,SAAO;AACT;",
6
+ "names": []
7
+ }
@@ -0,0 +1,25 @@
1
+ function routingRuleVersion(rule) {
2
+ return typeof rule.updatedAt === "string" && rule.updatedAt.length > 0 ? rule.updatedAt : null;
3
+ }
4
+ function buildRoutingRuleEditDraft(rule) {
5
+ return {
6
+ mode: "edit",
7
+ ruleId: rule.id,
8
+ categoryValue: rule.categoryValue ?? "",
9
+ timeProjectId: rule.project.id,
10
+ expectedUpdatedAt: routingRuleVersion(rule)
11
+ };
12
+ }
13
+ function rebaseRoutingRuleDialog(draft, snapshot) {
14
+ if (!draft || draft.mode === "create") return draft;
15
+ const current = snapshot.rules.find((rule) => rule.id === draft.ruleId);
16
+ if (!current) return null;
17
+ if (routingRuleVersion(current) === draft.expectedUpdatedAt) return draft;
18
+ return buildRoutingRuleEditDraft(current);
19
+ }
20
+ export {
21
+ buildRoutingRuleEditDraft,
22
+ rebaseRoutingRuleDialog,
23
+ routingRuleVersion
24
+ };
25
+ //# sourceMappingURL=routingRuleDraft.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/modules/staff/lib/support/routingRuleDraft.ts"],
4
+ "sourcesContent": ["export type RoutingRuleSnapshot = {\n id: string\n categoryValue: string | null\n project: { id: string }\n updatedAt?: string | null\n}\n\nexport type RoutingRuleSnapshotSet = {\n rules: RoutingRuleSnapshot[]\n}\n\nexport type RoutingRuleDialogState =\n | { mode: 'create'; categoryValue: string; timeProjectId: string }\n | {\n mode: 'edit'\n ruleId: string\n categoryValue: string\n timeProjectId: string\n expectedUpdatedAt: string | null\n }\n\nexport function routingRuleVersion(rule: { updatedAt?: string | null }): string | null {\n return typeof rule.updatedAt === 'string' && rule.updatedAt.length > 0 ? rule.updatedAt : null\n}\n\nexport function buildRoutingRuleEditDraft(rule: RoutingRuleSnapshot): RoutingRuleDialogState {\n return {\n mode: 'edit',\n ruleId: rule.id,\n categoryValue: rule.categoryValue ?? '',\n timeProjectId: rule.project.id,\n expectedUpdatedAt: routingRuleVersion(rule),\n }\n}\n\nexport function rebaseRoutingRuleDialog(\n draft: RoutingRuleDialogState | null,\n snapshot: RoutingRuleSnapshotSet,\n): RoutingRuleDialogState | null {\n if (!draft || draft.mode === 'create') return draft\n const current = snapshot.rules.find((rule) => rule.id === draft.ruleId)\n if (!current) return null\n if (routingRuleVersion(current) === draft.expectedUpdatedAt) return draft\n return buildRoutingRuleEditDraft(current)\n}\n"],
5
+ "mappings": "AAqBO,SAAS,mBAAmB,MAAoD;AACrF,SAAO,OAAO,KAAK,cAAc,YAAY,KAAK,UAAU,SAAS,IAAI,KAAK,YAAY;AAC5F;AAEO,SAAS,0BAA0B,MAAmD;AAC3F,SAAO;AAAA,IACL,MAAM;AAAA,IACN,QAAQ,KAAK;AAAA,IACb,eAAe,KAAK,iBAAiB;AAAA,IACrC,eAAe,KAAK,QAAQ;AAAA,IAC5B,mBAAmB,mBAAmB,IAAI;AAAA,EAC5C;AACF;AAEO,SAAS,wBACd,OACA,UAC+B;AAC/B,MAAI,CAAC,SAAS,MAAM,SAAS,SAAU,QAAO;AAC9C,QAAM,UAAU,SAAS,MAAM,KAAK,CAAC,SAAS,KAAK,OAAO,MAAM,MAAM;AACtE,MAAI,CAAC,QAAS,QAAO;AACrB,MAAI,mBAAmB,OAAO,MAAM,MAAM,kBAAmB,QAAO;AACpE,SAAO,0BAA0B,OAAO;AAC1C;",
6
+ "names": []
7
+ }
@@ -0,0 +1,114 @@
1
+ import { withAtomicFlush } from "@open-mercato/shared/lib/commands/flush";
2
+ import { isUniqueViolation } from "@open-mercato/shared/lib/crud/errors";
3
+ import { StaffSupportRoutingRule, StaffTimeProject } from "../../data/entities.js";
4
+ import { seedProjectTaskStatuses } from "../timesheets-tasks/seedProjectStatuses.js";
5
+ import { deriveProjectCode } from "../time-tracking/projectCode.js";
6
+ import { seedSupportTicketCategories } from "./dictionaries.js";
7
+ const SUPPORT_FALLBACK_PROJECT_NAME = "Customer support";
8
+ function resolveContainerProjectCodeGenerator(container) {
9
+ if (!container) return void 0;
10
+ try {
11
+ const resolver = container.resolve("timeProjectCodeResolver");
12
+ if (!resolver || typeof resolver.generate !== "function") return void 0;
13
+ return (name, taken) => resolver.generate(name, taken);
14
+ } catch {
15
+ return void 0;
16
+ }
17
+ }
18
+ async function seedSupportDefaults(em, scope, options) {
19
+ await seedSupportTicketCategories(em, scope);
20
+ await ensureFallbackProjectAndRule(em, scope, {
21
+ translate: options.translate,
22
+ generateCode: options.generateCode,
23
+ projectName: options.translate("staff.support.fallbackProject.name", SUPPORT_FALLBACK_PROJECT_NAME)
24
+ });
25
+ }
26
+ async function findFallbackRule(em, scope) {
27
+ return em.findOne(StaffSupportRoutingRule, {
28
+ tenantId: scope.tenantId,
29
+ organizationId: scope.organizationId,
30
+ categoryValue: null,
31
+ deletedAt: null
32
+ });
33
+ }
34
+ async function ensureFallbackProjectAndRule(em, scope, options) {
35
+ const existingRule = await findFallbackRule(em, scope);
36
+ if (existingRule) {
37
+ return { ruleId: existingRule.id, timeProjectId: existingRule.timeProjectId, created: false, taskStatusIds: [] };
38
+ }
39
+ const takenRows = await em.find(
40
+ StaffTimeProject,
41
+ { tenantId: scope.tenantId, organizationId: scope.organizationId, deletedAt: null },
42
+ { fields: ["code"] }
43
+ );
44
+ const taken = new Set(takenRows.map((row) => row.code));
45
+ const name = options.projectName ?? SUPPORT_FALLBACK_PROJECT_NAME;
46
+ const generate = options.generateCode ?? ((projectName, codes) => deriveProjectCode(projectName, codes));
47
+ const now = /* @__PURE__ */ new Date();
48
+ const project = em.create(StaffTimeProject, {
49
+ tenantId: scope.tenantId,
50
+ organizationId: scope.organizationId,
51
+ name,
52
+ code: generate(name, taken),
53
+ customerId: null,
54
+ customerSnapshot: null,
55
+ projectType: "internal",
56
+ status: "active",
57
+ billableByDefault: false,
58
+ budgetKind: "none",
59
+ createdAt: now,
60
+ updatedAt: now,
61
+ deletedAt: null
62
+ });
63
+ let rule = null;
64
+ let statuses = [];
65
+ try {
66
+ await withAtomicFlush(
67
+ em,
68
+ [
69
+ () => {
70
+ em.persist(project);
71
+ },
72
+ () => {
73
+ statuses = seedProjectTaskStatuses(
74
+ em,
75
+ { tenantId: scope.tenantId, organizationId: scope.organizationId, timeProjectId: project.id },
76
+ options.translate
77
+ );
78
+ rule = em.create(StaffSupportRoutingRule, {
79
+ tenantId: scope.tenantId,
80
+ organizationId: scope.organizationId,
81
+ categoryValue: null,
82
+ timeProjectId: project.id,
83
+ isActive: true,
84
+ createdAt: now,
85
+ updatedAt: now,
86
+ deletedAt: null
87
+ });
88
+ em.persist(rule);
89
+ }
90
+ ],
91
+ { transaction: true, label: "staff.support.ensure_fallback_project" }
92
+ );
93
+ } catch (err) {
94
+ if (!isUniqueViolation(err)) throw err;
95
+ const winner = await findFallbackRule(em.fork(), scope);
96
+ if (!winner) throw err;
97
+ return { ruleId: winner.id, timeProjectId: winner.timeProjectId, created: false, taskStatusIds: [] };
98
+ }
99
+ const created = rule;
100
+ if (!created) throw new Error("[internal] fallback routing rule was not created");
101
+ return {
102
+ ruleId: created.id,
103
+ timeProjectId: project.id,
104
+ created: true,
105
+ taskStatusIds: statuses.map((status) => status.id)
106
+ };
107
+ }
108
+ export {
109
+ SUPPORT_FALLBACK_PROJECT_NAME,
110
+ ensureFallbackProjectAndRule,
111
+ resolveContainerProjectCodeGenerator,
112
+ seedSupportDefaults
113
+ };
114
+ //# sourceMappingURL=seedDefaults.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/modules/staff/lib/support/seedDefaults.ts"],
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'\nimport { isUniqueViolation } from '@open-mercato/shared/lib/crud/errors'\nimport { StaffSupportRoutingRule, StaffTimeProject, StaffTimeTaskStatus } from '../../data/entities'\nimport { seedProjectTaskStatuses } from '../timesheets-tasks/seedProjectStatuses'\nimport type { TaskStatusLabelTranslate } from '../timesheets-tasks/defaultStatusTemplate'\nimport { deriveProjectCode } from '../time-tracking/projectCode'\nimport { seedSupportTicketCategories } from './dictionaries'\n\nexport const SUPPORT_FALLBACK_PROJECT_NAME = 'Customer support'\n\ntype SupportScope = {\n tenantId: string\n organizationId: string\n}\n\nexport type ProjectCodeGenerator = (name: string, taken: Set<string>) => string\n\nexport type EnsureFallbackResult = {\n ruleId: string\n timeProjectId: string\n created: boolean\n /** Board columns seeded for a freshly created project; empty when nothing was created. */\n taskStatusIds: string[]\n}\n\ntype ProjectCodeResolverLike = { generate(name: string, taken: Set<string>): string }\n\n/**\n * The `timeProjectCodeResolver` DI override (EP-39), so a seeded project gets the\n * same code a project created through the UI would. `undefined` (built-in\n * generator) when the container does not carry one.\n */\nexport function resolveContainerProjectCodeGenerator(\n container: { resolve: (name: string) => unknown } | null | undefined,\n): ProjectCodeGenerator | undefined {\n if (!container) return undefined\n try {\n const resolver = container.resolve('timeProjectCodeResolver') as ProjectCodeResolverLike | undefined\n if (!resolver || typeof resolver.generate !== 'function') return undefined\n return (name, taken) => resolver.generate(name, taken)\n } catch {\n return undefined\n }\n}\n\nexport async function seedSupportDefaults(\n em: EntityManager,\n scope: SupportScope,\n options: { translate: TaskStatusLabelTranslate; generateCode?: ProjectCodeGenerator },\n): Promise<void> {\n await seedSupportTicketCategories(em, scope)\n await ensureFallbackProjectAndRule(em, scope, {\n translate: options.translate,\n generateCode: options.generateCode,\n projectName: options.translate('staff.support.fallbackProject.name', SUPPORT_FALLBACK_PROJECT_NAME),\n })\n}\n\nasync function findFallbackRule(em: EntityManager, scope: SupportScope): Promise<StaffSupportRoutingRule | null> {\n return em.findOne(StaffSupportRoutingRule, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n categoryValue: null,\n deletedAt: null,\n })\n}\n\n/**\n * Idempotent: an existing fallback rule is returned untouched, and a call that\n * loses a race to a concurrent one (unique index on the fallback rule or on the\n * project code) re-reads the winner's rule and reports `created: false`.\n */\nexport async function ensureFallbackProjectAndRule(\n em: EntityManager,\n scope: SupportScope,\n options: { translate: TaskStatusLabelTranslate; generateCode?: ProjectCodeGenerator; projectName?: string },\n): Promise<EnsureFallbackResult> {\n const existingRule = await findFallbackRule(em, scope)\n if (existingRule) {\n return { ruleId: existingRule.id, timeProjectId: existingRule.timeProjectId, created: false, taskStatusIds: [] }\n }\n const takenRows = await em.find(\n StaffTimeProject,\n { tenantId: scope.tenantId, organizationId: scope.organizationId, deletedAt: null },\n { fields: ['code'] },\n )\n const taken = new Set(takenRows.map((row) => row.code))\n const name = options.projectName ?? SUPPORT_FALLBACK_PROJECT_NAME\n const generate = options.generateCode ?? ((projectName: string, codes: Set<string>) => deriveProjectCode(projectName, codes))\n const now = new Date()\n const project = em.create(StaffTimeProject, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n name,\n code: generate(name, taken),\n customerId: null,\n customerSnapshot: null,\n projectType: 'internal',\n status: 'active',\n billableByDefault: false,\n budgetKind: 'none',\n createdAt: now,\n updatedAt: now,\n deletedAt: null,\n })\n let rule: StaffSupportRoutingRule | null = null\n let statuses: StaffTimeTaskStatus[] = []\n try {\n await withAtomicFlush(\n em,\n [\n () => {\n em.persist(project)\n },\n () => {\n statuses = seedProjectTaskStatuses(\n em,\n { tenantId: scope.tenantId, organizationId: scope.organizationId, timeProjectId: project.id },\n options.translate,\n )\n rule = em.create(StaffSupportRoutingRule, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n categoryValue: null,\n timeProjectId: project.id,\n isActive: true,\n createdAt: now,\n updatedAt: now,\n deletedAt: null,\n })\n em.persist(rule)\n },\n ],\n { transaction: true, label: 'staff.support.ensure_fallback_project' },\n )\n } catch (err) {\n if (!isUniqueViolation(err)) throw err\n const winner = await findFallbackRule(em.fork(), scope)\n if (!winner) throw err\n return { ruleId: winner.id, timeProjectId: winner.timeProjectId, created: false, taskStatusIds: [] }\n }\n const created = rule as StaffSupportRoutingRule | null\n if (!created) throw new Error('[internal] fallback routing rule was not created')\n return {\n ruleId: created.id,\n timeProjectId: project.id,\n created: true,\n taskStatusIds: statuses.map((status) => status.id),\n }\n}\n"],
5
+ "mappings": "AACA,SAAS,uBAAuB;AAChC,SAAS,yBAAyB;AAClC,SAAS,yBAAyB,wBAA6C;AAC/E,SAAS,+BAA+B;AAExC,SAAS,yBAAyB;AAClC,SAAS,mCAAmC;AAErC,MAAM,gCAAgC;AAwBtC,SAAS,qCACd,WACkC;AAClC,MAAI,CAAC,UAAW,QAAO;AACvB,MAAI;AACF,UAAM,WAAW,UAAU,QAAQ,yBAAyB;AAC5D,QAAI,CAAC,YAAY,OAAO,SAAS,aAAa,WAAY,QAAO;AACjE,WAAO,CAAC,MAAM,UAAU,SAAS,SAAS,MAAM,KAAK;AAAA,EACvD,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAsB,oBACpB,IACA,OACA,SACe;AACf,QAAM,4BAA4B,IAAI,KAAK;AAC3C,QAAM,6BAA6B,IAAI,OAAO;AAAA,IAC5C,WAAW,QAAQ;AAAA,IACnB,cAAc,QAAQ;AAAA,IACtB,aAAa,QAAQ,UAAU,sCAAsC,6BAA6B;AAAA,EACpG,CAAC;AACH;AAEA,eAAe,iBAAiB,IAAmB,OAA8D;AAC/G,SAAO,GAAG,QAAQ,yBAAyB;AAAA,IACzC,UAAU,MAAM;AAAA,IAChB,gBAAgB,MAAM;AAAA,IACtB,eAAe;AAAA,IACf,WAAW;AAAA,EACb,CAAC;AACH;AAOA,eAAsB,6BACpB,IACA,OACA,SAC+B;AAC/B,QAAM,eAAe,MAAM,iBAAiB,IAAI,KAAK;AACrD,MAAI,cAAc;AAChB,WAAO,EAAE,QAAQ,aAAa,IAAI,eAAe,aAAa,eAAe,SAAS,OAAO,eAAe,CAAC,EAAE;AAAA,EACjH;AACA,QAAM,YAAY,MAAM,GAAG;AAAA,IACzB;AAAA,IACA,EAAE,UAAU,MAAM,UAAU,gBAAgB,MAAM,gBAAgB,WAAW,KAAK;AAAA,IAClF,EAAE,QAAQ,CAAC,MAAM,EAAE;AAAA,EACrB;AACA,QAAM,QAAQ,IAAI,IAAI,UAAU,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC;AACtD,QAAM,OAAO,QAAQ,eAAe;AACpC,QAAM,WAAW,QAAQ,iBAAiB,CAAC,aAAqB,UAAuB,kBAAkB,aAAa,KAAK;AAC3H,QAAM,MAAM,oBAAI,KAAK;AACrB,QAAM,UAAU,GAAG,OAAO,kBAAkB;AAAA,IAC1C,UAAU,MAAM;AAAA,IAChB,gBAAgB,MAAM;AAAA,IACtB;AAAA,IACA,MAAM,SAAS,MAAM,KAAK;AAAA,IAC1B,YAAY;AAAA,IACZ,kBAAkB;AAAA,IAClB,aAAa;AAAA,IACb,QAAQ;AAAA,IACR,mBAAmB;AAAA,IACnB,YAAY;AAAA,IACZ,WAAW;AAAA,IACX,WAAW;AAAA,IACX,WAAW;AAAA,EACb,CAAC;AACD,MAAI,OAAuC;AAC3C,MAAI,WAAkC,CAAC;AACvC,MAAI;AACF,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,QACE,MAAM;AACJ,aAAG,QAAQ,OAAO;AAAA,QACpB;AAAA,QACA,MAAM;AACJ,qBAAW;AAAA,YACT;AAAA,YACA,EAAE,UAAU,MAAM,UAAU,gBAAgB,MAAM,gBAAgB,eAAe,QAAQ,GAAG;AAAA,YAC5F,QAAQ;AAAA,UACV;AACA,iBAAO,GAAG,OAAO,yBAAyB;AAAA,YACxC,UAAU,MAAM;AAAA,YAChB,gBAAgB,MAAM;AAAA,YACtB,eAAe;AAAA,YACf,eAAe,QAAQ;AAAA,YACvB,UAAU;AAAA,YACV,WAAW;AAAA,YACX,WAAW;AAAA,YACX,WAAW;AAAA,UACb,CAAC;AACD,aAAG,QAAQ,IAAI;AAAA,QACjB;AAAA,MACF;AAAA,MACA,EAAE,aAAa,MAAM,OAAO,wCAAwC;AAAA,IACtE;AAAA,EACF,SAAS,KAAK;AACZ,QAAI,CAAC,kBAAkB,GAAG,EAAG,OAAM;AACnC,UAAM,SAAS,MAAM,iBAAiB,GAAG,KAAK,GAAG,KAAK;AACtD,QAAI,CAAC,OAAQ,OAAM;AACnB,WAAO,EAAE,QAAQ,OAAO,IAAI,eAAe,OAAO,eAAe,SAAS,OAAO,eAAe,CAAC,EAAE;AAAA,EACrG;AACA,QAAM,UAAU;AAChB,MAAI,CAAC,QAAS,OAAM,IAAI,MAAM,kDAAkD;AAChF,SAAO;AAAA,IACL,QAAQ,QAAQ;AAAA,IAChB,eAAe,QAAQ;AAAA,IACvB,SAAS;AAAA,IACT,eAAe,SAAS,IAAI,CAAC,WAAW,OAAO,EAAE;AAAA,EACnD;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,26 @@
1
+ import { createLogger } from "@open-mercato/shared/lib/logger";
2
+ import { getTelemetryRuntime } from "@open-mercato/shared/lib/telemetry/runtime";
3
+ import { emitStaffEvent } from "../../events.js";
4
+ const logger = createLogger("staff").child({ component: "lib/support/supportActivity" });
5
+ function emitSupportAttachmentActivity(task, ticket, kind, attachmentId) {
6
+ void emitStaffEvent(
7
+ "staff.support_ticket.activity_recorded",
8
+ {
9
+ id: ticket.id,
10
+ tenantId: task.tenantId,
11
+ organizationId: task.organizationId,
12
+ taskId: task.id,
13
+ projectId: task.timeProjectId,
14
+ kind,
15
+ attachmentId
16
+ },
17
+ { persistent: true }
18
+ ).catch((err) => {
19
+ logger.error("staff support attachment activity emit failed", { err, taskId: task.id });
20
+ getTelemetryRuntime()?.reportError(err, { module: "staff", code: "staff.support_activity_emit_failed" });
21
+ });
22
+ }
23
+ export {
24
+ emitSupportAttachmentActivity
25
+ };
26
+ //# sourceMappingURL=supportActivity.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/modules/staff/lib/support/supportActivity.ts"],
4
+ "sourcesContent": ["import { createLogger } from '@open-mercato/shared/lib/logger'\nimport { getTelemetryRuntime } from '@open-mercato/shared/lib/telemetry/runtime'\nimport type { StaffSupportTicket, StaffTimeTask } from '../../data/entities'\nimport { emitStaffEvent } from '../../events'\n\nconst logger = createLogger('staff').child({ component: 'lib/support/supportActivity' })\n\nexport type SupportAttachmentActivityKind = 'attachment_added' | 'attachment_removed'\n\n/**\n * Tells open backoffice screens that a customer changed the files on a ticket.\n * Ids only, like every `activity_recorded` broadcast. The attachment id keeps\n * two quick changes of the same kind distinct for the browser bridge's 500 ms\n * duplicate filter. A failed publish is reported and never fails the upload or\n * deletion that already happened.\n */\nexport function emitSupportAttachmentActivity(\n task: Pick<StaffTimeTask, 'id' | 'tenantId' | 'organizationId' | 'timeProjectId'>,\n ticket: Pick<StaffSupportTicket, 'id'>,\n kind: SupportAttachmentActivityKind,\n attachmentId: string,\n): void {\n void emitStaffEvent(\n 'staff.support_ticket.activity_recorded',\n {\n id: ticket.id,\n tenantId: task.tenantId,\n organizationId: task.organizationId,\n taskId: task.id,\n projectId: task.timeProjectId,\n kind,\n attachmentId,\n },\n { persistent: true },\n ).catch((err) => {\n logger.error('staff support attachment activity emit failed', { err, taskId: task.id })\n getTelemetryRuntime()?.reportError(err, { module: 'staff', code: 'staff.support_activity_emit_failed' })\n })\n}\n"],
5
+ "mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,2BAA2B;AAEpC,SAAS,sBAAsB;AAE/B,MAAM,SAAS,aAAa,OAAO,EAAE,MAAM,EAAE,WAAW,8BAA8B,CAAC;AAWhF,SAAS,8BACd,MACA,QACA,MACA,cACM;AACN,OAAK;AAAA,IACH;AAAA,IACA;AAAA,MACE,IAAI,OAAO;AAAA,MACX,UAAU,KAAK;AAAA,MACf,gBAAgB,KAAK;AAAA,MACrB,QAAQ,KAAK;AAAA,MACb,WAAW,KAAK;AAAA,MAChB;AAAA,MACA;AAAA,IACF;AAAA,IACA,EAAE,YAAY,KAAK;AAAA,EACrB,EAAE,MAAM,CAAC,QAAQ;AACf,WAAO,MAAM,iDAAiD,EAAE,KAAK,QAAQ,KAAK,GAAG,CAAC;AACtF,wBAAoB,GAAG,YAAY,KAAK,EAAE,QAAQ,SAAS,MAAM,qCAAqC,CAAC;AAAA,EACzG,CAAC;AACH;",
6
+ "names": []
7
+ }
@@ -0,0 +1,23 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { useT } from "@open-mercato/shared/lib/i18n/context";
4
+ import { EmptyState } from "@open-mercato/ui/primitives/empty-state";
5
+ function PortalSupportNotLinked({ size = "lg" }) {
6
+ const t = useT();
7
+ return /* @__PURE__ */ jsx(
8
+ EmptyState,
9
+ {
10
+ variant: "subtle",
11
+ size,
12
+ title: t("staff.portal.support.notLinked.title", "Support tickets are not available for your account"),
13
+ description: t(
14
+ "staff.portal.support.notLinked.description",
15
+ "Your portal account is not linked to a company, so there are no tickets to show. Ask your account administrator to link it."
16
+ )
17
+ }
18
+ );
19
+ }
20
+ export {
21
+ PortalSupportNotLinked
22
+ };
23
+ //# sourceMappingURL=PortalSupportNotLinked.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/modules/staff/lib/support-ui/PortalSupportNotLinked.tsx"],
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { EmptyState } from '@open-mercato/ui/primitives/empty-state'\n\nexport function PortalSupportNotLinked({ size = 'lg' }: { size?: 'sm' | 'lg' }) {\n const t = useT()\n return (\n <EmptyState\n variant=\"subtle\"\n size={size}\n title={t('staff.portal.support.notLinked.title', 'Support tickets are not available for your account')}\n description={t(\n 'staff.portal.support.notLinked.description',\n 'Your portal account is not linked to a company, so there are no tickets to show. Ask your account administrator to link it.',\n )}\n />\n )\n}\n"],
5
+ "mappings": ";AASI;AANJ,SAAS,YAAY;AACrB,SAAS,kBAAkB;AAEpB,SAAS,uBAAuB,EAAE,OAAO,KAAK,GAA2B;AAC9E,QAAM,IAAI,KAAK;AACf,SACE;AAAA,IAAC;AAAA;AAAA,MACC,SAAQ;AAAA,MACR;AAAA,MACA,OAAO,EAAE,wCAAwC,oDAAoD;AAAA,MACrG,aAAa;AAAA,QACX;AAAA,QACA;AAAA,MACF;AAAA;AAAA,EACF;AAEJ;",
6
+ "names": []
7
+ }
@@ -0,0 +1,112 @@
1
+ const DEFAULT_ATTACHMENT_MAX_UPLOAD_BYTES = 25 * 1024 * 1024;
2
+ const ATTACHMENT_ACCEPT_TYPES = [
3
+ ".avif",
4
+ ".bmp",
5
+ ".csv",
6
+ ".docx",
7
+ ".gif",
8
+ ".jpeg",
9
+ ".jpg",
10
+ ".json",
11
+ ".md",
12
+ ".pdf",
13
+ ".png",
14
+ ".pptx",
15
+ ".txt",
16
+ ".webp",
17
+ ".xlsx",
18
+ ".zip",
19
+ "application/json",
20
+ "application/pdf",
21
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation",
22
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
23
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
24
+ "application/zip",
25
+ "image/avif",
26
+ "image/bmp",
27
+ "image/gif",
28
+ "image/jpeg",
29
+ "image/png",
30
+ "image/webp",
31
+ "text/csv",
32
+ "text/markdown",
33
+ "text/plain"
34
+ ].join(",");
35
+ const BLOCKED_ATTACHMENT_MIME_TYPES = /* @__PURE__ */ new Set([
36
+ "application/xhtml+xml",
37
+ "application/xml",
38
+ "image/svg+xml",
39
+ "text/html",
40
+ "text/xml"
41
+ ]);
42
+ const ACTIVE_CONTENT_ATTACHMENT_EXTENSIONS = /* @__PURE__ */ new Set([
43
+ "htm",
44
+ "html",
45
+ "svg",
46
+ "xhtml",
47
+ "xml"
48
+ ]);
49
+ const DANGEROUS_EXECUTABLE_EXTENSIONS = /* @__PURE__ */ new Set([
50
+ "app",
51
+ "apk",
52
+ "bat",
53
+ "cmd",
54
+ "com",
55
+ "dll",
56
+ "exe",
57
+ "hta",
58
+ "htm",
59
+ "html",
60
+ "jar",
61
+ "js",
62
+ "jse",
63
+ "lnk",
64
+ "msi",
65
+ "pif",
66
+ "ps1",
67
+ "psm1",
68
+ "reg",
69
+ "scr",
70
+ "sh",
71
+ "vbe",
72
+ "vbs",
73
+ "wsf",
74
+ "wsh"
75
+ ]);
76
+ function getFileExtensionSegments(fileName) {
77
+ const parts = fileName.trim().split(".").filter(Boolean);
78
+ if (parts.length < 2) return [];
79
+ return parts.slice(1).map((part) => part.toLowerCase().replace(/[^a-z0-9]/g, "")).filter(Boolean);
80
+ }
81
+ async function fileLooksLikeActiveContent(file) {
82
+ try {
83
+ const sniff = (await file.slice(0, 512).text()).trimStart().toLowerCase();
84
+ return sniff.startsWith("<svg") || sniff.startsWith("<?xml") || sniff.startsWith("<!doctype html") || sniff.startsWith("<html");
85
+ } catch {
86
+ return false;
87
+ }
88
+ }
89
+ function resolvePortalMaxUploadBytes(serverMaxBytes) {
90
+ return typeof serverMaxBytes === "number" && Number.isFinite(serverMaxBytes) && serverMaxBytes > 0 ? serverMaxBytes : DEFAULT_ATTACHMENT_MAX_UPLOAD_BYTES;
91
+ }
92
+ async function validateAttachmentFile(file, t, maxBytes = DEFAULT_ATTACHMENT_MAX_UPLOAD_BYTES) {
93
+ if (file.size > maxBytes) {
94
+ return t("attachments.errors.maxUploadSize");
95
+ }
96
+ const extensionSegments = getFileExtensionSegments(file.name);
97
+ const mimeType = file.type.trim().toLowerCase();
98
+ if (extensionSegments.some((extension) => DANGEROUS_EXECUTABLE_EXTENSIONS.has(extension))) {
99
+ return t("attachments.errors.dangerousExecutable");
100
+ }
101
+ if (extensionSegments.some((extension) => ACTIVE_CONTENT_ATTACHMENT_EXTENSIONS.has(extension)) || mimeType && BLOCKED_ATTACHMENT_MIME_TYPES.has(mimeType) || await fileLooksLikeActiveContent(file)) {
102
+ return t("attachments.errors.activeContentBlocked");
103
+ }
104
+ return null;
105
+ }
106
+ export {
107
+ ATTACHMENT_ACCEPT_TYPES,
108
+ DEFAULT_ATTACHMENT_MAX_UPLOAD_BYTES,
109
+ resolvePortalMaxUploadBytes,
110
+ validateAttachmentFile
111
+ };
112
+ //# sourceMappingURL=portalAttachmentValidation.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/modules/staff/lib/support-ui/portalAttachmentValidation.ts"],
4
+ "sourcesContent": ["import type { TranslateFn } from '@open-mercato/shared/lib/i18n/context'\n\nexport const DEFAULT_ATTACHMENT_MAX_UPLOAD_BYTES = 25 * 1024 * 1024\n\nexport const ATTACHMENT_ACCEPT_TYPES = [\n '.avif',\n '.bmp',\n '.csv',\n '.docx',\n '.gif',\n '.jpeg',\n '.jpg',\n '.json',\n '.md',\n '.pdf',\n '.png',\n '.pptx',\n '.txt',\n '.webp',\n '.xlsx',\n '.zip',\n 'application/json',\n 'application/pdf',\n 'application/vnd.openxmlformats-officedocument.presentationml.presentation',\n 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',\n 'application/zip',\n 'image/avif',\n 'image/bmp',\n 'image/gif',\n 'image/jpeg',\n 'image/png',\n 'image/webp',\n 'text/csv',\n 'text/markdown',\n 'text/plain',\n].join(',')\n\nconst BLOCKED_ATTACHMENT_MIME_TYPES = new Set([\n 'application/xhtml+xml',\n 'application/xml',\n 'image/svg+xml',\n 'text/html',\n 'text/xml',\n])\n\nconst ACTIVE_CONTENT_ATTACHMENT_EXTENSIONS = new Set([\n 'htm',\n 'html',\n 'svg',\n 'xhtml',\n 'xml',\n])\n\nconst DANGEROUS_EXECUTABLE_EXTENSIONS = new Set([\n 'app',\n 'apk',\n 'bat',\n 'cmd',\n 'com',\n 'dll',\n 'exe',\n 'hta',\n 'htm',\n 'html',\n 'jar',\n 'js',\n 'jse',\n 'lnk',\n 'msi',\n 'pif',\n 'ps1',\n 'psm1',\n 'reg',\n 'scr',\n 'sh',\n 'vbe',\n 'vbs',\n 'wsf',\n 'wsh',\n])\n\nfunction getFileExtensionSegments(fileName: string): string[] {\n const parts = fileName.trim().split('.').filter(Boolean)\n if (parts.length < 2) return []\n return parts.slice(1).map((part) => part.toLowerCase().replace(/[^a-z0-9]/g, '')).filter(Boolean)\n}\n\nasync function fileLooksLikeActiveContent(file: File): Promise<boolean> {\n try {\n const sniff = (await file.slice(0, 512).text()).trimStart().toLowerCase()\n return sniff.startsWith('<svg') || sniff.startsWith('<?xml') || sniff.startsWith('<!doctype html') || sniff.startsWith('<html')\n } catch {\n return false\n }\n}\n\nexport function resolvePortalMaxUploadBytes(serverMaxBytes: number | null | undefined): number {\n return typeof serverMaxBytes === 'number' && Number.isFinite(serverMaxBytes) && serverMaxBytes > 0\n ? serverMaxBytes\n : DEFAULT_ATTACHMENT_MAX_UPLOAD_BYTES\n}\n\nexport async function validateAttachmentFile(\n file: File,\n t: TranslateFn,\n maxBytes: number = DEFAULT_ATTACHMENT_MAX_UPLOAD_BYTES,\n): Promise<string | null> {\n if (file.size > maxBytes) {\n return t('attachments.errors.maxUploadSize')\n }\n const extensionSegments = getFileExtensionSegments(file.name)\n const mimeType = file.type.trim().toLowerCase()\n if (extensionSegments.some((extension) => DANGEROUS_EXECUTABLE_EXTENSIONS.has(extension))) {\n return t('attachments.errors.dangerousExecutable')\n }\n if (\n extensionSegments.some((extension) => ACTIVE_CONTENT_ATTACHMENT_EXTENSIONS.has(extension)) ||\n (mimeType && BLOCKED_ATTACHMENT_MIME_TYPES.has(mimeType)) ||\n await fileLooksLikeActiveContent(file)\n ) {\n return t('attachments.errors.activeContentBlocked')\n }\n return null\n}\n"],
5
+ "mappings": "AAEO,MAAM,sCAAsC,KAAK,OAAO;AAExD,MAAM,0BAA0B;AAAA,EACrC;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,EAAE,KAAK,GAAG;AAEV,MAAM,gCAAgC,oBAAI,IAAI;AAAA,EAC5C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,MAAM,uCAAuC,oBAAI,IAAI;AAAA,EACnD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,MAAM,kCAAkC,oBAAI,IAAI;AAAA,EAC9C;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,CAAC;AAED,SAAS,yBAAyB,UAA4B;AAC5D,QAAM,QAAQ,SAAS,KAAK,EAAE,MAAM,GAAG,EAAE,OAAO,OAAO;AACvD,MAAI,MAAM,SAAS,EAAG,QAAO,CAAC;AAC9B,SAAO,MAAM,MAAM,CAAC,EAAE,IAAI,CAAC,SAAS,KAAK,YAAY,EAAE,QAAQ,cAAc,EAAE,CAAC,EAAE,OAAO,OAAO;AAClG;AAEA,eAAe,2BAA2B,MAA8B;AACtE,MAAI;AACF,UAAM,SAAS,MAAM,KAAK,MAAM,GAAG,GAAG,EAAE,KAAK,GAAG,UAAU,EAAE,YAAY;AACxE,WAAO,MAAM,WAAW,MAAM,KAAK,MAAM,WAAW,OAAO,KAAK,MAAM,WAAW,gBAAgB,KAAK,MAAM,WAAW,OAAO;AAAA,EAChI,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,4BAA4B,gBAAmD;AAC7F,SAAO,OAAO,mBAAmB,YAAY,OAAO,SAAS,cAAc,KAAK,iBAAiB,IAC7F,iBACA;AACN;AAEA,eAAsB,uBACpB,MACA,GACA,WAAmB,qCACK;AACxB,MAAI,KAAK,OAAO,UAAU;AACxB,WAAO,EAAE,kCAAkC;AAAA,EAC7C;AACA,QAAM,oBAAoB,yBAAyB,KAAK,IAAI;AAC5D,QAAM,WAAW,KAAK,KAAK,KAAK,EAAE,YAAY;AAC9C,MAAI,kBAAkB,KAAK,CAAC,cAAc,gCAAgC,IAAI,SAAS,CAAC,GAAG;AACzF,WAAO,EAAE,wCAAwC;AAAA,EACnD;AACA,MACE,kBAAkB,KAAK,CAAC,cAAc,qCAAqC,IAAI,SAAS,CAAC,KACxF,YAAY,8BAA8B,IAAI,QAAQ,KACvD,MAAM,2BAA2B,IAAI,GACrC;AACA,WAAO,EAAE,yCAAyC;AAAA,EACpD;AACA,SAAO;AACT;",
6
+ "names": []
7
+ }
@@ -0,0 +1,45 @@
1
+ import { failedUploadsStorageKey } from "./portalSupport.js";
2
+ const retryableFilesByTicket = /* @__PURE__ */ new Map();
3
+ function rememberFailedUploads(ticketId, files) {
4
+ if (!files.length) return;
5
+ const current = retryableFilesByTicket.get(ticketId) ?? /* @__PURE__ */ new Map();
6
+ for (const file of files) current.set(file.name, file);
7
+ retryableFilesByTicket.set(ticketId, current);
8
+ writeFailedUploadNames(ticketId, [.../* @__PURE__ */ new Set([...readFailedUploadNames(ticketId), ...files.map((file) => file.name)])]);
9
+ }
10
+ function getRetryableUpload(ticketId, fileName) {
11
+ return retryableFilesByTicket.get(ticketId)?.get(fileName) ?? null;
12
+ }
13
+ function forgetFailedUpload(ticketId, fileName) {
14
+ const files = retryableFilesByTicket.get(ticketId);
15
+ files?.delete(fileName);
16
+ if (files && files.size === 0) retryableFilesByTicket.delete(ticketId);
17
+ const remaining = readFailedUploadNames(ticketId).filter((name) => name !== fileName);
18
+ writeFailedUploadNames(ticketId, remaining);
19
+ return remaining;
20
+ }
21
+ function readFailedUploadNames(ticketId) {
22
+ try {
23
+ const raw = window.sessionStorage.getItem(failedUploadsStorageKey(ticketId));
24
+ if (!raw) return [];
25
+ const parsed = JSON.parse(raw);
26
+ return Array.isArray(parsed) ? parsed.filter((entry) => typeof entry === "string") : [];
27
+ } catch {
28
+ return [];
29
+ }
30
+ }
31
+ function writeFailedUploadNames(ticketId, names) {
32
+ try {
33
+ if (names.length) window.sessionStorage.setItem(failedUploadsStorageKey(ticketId), JSON.stringify(names));
34
+ else window.sessionStorage.removeItem(failedUploadsStorageKey(ticketId));
35
+ } catch {
36
+ return;
37
+ }
38
+ }
39
+ export {
40
+ forgetFailedUpload,
41
+ getRetryableUpload,
42
+ readFailedUploadNames,
43
+ rememberFailedUploads
44
+ };
45
+ //# sourceMappingURL=portalPendingUploads.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/modules/staff/lib/support-ui/portalPendingUploads.ts"],
4
+ "sourcesContent": ["import { failedUploadsStorageKey } from './portalSupport'\n\n/**\n * Files that failed to upload while filing a ticket. The names survive a reload in\n * `sessionStorage`; the `File` objects themselves can only live in memory, so a retry\n * is possible for files picked in this browser session and a re-attach otherwise.\n */\nconst retryableFilesByTicket = new Map<string, Map<string, File>>()\n\nexport function rememberFailedUploads(ticketId: string, files: readonly File[]): void {\n if (!files.length) return\n const current = retryableFilesByTicket.get(ticketId) ?? new Map<string, File>()\n for (const file of files) current.set(file.name, file)\n retryableFilesByTicket.set(ticketId, current)\n writeFailedUploadNames(ticketId, [...new Set([...readFailedUploadNames(ticketId), ...files.map((file) => file.name)])])\n}\n\nexport function getRetryableUpload(ticketId: string, fileName: string): File | null {\n return retryableFilesByTicket.get(ticketId)?.get(fileName) ?? null\n}\n\nexport function forgetFailedUpload(ticketId: string, fileName: string): string[] {\n const files = retryableFilesByTicket.get(ticketId)\n files?.delete(fileName)\n if (files && files.size === 0) retryableFilesByTicket.delete(ticketId)\n const remaining = readFailedUploadNames(ticketId).filter((name) => name !== fileName)\n writeFailedUploadNames(ticketId, remaining)\n return remaining\n}\n\nexport function readFailedUploadNames(ticketId: string): string[] {\n try {\n const raw = window.sessionStorage.getItem(failedUploadsStorageKey(ticketId))\n if (!raw) return []\n const parsed: unknown = JSON.parse(raw)\n return Array.isArray(parsed) ? parsed.filter((entry): entry is string => typeof entry === 'string') : []\n } catch {\n return []\n }\n}\n\nfunction writeFailedUploadNames(ticketId: string, names: readonly string[]): void {\n try {\n if (names.length) window.sessionStorage.setItem(failedUploadsStorageKey(ticketId), JSON.stringify(names))\n else window.sessionStorage.removeItem(failedUploadsStorageKey(ticketId))\n } catch {\n return\n }\n}\n"],
5
+ "mappings": "AAAA,SAAS,+BAA+B;AAOxC,MAAM,yBAAyB,oBAAI,IAA+B;AAE3D,SAAS,sBAAsB,UAAkB,OAA8B;AACpF,MAAI,CAAC,MAAM,OAAQ;AACnB,QAAM,UAAU,uBAAuB,IAAI,QAAQ,KAAK,oBAAI,IAAkB;AAC9E,aAAW,QAAQ,MAAO,SAAQ,IAAI,KAAK,MAAM,IAAI;AACrD,yBAAuB,IAAI,UAAU,OAAO;AAC5C,yBAAuB,UAAU,CAAC,GAAG,oBAAI,IAAI,CAAC,GAAG,sBAAsB,QAAQ,GAAG,GAAG,MAAM,IAAI,CAAC,SAAS,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC;AACxH;AAEO,SAAS,mBAAmB,UAAkB,UAA+B;AAClF,SAAO,uBAAuB,IAAI,QAAQ,GAAG,IAAI,QAAQ,KAAK;AAChE;AAEO,SAAS,mBAAmB,UAAkB,UAA4B;AAC/E,QAAM,QAAQ,uBAAuB,IAAI,QAAQ;AACjD,SAAO,OAAO,QAAQ;AACtB,MAAI,SAAS,MAAM,SAAS,EAAG,wBAAuB,OAAO,QAAQ;AACrE,QAAM,YAAY,sBAAsB,QAAQ,EAAE,OAAO,CAAC,SAAS,SAAS,QAAQ;AACpF,yBAAuB,UAAU,SAAS;AAC1C,SAAO;AACT;AAEO,SAAS,sBAAsB,UAA4B;AAChE,MAAI;AACF,UAAM,MAAM,OAAO,eAAe,QAAQ,wBAAwB,QAAQ,CAAC;AAC3E,QAAI,CAAC,IAAK,QAAO,CAAC;AAClB,UAAM,SAAkB,KAAK,MAAM,GAAG;AACtC,WAAO,MAAM,QAAQ,MAAM,IAAI,OAAO,OAAO,CAAC,UAA2B,OAAO,UAAU,QAAQ,IAAI,CAAC;AAAA,EACzG,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAEA,SAAS,uBAAuB,UAAkB,OAAgC;AAChF,MAAI;AACF,QAAI,MAAM,OAAQ,QAAO,eAAe,QAAQ,wBAAwB,QAAQ,GAAG,KAAK,UAAU,KAAK,CAAC;AAAA,QACnG,QAAO,eAAe,WAAW,wBAAwB,QAAQ,CAAC;AAAA,EACzE,QAAQ;AACN;AAAA,EACF;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,51 @@
1
+ const PORTAL_SUPPORT_UPDATED_EVENT = "staff.support_ticket.portal_updated";
2
+ const PORTAL_SUPPORT_NOT_LINKED_CODE = "customer_account_not_linked";
3
+ function isPortalSupportAccountNotLinked(call) {
4
+ if (call.status !== 403 || !call.result || typeof call.result !== "object") return false;
5
+ return call.result.code === PORTAL_SUPPORT_NOT_LINKED_CODE;
6
+ }
7
+ const PORTAL_SUPPORT_STATUS_VARIANTS = {
8
+ received: "info",
9
+ in_progress: "warning",
10
+ resolved: "success"
11
+ };
12
+ function portalSupportStatusLabel(t, status) {
13
+ if (status === "resolved") return t("staff.portal.support.status.resolved", "Resolved");
14
+ if (status === "in_progress") return t("staff.portal.support.status.inProgress", "In progress");
15
+ return t("staff.portal.support.status.received", "Received");
16
+ }
17
+ function formatPortalDate(value, locale) {
18
+ if (!value) return "";
19
+ const date = new Date(value);
20
+ if (Number.isNaN(date.getTime())) return "";
21
+ return date.toLocaleString(locale, { dateStyle: "medium", timeStyle: "short" });
22
+ }
23
+ function formatFileSize(t, bytes) {
24
+ if (!Number.isFinite(bytes) || bytes <= 0) return t("staff.portal.support.fileSize.bytes", "{size} B", { size: 0 });
25
+ if (bytes < 1024) return t("staff.portal.support.fileSize.bytes", "{size} B", { size: bytes });
26
+ if (bytes < 1024 * 1024) {
27
+ return t("staff.portal.support.fileSize.kilobytes", "{size} KB", { size: (bytes / 1024).toFixed(1) });
28
+ }
29
+ return t("staff.portal.support.fileSize.megabytes", "{size} MB", { size: (bytes / (1024 * 1024)).toFixed(1) });
30
+ }
31
+ function failedUploadsStorageKey(ticketId) {
32
+ return `om:staff:support:failed-uploads:${ticketId}`;
33
+ }
34
+ function readPortalTicketIdFromEvent(event) {
35
+ const payload = event?.payload;
36
+ if (!payload || typeof payload !== "object") return null;
37
+ const candidate = payload.ticketId ?? payload.id;
38
+ return typeof candidate === "string" ? candidate : null;
39
+ }
40
+ export {
41
+ PORTAL_SUPPORT_NOT_LINKED_CODE,
42
+ PORTAL_SUPPORT_STATUS_VARIANTS,
43
+ PORTAL_SUPPORT_UPDATED_EVENT,
44
+ failedUploadsStorageKey,
45
+ formatFileSize,
46
+ formatPortalDate,
47
+ isPortalSupportAccountNotLinked,
48
+ portalSupportStatusLabel,
49
+ readPortalTicketIdFromEvent
50
+ };
51
+ //# sourceMappingURL=portalSupport.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/modules/staff/lib/support-ui/portalSupport.ts"],
4
+ "sourcesContent": ["import type { StatusBadgeVariant } from '@open-mercato/ui/primitives/status-badge'\nimport type { TranslateFn } from '@open-mercato/shared/lib/i18n/context'\n\nexport type PortalSupportStatus = 'received' | 'in_progress' | 'resolved'\n\nexport type PortalSupportTicketListItem = {\n id: string\n supportReference: string\n subject: string\n category: { value: string; label: string }\n portalStatus: PortalSupportStatus\n createdAt: string | null\n updatedAt: string | null\n lastActivityAt: string | null\n}\n\nexport type PortalSupportTicketList = {\n items?: PortalSupportTicketListItem[]\n total?: number\n page?: number\n pageSize?: number\n totalPages?: number\n}\n\nexport type PortalSupportOptions = {\n categories?: Array<{ value: string; label: string }>\n canCreate?: boolean\n maxUploadBytes?: number | null\n}\n\nexport type PortalSupportMessage = {\n id: string\n authorKind: 'staff' | 'customer'\n authorName: string | null\n isSelf?: boolean\n body: string\n createdAt: string | null\n}\n\nexport type PortalSupportTicketDetail = {\n id: string\n supportReference: string\n subject: string\n description: string\n category: { value: string; label: string }\n portalStatus: PortalSupportStatus\n createdAt: string | null\n updatedAt: string | null\n canReply: boolean\n requester?: { name: string | null; isSelf: boolean }\n messages: PortalSupportMessage[]\n attachments?: PortalSupportAttachment[]\n}\n\nexport type PortalSupportAttachment = {\n id: string\n fileName: string\n fileSize: number\n mimeType: string | null\n downloadUrl: string\n uploadedByCustomer: boolean\n canDelete: boolean\n createdAt: string | null\n}\n\nexport const PORTAL_SUPPORT_UPDATED_EVENT = 'staff.support_ticket.portal_updated'\n\nexport const PORTAL_SUPPORT_NOT_LINKED_CODE = 'customer_account_not_linked'\n\n/**\n * A portal account without a company link can never see or file tickets, so the\n * support screens explain that instead of offering a retry that cannot succeed.\n */\nexport function isPortalSupportAccountNotLinked(call: { status: number; result?: unknown }): boolean {\n if (call.status !== 403 || !call.result || typeof call.result !== 'object') return false\n return (call.result as { code?: unknown }).code === PORTAL_SUPPORT_NOT_LINKED_CODE\n}\n\nexport const PORTAL_SUPPORT_STATUS_VARIANTS: Record<PortalSupportStatus, StatusBadgeVariant> = {\n received: 'info',\n in_progress: 'warning',\n resolved: 'success',\n}\n\nexport function portalSupportStatusLabel(t: TranslateFn, status: PortalSupportStatus): string {\n if (status === 'resolved') return t('staff.portal.support.status.resolved', 'Resolved')\n if (status === 'in_progress') return t('staff.portal.support.status.inProgress', 'In progress')\n return t('staff.portal.support.status.received', 'Received')\n}\n\nexport function formatPortalDate(value: string | null | undefined, locale?: string): string {\n if (!value) return ''\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) return ''\n return date.toLocaleString(locale, { dateStyle: 'medium', timeStyle: 'short' })\n}\n\nexport function formatFileSize(t: TranslateFn, bytes: number): string {\n if (!Number.isFinite(bytes) || bytes <= 0) return t('staff.portal.support.fileSize.bytes', '{size} B', { size: 0 })\n if (bytes < 1024) return t('staff.portal.support.fileSize.bytes', '{size} B', { size: bytes })\n if (bytes < 1024 * 1024) {\n return t('staff.portal.support.fileSize.kilobytes', '{size} KB', { size: (bytes / 1024).toFixed(1) })\n }\n return t('staff.portal.support.fileSize.megabytes', '{size} MB', { size: (bytes / (1024 * 1024)).toFixed(1) })\n}\n\nexport function failedUploadsStorageKey(ticketId: string): string {\n return `om:staff:support:failed-uploads:${ticketId}`\n}\n\nexport function readPortalTicketIdFromEvent(event: { payload?: Record<string, unknown> } | null | undefined): string | null {\n const payload = event?.payload\n if (!payload || typeof payload !== 'object') return null\n const candidate = payload.ticketId ?? payload.id\n return typeof candidate === 'string' ? candidate : null\n}\n"],
5
+ "mappings": "AAiEO,MAAM,+BAA+B;AAErC,MAAM,iCAAiC;AAMvC,SAAS,gCAAgC,MAAqD;AACnG,MAAI,KAAK,WAAW,OAAO,CAAC,KAAK,UAAU,OAAO,KAAK,WAAW,SAAU,QAAO;AACnF,SAAQ,KAAK,OAA8B,SAAS;AACtD;AAEO,MAAM,iCAAkF;AAAA,EAC7F,UAAU;AAAA,EACV,aAAa;AAAA,EACb,UAAU;AACZ;AAEO,SAAS,yBAAyB,GAAgB,QAAqC;AAC5F,MAAI,WAAW,WAAY,QAAO,EAAE,wCAAwC,UAAU;AACtF,MAAI,WAAW,cAAe,QAAO,EAAE,0CAA0C,aAAa;AAC9F,SAAO,EAAE,wCAAwC,UAAU;AAC7D;AAEO,SAAS,iBAAiB,OAAkC,QAAyB;AAC1F,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,OAAO,IAAI,KAAK,KAAK;AAC3B,MAAI,OAAO,MAAM,KAAK,QAAQ,CAAC,EAAG,QAAO;AACzC,SAAO,KAAK,eAAe,QAAQ,EAAE,WAAW,UAAU,WAAW,QAAQ,CAAC;AAChF;AAEO,SAAS,eAAe,GAAgB,OAAuB;AACpE,MAAI,CAAC,OAAO,SAAS,KAAK,KAAK,SAAS,EAAG,QAAO,EAAE,uCAAuC,YAAY,EAAE,MAAM,EAAE,CAAC;AAClH,MAAI,QAAQ,KAAM,QAAO,EAAE,uCAAuC,YAAY,EAAE,MAAM,MAAM,CAAC;AAC7F,MAAI,QAAQ,OAAO,MAAM;AACvB,WAAO,EAAE,2CAA2C,aAAa,EAAE,OAAO,QAAQ,MAAM,QAAQ,CAAC,EAAE,CAAC;AAAA,EACtG;AACA,SAAO,EAAE,2CAA2C,aAAa,EAAE,OAAO,SAAS,OAAO,OAAO,QAAQ,CAAC,EAAE,CAAC;AAC/G;AAEO,SAAS,wBAAwB,UAA0B;AAChE,SAAO,mCAAmC,QAAQ;AACpD;AAEO,SAAS,4BAA4B,OAAgF;AAC1H,QAAM,UAAU,OAAO;AACvB,MAAI,CAAC,WAAW,OAAO,YAAY,SAAU,QAAO;AACpD,QAAM,YAAY,QAAQ,YAAY,QAAQ;AAC9C,SAAO,OAAO,cAAc,WAAW,YAAY;AACrD;",
6
+ "names": []
7
+ }