@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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/modules/staff/extension-points.ts"],
4
- "sourcesContent": ["import {\n componentExtensionHost,\n crudFormExtensionHost,\n dataTableExtensionHost,\n defineModuleExtensionPoints,\n injectionExtensionHost,\n} from '@open-mercato/shared/modules/widgets/extension-points'\n\n/**\n * Time-tracking host catalog (EP-01 of\n * `.ai/specs/2026-08-24-time-tracking-umes-extension-points.md`).\n *\n * Two id conventions meet here and neither is negotiable:\n *\n * - `crud-form:` hosts carry the **dot** form of the entity id\n * (`staff.staff_time_project`). `CrudForm` derives its own spot id from\n * `entityIds` by replacing every colon with a dot, so the colon form would\n * name a spot no widget is ever loaded for.\n * - `detail:` hosts carry the **colon** form (`detail:staff:staff_time_task:header`).\n * Nothing derives those ids; the host picks them, and these are the ids the\n * spec froze.\n *\n * Every id below is a FROZEN contract surface once shipped \u2014 see\n * `BACKWARD_COMPATIBILITY.md`.\n */\n\nconst detailHost = (spotId: string, source: string) => injectionExtensionHost({\n family: 'detail',\n spotId,\n supported: ['render-widget'],\n contextContract: 'staff.time_tracking.detail.v1',\n scopeContract: 'tenant+organization',\n source,\n})\n\nconst pageHost = (spotId: string, source: string) => injectionExtensionHost({\n family: 'generic',\n spotId,\n supported: ['render-widget'],\n contextContract: 'staff.time_tracking.page.v1',\n scopeContract: 'tenant+organization',\n source,\n})\n\n/**\n * EP-32\u2026EP-41. Each registry is a `specialized-registry` host: contributions\n * arrive through the module's own `register*` function rather than through the\n * widget registries, which is what `registry-contribution` names.\n *\n * `runtimeContract` is the id of the built-in strategy the registry ships with.\n * It is the guarantee that makes this whole group additive: with no contribution\n * the built-in is the only candidate, and it is the same code the module ran\n * before the registry existed.\n */\nconst registryHost = (\n spotId: string,\n source: string,\n contextContract: string,\n builtInId: string,\n) => injectionExtensionHost({\n family: 'specialized-registry',\n spotId,\n supported: ['registry-contribution'],\n contextContract,\n scopeContract: 'tenant+organization',\n runtimeContract: builtInId,\n source,\n})\n\nexport const extensionPoints = defineModuleExtensionPoints({\n moduleId: 'staff',\n hosts: {\n timeEntriesTable: dataTableExtensionHost({\n tableId: 'staff.time_entries.list',\n source: 'backend/staff/time-tracking/entries/page.tsx',\n }),\n timeProjectsTable: dataTableExtensionHost({\n tableId: 'staff.time_projects.list',\n source: 'backend/staff/time-tracking/projects/page.tsx',\n }),\n timeReportsTable: dataTableExtensionHost({\n tableId: 'staff.time_reports.list',\n source: 'backend/staff/time-tracking/reports/page.tsx',\n }),\n myWorkTodayEntriesTable: dataTableExtensionHost({\n tableId: 'staff.my_work.today_entries.list',\n source: 'backend/staff/time-tracking/page.tsx',\n }),\n projectDetailTimeEntriesTable: dataTableExtensionHost({\n tableId: 'staff.time_project_detail.entries.list',\n source: 'backend/staff/time-tracking/projects/[id]/page.tsx',\n }),\n reportDetailLockedEntriesTable: dataTableExtensionHost({\n tableId: 'staff.time_report_detail.locked_entries.list',\n source: 'backend/staff/time-tracking/reports/[id]/page.tsx',\n }),\n taskBoardListViewTable: dataTableExtensionHost({\n tableId: 'staff.time_task_board.list_view',\n source: 'lib/time-tracking-ui/TaskListView.tsx',\n }),\n\n projectForm: crudFormExtensionHost({\n entityId: 'staff.staff_time_project',\n spotId: 'crud-form:staff.staff_time_project',\n source: 'backend/staff/time-tracking/projects/create/page.tsx',\n }),\n timeEntryForm: crudFormExtensionHost({\n entityId: 'staff.staff_time_entry',\n spotId: 'crud-form:staff.staff_time_entry',\n source: 'lib/time-tracking-ui/TimeEntryDialog.tsx',\n }),\n taskForm: crudFormExtensionHost({\n entityId: 'staff.staff_time_task',\n spotId: 'crud-form:staff.staff_time_task',\n source: 'lib/time-tracking-ui/NewTaskDialog.tsx',\n }),\n reportForm: crudFormExtensionHost({\n entityId: 'staff.staff_time_report',\n spotId: 'crud-form:staff.staff_time_report',\n source: 'backend/staff/time-tracking/reports/create/page.tsx',\n }),\n\n projectDetailHeader: detailHost('detail:staff:staff_time_project:header', 'backend/staff/time-tracking/projects/[id]/page.tsx'),\n projectDetailStatusBadges: detailHost('detail:staff:staff_time_project:status-badges', 'backend/staff/time-tracking/projects/[id]/page.tsx'),\n projectDetailTabs: detailHost('detail:staff:staff_time_project:tabs', 'backend/staff/time-tracking/projects/[id]/page.tsx'),\n projectDetailSidebar: detailHost('detail:staff:staff_time_project:sidebar', 'backend/staff/time-tracking/projects/[id]/page.tsx'),\n projectDetailFooter: detailHost('detail:staff:staff_time_project:footer', 'backend/staff/time-tracking/projects/[id]/page.tsx'),\n\n taskDetailHeader: detailHost('detail:staff:staff_time_task:header', 'lib/time-tracking-ui/TaskDrawer.tsx'),\n taskDetailStatusBadges: detailHost('detail:staff:staff_time_task:status-badges', 'lib/time-tracking-ui/TaskDrawer.tsx'),\n taskDetailTabs: detailHost('detail:staff:staff_time_task:tabs', 'lib/time-tracking-ui/TaskDrawer.tsx'),\n taskDetailSidebar: detailHost('detail:staff:staff_time_task:sidebar', 'lib/time-tracking-ui/TaskDrawer.tsx'),\n taskDetailFooter: detailHost('detail:staff:staff_time_task:footer', 'lib/time-tracking-ui/TaskDrawer.tsx'),\n\n reportDetailHeader: detailHost('detail:staff:staff_time_report:header', 'backend/staff/time-tracking/reports/[id]/page.tsx'),\n reportDetailStatusBadges: detailHost('detail:staff:staff_time_report:status-badges', 'backend/staff/time-tracking/reports/[id]/page.tsx'),\n reportDetailFooter: detailHost('detail:staff:staff_time_report:footer', 'backend/staff/time-tracking/reports/[id]/page.tsx'),\n\n reportSheetBeforeLines: pageHost('staff.time_report.sheet:before-lines', 'lib/time-tracking-ui/ReportSheet.tsx'),\n reportSheetAfterTotals: pageHost('staff.time_report.sheet:after-totals', 'lib/time-tracking-ui/ReportSheet.tsx'),\n\n timesheetToolbar: pageHost('staff.timesheet:toolbar', 'backend/staff/time-tracking/timesheet/page.tsx'),\n timesheetPeriodFooter: pageHost('staff.timesheet:period-footer', 'lib/time-tracking-ui/TimesheetPeriodFooter.tsx'),\n timesheetDayCellActions: pageHost('staff.timesheet:day-cell-actions', 'lib/time-tracking-ui/TimesheetCalendar.tsx'),\n\n taskBoardToolbar: pageHost('staff.time_task.board:toolbar', 'lib/time-tracking-ui/KanbanBoard.tsx'),\n taskBoardColumnHeader: pageHost('staff.time_task.board:column-header', 'lib/time-tracking-ui/KanbanColumn.tsx'),\n taskBoardCardBadges: pageHost('staff.time_task.board:card-badges', 'lib/time-tracking-ui/KanbanCard.tsx'),\n taskBoardCardFooter: pageHost('staff.time_task.board:card-footer', 'lib/time-tracking-ui/KanbanCard.tsx'),\n\n myWorkBeforeSections: pageHost('staff.my_work:before-sections', 'backend/staff/time-tracking/page.tsx'),\n myWorkAfterSections: pageHost('staff.my_work:after-sections', 'backend/staff/time-tracking/page.tsx'),\n\n timeTrackingSettingsSections: pageHost('staff.time_tracking.settings:sections', 'backend/staff/time-tracking/settings/page.tsx'),\n\n timerBarActions: pageHost('staff.timesheets.timer-bar:actions', 'lib/timesheets-ui/TimerBar.tsx'),\n\n /**\n * EP-50. The two customer-portal spots. `scopeContract` names the third\n * dimension the backoffice spots do not have: a portal context is scoped by\n * the signed-in **customer** as well as the tenant and organization, and the\n * host passes no money and no staff identity into either spot.\n */\n portalTimeReportBefore: injectionExtensionHost({\n family: 'portal-page',\n spotId: 'portal:staff.time_report:before',\n supported: ['render-widget'],\n contextContract: 'staff.time_tracking.portal.report.v1',\n scopeContract: 'tenant.organization.customer-session',\n source: 'frontend/[orgSlug]/portal/time-reports/[id]/page.tsx',\n }),\n portalTimeReportAfter: injectionExtensionHost({\n family: 'portal-page',\n spotId: 'portal:staff.time_report:after',\n supported: ['render-widget'],\n contextContract: 'staff.time_tracking.portal.report.v1',\n scopeContract: 'tenant.organization.customer-session',\n source: 'frontend/[orgSlug]/portal/time-reports/[id]/page.tsx',\n }),\n\n timeRoundingRegistry: registryHost(\n 'staff.time_tracking.rounding',\n 'lib/time-tracking/rounding.ts',\n 'staff.time_tracking.rounding.v1',\n 'staff.time_tracking.rounding.unit',\n ),\n timeRateRegistry: registryHost(\n 'staff.time_tracking.rate',\n 'lib/time-tracking/cost.ts',\n 'staff.time_tracking.rate.v1',\n 'staff.time_tracking.rate.entry_override_then_project',\n ),\n timeBillabilityRegistry: registryHost(\n 'staff.time_tracking.billability',\n 'lib/time-tracking/billability.ts',\n 'staff.time_tracking.billability.v1',\n 'staff.time_tracking.billability.project_then_tenant',\n ),\n reportExportFormatRegistry: registryHost(\n 'staff.time_tracking.report_export_format',\n 'lib/timesheets-reports/reportExportFormats.ts',\n 'staff.time_tracking.report_export_format.v1',\n 'pdf',\n ),\n reportGroupingRegistry: registryHost(\n 'staff.time_tracking.report_grouping',\n 'lib/timesheets-reports/reportGroupings.ts',\n 'staff.time_tracking.report_grouping.v1',\n 'project_task',\n ),\n timeEntrySourceRegistry: registryHost(\n 'staff.time_tracking.time_entry_source',\n 'lib/time-tracking/timeEntrySources.ts',\n 'staff.time_tracking.time_entry_source.v1',\n 'manual',\n ),\n overlapPolicyRegistry: registryHost(\n 'staff.time_tracking.overlap_policy',\n 'lib/time-tracking/overlap.ts',\n 'staff.time_tracking.overlap_policy.v1',\n 'staff.time_tracking.overlap.warn_when_enabled',\n ),\n projectCodeGeneratorRegistry: registryHost(\n 'staff.time_tracking.project_code_generator',\n 'lib/time-tracking/projectCode.ts',\n 'staff.time_tracking.project_code_generator.v1',\n 'staff.time_tracking.project_code.initials',\n ),\n capacityProviderRegistry: registryHost(\n 'staff.time_tracking.capacity_provider',\n 'lib/time-tracking/capacity.ts',\n 'staff.time_tracking.capacity_provider.v1',\n 'staff.time_tracking.capacity.flat_daily_hours',\n ),\n reportApprovalPolicyRegistry: registryHost(\n 'staff.time_tracking.report_approval_policy',\n 'lib/timesheets-reports/reportApprovalPolicies.ts',\n 'staff.time_tracking.report_approval_policy.v1',\n 'staff.time_tracking.report_approval.acl_only',\n ),\n /**\n * EP-51. The recalculation registry: a hook is a tenant-wide restatement of\n * derived time-tracking values, run by the existing queue worker under the\n * existing `ProgressJob`. The built-in is the retro-rounding pass the module\n * already shipped, so a deployment with no contribution runs exactly what it\n * ran before.\n */\n recalculationRegistry: registryHost(\n 'staff.time_tracking.recalculation',\n 'lib/time-tracking/recalculations.ts',\n 'staff.time_tracking.recalculation.v1',\n 'staff.time_tracking.recalculation.rounding',\n ),\n\n /**\n * EP-42. The one registry whose scope contract is `tenant` rather than\n * `tenant+organization`: time-tracking settings are tenant-global by spec \u00A710,\n * stored through `ModuleConfigService` with `organization_id` null, and a\n * contributed key inherits that scope rather than choosing its own.\n */\n settingKeyRegistry: injectionExtensionHost({\n family: 'specialized-registry',\n spotId: 'staff.time_tracking.setting_key',\n supported: ['registry-contribution'],\n contextContract: 'staff.time_tracking.setting_key.v1',\n scopeContract: 'tenant',\n runtimeContract: 'rounding.unitMinutes',\n source: 'lib/time-tracking/settingKeys.ts',\n }),\n\n timeEntryDialogComponent: componentExtensionHost({\n componentId: 'staff.time_entry_dialog',\n propsContract: 'staff.time_entry_dialog.props.v1',\n source: 'lib/time-tracking-ui/TimeEntryDialog.tsx',\n }),\n timerBarComponent: componentExtensionHost({\n componentId: 'staff.timer_bar',\n propsContract: 'staff.timer_bar.props.v1',\n source: 'lib/timesheets-ui/TimerBar.tsx',\n }),\n kanbanCardComponent: componentExtensionHost({\n componentId: 'staff.kanban_card',\n propsContract: 'staff.kanban_card.props.v1',\n source: 'lib/time-tracking-ui/KanbanCard.tsx',\n }),\n kanbanColumnComponent: componentExtensionHost({\n componentId: 'staff.kanban_column',\n propsContract: 'staff.kanban_column.props.v1',\n source: 'lib/time-tracking-ui/KanbanColumn.tsx',\n }),\n timesheetGridComponent: componentExtensionHost({\n componentId: 'staff.timesheet_grid',\n propsContract: 'staff.timesheet_grid.props.v1',\n source: 'backend/staff/time-tracking/timesheet/GridView.tsx',\n }),\n timesheetListComponent: componentExtensionHost({\n componentId: 'staff.timesheet_list',\n propsContract: 'staff.timesheet_list.props.v1',\n source: 'lib/timesheets-ui/ListView.tsx',\n }),\n timesheetCalendarComponent: componentExtensionHost({\n componentId: 'staff.timesheet_calendar',\n propsContract: 'staff.timesheet_calendar.props.v1',\n source: 'lib/time-tracking-ui/TimesheetCalendar.tsx',\n }),\n reportSheetComponent: componentExtensionHost({\n componentId: 'staff.report_sheet',\n propsContract: 'staff.report_sheet.props.v1',\n source: 'lib/time-tracking-ui/ReportSheet.tsx',\n }),\n projectCardComponent: componentExtensionHost({\n componentId: 'staff.project_card',\n propsContract: 'staff.project_card.props.v1',\n source: 'lib/timesheets-projects-ui/ProjectCard.tsx',\n }),\n entriesSummaryFooterComponent: componentExtensionHost({\n componentId: 'staff.entries_summary_footer',\n propsContract: 'staff.entries_summary_footer.props.v1',\n source: 'lib/time-tracking-ui/TimeEntriesSummaryFooter.tsx',\n }),\n },\n})\n\nexport default extensionPoints\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import {\n componentExtensionHost,\n crudFormExtensionHost,\n dataTableExtensionHost,\n defineModuleExtensionPoints,\n injectionExtensionHost,\n} from '@open-mercato/shared/modules/widgets/extension-points'\n\n/**\n * Time-tracking host catalog (EP-01 of\n * `.ai/specs/2026-08-24-time-tracking-umes-extension-points.md`).\n *\n * Two id conventions meet here and neither is negotiable:\n *\n * - `crud-form:` hosts carry the **dot** form of the entity id\n * (`staff.staff_time_project`). `CrudForm` derives its own spot id from\n * `entityIds` by replacing every colon with a dot, so the colon form would\n * name a spot no widget is ever loaded for.\n * - `detail:` hosts carry the **colon** form (`detail:staff:staff_time_task:header`).\n * Nothing derives those ids; the host picks them, and these are the ids the\n * spec froze.\n *\n * Every id below is a FROZEN contract surface once shipped \u2014 see\n * `BACKWARD_COMPATIBILITY.md`.\n */\n\nconst detailHost = (spotId: string, source: string) => injectionExtensionHost({\n family: 'detail',\n spotId,\n supported: ['render-widget'],\n contextContract: 'staff.time_tracking.detail.v1',\n scopeContract: 'tenant+organization',\n source,\n})\n\nconst pageHost = (spotId: string, source: string) => injectionExtensionHost({\n family: 'generic',\n spotId,\n supported: ['render-widget'],\n contextContract: 'staff.time_tracking.page.v1',\n scopeContract: 'tenant+organization',\n source,\n})\n\n/**\n * EP-32\u2026EP-41. Each registry is a `specialized-registry` host: contributions\n * arrive through the module's own `register*` function rather than through the\n * widget registries, which is what `registry-contribution` names.\n *\n * `runtimeContract` is the id of the built-in strategy the registry ships with.\n * It is the guarantee that makes this whole group additive: with no contribution\n * the built-in is the only candidate, and it is the same code the module ran\n * before the registry existed.\n */\nconst registryHost = (\n spotId: string,\n source: string,\n contextContract: string,\n builtInId: string,\n) => injectionExtensionHost({\n family: 'specialized-registry',\n spotId,\n supported: ['registry-contribution'],\n contextContract,\n scopeContract: 'tenant+organization',\n runtimeContract: builtInId,\n source,\n})\n\nexport const extensionPoints = defineModuleExtensionPoints({\n moduleId: 'staff',\n hosts: {\n timeEntriesTable: dataTableExtensionHost({\n tableId: 'staff.time_entries.list',\n source: 'backend/staff/time-tracking/entries/page.tsx',\n }),\n timeProjectsTable: dataTableExtensionHost({\n tableId: 'staff.time_projects.list',\n source: 'backend/staff/time-tracking/projects/page.tsx',\n }),\n timeReportsTable: dataTableExtensionHost({\n tableId: 'staff.time_reports.list',\n source: 'backend/staff/time-tracking/reports/page.tsx',\n }),\n myWorkTodayEntriesTable: dataTableExtensionHost({\n tableId: 'staff.my_work.today_entries.list',\n source: 'backend/staff/time-tracking/page.tsx',\n }),\n projectDetailTimeEntriesTable: dataTableExtensionHost({\n tableId: 'staff.time_project_detail.entries.list',\n source: 'backend/staff/time-tracking/projects/[id]/page.tsx',\n }),\n reportDetailLockedEntriesTable: dataTableExtensionHost({\n tableId: 'staff.time_report_detail.locked_entries.list',\n source: 'backend/staff/time-tracking/reports/[id]/page.tsx',\n }),\n taskBoardListViewTable: dataTableExtensionHost({\n tableId: 'staff.time_task_board.list_view',\n source: 'lib/time-tracking-ui/TaskListView.tsx',\n }),\n\n projectForm: crudFormExtensionHost({\n entityId: 'staff.staff_time_project',\n spotId: 'crud-form:staff.staff_time_project',\n source: 'backend/staff/time-tracking/projects/create/page.tsx',\n }),\n timeEntryForm: crudFormExtensionHost({\n entityId: 'staff.staff_time_entry',\n spotId: 'crud-form:staff.staff_time_entry',\n source: 'lib/time-tracking-ui/TimeEntryDialog.tsx',\n }),\n taskForm: crudFormExtensionHost({\n entityId: 'staff.staff_time_task',\n spotId: 'crud-form:staff.staff_time_task',\n source: 'lib/time-tracking-ui/NewTaskDialog.tsx',\n }),\n reportForm: crudFormExtensionHost({\n entityId: 'staff.staff_time_report',\n spotId: 'crud-form:staff.staff_time_report',\n source: 'backend/staff/time-tracking/reports/create/page.tsx',\n }),\n\n projectDetailHeader: detailHost('detail:staff:staff_time_project:header', 'backend/staff/time-tracking/projects/[id]/page.tsx'),\n projectDetailStatusBadges: detailHost('detail:staff:staff_time_project:status-badges', 'backend/staff/time-tracking/projects/[id]/page.tsx'),\n projectDetailTabs: detailHost('detail:staff:staff_time_project:tabs', 'backend/staff/time-tracking/projects/[id]/page.tsx'),\n projectDetailSidebar: detailHost('detail:staff:staff_time_project:sidebar', 'backend/staff/time-tracking/projects/[id]/page.tsx'),\n projectDetailFooter: detailHost('detail:staff:staff_time_project:footer', 'backend/staff/time-tracking/projects/[id]/page.tsx'),\n\n taskDetailHeader: detailHost('detail:staff:staff_time_task:header', 'lib/time-tracking-ui/TaskDrawer.tsx'),\n taskDetailStatusBadges: detailHost('detail:staff:staff_time_task:status-badges', 'lib/time-tracking-ui/TaskDrawer.tsx'),\n taskDetailTabs: detailHost('detail:staff:staff_time_task:tabs', 'lib/time-tracking-ui/TaskDrawer.tsx'),\n taskDetailSidebar: detailHost('detail:staff:staff_time_task:sidebar', 'lib/time-tracking-ui/TaskDrawer.tsx'),\n taskDetailFooter: detailHost('detail:staff:staff_time_task:footer', 'lib/time-tracking-ui/TaskDrawer.tsx'),\n\n reportDetailHeader: detailHost('detail:staff:staff_time_report:header', 'backend/staff/time-tracking/reports/[id]/page.tsx'),\n reportDetailStatusBadges: detailHost('detail:staff:staff_time_report:status-badges', 'backend/staff/time-tracking/reports/[id]/page.tsx'),\n reportDetailFooter: detailHost('detail:staff:staff_time_report:footer', 'backend/staff/time-tracking/reports/[id]/page.tsx'),\n\n reportSheetBeforeLines: pageHost('staff.time_report.sheet:before-lines', 'lib/time-tracking-ui/ReportSheet.tsx'),\n reportSheetAfterTotals: pageHost('staff.time_report.sheet:after-totals', 'lib/time-tracking-ui/ReportSheet.tsx'),\n\n timesheetToolbar: pageHost('staff.timesheet:toolbar', 'backend/staff/time-tracking/timesheet/page.tsx'),\n timesheetPeriodFooter: pageHost('staff.timesheet:period-footer', 'lib/time-tracking-ui/TimesheetPeriodFooter.tsx'),\n timesheetDayCellActions: pageHost('staff.timesheet:day-cell-actions', 'lib/time-tracking-ui/TimesheetCalendar.tsx'),\n\n taskBoardToolbar: pageHost('staff.time_task.board:toolbar', 'lib/time-tracking-ui/KanbanBoard.tsx'),\n taskBoardColumnHeader: pageHost('staff.time_task.board:column-header', 'lib/time-tracking-ui/KanbanColumn.tsx'),\n taskBoardCardBadges: pageHost('staff.time_task.board:card-badges', 'lib/time-tracking-ui/KanbanCard.tsx'),\n taskBoardCardFooter: pageHost('staff.time_task.board:card-footer', 'lib/time-tracking-ui/KanbanCard.tsx'),\n\n myWorkBeforeSections: pageHost('staff.my_work:before-sections', 'backend/staff/time-tracking/page.tsx'),\n myWorkAfterSections: pageHost('staff.my_work:after-sections', 'backend/staff/time-tracking/page.tsx'),\n\n timeTrackingSettingsSections: pageHost('staff.time_tracking.settings:sections', 'backend/staff/time-tracking/settings/page.tsx'),\n timeTrackingSettingsTabs: pageHost('staff.time_tracking.settings:tabs', 'backend/staff/time-tracking/settings/page.tsx'),\n\n timerBarActions: pageHost('staff.timesheets.timer-bar:actions', 'lib/timesheets-ui/TimerBar.tsx'),\n\n /**\n * EP-50. The two customer-portal spots. `scopeContract` names the third\n * dimension the backoffice spots do not have: a portal context is scoped by\n * the signed-in **customer** as well as the tenant and organization, and the\n * host passes no money and no staff identity into either spot.\n */\n portalTimeReportBefore: injectionExtensionHost({\n family: 'portal-page',\n spotId: 'portal:staff.time_report:before',\n supported: ['render-widget'],\n contextContract: 'staff.time_tracking.portal.report.v1',\n scopeContract: 'tenant.organization.customer-session',\n source: 'frontend/[orgSlug]/portal/time-reports/[id]/page.tsx',\n }),\n portalTimeReportAfter: injectionExtensionHost({\n family: 'portal-page',\n spotId: 'portal:staff.time_report:after',\n supported: ['render-widget'],\n contextContract: 'staff.time_tracking.portal.report.v1',\n scopeContract: 'tenant.organization.customer-session',\n source: 'frontend/[orgSlug]/portal/time-reports/[id]/page.tsx',\n }),\n\n timeRoundingRegistry: registryHost(\n 'staff.time_tracking.rounding',\n 'lib/time-tracking/rounding.ts',\n 'staff.time_tracking.rounding.v1',\n 'staff.time_tracking.rounding.unit',\n ),\n timeRateRegistry: registryHost(\n 'staff.time_tracking.rate',\n 'lib/time-tracking/cost.ts',\n 'staff.time_tracking.rate.v1',\n 'staff.time_tracking.rate.entry_override_then_project',\n ),\n timeBillabilityRegistry: registryHost(\n 'staff.time_tracking.billability',\n 'lib/time-tracking/billability.ts',\n 'staff.time_tracking.billability.v1',\n 'staff.time_tracking.billability.project_then_tenant',\n ),\n reportExportFormatRegistry: registryHost(\n 'staff.time_tracking.report_export_format',\n 'lib/timesheets-reports/reportExportFormats.ts',\n 'staff.time_tracking.report_export_format.v1',\n 'pdf',\n ),\n reportGroupingRegistry: registryHost(\n 'staff.time_tracking.report_grouping',\n 'lib/timesheets-reports/reportGroupings.ts',\n 'staff.time_tracking.report_grouping.v1',\n 'project_task',\n ),\n timeEntrySourceRegistry: registryHost(\n 'staff.time_tracking.time_entry_source',\n 'lib/time-tracking/timeEntrySources.ts',\n 'staff.time_tracking.time_entry_source.v1',\n 'manual',\n ),\n overlapPolicyRegistry: registryHost(\n 'staff.time_tracking.overlap_policy',\n 'lib/time-tracking/overlap.ts',\n 'staff.time_tracking.overlap_policy.v1',\n 'staff.time_tracking.overlap.warn_when_enabled',\n ),\n projectCodeGeneratorRegistry: registryHost(\n 'staff.time_tracking.project_code_generator',\n 'lib/time-tracking/projectCode.ts',\n 'staff.time_tracking.project_code_generator.v1',\n 'staff.time_tracking.project_code.initials',\n ),\n capacityProviderRegistry: registryHost(\n 'staff.time_tracking.capacity_provider',\n 'lib/time-tracking/capacity.ts',\n 'staff.time_tracking.capacity_provider.v1',\n 'staff.time_tracking.capacity.flat_daily_hours',\n ),\n reportApprovalPolicyRegistry: registryHost(\n 'staff.time_tracking.report_approval_policy',\n 'lib/timesheets-reports/reportApprovalPolicies.ts',\n 'staff.time_tracking.report_approval_policy.v1',\n 'staff.time_tracking.report_approval.acl_only',\n ),\n /**\n * EP-51. The recalculation registry: a hook is a tenant-wide restatement of\n * derived time-tracking values, run by the existing queue worker under the\n * existing `ProgressJob`. The built-in is the retro-rounding pass the module\n * already shipped, so a deployment with no contribution runs exactly what it\n * ran before.\n */\n recalculationRegistry: registryHost(\n 'staff.time_tracking.recalculation',\n 'lib/time-tracking/recalculations.ts',\n 'staff.time_tracking.recalculation.v1',\n 'staff.time_tracking.recalculation.rounding',\n ),\n\n /**\n * EP-42. The one registry whose scope contract is `tenant` rather than\n * `tenant+organization`: time-tracking settings are tenant-global by spec \u00A710,\n * stored through `ModuleConfigService` with `organization_id` null, and a\n * contributed key inherits that scope rather than choosing its own.\n */\n settingKeyRegistry: injectionExtensionHost({\n family: 'specialized-registry',\n spotId: 'staff.time_tracking.setting_key',\n supported: ['registry-contribution'],\n contextContract: 'staff.time_tracking.setting_key.v1',\n scopeContract: 'tenant',\n runtimeContract: 'rounding.unitMinutes',\n source: 'lib/time-tracking/settingKeys.ts',\n }),\n\n timeEntryDialogComponent: componentExtensionHost({\n componentId: 'staff.time_entry_dialog',\n propsContract: 'staff.time_entry_dialog.props.v1',\n source: 'lib/time-tracking-ui/TimeEntryDialog.tsx',\n }),\n timerBarComponent: componentExtensionHost({\n componentId: 'staff.timer_bar',\n propsContract: 'staff.timer_bar.props.v1',\n source: 'lib/timesheets-ui/TimerBar.tsx',\n }),\n kanbanCardComponent: componentExtensionHost({\n componentId: 'staff.kanban_card',\n propsContract: 'staff.kanban_card.props.v1',\n source: 'lib/time-tracking-ui/KanbanCard.tsx',\n }),\n kanbanColumnComponent: componentExtensionHost({\n componentId: 'staff.kanban_column',\n propsContract: 'staff.kanban_column.props.v1',\n source: 'lib/time-tracking-ui/KanbanColumn.tsx',\n }),\n timesheetGridComponent: componentExtensionHost({\n componentId: 'staff.timesheet_grid',\n propsContract: 'staff.timesheet_grid.props.v1',\n source: 'backend/staff/time-tracking/timesheet/GridView.tsx',\n }),\n timesheetListComponent: componentExtensionHost({\n componentId: 'staff.timesheet_list',\n propsContract: 'staff.timesheet_list.props.v1',\n source: 'lib/timesheets-ui/ListView.tsx',\n }),\n timesheetCalendarComponent: componentExtensionHost({\n componentId: 'staff.timesheet_calendar',\n propsContract: 'staff.timesheet_calendar.props.v1',\n source: 'lib/time-tracking-ui/TimesheetCalendar.tsx',\n }),\n reportSheetComponent: componentExtensionHost({\n componentId: 'staff.report_sheet',\n propsContract: 'staff.report_sheet.props.v1',\n source: 'lib/time-tracking-ui/ReportSheet.tsx',\n }),\n projectCardComponent: componentExtensionHost({\n componentId: 'staff.project_card',\n propsContract: 'staff.project_card.props.v1',\n source: 'lib/timesheets-projects-ui/ProjectCard.tsx',\n }),\n entriesSummaryFooterComponent: componentExtensionHost({\n componentId: 'staff.entries_summary_footer',\n propsContract: 'staff.entries_summary_footer.props.v1',\n source: 'lib/time-tracking-ui/TimeEntriesSummaryFooter.tsx',\n }),\n },\n})\n\nexport default extensionPoints\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,468 @@
1
+ "use client";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import Link from "next/link";
5
+ import { ArrowLeft, Download, Paperclip, Trash2 } from "lucide-react";
6
+ import { replaceWithPageReload } from "@open-mercato/shared/lib/navigation/pageReload";
7
+ import { useLocale, useT } from "@open-mercato/shared/lib/i18n/context";
8
+ import { Button } from "@open-mercato/ui/primitives/button";
9
+ import { IconButton } from "@open-mercato/ui/primitives/icon-button";
10
+ import { Spinner } from "@open-mercato/ui/primitives/spinner";
11
+ import { StatusBadge } from "@open-mercato/ui/primitives/status-badge";
12
+ import { Textarea } from "@open-mercato/ui/primitives/textarea";
13
+ import { ErrorMessage } from "@open-mercato/ui/backend/detail";
14
+ import { flash } from "@open-mercato/ui/backend/FlashMessages";
15
+ import { useConfirmDialog } from "@open-mercato/ui/backend/confirm-dialog";
16
+ import { useGuardedMutation } from "@open-mercato/ui/backend/injection/useGuardedMutation";
17
+ import { InjectionSpot } from "@open-mercato/ui/backend/injection/InjectionSpot";
18
+ import { apiCall } from "@open-mercato/ui/backend/utils/apiCall";
19
+ import { usePortalContext } from "@open-mercato/ui/portal/PortalContext";
20
+ import { PortalCard } from "@open-mercato/ui/portal/components/PortalCard";
21
+ import { PortalEmptyState } from "@open-mercato/ui/portal/components/PortalEmptyState";
22
+ import { usePortalAppEvent } from "@open-mercato/ui/portal/hooks/usePortalAppEvent";
23
+ import { getTelemetryRuntime } from "@open-mercato/shared/lib/telemetry/runtime";
24
+ import {
25
+ ATTACHMENT_ACCEPT_TYPES,
26
+ resolvePortalMaxUploadBytes,
27
+ validateAttachmentFile
28
+ } from "@open-mercato/core/modules/staff/lib/support-ui/portalAttachmentValidation";
29
+ import {
30
+ forgetFailedUpload,
31
+ getRetryableUpload,
32
+ readFailedUploadNames,
33
+ rememberFailedUploads
34
+ } from "@open-mercato/core/modules/staff/lib/support-ui/portalPendingUploads";
35
+ import {
36
+ PORTAL_SUPPORT_STATUS_VARIANTS,
37
+ PORTAL_SUPPORT_UPDATED_EVENT,
38
+ formatFileSize,
39
+ formatPortalDate,
40
+ isPortalSupportAccountNotLinked,
41
+ portalSupportStatusLabel,
42
+ readPortalTicketIdFromEvent
43
+ } from "@open-mercato/core/modules/staff/lib/support-ui/portalSupport";
44
+ import { PortalSupportNotLinked } from "@open-mercato/core/modules/staff/lib/support-ui/PortalSupportNotLinked";
45
+ const MUTATION_CONTEXT_ID = "staff-portal-support-detail";
46
+ const SPOT_BEFORE = "portal:staff.support_ticket:before";
47
+ const SPOT_AFTER = "portal:staff.support_ticket:after";
48
+ function PortalSupportTicketDetailPage({ params }) {
49
+ const t = useT();
50
+ const locale = useLocale();
51
+ const { auth } = usePortalContext();
52
+ const { user, loading } = auth;
53
+ const guardedMutation = useGuardedMutation({
54
+ contextId: MUTATION_CONTEXT_ID,
55
+ blockedMessage: t("staff.portal.support.blocked", "This action is not available right now.")
56
+ });
57
+ const { confirm, ConfirmDialogElement } = useConfirmDialog();
58
+ const [ticket, setTicket] = React.useState(null);
59
+ const [attachments, setAttachments] = React.useState([]);
60
+ const [failedUploads, setFailedUploads] = React.useState([]);
61
+ const [isLoading, setIsLoading] = React.useState(true);
62
+ const [notFound, setNotFound] = React.useState(false);
63
+ const [notLinked, setNotLinked] = React.useState(false);
64
+ const [error, setError] = React.useState(null);
65
+ const [reply, setReply] = React.useState("");
66
+ const [replying, setReplying] = React.useState(false);
67
+ const [uploading, setUploading] = React.useState(false);
68
+ const [maxUploadBytes, setMaxUploadBytes] = React.useState(() => resolvePortalMaxUploadBytes(null));
69
+ const fileInputRef = React.useRef(null);
70
+ React.useEffect(() => {
71
+ if (!loading && !user) replaceWithPageReload(`/${params.orgSlug}/portal/login`);
72
+ }, [loading, user, params.orgSlug]);
73
+ React.useEffect(() => {
74
+ setFailedUploads(readFailedUploadNames(params.id));
75
+ }, [params.id]);
76
+ React.useEffect(() => {
77
+ if (!user) return;
78
+ let cancelled = false;
79
+ void (async () => {
80
+ try {
81
+ const response = await apiCall("/api/staff/portal/support-tickets/options", { credentials: "include" });
82
+ if (!cancelled && response.ok) setMaxUploadBytes(resolvePortalMaxUploadBytes(response.result?.maxUploadBytes));
83
+ } catch (err) {
84
+ getTelemetryRuntime()?.reportError(err, { module: "staff", code: "staff.portal_support_options_failed" });
85
+ }
86
+ })();
87
+ return () => {
88
+ cancelled = true;
89
+ };
90
+ }, [user]);
91
+ const loadSeqRef = React.useRef(0);
92
+ const load = React.useCallback(async () => {
93
+ if (!user) return;
94
+ const requestSeq = loadSeqRef.current + 1;
95
+ loadSeqRef.current = requestSeq;
96
+ const isStale = () => requestSeq !== loadSeqRef.current;
97
+ setError(null);
98
+ try {
99
+ const detail = await apiCall(`/api/staff/portal/support-tickets/${encodeURIComponent(params.id)}`, {
100
+ credentials: "include"
101
+ });
102
+ if (isStale()) return;
103
+ if (isPortalSupportAccountNotLinked(detail)) {
104
+ setNotLinked(true);
105
+ setTicket(null);
106
+ return;
107
+ }
108
+ if (detail.status === 404) {
109
+ setNotFound(true);
110
+ setTicket(null);
111
+ return;
112
+ }
113
+ if (!detail.ok || !detail.result) {
114
+ setError(t("staff.portal.support.detail.loadFailed", "Could not load this ticket."));
115
+ return;
116
+ }
117
+ setNotFound(false);
118
+ setTicket(detail.result);
119
+ setAttachments(detail.result.attachments ?? []);
120
+ } catch (err) {
121
+ if (isStale()) return;
122
+ getTelemetryRuntime()?.reportError(err, { module: "staff", code: "staff.portal_support_detail_failed" });
123
+ setError(t("staff.portal.support.detail.loadFailed", "Could not load this ticket."));
124
+ } finally {
125
+ if (!isStale()) setIsLoading(false);
126
+ }
127
+ }, [user, params.id, t]);
128
+ React.useEffect(() => {
129
+ void load();
130
+ }, [load]);
131
+ usePortalAppEvent(PORTAL_SUPPORT_UPDATED_EVENT, (event) => {
132
+ if (readPortalTicketIdFromEvent(event) === params.id) void load();
133
+ }, [load, params.id]);
134
+ const clearFailed = React.useCallback((fileName) => {
135
+ forgetFailedUpload(params.id, fileName);
136
+ setFailedUploads((current) => current.filter((entry) => entry !== fileName));
137
+ }, [params.id]);
138
+ const markFailed = React.useCallback((file) => {
139
+ rememberFailedUploads(params.id, [file]);
140
+ setFailedUploads((current) => current.includes(file.name) ? current : [...current, file.name]);
141
+ }, [params.id]);
142
+ const sendReply = React.useCallback(async () => {
143
+ const body = reply.trim();
144
+ if (!ticket || !body || replying) return;
145
+ setReplying(true);
146
+ setError(null);
147
+ try {
148
+ const result = await guardedMutation.runMutation({
149
+ operation: () => apiCall(`/api/staff/portal/support-tickets/${encodeURIComponent(ticket.id)}/messages`, {
150
+ method: "POST",
151
+ headers: { "Content-Type": "application/json" },
152
+ credentials: "include",
153
+ body: JSON.stringify({ body })
154
+ }),
155
+ context: {
156
+ moduleId: "staff",
157
+ entityId: "staff.support_ticket",
158
+ operation: "portal_support_message",
159
+ formId: MUTATION_CONTEXT_ID,
160
+ ticketId: ticket.id,
161
+ retryLastMutation: guardedMutation.retryLastMutation
162
+ },
163
+ mutationPayload: { ticketId: ticket.id, body }
164
+ });
165
+ if (!result.ok) {
166
+ setError(
167
+ result.status === 429 ? t("staff.portal.support.detail.rateLimited", "You have sent many messages recently. Please try again later.") : t("staff.portal.support.detail.replyFailed", "Could not send your message.")
168
+ );
169
+ return;
170
+ }
171
+ setReply("");
172
+ flash(
173
+ result.result?.reopened ? t("staff.portal.support.detail.reopened", "Your message was sent and the ticket was reopened.") : t("staff.portal.support.detail.replySent", "Your message was sent."),
174
+ "success"
175
+ );
176
+ await load();
177
+ } catch {
178
+ setError(t("staff.portal.support.detail.replyFailed", "Could not send your message."));
179
+ } finally {
180
+ setReplying(false);
181
+ }
182
+ }, [ticket, reply, replying, guardedMutation, load, t]);
183
+ const uploadFile = React.useCallback(async (file) => {
184
+ if (!ticket) return { status: "failed", reason: null };
185
+ const validationError = await validateAttachmentFile(file, t, maxUploadBytes);
186
+ if (validationError) {
187
+ flash(`${file.name}: ${validationError}`, "error");
188
+ return { status: "rejected" };
189
+ }
190
+ const form = new FormData();
191
+ form.set("file", file);
192
+ const result = await guardedMutation.runMutation({
193
+ operation: () => apiCall(`/api/staff/portal/support-tickets/${encodeURIComponent(ticket.id)}/attachments`, {
194
+ method: "POST",
195
+ credentials: "include",
196
+ body: form
197
+ }),
198
+ context: {
199
+ moduleId: "staff",
200
+ entityId: "attachments.attachment",
201
+ operation: "portal_support_attachment_upload",
202
+ formId: MUTATION_CONTEXT_ID,
203
+ ticketId: ticket.id,
204
+ retryLastMutation: guardedMutation.retryLastMutation
205
+ },
206
+ mutationPayload: { ticketId: ticket.id, fileName: file.name }
207
+ });
208
+ if (result.ok) return { status: "uploaded" };
209
+ const reason = result.result?.error;
210
+ return { status: "failed", reason: typeof reason === "string" && reason.trim() ? reason.trim() : null };
211
+ }, [ticket, guardedMutation, maxUploadBytes, t]);
212
+ const uploadFiles = React.useCallback(async (files) => {
213
+ if (!files.length) return;
214
+ setUploading(true);
215
+ try {
216
+ for (const file of files) {
217
+ let outcome;
218
+ try {
219
+ outcome = await uploadFile(file);
220
+ } catch (err) {
221
+ getTelemetryRuntime()?.reportError(err, { module: "staff", code: "staff.portal_support_upload_failed" });
222
+ outcome = { status: "failed", reason: null };
223
+ }
224
+ if (outcome.status === "uploaded") {
225
+ clearFailed(file.name);
226
+ } else if (outcome.status === "failed") {
227
+ markFailed(file);
228
+ flash(
229
+ outcome.reason ? t("staff.portal.support.attachments.uploadFailedReason", "Could not upload {name}: {reason}", {
230
+ name: file.name,
231
+ reason: outcome.reason
232
+ }) : t("staff.portal.support.attachments.uploadFailed", "Could not upload {name}.", { name: file.name }),
233
+ "error"
234
+ );
235
+ }
236
+ }
237
+ await load();
238
+ } finally {
239
+ setUploading(false);
240
+ if (fileInputRef.current) fileInputRef.current.value = "";
241
+ }
242
+ }, [uploadFile, clearFailed, markFailed, load, t]);
243
+ const onFilesPicked = React.useCallback(async (list) => {
244
+ if (!list || !list.length) return;
245
+ await uploadFiles(Array.from(list));
246
+ }, [uploadFiles]);
247
+ const retryUpload = React.useCallback(async (fileName) => {
248
+ const file = getRetryableUpload(params.id, fileName);
249
+ if (!file) return;
250
+ await uploadFiles([file]);
251
+ }, [params.id, uploadFiles]);
252
+ const deleteAttachment = React.useCallback(async (attachment) => {
253
+ if (!ticket) return;
254
+ const confirmed = await confirm({
255
+ title: t("staff.portal.support.attachments.deleteConfirm", "Delete {name}?", { name: attachment.fileName }),
256
+ variant: "destructive"
257
+ });
258
+ if (!confirmed) return;
259
+ const result = await guardedMutation.runMutation({
260
+ operation: () => apiCall(
261
+ `/api/staff/portal/support-tickets/${encodeURIComponent(ticket.id)}/attachments?attachmentId=${encodeURIComponent(attachment.id)}`,
262
+ { method: "DELETE", credentials: "include" }
263
+ ),
264
+ context: {
265
+ moduleId: "staff",
266
+ entityId: "attachments.attachment",
267
+ operation: "portal_support_attachment_delete",
268
+ formId: MUTATION_CONTEXT_ID,
269
+ ticketId: ticket.id,
270
+ retryLastMutation: guardedMutation.retryLastMutation
271
+ },
272
+ mutationPayload: { ticketId: ticket.id, attachmentId: attachment.id }
273
+ });
274
+ if (!result.ok) {
275
+ flash(t("staff.portal.support.attachments.deleteFailed", "Could not delete the file."), "error");
276
+ return;
277
+ }
278
+ await load();
279
+ }, [ticket, confirm, guardedMutation, load, t]);
280
+ const injectionContext = React.useMemo(
281
+ () => ({
282
+ orgSlug: params.orgSlug,
283
+ ticketId: params.id,
284
+ supportReference: ticket?.supportReference ?? null,
285
+ portalStatus: ticket?.portalStatus ?? null,
286
+ resolvedFeatures: auth.resolvedFeatures
287
+ }),
288
+ [params.orgSlug, params.id, ticket?.supportReference, ticket?.portalStatus, auth.resolvedFeatures]
289
+ );
290
+ const customerAuthorLabel = (isSelf, name) => {
291
+ if (isSelf) return t("staff.portal.support.detail.you", "You");
292
+ return name ?? t("staff.portal.support.detail.colleague", "A colleague");
293
+ };
294
+ if (loading || isLoading && !ticket && !notFound && !notLinked) {
295
+ return /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center py-20", children: /* @__PURE__ */ jsx(Spinner, {}) });
296
+ }
297
+ if (!user) return null;
298
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
299
+ /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(Button, { variant: "ghost", asChild: true, children: /* @__PURE__ */ jsxs(Link, { href: `/${params.orgSlug}/portal/support`, children: [
300
+ /* @__PURE__ */ jsx(ArrowLeft, { className: "mr-2 h-4 w-4" }),
301
+ t("staff.portal.support.backToList", "All tickets")
302
+ ] }) }) }),
303
+ /* @__PURE__ */ jsx(InjectionSpot, { spotId: SPOT_BEFORE, context: injectionContext }),
304
+ error ? /* @__PURE__ */ jsx(
305
+ ErrorMessage,
306
+ {
307
+ label: error,
308
+ action: /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: () => void load(), children: t("staff.portal.support.retry", "Try again") })
309
+ }
310
+ ) : null,
311
+ notLinked ? /* @__PURE__ */ jsx(PortalSupportNotLinked, {}) : notFound || !ticket && !error ? /* @__PURE__ */ jsx(
312
+ PortalEmptyState,
313
+ {
314
+ title: t("staff.portal.support.detail.notFoundTitle", "Ticket not found"),
315
+ description: t("staff.portal.support.detail.notFoundDescription", "This ticket does not exist or is not yours.")
316
+ }
317
+ ) : !ticket ? null : /* @__PURE__ */ jsxs(Fragment, { children: [
318
+ /* @__PURE__ */ jsx(PortalCard, { children: /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-start justify-between gap-4", children: [
319
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 space-y-1", children: [
320
+ /* @__PURE__ */ jsxs("p", { className: "text-sm text-muted-foreground", children: [
321
+ ticket.supportReference,
322
+ " \xB7 ",
323
+ ticket.category.label
324
+ ] }),
325
+ /* @__PURE__ */ jsx("h1", { className: "text-xl font-semibold text-foreground", children: ticket.subject })
326
+ ] }),
327
+ /* @__PURE__ */ jsx(StatusBadge, { variant: PORTAL_SUPPORT_STATUS_VARIANTS[ticket.portalStatus] ?? "neutral", dot: true, children: portalSupportStatusLabel(t, ticket.portalStatus) })
328
+ ] }) }),
329
+ /* @__PURE__ */ jsxs(PortalCard, { children: [
330
+ /* @__PURE__ */ jsx("h2", { className: "mb-3 text-sm font-semibold text-foreground", children: t("staff.portal.support.detail.conversation", "Conversation") }),
331
+ /* @__PURE__ */ jsxs("ol", { className: "space-y-3", "data-testid": "support-thread", children: [
332
+ /* @__PURE__ */ jsxs("li", { className: "rounded-md border border-border bg-muted p-3", children: [
333
+ /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
334
+ customerAuthorLabel(ticket.requester?.isSelf ?? false, ticket.requester?.name ?? null),
335
+ " \xB7 ",
336
+ formatPortalDate(ticket.createdAt, locale)
337
+ ] }),
338
+ /* @__PURE__ */ jsx("p", { className: "mt-1 whitespace-pre-wrap text-sm text-foreground", children: ticket.description })
339
+ ] }),
340
+ ticket.messages.map((message) => /* @__PURE__ */ jsxs(
341
+ "li",
342
+ {
343
+ "data-author-kind": message.authorKind,
344
+ className: message.authorKind === "customer" ? "rounded-md border border-border bg-muted p-3" : "rounded-md border border-border bg-card p-3",
345
+ children: [
346
+ /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
347
+ message.authorKind === "customer" ? customerAuthorLabel(message.isSelf ?? false, message.authorName) : message.authorName ? t("staff.portal.support.detail.supportTeamNamed", "Support team \xB7 {name}", { name: message.authorName }) : t("staff.portal.support.detail.supportTeam", "Support team"),
348
+ " \xB7 ",
349
+ formatPortalDate(message.createdAt, locale)
350
+ ] }),
351
+ /* @__PURE__ */ jsx("p", { className: "mt-1 whitespace-pre-wrap text-sm text-foreground", children: message.body })
352
+ ]
353
+ },
354
+ message.id
355
+ ))
356
+ ] }),
357
+ ticket.canReply ? /* @__PURE__ */ jsxs(
358
+ "form",
359
+ {
360
+ className: "mt-4 space-y-2",
361
+ onSubmit: (event) => {
362
+ event.preventDefault();
363
+ void sendReply();
364
+ },
365
+ children: [
366
+ /* @__PURE__ */ jsx(
367
+ Textarea,
368
+ {
369
+ rows: 4,
370
+ value: reply,
371
+ maxLength: 1e4,
372
+ "aria-label": t("staff.portal.support.detail.replyLabel", "Your reply"),
373
+ placeholder: t("staff.portal.support.detail.replyPlaceholder", "Write a reply"),
374
+ onChange: (event) => setReply(event.target.value),
375
+ onKeyDown: (event) => {
376
+ if ((event.metaKey || event.ctrlKey) && event.key === "Enter") {
377
+ event.preventDefault();
378
+ void sendReply();
379
+ }
380
+ }
381
+ }
382
+ ),
383
+ ticket.portalStatus === "resolved" ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("staff.portal.support.detail.reopenHint", "Replying reopens this ticket.") }) : null,
384
+ /* @__PURE__ */ jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxs(Button, { type: "submit", disabled: !reply.trim() || replying, children: [
385
+ replying ? /* @__PURE__ */ jsx(Spinner, { className: "mr-2 h-4 w-4" }) : null,
386
+ t("staff.portal.support.detail.send", "Send reply")
387
+ ] }) })
388
+ ]
389
+ }
390
+ ) : null
391
+ ] }),
392
+ /* @__PURE__ */ jsxs(PortalCard, { children: [
393
+ /* @__PURE__ */ jsxs("div", { className: "mb-3 flex items-center justify-between gap-2", children: [
394
+ /* @__PURE__ */ jsx("h2", { className: "text-sm font-semibold text-foreground", children: t("staff.portal.support.attachments.title", "Files") }),
395
+ ticket.canReply ? /* @__PURE__ */ jsxs(Fragment, { children: [
396
+ /* @__PURE__ */ jsx(
397
+ "input",
398
+ {
399
+ ref: fileInputRef,
400
+ type: "file",
401
+ multiple: true,
402
+ accept: ATTACHMENT_ACCEPT_TYPES,
403
+ className: "sr-only",
404
+ "data-testid": "support-detail-file-input",
405
+ onChange: (event) => void onFilesPicked(event.target.files)
406
+ }
407
+ ),
408
+ /* @__PURE__ */ jsxs(Button, { type: "button", variant: "outline", size: "sm", disabled: uploading, onClick: () => fileInputRef.current?.click(), children: [
409
+ uploading ? /* @__PURE__ */ jsx(Spinner, { className: "mr-2 h-4 w-4" }) : /* @__PURE__ */ jsx(Paperclip, { className: "mr-2 h-4 w-4" }),
410
+ t("staff.portal.support.attachments.add", "Add files")
411
+ ] })
412
+ ] }) : null
413
+ ] }),
414
+ failedUploads.length ? /* @__PURE__ */ jsxs(
415
+ "div",
416
+ {
417
+ className: "mb-3 space-y-2 rounded-md border border-status-error-border bg-status-error-bg p-3",
418
+ "data-testid": "support-failed-uploads",
419
+ children: [
420
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-status-error-text", children: t("staff.portal.support.attachments.failedHeading", "These files could not be uploaded.") }),
421
+ /* @__PURE__ */ jsx("ul", { className: "space-y-1 text-sm text-status-error-text", children: failedUploads.map((name) => {
422
+ const retryable = ticket.canReply && getRetryableUpload(params.id, name) !== null;
423
+ return /* @__PURE__ */ jsxs("li", { className: "flex flex-wrap items-center justify-between gap-2", children: [
424
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: name }),
425
+ retryable ? /* @__PURE__ */ jsx(
426
+ Button,
427
+ {
428
+ type: "button",
429
+ variant: "outline",
430
+ size: "sm",
431
+ disabled: uploading,
432
+ onClick: () => void retryUpload(name),
433
+ children: t("staff.portal.support.attachments.retry", "Retry")
434
+ }
435
+ ) : /* @__PURE__ */ jsx("span", { className: "text-xs", children: t("staff.portal.support.attachments.reattach", "Please attach this file again.") })
436
+ ] }, name);
437
+ }) })
438
+ ]
439
+ }
440
+ ) : null,
441
+ attachments.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t("staff.portal.support.attachments.empty", "No files yet.") }) : /* @__PURE__ */ jsx("ul", { className: "space-y-2", "data-testid": "support-attachments", children: attachments.map((attachment) => /* @__PURE__ */ jsxs("li", { className: "flex items-center justify-between gap-2 text-sm", children: [
442
+ /* @__PURE__ */ jsxs("a", { className: "flex min-w-0 items-center gap-2 text-foreground hover:underline", href: attachment.downloadUrl, children: [
443
+ /* @__PURE__ */ jsx(Download, { className: "h-4 w-4 shrink-0" }),
444
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: attachment.fileName }),
445
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 text-muted-foreground", children: formatFileSize(t, attachment.fileSize) })
446
+ ] }),
447
+ ticket.canReply && attachment.canDelete ? /* @__PURE__ */ jsx(
448
+ IconButton,
449
+ {
450
+ type: "button",
451
+ variant: "ghost",
452
+ size: "sm",
453
+ "aria-label": t("staff.portal.support.attachments.delete", "Delete file"),
454
+ onClick: () => void deleteAttachment(attachment),
455
+ children: /* @__PURE__ */ jsx(Trash2, { className: "h-4 w-4" })
456
+ }
457
+ ) : null
458
+ ] }, attachment.id)) })
459
+ ] })
460
+ ] }),
461
+ /* @__PURE__ */ jsx(InjectionSpot, { spotId: SPOT_AFTER, context: injectionContext }),
462
+ ConfirmDialogElement
463
+ ] });
464
+ }
465
+ export {
466
+ PortalSupportTicketDetailPage as default
467
+ };
468
+ //# sourceMappingURL=page.js.map