@open-mercato/core 0.7.1-develop.7178.1.ab93fbaf4e → 0.7.1-develop.7180.1.9717fbbb43

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 (808) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/dist/generated/entities/staff_time_entry/index.js +14 -0
  3. package/dist/generated/entities/staff_time_entry/index.js.map +2 -2
  4. package/dist/generated/entities/staff_time_entry_tag/index.js +15 -0
  5. package/dist/generated/entities/staff_time_entry_tag/index.js.map +7 -0
  6. package/dist/generated/entities/staff_time_project/index.js +16 -0
  7. package/dist/generated/entities/staff_time_project/index.js.map +2 -2
  8. package/dist/generated/entities/staff_time_report/index.js +57 -0
  9. package/dist/generated/entities/staff_time_report/index.js.map +7 -0
  10. package/dist/generated/entities/staff_time_report_entry/index.js +27 -0
  11. package/dist/generated/entities/staff_time_report_entry/index.js.map +7 -0
  12. package/dist/generated/entities/staff_time_report_event/index.js +21 -0
  13. package/dist/generated/entities/staff_time_report_event/index.js.map +7 -0
  14. package/dist/generated/entities/staff_time_report_project/index.js +15 -0
  15. package/dist/generated/entities/staff_time_report_project/index.js.map +7 -0
  16. package/dist/generated/entities/staff_time_tag/index.js +21 -0
  17. package/dist/generated/entities/staff_time_tag/index.js.map +7 -0
  18. package/dist/generated/entities/staff_time_task/index.js +37 -0
  19. package/dist/generated/entities/staff_time_task/index.js.map +7 -0
  20. package/dist/generated/entities/staff_time_task_comment/index.js +21 -0
  21. package/dist/generated/entities/staff_time_task_comment/index.js.map +7 -0
  22. package/dist/generated/entities/staff_time_task_status/index.js +29 -0
  23. package/dist/generated/entities/staff_time_task_status/index.js.map +7 -0
  24. package/dist/generated/entities/staff_time_task_tag/index.js +15 -0
  25. package/dist/generated/entities/staff_time_task_tag/index.js.map +7 -0
  26. package/dist/generated/entities.ids.generated.js +11 -1
  27. package/dist/generated/entities.ids.generated.js.map +2 -2
  28. package/dist/generated/entity-fields-registry.js +149 -0
  29. package/dist/generated/entity-fields-registry.js.map +2 -2
  30. package/dist/helpers/integration/timesheetFixtures.js +7 -0
  31. package/dist/helpers/integration/timesheetFixtures.js.map +2 -2
  32. package/dist/modules/auth/cli.js +16 -0
  33. package/dist/modules/auth/cli.js.map +2 -2
  34. package/dist/modules/auth/lib/backendChrome.js +1 -0
  35. package/dist/modules/auth/lib/backendChrome.js.map +2 -2
  36. package/dist/modules/customer_accounts/lib/customerRoleAcls.js +44 -0
  37. package/dist/modules/customer_accounts/lib/customerRoleAcls.js.map +7 -0
  38. package/dist/modules/customer_accounts/setup.js +1 -28
  39. package/dist/modules/customer_accounts/setup.js.map +2 -2
  40. package/dist/modules/data_sync/api/options.js +2 -0
  41. package/dist/modules/data_sync/api/options.js.map +2 -2
  42. package/dist/modules/data_sync/backend/data-sync/page.js +39 -17
  43. package/dist/modules/data_sync/backend/data-sync/page.js.map +2 -2
  44. package/dist/modules/data_sync/lib/start-controls.js +42 -0
  45. package/dist/modules/data_sync/lib/start-controls.js.map +7 -0
  46. package/dist/modules/staff/acl.js +9 -1
  47. package/dist/modules/staff/acl.js.map +2 -2
  48. package/dist/modules/staff/ai-agents.js +142 -0
  49. package/dist/modules/staff/ai-agents.js.map +7 -0
  50. package/dist/modules/staff/ai-tools/time-tracking-pack.js +348 -0
  51. package/dist/modules/staff/ai-tools/time-tracking-pack.js.map +7 -0
  52. package/dist/modules/staff/ai-tools/types.js +54 -0
  53. package/dist/modules/staff/ai-tools/types.js.map +7 -0
  54. package/dist/modules/staff/ai-tools.js +8 -0
  55. package/dist/modules/staff/ai-tools.js.map +7 -0
  56. package/dist/modules/staff/analytics.js +84 -1
  57. package/dist/modules/staff/analytics.js.map +2 -2
  58. package/dist/modules/staff/api/guards.js +37 -3
  59. package/dist/modules/staff/api/guards.js.map +2 -2
  60. package/dist/modules/staff/api/leave-requests/accept/route.js +1 -3
  61. package/dist/modules/staff/api/leave-requests/accept/route.js.map +2 -2
  62. package/dist/modules/staff/api/leave-requests/reject/route.js +1 -3
  63. package/dist/modules/staff/api/leave-requests/reject/route.js.map +2 -2
  64. package/dist/modules/staff/api/portal/time-reports/[id]/route.js +131 -0
  65. package/dist/modules/staff/api/portal/time-reports/[id]/route.js.map +7 -0
  66. package/dist/modules/staff/api/portal/time-reports/route.js +179 -0
  67. package/dist/modules/staff/api/portal/time-reports/route.js.map +7 -0
  68. package/dist/modules/staff/api/team-members/self/route.js +1 -3
  69. package/dist/modules/staff/api/team-members/self/route.js.map +2 -2
  70. package/dist/modules/staff/api/team-members/tags/assign/route.js +1 -3
  71. package/dist/modules/staff/api/team-members/tags/assign/route.js.map +2 -2
  72. package/dist/modules/staff/api/team-members/tags/unassign/route.js +1 -3
  73. package/dist/modules/staff/api/team-members/tags/unassign/route.js.map +2 -2
  74. package/dist/modules/staff/api/timesheets/_shared/withTimesheetInterceptors.js +174 -0
  75. package/dist/modules/staff/api/timesheets/_shared/withTimesheetInterceptors.js.map +7 -0
  76. package/dist/modules/staff/api/timesheets/access-requests/route.js +226 -0
  77. package/dist/modules/staff/api/timesheets/access-requests/route.js.map +7 -0
  78. package/dist/modules/staff/api/timesheets/my-projects/[projectId]/route.js +15 -8
  79. package/dist/modules/staff/api/timesheets/my-projects/[projectId]/route.js.map +2 -2
  80. package/dist/modules/staff/api/timesheets/my-projects/route.js +13 -1
  81. package/dist/modules/staff/api/timesheets/my-projects/route.js.map +2 -2
  82. package/dist/modules/staff/api/timesheets/my-work/myWorkAggregate.js +42 -0
  83. package/dist/modules/staff/api/timesheets/my-work/myWorkAggregate.js.map +7 -0
  84. package/dist/modules/staff/api/timesheets/my-work/route.js +383 -0
  85. package/dist/modules/staff/api/timesheets/my-work/route.js.map +7 -0
  86. package/dist/modules/staff/api/timesheets/projects/kpis/route.js +14 -2
  87. package/dist/modules/staff/api/timesheets/projects/kpis/route.js.map +2 -2
  88. package/dist/modules/staff/api/timesheets/reports/[id]/close/route.js +168 -0
  89. package/dist/modules/staff/api/timesheets/reports/[id]/close/route.js.map +7 -0
  90. package/dist/modules/staff/api/timesheets/reports/[id]/export/route.js +246 -0
  91. package/dist/modules/staff/api/timesheets/reports/[id]/export/route.js.map +7 -0
  92. package/dist/modules/staff/api/timesheets/reports/[id]/sheet/route.js +183 -0
  93. package/dist/modules/staff/api/timesheets/reports/[id]/sheet/route.js.map +7 -0
  94. package/dist/modules/staff/api/timesheets/reports/[id]/unlock/route.js +164 -0
  95. package/dist/modules/staff/api/timesheets/reports/[id]/unlock/route.js.map +7 -0
  96. package/dist/modules/staff/api/timesheets/reports/preview/route.js +360 -0
  97. package/dist/modules/staff/api/timesheets/reports/preview/route.js.map +7 -0
  98. package/dist/modules/staff/api/timesheets/reports/route.js +316 -0
  99. package/dist/modules/staff/api/timesheets/reports/route.js.map +7 -0
  100. package/dist/modules/staff/api/timesheets/reports/shared.js +63 -0
  101. package/dist/modules/staff/api/timesheets/reports/shared.js.map +7 -0
  102. package/dist/modules/staff/api/timesheets/settings/reapply-rounding/route.js +164 -0
  103. package/dist/modules/staff/api/timesheets/settings/reapply-rounding/route.js.map +7 -0
  104. package/dist/modules/staff/api/timesheets/settings/rounding-impact/route.js +168 -0
  105. package/dist/modules/staff/api/timesheets/settings/rounding-impact/route.js.map +7 -0
  106. package/dist/modules/staff/api/timesheets/settings/route.js +199 -0
  107. package/dist/modules/staff/api/timesheets/settings/route.js.map +7 -0
  108. package/dist/modules/staff/api/timesheets/tags/access.js +76 -0
  109. package/dist/modules/staff/api/timesheets/tags/access.js.map +7 -0
  110. package/dist/modules/staff/api/timesheets/tags/entry-assignments/route.js +235 -0
  111. package/dist/modules/staff/api/timesheets/tags/entry-assignments/route.js.map +7 -0
  112. package/dist/modules/staff/api/timesheets/tags/route.js +160 -0
  113. package/dist/modules/staff/api/timesheets/tags/route.js.map +7 -0
  114. package/dist/modules/staff/api/timesheets/tags/task-assignments/route.js +233 -0
  115. package/dist/modules/staff/api/timesheets/tags/task-assignments/route.js.map +7 -0
  116. package/dist/modules/staff/api/timesheets/task-statuses/route.js +188 -0
  117. package/dist/modules/staff/api/timesheets/task-statuses/route.js.map +7 -0
  118. package/dist/modules/staff/api/timesheets/tasks/[id]/comments/route.js +399 -0
  119. package/dist/modules/staff/api/timesheets/tasks/[id]/comments/route.js.map +7 -0
  120. package/dist/modules/staff/api/timesheets/tasks/[id]/status/route.js +210 -0
  121. package/dist/modules/staff/api/timesheets/tasks/[id]/status/route.js.map +7 -0
  122. package/dist/modules/staff/api/timesheets/tasks/route.js +333 -0
  123. package/dist/modules/staff/api/timesheets/tasks/route.js.map +7 -0
  124. package/dist/modules/staff/api/timesheets/time-entries/[id]/duplicate/route.js +228 -0
  125. package/dist/modules/staff/api/timesheets/time-entries/[id]/duplicate/route.js.map +7 -0
  126. package/dist/modules/staff/api/timesheets/time-entries/[id]/segments/[segmentId]/route.js +29 -10
  127. package/dist/modules/staff/api/timesheets/time-entries/[id]/segments/[segmentId]/route.js.map +2 -2
  128. package/dist/modules/staff/api/timesheets/time-entries/[id]/segments/route.js +31 -18
  129. package/dist/modules/staff/api/timesheets/time-entries/[id]/segments/route.js.map +2 -2
  130. package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.js +21 -7
  131. package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.js.map +2 -2
  132. package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-stop/route.js +27 -7
  133. package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-stop/route.js.map +2 -2
  134. package/dist/modules/staff/api/timesheets/time-entries/bulk/route.js +215 -32
  135. package/dist/modules/staff/api/timesheets/time-entries/bulk/route.js.map +2 -2
  136. package/dist/modules/staff/api/timesheets/time-entries/copy-day/route.js +378 -0
  137. package/dist/modules/staff/api/timesheets/time-entries/copy-day/route.js.map +7 -0
  138. package/dist/modules/staff/api/timesheets/time-entries/overlaps/route.js +340 -0
  139. package/dist/modules/staff/api/timesheets/time-entries/overlaps/route.js.map +7 -0
  140. package/dist/modules/staff/api/timesheets/time-entries/route.js +238 -26
  141. package/dist/modules/staff/api/timesheets/time-entries/route.js.map +2 -2
  142. package/dist/modules/staff/api/timesheets/time-entries/start-timer/route.js +23 -8
  143. package/dist/modules/staff/api/timesheets/time-entries/start-timer/route.js.map +2 -2
  144. package/dist/modules/staff/api/timesheets/time-projects/[id]/change-currency/route.js +209 -0
  145. package/dist/modules/staff/api/timesheets/time-projects/[id]/change-currency/route.js.map +7 -0
  146. package/dist/modules/staff/api/timesheets/time-projects/[id]/employees/route.js +59 -2
  147. package/dist/modules/staff/api/timesheets/time-projects/[id]/employees/route.js.map +2 -2
  148. package/dist/modules/staff/api/timesheets/time-projects/route.js +264 -22
  149. package/dist/modules/staff/api/timesheets/time-projects/route.js.map +2 -2
  150. package/dist/modules/staff/backend/staff/time-tracking/board/page.js +110 -0
  151. package/dist/modules/staff/backend/staff/time-tracking/board/page.js.map +7 -0
  152. package/dist/modules/staff/backend/staff/time-tracking/board/page.meta.js +18 -0
  153. package/dist/modules/staff/backend/staff/time-tracking/board/page.meta.js.map +7 -0
  154. package/dist/modules/staff/backend/staff/time-tracking/entries/page.js +981 -0
  155. package/dist/modules/staff/backend/staff/time-tracking/entries/page.js.map +7 -0
  156. package/dist/modules/staff/backend/staff/time-tracking/entries/page.meta.js +18 -0
  157. package/dist/modules/staff/backend/staff/time-tracking/entries/page.meta.js.map +7 -0
  158. package/dist/modules/staff/backend/staff/time-tracking/page.js +583 -0
  159. package/dist/modules/staff/backend/staff/time-tracking/page.js.map +7 -0
  160. package/dist/modules/staff/backend/staff/time-tracking/page.meta.js +15 -0
  161. package/dist/modules/staff/backend/staff/time-tracking/page.meta.js.map +7 -0
  162. package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/board/page.js +65 -0
  163. package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/board/page.js.map +7 -0
  164. package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/board/page.meta.js +16 -0
  165. package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/board/page.meta.js.map +7 -0
  166. package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/edit/page.js +229 -0
  167. package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/edit/page.js.map +7 -0
  168. package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/edit/page.meta.js +16 -0
  169. package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/edit/page.meta.js.map +7 -0
  170. package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js +1014 -0
  171. package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js.map +7 -0
  172. package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.meta.js +16 -0
  173. package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.meta.js.map +7 -0
  174. package/dist/modules/staff/backend/staff/time-tracking/projects/create/page.js +97 -0
  175. package/dist/modules/staff/backend/staff/time-tracking/projects/create/page.js.map +7 -0
  176. package/dist/modules/staff/backend/staff/time-tracking/projects/create/page.meta.js +16 -0
  177. package/dist/modules/staff/backend/staff/time-tracking/projects/create/page.meta.js.map +7 -0
  178. package/dist/modules/staff/backend/staff/time-tracking/projects/page.js +1040 -0
  179. package/dist/modules/staff/backend/staff/time-tracking/projects/page.js.map +7 -0
  180. package/dist/modules/staff/backend/staff/time-tracking/projects/page.meta.js +19 -0
  181. package/dist/modules/staff/backend/staff/time-tracking/projects/page.meta.js.map +7 -0
  182. package/dist/modules/staff/backend/staff/time-tracking/projects/projectFormConfig.js +353 -0
  183. package/dist/modules/staff/backend/staff/time-tracking/projects/projectFormConfig.js.map +7 -0
  184. package/dist/modules/staff/backend/staff/time-tracking/reports/[id]/page.js +457 -0
  185. package/dist/modules/staff/backend/staff/time-tracking/reports/[id]/page.js.map +7 -0
  186. package/dist/modules/staff/backend/staff/time-tracking/reports/[id]/page.meta.js +16 -0
  187. package/dist/modules/staff/backend/staff/time-tracking/reports/[id]/page.meta.js.map +7 -0
  188. package/dist/modules/staff/backend/staff/time-tracking/reports/create/page.js +697 -0
  189. package/dist/modules/staff/backend/staff/time-tracking/reports/create/page.js.map +7 -0
  190. package/dist/modules/staff/backend/staff/time-tracking/reports/create/page.meta.js +16 -0
  191. package/dist/modules/staff/backend/staff/time-tracking/reports/create/page.meta.js.map +7 -0
  192. package/dist/modules/staff/backend/staff/time-tracking/reports/page.js +174 -0
  193. package/dist/modules/staff/backend/staff/time-tracking/reports/page.js.map +7 -0
  194. package/dist/modules/staff/backend/staff/time-tracking/reports/page.meta.js +18 -0
  195. package/dist/modules/staff/backend/staff/time-tracking/reports/page.meta.js.map +7 -0
  196. package/dist/modules/staff/backend/staff/time-tracking/settings/page.js +546 -0
  197. package/dist/modules/staff/backend/staff/time-tracking/settings/page.js.map +7 -0
  198. package/dist/modules/staff/backend/staff/time-tracking/settings/page.meta.js +18 -0
  199. package/dist/modules/staff/backend/staff/time-tracking/settings/page.meta.js.map +7 -0
  200. package/dist/modules/staff/backend/staff/time-tracking/timesheet/GridView.js +710 -0
  201. package/dist/modules/staff/backend/staff/time-tracking/timesheet/GridView.js.map +7 -0
  202. package/dist/modules/staff/backend/staff/time-tracking/timesheet/page.js +646 -0
  203. package/dist/modules/staff/backend/staff/time-tracking/timesheet/page.js.map +7 -0
  204. package/dist/modules/staff/backend/staff/time-tracking/timesheet/page.meta.js +18 -0
  205. package/dist/modules/staff/backend/staff/time-tracking/timesheet/page.meta.js.map +7 -0
  206. package/dist/modules/staff/backend/staff/timesheets/page.js +4 -793
  207. package/dist/modules/staff/backend/staff/timesheets/page.js.map +2 -2
  208. package/dist/modules/staff/backend/staff/timesheets/page.meta.js +2 -7
  209. package/dist/modules/staff/backend/staff/timesheets/page.meta.js.map +2 -2
  210. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.js +5 -136
  211. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.js.map +2 -2
  212. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.meta.js +1 -7
  213. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.meta.js.map +2 -2
  214. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/page.js +5 -445
  215. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/page.js.map +3 -3
  216. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/page.meta.js +1 -7
  217. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/page.meta.js.map +2 -2
  218. package/dist/modules/staff/backend/staff/timesheets/projects/create/page.js +4 -76
  219. package/dist/modules/staff/backend/staff/timesheets/projects/create/page.js.map +2 -2
  220. package/dist/modules/staff/backend/staff/timesheets/projects/create/page.meta.js +1 -7
  221. package/dist/modules/staff/backend/staff/timesheets/projects/create/page.meta.js.map +2 -2
  222. package/dist/modules/staff/backend/staff/timesheets/projects/page.js +4 -613
  223. package/dist/modules/staff/backend/staff/timesheets/projects/page.js.map +2 -2
  224. package/dist/modules/staff/backend/staff/timesheets/projects/page.meta.js +2 -17
  225. package/dist/modules/staff/backend/staff/timesheets/projects/page.meta.js.map +2 -2
  226. package/dist/modules/staff/ce.js +40 -0
  227. package/dist/modules/staff/ce.js.map +2 -2
  228. package/dist/modules/staff/cli.js +166 -1
  229. package/dist/modules/staff/cli.js.map +2 -2
  230. package/dist/modules/staff/commands/index.js +5 -0
  231. package/dist/modules/staff/commands/index.js.map +2 -2
  232. package/dist/modules/staff/commands/timesheets-entries.js +569 -21
  233. package/dist/modules/staff/commands/timesheets-entries.js.map +3 -3
  234. package/dist/modules/staff/commands/timesheets-projects.js +450 -7
  235. package/dist/modules/staff/commands/timesheets-projects.js.map +2 -2
  236. package/dist/modules/staff/commands/timesheets-reports.js +1036 -0
  237. package/dist/modules/staff/commands/timesheets-reports.js.map +7 -0
  238. package/dist/modules/staff/commands/timesheets-tags.js +1015 -0
  239. package/dist/modules/staff/commands/timesheets-tags.js.map +7 -0
  240. package/dist/modules/staff/commands/timesheets-task-comments.js +438 -0
  241. package/dist/modules/staff/commands/timesheets-task-comments.js.map +7 -0
  242. package/dist/modules/staff/commands/timesheets-task-statuses.js +723 -0
  243. package/dist/modules/staff/commands/timesheets-task-statuses.js.map +7 -0
  244. package/dist/modules/staff/commands/timesheets-tasks.js +834 -0
  245. package/dist/modules/staff/commands/timesheets-tasks.js.map +7 -0
  246. package/dist/modules/staff/data/enrichers.js +426 -12
  247. package/dist/modules/staff/data/enrichers.js.map +2 -2
  248. package/dist/modules/staff/data/entities.js +552 -3
  249. package/dist/modules/staff/data/entities.js.map +2 -2
  250. package/dist/modules/staff/data/extensions.js +41 -0
  251. package/dist/modules/staff/data/extensions.js.map +7 -0
  252. package/dist/modules/staff/data/validators.js +254 -9
  253. package/dist/modules/staff/data/validators.js.map +2 -2
  254. package/dist/modules/staff/di.js +42 -1
  255. package/dist/modules/staff/di.js.map +2 -2
  256. package/dist/modules/staff/events.js +84 -6
  257. package/dist/modules/staff/events.js.map +2 -2
  258. package/dist/modules/staff/events.payloads.js +158 -0
  259. package/dist/modules/staff/events.payloads.js.map +7 -0
  260. package/dist/modules/staff/extension-points.js +261 -0
  261. package/dist/modules/staff/extension-points.js.map +7 -0
  262. package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/[id]/page.js +138 -0
  263. package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/[id]/page.js.map +7 -0
  264. package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/[id]/page.meta.js +12 -0
  265. package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/[id]/page.meta.js.map +7 -0
  266. package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/page.js +133 -0
  267. package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/page.js.map +7 -0
  268. package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/page.meta.js +19 -0
  269. package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/page.meta.js.map +7 -0
  270. package/dist/modules/staff/lib/crud.js +11 -1
  271. package/dist/modules/staff/lib/crud.js.map +2 -2
  272. package/dist/modules/staff/lib/time-tracking/access.js +92 -0
  273. package/dist/modules/staff/lib/time-tracking/access.js.map +7 -0
  274. package/dist/modules/staff/lib/time-tracking/billability.js +39 -0
  275. package/dist/modules/staff/lib/time-tracking/billability.js.map +7 -0
  276. package/dist/modules/staff/lib/time-tracking/capacity.js +68 -0
  277. package/dist/modules/staff/lib/time-tracking/capacity.js.map +7 -0
  278. package/dist/modules/staff/lib/time-tracking/componentContracts.js +15 -0
  279. package/dist/modules/staff/lib/time-tracking/componentContracts.js.map +7 -0
  280. package/dist/modules/staff/lib/time-tracking/cost.js +82 -0
  281. package/dist/modules/staff/lib/time-tracking/cost.js.map +7 -0
  282. package/dist/modules/staff/lib/time-tracking/duration.js +55 -0
  283. package/dist/modules/staff/lib/time-tracking/duration.js.map +7 -0
  284. package/dist/modules/staff/lib/time-tracking/featureAccess.js +41 -0
  285. package/dist/modules/staff/lib/time-tracking/featureAccess.js.map +7 -0
  286. package/dist/modules/staff/lib/time-tracking/interval.js +72 -0
  287. package/dist/modules/staff/lib/time-tracking/interval.js.map +7 -0
  288. package/dist/modules/staff/lib/time-tracking/migrateProjectCodes.js +88 -0
  289. package/dist/modules/staff/lib/time-tracking/migrateProjectCodes.js.map +7 -0
  290. package/dist/modules/staff/lib/time-tracking/moneyVisibility.js +10 -0
  291. package/dist/modules/staff/lib/time-tracking/moneyVisibility.js.map +7 -0
  292. package/dist/modules/staff/lib/time-tracking/overlap.js +87 -0
  293. package/dist/modules/staff/lib/time-tracking/overlap.js.map +7 -0
  294. package/dist/modules/staff/lib/time-tracking/portalRecipients.js +27 -0
  295. package/dist/modules/staff/lib/time-tracking/portalRecipients.js.map +7 -0
  296. package/dist/modules/staff/lib/time-tracking/portalReports.js +16 -0
  297. package/dist/modules/staff/lib/time-tracking/portalReports.js.map +7 -0
  298. package/dist/modules/staff/lib/time-tracking/projectCode.js +135 -0
  299. package/dist/modules/staff/lib/time-tracking/projectCode.js.map +7 -0
  300. package/dist/modules/staff/lib/time-tracking/reapplyRounding.js +187 -0
  301. package/dist/modules/staff/lib/time-tracking/reapplyRounding.js.map +7 -0
  302. package/dist/modules/staff/lib/time-tracking/recalculationRunner.js +80 -0
  303. package/dist/modules/staff/lib/time-tracking/recalculationRunner.js.map +7 -0
  304. package/dist/modules/staff/lib/time-tracking/recalculations.js +56 -0
  305. package/dist/modules/staff/lib/time-tracking/recalculations.js.map +7 -0
  306. package/dist/modules/staff/lib/time-tracking/registries/invoke.js +63 -0
  307. package/dist/modules/staff/lib/time-tracking/registries/invoke.js.map +7 -0
  308. package/dist/modules/staff/lib/time-tracking/registries/registry.js +62 -0
  309. package/dist/modules/staff/lib/time-tracking/registries/registry.js.map +7 -0
  310. package/dist/modules/staff/lib/time-tracking/registries/scope.js +21 -0
  311. package/dist/modules/staff/lib/time-tracking/registries/scope.js.map +7 -0
  312. package/dist/modules/staff/lib/time-tracking/rollup.js +34 -0
  313. package/dist/modules/staff/lib/time-tracking/rollup.js.map +7 -0
  314. package/dist/modules/staff/lib/time-tracking/rounding.js +66 -0
  315. package/dist/modules/staff/lib/time-tracking/rounding.js.map +7 -0
  316. package/dist/modules/staff/lib/time-tracking/roundingImpact.js +39 -0
  317. package/dist/modules/staff/lib/time-tracking/roundingImpact.js.map +7 -0
  318. package/dist/modules/staff/lib/time-tracking/settingKeys.js +214 -0
  319. package/dist/modules/staff/lib/time-tracking/settingKeys.js.map +7 -0
  320. package/dist/modules/staff/lib/time-tracking/settings.js +87 -0
  321. package/dist/modules/staff/lib/time-tracking/settings.js.map +7 -0
  322. package/dist/modules/staff/lib/time-tracking/sqlInClause.js +9 -0
  323. package/dist/modules/staff/lib/time-tracking/sqlInClause.js.map +7 -0
  324. package/dist/modules/staff/lib/time-tracking/timeEntrySources.js +48 -0
  325. package/dist/modules/staff/lib/time-tracking/timeEntrySources.js.map +7 -0
  326. package/dist/modules/staff/lib/time-tracking-ui/BoardFilterChips.js +283 -0
  327. package/dist/modules/staff/lib/time-tracking-ui/BoardFilterChips.js.map +7 -0
  328. package/dist/modules/staff/lib/time-tracking-ui/CustomerPicker.js +324 -0
  329. package/dist/modules/staff/lib/time-tracking-ui/CustomerPicker.js.map +7 -0
  330. package/dist/modules/staff/lib/time-tracking-ui/DurationInput.js +121 -0
  331. package/dist/modules/staff/lib/time-tracking-ui/DurationInput.js.map +7 -0
  332. package/dist/modules/staff/lib/time-tracking-ui/EntryDetailsFields.js +188 -0
  333. package/dist/modules/staff/lib/time-tracking-ui/EntryDetailsFields.js.map +7 -0
  334. package/dist/modules/staff/lib/time-tracking-ui/EntryFilterBar.js +89 -0
  335. package/dist/modules/staff/lib/time-tracking-ui/EntryFilterBar.js.map +7 -0
  336. package/dist/modules/staff/lib/time-tracking-ui/KanbanBoard.js +855 -0
  337. package/dist/modules/staff/lib/time-tracking-ui/KanbanBoard.js.map +7 -0
  338. package/dist/modules/staff/lib/time-tracking-ui/KanbanCard.js +360 -0
  339. package/dist/modules/staff/lib/time-tracking-ui/KanbanCard.js.map +7 -0
  340. package/dist/modules/staff/lib/time-tracking-ui/KanbanColumn.js +259 -0
  341. package/dist/modules/staff/lib/time-tracking-ui/KanbanColumn.js.map +7 -0
  342. package/dist/modules/staff/lib/time-tracking-ui/NewTaskDialog.js +238 -0
  343. package/dist/modules/staff/lib/time-tracking-ui/NewTaskDialog.js.map +7 -0
  344. package/dist/modules/staff/lib/time-tracking-ui/NoProjectAccess.js +130 -0
  345. package/dist/modules/staff/lib/time-tracking-ui/NoProjectAccess.js.map +7 -0
  346. package/dist/modules/staff/lib/time-tracking-ui/PeriodSelector.js +98 -0
  347. package/dist/modules/staff/lib/time-tracking-ui/PeriodSelector.js.map +7 -0
  348. package/dist/modules/staff/lib/time-tracking-ui/PhasePlaceholder.js +27 -0
  349. package/dist/modules/staff/lib/time-tracking-ui/PhasePlaceholder.js.map +7 -0
  350. package/dist/modules/staff/lib/time-tracking-ui/ProjectCodeField.js +166 -0
  351. package/dist/modules/staff/lib/time-tracking-ui/ProjectCodeField.js.map +7 -0
  352. package/dist/modules/staff/lib/time-tracking-ui/ProjectCurrencyField.js +295 -0
  353. package/dist/modules/staff/lib/time-tracking-ui/ProjectCurrencyField.js.map +7 -0
  354. package/dist/modules/staff/lib/time-tracking-ui/ProjectFormSections.js +212 -0
  355. package/dist/modules/staff/lib/time-tracking-ui/ProjectFormSections.js.map +7 -0
  356. package/dist/modules/staff/lib/time-tracking-ui/ProjectTeamDrawer.js +729 -0
  357. package/dist/modules/staff/lib/time-tracking-ui/ProjectTeamDrawer.js.map +7 -0
  358. package/dist/modules/staff/lib/time-tracking-ui/ReportSheet.js +217 -0
  359. package/dist/modules/staff/lib/time-tracking-ui/ReportSheet.js.map +7 -0
  360. package/dist/modules/staff/lib/time-tracking-ui/TagPicker.js +158 -0
  361. package/dist/modules/staff/lib/time-tracking-ui/TagPicker.js.map +7 -0
  362. package/dist/modules/staff/lib/time-tracking-ui/TaskBoardScreen.js +224 -0
  363. package/dist/modules/staff/lib/time-tracking-ui/TaskBoardScreen.js.map +7 -0
  364. package/dist/modules/staff/lib/time-tracking-ui/TaskCommentThread.js +93 -0
  365. package/dist/modules/staff/lib/time-tracking-ui/TaskCommentThread.js.map +7 -0
  366. package/dist/modules/staff/lib/time-tracking-ui/TaskDrawer.js +1196 -0
  367. package/dist/modules/staff/lib/time-tracking-ui/TaskDrawer.js.map +7 -0
  368. package/dist/modules/staff/lib/time-tracking-ui/TaskListView.js +176 -0
  369. package/dist/modules/staff/lib/time-tracking-ui/TaskListView.js.map +7 -0
  370. package/dist/modules/staff/lib/time-tracking-ui/TaskPicker.js +307 -0
  371. package/dist/modules/staff/lib/time-tracking-ui/TaskPicker.js.map +7 -0
  372. package/dist/modules/staff/lib/time-tracking-ui/TaskQuickLog.js +284 -0
  373. package/dist/modules/staff/lib/time-tracking-ui/TaskQuickLog.js.map +7 -0
  374. package/dist/modules/staff/lib/time-tracking-ui/TimeEntriesSummaryFooter.js +73 -0
  375. package/dist/modules/staff/lib/time-tracking-ui/TimeEntriesSummaryFooter.js.map +7 -0
  376. package/dist/modules/staff/lib/time-tracking-ui/TimeEntryDialog.js +1499 -0
  377. package/dist/modules/staff/lib/time-tracking-ui/TimeEntryDialog.js.map +7 -0
  378. package/dist/modules/staff/lib/time-tracking-ui/TimeEntryDurationCell.js +95 -0
  379. package/dist/modules/staff/lib/time-tracking-ui/TimeEntryDurationCell.js.map +7 -0
  380. package/dist/modules/staff/lib/time-tracking-ui/TimesheetCalendar.js +188 -0
  381. package/dist/modules/staff/lib/time-tracking-ui/TimesheetCalendar.js.map +7 -0
  382. package/dist/modules/staff/lib/time-tracking-ui/TimesheetPeriodFooter.js +61 -0
  383. package/dist/modules/staff/lib/time-tracking-ui/TimesheetPeriodFooter.js.map +7 -0
  384. package/dist/modules/staff/lib/time-tracking-ui/TimesheetViewSwitch.js +45 -0
  385. package/dist/modules/staff/lib/time-tracking-ui/TimesheetViewSwitch.js.map +7 -0
  386. package/dist/modules/staff/lib/time-tracking-ui/boardDirectory.js +176 -0
  387. package/dist/modules/staff/lib/time-tracking-ui/boardDirectory.js.map +7 -0
  388. package/dist/modules/staff/lib/time-tracking-ui/boardFilters.js +82 -0
  389. package/dist/modules/staff/lib/time-tracking-ui/boardFilters.js.map +7 -0
  390. package/dist/modules/staff/lib/time-tracking-ui/boardSummary.js +5 -0
  391. package/dist/modules/staff/lib/time-tracking-ui/boardSummary.js.map +7 -0
  392. package/dist/modules/staff/lib/time-tracking-ui/kanbanBoardData.js +152 -0
  393. package/dist/modules/staff/lib/time-tracking-ui/kanbanBoardData.js.map +7 -0
  394. package/dist/modules/staff/lib/time-tracking-ui/projectTeamAssignment.js +92 -0
  395. package/dist/modules/staff/lib/time-tracking-ui/projectTeamAssignment.js.map +7 -0
  396. package/dist/modules/staff/lib/time-tracking-ui/taskDrawerData.js +111 -0
  397. package/dist/modules/staff/lib/time-tracking-ui/taskDrawerData.js.map +7 -0
  398. package/dist/modules/staff/lib/time-tracking-ui/timeEntryDialogState.js +243 -0
  399. package/dist/modules/staff/lib/time-tracking-ui/timeEntryDialogState.js.map +7 -0
  400. package/dist/modules/staff/lib/time-tracking-ui/timeEntryListData.js +151 -0
  401. package/dist/modules/staff/lib/time-tracking-ui/timeEntryListData.js.map +7 -0
  402. package/dist/modules/staff/lib/time-tracking-ui/timeTrackingSettingsForm.js +105 -0
  403. package/dist/modules/staff/lib/time-tracking-ui/timeTrackingSettingsForm.js.map +7 -0
  404. package/dist/modules/staff/lib/time-tracking-ui/timesheetData.js +132 -0
  405. package/dist/modules/staff/lib/time-tracking-ui/timesheetData.js.map +7 -0
  406. package/dist/modules/staff/lib/time-tracking-ui/timesheetPeriod.js +205 -0
  407. package/dist/modules/staff/lib/time-tracking-ui/timesheetPeriod.js.map +7 -0
  408. package/dist/modules/staff/lib/time-tracking-ui/timesheetTargets.js +53 -0
  409. package/dist/modules/staff/lib/time-tracking-ui/timesheetTargets.js.map +7 -0
  410. package/dist/modules/staff/lib/time-tracking-ui/useBoardFilters.js +77 -0
  411. package/dist/modules/staff/lib/time-tracking-ui/useBoardFilters.js.map +7 -0
  412. package/dist/modules/staff/lib/time-tracking-ui/useTimesheetPreferences.js +151 -0
  413. package/dist/modules/staff/lib/time-tracking-ui/useTimesheetPreferences.js.map +7 -0
  414. package/dist/modules/staff/lib/timeTrackingSeeds.js +809 -0
  415. package/dist/modules/staff/lib/timeTrackingSeeds.js.map +7 -0
  416. package/dist/modules/staff/lib/timesheets/timeEntryCacheInvalidation.js +2 -3
  417. package/dist/modules/staff/lib/timesheets/timeEntryCacheInvalidation.js.map +2 -2
  418. package/dist/modules/staff/lib/timesheets/timeEntryDecoration.js +152 -0
  419. package/dist/modules/staff/lib/timesheets/timeEntryDecoration.js.map +7 -0
  420. package/dist/modules/staff/lib/timesheets-projects/budgetBurn.js +36 -0
  421. package/dist/modules/staff/lib/timesheets-projects/budgetBurn.js.map +7 -0
  422. package/dist/modules/staff/lib/timesheets-projects/budgetThreshold.js +66 -0
  423. package/dist/modules/staff/lib/timesheets-projects/budgetThreshold.js.map +7 -0
  424. package/dist/modules/staff/lib/timesheets-projects/budgetThresholdState.js +78 -0
  425. package/dist/modules/staff/lib/timesheets-projects/budgetThresholdState.js.map +7 -0
  426. package/dist/modules/staff/lib/timesheets-projects/computeProjectFinancials.js +81 -0
  427. package/dist/modules/staff/lib/timesheets-projects/computeProjectFinancials.js.map +7 -0
  428. package/dist/modules/staff/lib/timesheets-projects/reportSelection.js +44 -0
  429. package/dist/modules/staff/lib/timesheets-projects/reportSelection.js.map +7 -0
  430. package/dist/modules/staff/lib/timesheets-projects-ui/AssignMembersDialog.js +169 -0
  431. package/dist/modules/staff/lib/timesheets-projects-ui/AssignMembersDialog.js.map +7 -0
  432. package/dist/modules/staff/lib/timesheets-projects-ui/ProjectBudgetCell.js +22 -0
  433. package/dist/modules/staff/lib/timesheets-projects-ui/ProjectBudgetCell.js.map +7 -0
  434. package/dist/modules/staff/lib/timesheets-projects-ui/ProjectCard.js +51 -1
  435. package/dist/modules/staff/lib/timesheets-projects-ui/ProjectCard.js.map +2 -2
  436. package/dist/modules/staff/lib/timesheets-projects-ui/ProjectsBulkActionsBar.js +70 -0
  437. package/dist/modules/staff/lib/timesheets-projects-ui/ProjectsBulkActionsBar.js.map +7 -0
  438. package/dist/modules/staff/lib/timesheets-reports/buildReportSheet.js +63 -0
  439. package/dist/modules/staff/lib/timesheets-reports/buildReportSheet.js.map +7 -0
  440. package/dist/modules/staff/lib/timesheets-reports/loadReportData.js +170 -0
  441. package/dist/modules/staff/lib/timesheets-reports/loadReportData.js.map +7 -0
  442. package/dist/modules/staff/lib/timesheets-reports/pdf.js +205 -0
  443. package/dist/modules/staff/lib/timesheets-reports/pdf.js.map +7 -0
  444. package/dist/modules/staff/lib/timesheets-reports/reportApprovalPolicies.js +68 -0
  445. package/dist/modules/staff/lib/timesheets-reports/reportApprovalPolicies.js.map +7 -0
  446. package/dist/modules/staff/lib/timesheets-reports/reportConfigData.js +135 -0
  447. package/dist/modules/staff/lib/timesheets-reports/reportConfigData.js.map +7 -0
  448. package/dist/modules/staff/lib/timesheets-reports/reportConfigState.js +48 -0
  449. package/dist/modules/staff/lib/timesheets-reports/reportConfigState.js.map +7 -0
  450. package/dist/modules/staff/lib/timesheets-reports/reportExport.js +262 -0
  451. package/dist/modules/staff/lib/timesheets-reports/reportExport.js.map +7 -0
  452. package/dist/modules/staff/lib/timesheets-reports/reportExportFormats.js +34 -0
  453. package/dist/modules/staff/lib/timesheets-reports/reportExportFormats.js.map +7 -0
  454. package/dist/modules/staff/lib/timesheets-reports/reportGroupings.js +79 -0
  455. package/dist/modules/staff/lib/timesheets-reports/reportGroupings.js.map +7 -0
  456. package/dist/modules/staff/lib/timesheets-reports/reportLabels.js +11 -0
  457. package/dist/modules/staff/lib/timesheets-reports/reportLabels.js.map +7 -0
  458. package/dist/modules/staff/lib/timesheets-reports/reportReference.js +83 -0
  459. package/dist/modules/staff/lib/timesheets-reports/reportReference.js.map +7 -0
  460. package/dist/modules/staff/lib/timesheets-reports/reportRows.js +42 -0
  461. package/dist/modules/staff/lib/timesheets-reports/reportRows.js.map +7 -0
  462. package/dist/modules/staff/lib/timesheets-reports/reportSheetData.js +107 -0
  463. package/dist/modules/staff/lib/timesheets-reports/reportSheetData.js.map +7 -0
  464. package/dist/modules/staff/lib/timesheets-reports/reportTotals.js +289 -0
  465. package/dist/modules/staff/lib/timesheets-reports/reportTotals.js.map +7 -0
  466. package/dist/modules/staff/lib/timesheets-reports/xlsx.js +145 -0
  467. package/dist/modules/staff/lib/timesheets-reports/xlsx.js.map +7 -0
  468. package/dist/modules/staff/lib/timesheets-tasks/computeTaskRollups.js +116 -0
  469. package/dist/modules/staff/lib/timesheets-tasks/computeTaskRollups.js.map +7 -0
  470. package/dist/modules/staff/lib/timesheets-tasks/defaultStatusTemplate.js +50 -0
  471. package/dist/modules/staff/lib/timesheets-tasks/defaultStatusTemplate.js.map +7 -0
  472. package/dist/modules/staff/lib/timesheets-tasks/seedProjectStatuses.js +27 -0
  473. package/dist/modules/staff/lib/timesheets-tasks/seedProjectStatuses.js.map +7 -0
  474. package/dist/modules/staff/lib/timesheets-tasks/statusPositions.js +65 -0
  475. package/dist/modules/staff/lib/timesheets-tasks/statusPositions.js.map +7 -0
  476. package/dist/modules/staff/lib/timesheets-tasks/statusSlug.js +31 -0
  477. package/dist/modules/staff/lib/timesheets-tasks/statusSlug.js.map +7 -0
  478. package/dist/modules/staff/lib/timesheets-tasks/taskHierarchy.js +31 -0
  479. package/dist/modules/staff/lib/timesheets-tasks/taskHierarchy.js.map +7 -0
  480. package/dist/modules/staff/lib/timesheets-tasks/taskHoursTotals.js +17 -0
  481. package/dist/modules/staff/lib/timesheets-tasks/taskHoursTotals.js.map +7 -0
  482. package/dist/modules/staff/lib/timesheets-tasks/taskReference.js +51 -0
  483. package/dist/modules/staff/lib/timesheets-tasks/taskReference.js.map +7 -0
  484. package/dist/modules/staff/lib/timesheets-ui/CreateProjectDialog.js +11 -4
  485. package/dist/modules/staff/lib/timesheets-ui/CreateProjectDialog.js.map +2 -2
  486. package/dist/modules/staff/lib/timesheets-ui/ListView.js +369 -111
  487. package/dist/modules/staff/lib/timesheets-ui/ListView.js.map +2 -2
  488. package/dist/modules/staff/lib/timesheets-ui/TimerBar.js +47 -2
  489. package/dist/modules/staff/lib/timesheets-ui/TimerBar.js.map +2 -2
  490. package/dist/modules/staff/migrations/Migration20260813073131_staff.js +79 -0
  491. package/dist/modules/staff/migrations/Migration20260813073131_staff.js.map +7 -0
  492. package/dist/modules/staff/migrations/Migration20260824143357_staff.js +15 -0
  493. package/dist/modules/staff/migrations/Migration20260824143357_staff.js.map +7 -0
  494. package/dist/modules/staff/notifications.client.js +16 -0
  495. package/dist/modules/staff/notifications.client.js.map +7 -0
  496. package/dist/modules/staff/notifications.handlers.js +80 -0
  497. package/dist/modules/staff/notifications.handlers.js.map +7 -0
  498. package/dist/modules/staff/notifications.js +168 -0
  499. package/dist/modules/staff/notifications.js.map +2 -2
  500. package/dist/modules/staff/search.js +159 -1
  501. package/dist/modules/staff/search.js.map +2 -2
  502. package/dist/modules/staff/setup.js +20 -1
  503. package/dist/modules/staff/setup.js.map +2 -2
  504. package/dist/modules/staff/subscribers/time-project-access-requested-notification.js +68 -0
  505. package/dist/modules/staff/subscribers/time-project-access-requested-notification.js.map +7 -0
  506. package/dist/modules/staff/subscribers/time-project-budget-threshold-notification.js +149 -0
  507. package/dist/modules/staff/subscribers/time-project-budget-threshold-notification.js.map +7 -0
  508. package/dist/modules/staff/subscribers/time-report-approved-notification.js +78 -0
  509. package/dist/modules/staff/subscribers/time-report-approved-notification.js.map +7 -0
  510. package/dist/modules/staff/subscribers/time-report-portal-broadcast.js +78 -0
  511. package/dist/modules/staff/subscribers/time-report-portal-broadcast.js.map +7 -0
  512. package/dist/modules/staff/translations.js +4 -1
  513. package/dist/modules/staff/translations.js.map +2 -2
  514. package/dist/modules/staff/widgets/components.js +7 -0
  515. package/dist/modules/staff/widgets/components.js.map +7 -0
  516. package/dist/modules/staff/widgets/injection/timer-sidebar-indicator/widget.js +5 -4
  517. package/dist/modules/staff/widgets/injection/timer-sidebar-indicator/widget.js.map +2 -2
  518. package/dist/modules/staff/widgets/notifications/TimeProjectAccessRequestedRenderer.js +135 -0
  519. package/dist/modules/staff/widgets/notifications/TimeProjectAccessRequestedRenderer.js.map +7 -0
  520. package/dist/modules/staff/widgets/notifications/index.js +5 -0
  521. package/dist/modules/staff/widgets/notifications/index.js.map +7 -0
  522. package/dist/modules/staff/workers/timesheets-reapply-rounding.js +43 -0
  523. package/dist/modules/staff/workers/timesheets-reapply-rounding.js.map +7 -0
  524. package/generated/entities/staff_time_entry/index.ts +7 -0
  525. package/generated/entities/staff_time_entry_tag/index.ts +6 -0
  526. package/generated/entities/staff_time_project/index.ts +8 -0
  527. package/generated/entities/staff_time_report/index.ts +27 -0
  528. package/generated/entities/staff_time_report_entry/index.ts +12 -0
  529. package/generated/entities/staff_time_report_event/index.ts +9 -0
  530. package/generated/entities/staff_time_report_project/index.ts +6 -0
  531. package/generated/entities/staff_time_tag/index.ts +9 -0
  532. package/generated/entities/staff_time_task/index.ts +17 -0
  533. package/generated/entities/staff_time_task_comment/index.ts +9 -0
  534. package/generated/entities/staff_time_task_status/index.ts +13 -0
  535. package/generated/entities/staff_time_task_tag/index.ts +6 -0
  536. package/generated/entities.ids.generated.ts +11 -1
  537. package/generated/entity-fields-registry.ts +149 -0
  538. package/package.json +7 -7
  539. package/src/helpers/integration/timesheetFixtures.ts +31 -1
  540. package/src/modules/auth/README.md +1 -1
  541. package/src/modules/auth/cli.ts +35 -1
  542. package/src/modules/auth/i18n/de.json +7 -0
  543. package/src/modules/auth/i18n/en.json +7 -0
  544. package/src/modules/auth/i18n/es.json +7 -0
  545. package/src/modules/auth/i18n/ko.json +7 -0
  546. package/src/modules/auth/i18n/pl.json +7 -0
  547. package/src/modules/auth/lib/backendChrome.tsx +1 -0
  548. package/src/modules/customer_accounts/lib/customerRoleAcls.ts +79 -0
  549. package/src/modules/customer_accounts/setup.ts +1 -45
  550. package/src/modules/data_sync/AGENTS.md +34 -0
  551. package/src/modules/data_sync/api/options.ts +2 -0
  552. package/src/modules/data_sync/backend/data-sync/page.tsx +82 -33
  553. package/src/modules/data_sync/i18n/de.json +1 -0
  554. package/src/modules/data_sync/i18n/en.json +1 -0
  555. package/src/modules/data_sync/i18n/es.json +1 -0
  556. package/src/modules/data_sync/i18n/ko.json +1 -0
  557. package/src/modules/data_sync/i18n/pl.json +1 -0
  558. package/src/modules/data_sync/lib/adapter.ts +45 -0
  559. package/src/modules/data_sync/lib/start-controls.ts +89 -0
  560. package/src/modules/staff/AGENTS.md +769 -1
  561. package/src/modules/staff/acl.ts +9 -0
  562. package/src/modules/staff/ai-agents.ts +189 -0
  563. package/src/modules/staff/ai-tools/time-tracking-pack.ts +494 -0
  564. package/src/modules/staff/ai-tools/types.ts +82 -0
  565. package/src/modules/staff/ai-tools.ts +13 -0
  566. package/src/modules/staff/analytics.ts +113 -0
  567. package/src/modules/staff/api/guards.ts +85 -3
  568. package/src/modules/staff/api/leave-requests/accept/route.ts +0 -2
  569. package/src/modules/staff/api/leave-requests/reject/route.ts +0 -2
  570. package/src/modules/staff/api/portal/time-reports/[id]/route.ts +182 -0
  571. package/src/modules/staff/api/portal/time-reports/route.ts +270 -0
  572. package/src/modules/staff/api/team-members/self/route.ts +0 -2
  573. package/src/modules/staff/api/team-members/tags/assign/route.ts +0 -2
  574. package/src/modules/staff/api/team-members/tags/unassign/route.ts +0 -2
  575. package/src/modules/staff/api/timesheets/_shared/withTimesheetInterceptors.ts +292 -0
  576. package/src/modules/staff/api/timesheets/access-requests/route.ts +299 -0
  577. package/src/modules/staff/api/timesheets/my-projects/[projectId]/route.ts +15 -7
  578. package/src/modules/staff/api/timesheets/my-projects/route.ts +14 -1
  579. package/src/modules/staff/api/timesheets/my-work/myWorkAggregate.ts +90 -0
  580. package/src/modules/staff/api/timesheets/my-work/route.ts +475 -0
  581. package/src/modules/staff/api/timesheets/projects/kpis/route.ts +15 -2
  582. package/src/modules/staff/api/timesheets/reports/[id]/close/route.ts +203 -0
  583. package/src/modules/staff/api/timesheets/reports/[id]/export/route.ts +315 -0
  584. package/src/modules/staff/api/timesheets/reports/[id]/sheet/route.ts +220 -0
  585. package/src/modules/staff/api/timesheets/reports/[id]/unlock/route.ts +208 -0
  586. package/src/modules/staff/api/timesheets/reports/preview/route.ts +449 -0
  587. package/src/modules/staff/api/timesheets/reports/route.ts +386 -0
  588. package/src/modules/staff/api/timesheets/reports/shared.ts +121 -0
  589. package/src/modules/staff/api/timesheets/settings/reapply-rounding/route.ts +205 -0
  590. package/src/modules/staff/api/timesheets/settings/rounding-impact/route.ts +226 -0
  591. package/src/modules/staff/api/timesheets/settings/route.ts +242 -0
  592. package/src/modules/staff/api/timesheets/tags/access.ts +134 -0
  593. package/src/modules/staff/api/timesheets/tags/entry-assignments/route.ts +290 -0
  594. package/src/modules/staff/api/timesheets/tags/route.ts +177 -0
  595. package/src/modules/staff/api/timesheets/tags/task-assignments/route.ts +294 -0
  596. package/src/modules/staff/api/timesheets/task-statuses/route.ts +232 -0
  597. package/src/modules/staff/api/timesheets/tasks/[id]/comments/route.ts +532 -0
  598. package/src/modules/staff/api/timesheets/tasks/[id]/status/route.ts +271 -0
  599. package/src/modules/staff/api/timesheets/tasks/route.ts +443 -0
  600. package/src/modules/staff/api/timesheets/time-entries/[id]/duplicate/route.ts +306 -0
  601. package/src/modules/staff/api/timesheets/time-entries/[id]/segments/[segmentId]/route.ts +32 -10
  602. package/src/modules/staff/api/timesheets/time-entries/[id]/segments/route.ts +32 -17
  603. package/src/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.ts +27 -6
  604. package/src/modules/staff/api/timesheets/time-entries/[id]/timer-stop/route.ts +36 -6
  605. package/src/modules/staff/api/timesheets/time-entries/bulk/route.ts +331 -34
  606. package/src/modules/staff/api/timesheets/time-entries/copy-day/route.ts +533 -0
  607. package/src/modules/staff/api/timesheets/time-entries/overlaps/route.ts +457 -0
  608. package/src/modules/staff/api/timesheets/time-entries/route.ts +378 -24
  609. package/src/modules/staff/api/timesheets/time-entries/start-timer/route.ts +31 -9
  610. package/src/modules/staff/api/timesheets/time-projects/[id]/change-currency/route.ts +246 -0
  611. package/src/modules/staff/api/timesheets/time-projects/[id]/employees/route.ts +62 -2
  612. package/src/modules/staff/api/timesheets/time-projects/route.ts +397 -22
  613. package/src/modules/staff/backend/staff/time-tracking/board/page.meta.ts +14 -0
  614. package/src/modules/staff/backend/staff/time-tracking/board/page.tsx +151 -0
  615. package/src/modules/staff/backend/staff/time-tracking/entries/page.meta.ts +14 -0
  616. package/src/modules/staff/backend/staff/time-tracking/entries/page.tsx +1229 -0
  617. package/src/modules/staff/backend/staff/time-tracking/page.meta.ts +11 -0
  618. package/src/modules/staff/backend/staff/time-tracking/page.tsx +823 -0
  619. package/src/modules/staff/backend/staff/time-tracking/projects/[id]/board/page.meta.ts +12 -0
  620. package/src/modules/staff/backend/staff/time-tracking/projects/[id]/board/page.tsx +81 -0
  621. package/src/modules/staff/backend/staff/time-tracking/projects/[id]/edit/page.meta.ts +12 -0
  622. package/src/modules/staff/backend/staff/time-tracking/projects/[id]/edit/page.tsx +275 -0
  623. package/src/modules/staff/backend/staff/time-tracking/projects/[id]/page.meta.ts +12 -0
  624. package/src/modules/staff/backend/staff/time-tracking/projects/[id]/page.tsx +1420 -0
  625. package/src/modules/staff/backend/staff/time-tracking/projects/create/page.meta.ts +12 -0
  626. package/src/modules/staff/backend/staff/time-tracking/projects/create/page.tsx +109 -0
  627. package/src/modules/staff/backend/staff/time-tracking/projects/page.meta.ts +15 -0
  628. package/src/modules/staff/backend/staff/time-tracking/projects/page.tsx +1254 -0
  629. package/src/modules/staff/backend/staff/time-tracking/projects/projectFormConfig.ts +466 -0
  630. package/src/modules/staff/backend/staff/time-tracking/reports/[id]/page.meta.ts +12 -0
  631. package/src/modules/staff/backend/staff/time-tracking/reports/[id]/page.tsx +632 -0
  632. package/src/modules/staff/backend/staff/time-tracking/reports/create/page.meta.ts +12 -0
  633. package/src/modules/staff/backend/staff/time-tracking/reports/create/page.tsx +867 -0
  634. package/src/modules/staff/backend/staff/time-tracking/reports/page.meta.ts +14 -0
  635. package/src/modules/staff/backend/staff/time-tracking/reports/page.tsx +246 -0
  636. package/src/modules/staff/backend/staff/time-tracking/settings/page.meta.ts +14 -0
  637. package/src/modules/staff/backend/staff/time-tracking/settings/page.tsx +701 -0
  638. package/src/modules/staff/backend/staff/time-tracking/timesheet/GridView.tsx +953 -0
  639. package/src/modules/staff/backend/staff/time-tracking/timesheet/page.meta.ts +14 -0
  640. package/src/modules/staff/backend/staff/time-tracking/timesheet/page.tsx +800 -0
  641. package/src/modules/staff/backend/staff/timesheets/page.meta.ts +6 -6
  642. package/src/modules/staff/backend/staff/timesheets/page.tsx +12 -1010
  643. package/src/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.meta.ts +0 -6
  644. package/src/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.tsx +4 -158
  645. package/src/modules/staff/backend/staff/timesheets/projects/[id]/page.meta.ts +0 -6
  646. package/src/modules/staff/backend/staff/timesheets/projects/[id]/page.tsx +4 -609
  647. package/src/modules/staff/backend/staff/timesheets/projects/create/page.meta.ts +0 -6
  648. package/src/modules/staff/backend/staff/timesheets/projects/create/page.tsx +3 -89
  649. package/src/modules/staff/backend/staff/timesheets/projects/page.meta.ts +2 -19
  650. package/src/modules/staff/backend/staff/timesheets/projects/page.tsx +3 -778
  651. package/src/modules/staff/ce.ts +63 -0
  652. package/src/modules/staff/cli.ts +207 -1
  653. package/src/modules/staff/commands/index.ts +5 -0
  654. package/src/modules/staff/commands/timesheets-entries.ts +1006 -28
  655. package/src/modules/staff/commands/timesheets-projects.ts +593 -7
  656. package/src/modules/staff/commands/timesheets-reports.ts +1329 -0
  657. package/src/modules/staff/commands/timesheets-tags.ts +1315 -0
  658. package/src/modules/staff/commands/timesheets-task-comments.ts +583 -0
  659. package/src/modules/staff/commands/timesheets-task-statuses.ts +889 -0
  660. package/src/modules/staff/commands/timesheets-tasks.ts +1121 -0
  661. package/src/modules/staff/data/enrichers.ts +675 -10
  662. package/src/modules/staff/data/entities.ts +494 -2
  663. package/src/modules/staff/data/extensions.ts +68 -0
  664. package/src/modules/staff/data/validators.ts +348 -3
  665. package/src/modules/staff/di.ts +96 -0
  666. package/src/modules/staff/events.payloads.ts +268 -0
  667. package/src/modules/staff/events.ts +87 -5
  668. package/src/modules/staff/extension-points.ts +308 -0
  669. package/src/modules/staff/frontend/[orgSlug]/portal/time-reports/[id]/page.meta.ts +10 -0
  670. package/src/modules/staff/frontend/[orgSlug]/portal/time-reports/[id]/page.tsx +225 -0
  671. package/src/modules/staff/frontend/[orgSlug]/portal/time-reports/page.meta.ts +17 -0
  672. package/src/modules/staff/frontend/[orgSlug]/portal/time-reports/page.tsx +182 -0
  673. package/src/modules/staff/i18n/de.json +899 -0
  674. package/src/modules/staff/i18n/en.json +899 -0
  675. package/src/modules/staff/i18n/es.json +899 -0
  676. package/src/modules/staff/i18n/ko.json +899 -0
  677. package/src/modules/staff/i18n/pl.json +899 -0
  678. package/src/modules/staff/lib/crud.ts +14 -3
  679. package/src/modules/staff/lib/time-tracking/access.ts +155 -0
  680. package/src/modules/staff/lib/time-tracking/billability.ts +68 -0
  681. package/src/modules/staff/lib/time-tracking/capacity.ts +124 -0
  682. package/src/modules/staff/lib/time-tracking/componentContracts.ts +26 -0
  683. package/src/modules/staff/lib/time-tracking/cost.ts +144 -0
  684. package/src/modules/staff/lib/time-tracking/duration.ts +68 -0
  685. package/src/modules/staff/lib/time-tracking/featureAccess.ts +115 -0
  686. package/src/modules/staff/lib/time-tracking/interval.ts +90 -0
  687. package/src/modules/staff/lib/time-tracking/migrateProjectCodes.ts +161 -0
  688. package/src/modules/staff/lib/time-tracking/moneyVisibility.ts +37 -0
  689. package/src/modules/staff/lib/time-tracking/overlap.ts +159 -0
  690. package/src/modules/staff/lib/time-tracking/portalRecipients.ts +76 -0
  691. package/src/modules/staff/lib/time-tracking/portalReports.ts +31 -0
  692. package/src/modules/staff/lib/time-tracking/projectCode.ts +221 -0
  693. package/src/modules/staff/lib/time-tracking/reapplyRounding.ts +313 -0
  694. package/src/modules/staff/lib/time-tracking/recalculationRunner.ts +118 -0
  695. package/src/modules/staff/lib/time-tracking/recalculations.ts +135 -0
  696. package/src/modules/staff/lib/time-tracking/registries/invoke.ts +122 -0
  697. package/src/modules/staff/lib/time-tracking/registries/registry.ts +124 -0
  698. package/src/modules/staff/lib/time-tracking/registries/scope.ts +56 -0
  699. package/src/modules/staff/lib/time-tracking/rollup.ts +59 -0
  700. package/src/modules/staff/lib/time-tracking/rounding.ts +126 -0
  701. package/src/modules/staff/lib/time-tracking/roundingImpact.ts +75 -0
  702. package/src/modules/staff/lib/time-tracking/settingKeys.ts +274 -0
  703. package/src/modules/staff/lib/time-tracking/settings.ts +168 -0
  704. package/src/modules/staff/lib/time-tracking/sqlInClause.ts +22 -0
  705. package/src/modules/staff/lib/time-tracking/timeEntrySources.ts +85 -0
  706. package/src/modules/staff/lib/time-tracking-ui/BoardFilterChips.tsx +360 -0
  707. package/src/modules/staff/lib/time-tracking-ui/CustomerPicker.tsx +427 -0
  708. package/src/modules/staff/lib/time-tracking-ui/DurationInput.tsx +192 -0
  709. package/src/modules/staff/lib/time-tracking-ui/EntryDetailsFields.tsx +224 -0
  710. package/src/modules/staff/lib/time-tracking-ui/EntryFilterBar.tsx +144 -0
  711. package/src/modules/staff/lib/time-tracking-ui/KanbanBoard.tsx +1065 -0
  712. package/src/modules/staff/lib/time-tracking-ui/KanbanCard.tsx +435 -0
  713. package/src/modules/staff/lib/time-tracking-ui/KanbanColumn.tsx +316 -0
  714. package/src/modules/staff/lib/time-tracking-ui/NewTaskDialog.tsx +291 -0
  715. package/src/modules/staff/lib/time-tracking-ui/NoProjectAccess.tsx +162 -0
  716. package/src/modules/staff/lib/time-tracking-ui/PeriodSelector.tsx +149 -0
  717. package/src/modules/staff/lib/time-tracking-ui/PhasePlaceholder.tsx +31 -0
  718. package/src/modules/staff/lib/time-tracking-ui/ProjectCodeField.tsx +230 -0
  719. package/src/modules/staff/lib/time-tracking-ui/ProjectCurrencyField.tsx +432 -0
  720. package/src/modules/staff/lib/time-tracking-ui/ProjectFormSections.tsx +323 -0
  721. package/src/modules/staff/lib/time-tracking-ui/ProjectTeamDrawer.tsx +930 -0
  722. package/src/modules/staff/lib/time-tracking-ui/ReportSheet.tsx +357 -0
  723. package/src/modules/staff/lib/time-tracking-ui/TagPicker.tsx +195 -0
  724. package/src/modules/staff/lib/time-tracking-ui/TaskBoardScreen.tsx +243 -0
  725. package/src/modules/staff/lib/time-tracking-ui/TaskCommentThread.tsx +137 -0
  726. package/src/modules/staff/lib/time-tracking-ui/TaskDrawer.tsx +1405 -0
  727. package/src/modules/staff/lib/time-tracking-ui/TaskListView.tsx +260 -0
  728. package/src/modules/staff/lib/time-tracking-ui/TaskPicker.tsx +435 -0
  729. package/src/modules/staff/lib/time-tracking-ui/TaskQuickLog.tsx +296 -0
  730. package/src/modules/staff/lib/time-tracking-ui/TimeEntriesSummaryFooter.tsx +97 -0
  731. package/src/modules/staff/lib/time-tracking-ui/TimeEntryDialog.tsx +1929 -0
  732. package/src/modules/staff/lib/time-tracking-ui/TimeEntryDurationCell.tsx +126 -0
  733. package/src/modules/staff/lib/time-tracking-ui/TimesheetCalendar.tsx +265 -0
  734. package/src/modules/staff/lib/time-tracking-ui/TimesheetPeriodFooter.tsx +78 -0
  735. package/src/modules/staff/lib/time-tracking-ui/TimesheetViewSwitch.tsx +70 -0
  736. package/src/modules/staff/lib/time-tracking-ui/boardDirectory.ts +231 -0
  737. package/src/modules/staff/lib/time-tracking-ui/boardFilters.ts +128 -0
  738. package/src/modules/staff/lib/time-tracking-ui/boardSummary.ts +20 -0
  739. package/src/modules/staff/lib/time-tracking-ui/kanbanBoardData.ts +228 -0
  740. package/src/modules/staff/lib/time-tracking-ui/projectTeamAssignment.ts +143 -0
  741. package/src/modules/staff/lib/time-tracking-ui/taskDrawerData.ts +194 -0
  742. package/src/modules/staff/lib/time-tracking-ui/timeEntryDialogState.ts +400 -0
  743. package/src/modules/staff/lib/time-tracking-ui/timeEntryListData.ts +271 -0
  744. package/src/modules/staff/lib/time-tracking-ui/timeTrackingSettingsForm.ts +168 -0
  745. package/src/modules/staff/lib/time-tracking-ui/timesheetData.ts +225 -0
  746. package/src/modules/staff/lib/time-tracking-ui/timesheetPeriod.ts +278 -0
  747. package/src/modules/staff/lib/time-tracking-ui/timesheetTargets.ts +93 -0
  748. package/src/modules/staff/lib/time-tracking-ui/useBoardFilters.ts +107 -0
  749. package/src/modules/staff/lib/time-tracking-ui/useTimesheetPreferences.ts +245 -0
  750. package/src/modules/staff/lib/timeTrackingSeeds.ts +1037 -0
  751. package/src/modules/staff/lib/timesheets/timeEntryCacheInvalidation.ts +11 -10
  752. package/src/modules/staff/lib/timesheets/timeEntryDecoration.ts +256 -0
  753. package/src/modules/staff/lib/timesheets-projects/budgetBurn.ts +60 -0
  754. package/src/modules/staff/lib/timesheets-projects/budgetThreshold.ts +135 -0
  755. package/src/modules/staff/lib/timesheets-projects/budgetThresholdState.ts +137 -0
  756. package/src/modules/staff/lib/timesheets-projects/computeProjectFinancials.ts +142 -0
  757. package/src/modules/staff/lib/timesheets-projects/reportSelection.ts +82 -0
  758. package/src/modules/staff/lib/timesheets-projects-ui/AssignMembersDialog.tsx +237 -0
  759. package/src/modules/staff/lib/timesheets-projects-ui/ProjectBudgetCell.tsx +33 -0
  760. package/src/modules/staff/lib/timesheets-projects-ui/ProjectCard.tsx +56 -1
  761. package/src/modules/staff/lib/timesheets-projects-ui/ProjectsBulkActionsBar.tsx +90 -0
  762. package/src/modules/staff/lib/timesheets-reports/buildReportSheet.ts +125 -0
  763. package/src/modules/staff/lib/timesheets-reports/loadReportData.ts +251 -0
  764. package/src/modules/staff/lib/timesheets-reports/pdf.ts +263 -0
  765. package/src/modules/staff/lib/timesheets-reports/reportApprovalPolicies.ts +137 -0
  766. package/src/modules/staff/lib/timesheets-reports/reportConfigData.ts +247 -0
  767. package/src/modules/staff/lib/timesheets-reports/reportConfigState.ts +89 -0
  768. package/src/modules/staff/lib/timesheets-reports/reportExport.ts +373 -0
  769. package/src/modules/staff/lib/timesheets-reports/reportExportFormats.ts +71 -0
  770. package/src/modules/staff/lib/timesheets-reports/reportGroupings.ts +153 -0
  771. package/src/modules/staff/lib/timesheets-reports/reportLabels.ts +23 -0
  772. package/src/modules/staff/lib/timesheets-reports/reportReference.ts +146 -0
  773. package/src/modules/staff/lib/timesheets-reports/reportRows.ts +89 -0
  774. package/src/modules/staff/lib/timesheets-reports/reportSheetData.ts +196 -0
  775. package/src/modules/staff/lib/timesheets-reports/reportTotals.ts +544 -0
  776. package/src/modules/staff/lib/timesheets-reports/xlsx.ts +207 -0
  777. package/src/modules/staff/lib/timesheets-tasks/computeTaskRollups.ts +238 -0
  778. package/src/modules/staff/lib/timesheets-tasks/defaultStatusTemplate.ts +90 -0
  779. package/src/modules/staff/lib/timesheets-tasks/seedProjectStatuses.ts +43 -0
  780. package/src/modules/staff/lib/timesheets-tasks/statusPositions.ts +132 -0
  781. package/src/modules/staff/lib/timesheets-tasks/statusSlug.ts +43 -0
  782. package/src/modules/staff/lib/timesheets-tasks/taskHierarchy.ts +99 -0
  783. package/src/modules/staff/lib/timesheets-tasks/taskHoursTotals.ts +42 -0
  784. package/src/modules/staff/lib/timesheets-tasks/taskReference.ts +108 -0
  785. package/src/modules/staff/lib/timesheets-ui/CreateProjectDialog.tsx +18 -4
  786. package/src/modules/staff/lib/timesheets-ui/ListView.tsx +504 -149
  787. package/src/modules/staff/lib/timesheets-ui/TimerBar.tsx +50 -2
  788. package/src/modules/staff/migrations/.snapshot-open-mercato.json +3244 -228
  789. package/src/modules/staff/migrations/Migration20260813073131_staff.ts +94 -0
  790. package/src/modules/staff/migrations/Migration20260824143357_staff.ts +17 -0
  791. package/src/modules/staff/notifications.client.ts +16 -0
  792. package/src/modules/staff/notifications.handlers.ts +88 -0
  793. package/src/modules/staff/notifications.ts +163 -0
  794. package/src/modules/staff/search.ts +191 -1
  795. package/src/modules/staff/setup.ts +22 -0
  796. package/src/modules/staff/subscribers/time-project-access-requested-notification.ts +95 -0
  797. package/src/modules/staff/subscribers/time-project-budget-threshold-notification.ts +226 -0
  798. package/src/modules/staff/subscribers/time-report-approved-notification.ts +116 -0
  799. package/src/modules/staff/subscribers/time-report-portal-broadcast.ts +120 -0
  800. package/src/modules/staff/translations.ts +17 -0
  801. package/src/modules/staff/widgets/components.ts +34 -0
  802. package/src/modules/staff/widgets/injection/timer-sidebar-indicator/widget.tsx +6 -5
  803. package/src/modules/staff/widgets/notifications/TimeProjectAccessRequestedRenderer.tsx +138 -0
  804. package/src/modules/staff/widgets/notifications/index.ts +1 -0
  805. package/src/modules/staff/workers/timesheets-reapply-rounding.ts +59 -0
  806. package/dist/modules/staff/backend/staff/timesheets/projects/projectFormConfig.js +0 -123
  807. package/dist/modules/staff/backend/staff/timesheets/projects/projectFormConfig.js.map +0 -7
  808. package/src/modules/staff/backend/staff/timesheets/projects/projectFormConfig.ts +0 -138
@@ -0,0 +1,1065 @@
1
+ "use client"
2
+
3
+ import * as React from 'react'
4
+ import { useRouter, useSearchParams } from 'next/navigation'
5
+ import { useQueries, useQuery, useQueryClient } from '@tanstack/react-query'
6
+ import {
7
+ DndContext,
8
+ DragOverlay,
9
+ MeasuringStrategy,
10
+ PointerSensor,
11
+ pointerWithin,
12
+ useSensor,
13
+ useSensors,
14
+ type Announcements,
15
+ type CollisionDetection,
16
+ type DragEndEvent,
17
+ type DragStartEvent,
18
+ type ScreenReaderInstructions,
19
+ type UniqueIdentifier,
20
+ } from '@dnd-kit/core'
21
+ import { Plus, RotateCcw } from 'lucide-react'
22
+ import { Alert, AlertDescription } from '@open-mercato/ui/primitives/alert'
23
+ import { Button } from '@open-mercato/ui/primitives/button'
24
+ import { EmptyState } from '@open-mercato/ui/primitives/empty-state'
25
+ import { Input } from '@open-mercato/ui/primitives/input'
26
+ import { ErrorMessage, LoadingMessage } from '@open-mercato/ui/backend/detail'
27
+ import { apiCall, apiCallOrThrow, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'
28
+ import { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'
29
+ import { surfaceRecordConflict } from '@open-mercato/ui/backend/conflicts'
30
+ import { useBackendChrome } from '@open-mercato/ui/backend/BackendChromeProvider'
31
+ import { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'
32
+ import { useAppEvent } from '@open-mercato/ui/backend/injection/useAppEvent'
33
+ import { flash } from '@open-mercato/ui/backend/FlashMessages'
34
+ import { hasFeature } from '@open-mercato/shared/security/features'
35
+ import { useT } from '@open-mercato/shared/lib/i18n/context'
36
+ import { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'
37
+ import { createLogger } from '@open-mercato/shared/lib/logger'
38
+ import { sumTaskLoggedMinutes } from '../timesheets-tasks/taskHoursTotals'
39
+ import { KanbanColumn, KANBAN_COLUMN_DROPPABLE_PREFIX } from './KanbanColumn'
40
+ import { InjectionSpot } from '@open-mercato/ui/backend/injection/InjectionSpot'
41
+ import { extensionPoints } from '@open-mercato/core/modules/staff/extension-points'
42
+ import { TaskDrawer } from './TaskDrawer'
43
+ import type { KanbanMoveTarget, KanbanTagOption } from './KanbanCard'
44
+ import {
45
+ BOARD_DIRECTORY_PAGE_SIZE,
46
+ BOARD_PAGE_SIZE,
47
+ BOARD_QUERY_ROOT,
48
+ boardColumnQueryKey,
49
+ fetchTaskPages,
50
+ useAssigneeNames,
51
+ useBoardSelf,
52
+ useBoardStatuses,
53
+ useTagLabels,
54
+ } from './boardDirectory'
55
+ import {
56
+ EMPTY_BOARD_FILTERS,
57
+ boardServerFilterKey,
58
+ boardServerFilterParams,
59
+ visibleBoardStatusIds,
60
+ type BoardFilters,
61
+ } from './boardFilters'
62
+ import type { BoardSummary } from './boardSummary'
63
+ import {
64
+ computeSubtaskProgress,
65
+ formatBoardMinutes,
66
+ nextPositionAtColumnTop,
67
+ readItems,
68
+ readString,
69
+ sortByPosition,
70
+ toBoardTask,
71
+ type BoardApiRow,
72
+ type BoardTask,
73
+ } from './kanbanBoardData'
74
+
75
+ const logger = createLogger('staff').child({ component: 'KanbanBoard' })
76
+
77
+ export { BOARD_PAGE_SIZE, BOARD_DIRECTORY_PAGE_SIZE }
78
+ export const BOARD_MAX_CHILD_PAGES = 5
79
+ export const MOVE_MUTATION_CONTEXT_ID = 'staff.time_tracking.board.move'
80
+ export const CREATE_MUTATION_CONTEXT_ID = 'staff.time_tracking.board.create'
81
+ export const MANAGE_COLUMNS_FEATURE = 'staff.timesheets.projects.manage'
82
+ export const MANAGE_TASKS_FEATURE = 'staff.timesheets.tasks.manage'
83
+ export const TASK_STATUS_CHANGED_EVENT = 'staff.timesheets.time_task.status_changed'
84
+
85
+ /** Window a locally-originated move stays "mine" for, so a lost SSE cannot wedge the filter. */
86
+ const OWN_MOVE_ECHO_TTL_MS = 15_000
87
+
88
+ type ColumnCache = { items: BoardApiRow[]; total: number }
89
+
90
+ type PendingMove = {
91
+ taskId: string
92
+ targetStatusId: string
93
+ }
94
+
95
+ export type KanbanBoardProps = {
96
+ timeProjectId: string
97
+ /** Used by the drawer's header; the page owns the breadcrumb and the rest of the chrome. */
98
+ projectName?: string | null
99
+ /** Board filters (T3.8). Assignee narrows the request, status picks columns, tag filters rows. */
100
+ filters?: BoardFilters
101
+ /** Reports what is on screen so the page head can describe it without a second fetch. */
102
+ onSummaryChange?: (summary: BoardSummary) => void
103
+ }
104
+
105
+ function statusIdFromColumnKey(key: readonly unknown[]): string | null {
106
+ const part = key.find((entry) => typeof entry === 'string' && entry.startsWith('status:'))
107
+ return typeof part === 'string' ? part.slice('status:'.length) : null
108
+ }
109
+
110
+ /**
111
+ * `true` when `candidate` names a strictly earlier version than `reference`.
112
+ * Unparseable input answers `false`, so an unexpected format leaves whatever the
113
+ * list handed us in charge rather than pinning the board to a remembered token.
114
+ */
115
+ function isOlderVersion(candidate: string, reference: string): boolean {
116
+ const candidateAt = Date.parse(candidate)
117
+ const referenceAt = Date.parse(reference)
118
+ if (!Number.isFinite(candidateAt) || !Number.isFinite(referenceAt)) return false
119
+ return candidateAt < referenceAt
120
+ }
121
+
122
+ /**
123
+ * The Kanban board of screen 6 (T3.6, US-C1/US-C2).
124
+ *
125
+ * Four things carry the design intent and are worth reading before changing anything:
126
+ *
127
+ * 1. **The move is optimistic and the rollback is exact.** Before the PATCH the board
128
+ * snapshots every column cache; the card is moved between them immediately. On any
129
+ * failure the whole snapshot is written back, which returns the card to the column
130
+ * it came from even when the drag crossed columns whose pages had shifted.
131
+ * 2. **A 409 is not a retryable failure.** The PATCH carries the card's `updatedAt` in
132
+ * the optimistic-lock header, so a 409 means somebody else moved this card while the
133
+ * drag was in flight. `surfaceRecordConflict` raises the shared conflict bar, the
134
+ * board refetches, and no Retry is offered — retrying would overwrite the other
135
+ * move. Every other failure gets the retry affordance of note 2.
136
+ * 3. **Echoes of your own move are ignored.** `status_changed` is broadcast to every
137
+ * connected board, including the one that caused it. Each locally-issued move
138
+ * registers a `taskId|statusId` key that the event handler consumes instead of
139
+ * refetching, so a colleague's drag moves the card here and yours does not fight the
140
+ * optimistic update that already landed.
141
+ * 4. **The next move's version comes out of the last move's response.** A move bumps
142
+ * the row's `updated_at`, so the version the board is holding for that card is spent
143
+ * the moment the PATCH commits — and the optimistic write of note 1 carries the
144
+ * pre-move version forward until a refetch replaces it. Reading the version back off
145
+ * the response (as `TaskDrawer` already does) is what lets the same card be moved
146
+ * twice in a row; waiting for the refetch made every second move answer 409 "changed
147
+ * by someone else" with nobody else editing.
148
+ */
149
+ export function KanbanBoard({
150
+ timeProjectId,
151
+ projectName,
152
+ filters = EMPTY_BOARD_FILTERS,
153
+ onSummaryChange,
154
+ }: KanbanBoardProps): React.ReactElement {
155
+ const t = useT()
156
+ const router = useRouter()
157
+ const searchParams = useSearchParams()
158
+ const queryClient = useQueryClient()
159
+ const scopeVersion = useOrganizationScopeVersion()
160
+ const { payload } = useBackendChrome()
161
+ const canManageColumns = hasFeature(payload?.grantedFeatures, MANAGE_COLUMNS_FEATURE)
162
+ const canManageTasks = hasFeature(payload?.grantedFeatures, MANAGE_TASKS_FEATURE)
163
+ const serverFilterKey = boardServerFilterKey(filters)
164
+ const serverFilterParams = boardServerFilterParams(filters)
165
+
166
+ const [pagesByStatus, setPagesByStatus] = React.useState<Record<string, number>>({})
167
+ const [activeDragTaskId, setActiveDragTaskId] = React.useState<string | null>(null)
168
+ const [pendingTaskIds, setPendingTaskIds] = React.useState<ReadonlySet<string>>(new Set())
169
+ const [failedMove, setFailedMove] = React.useState<PendingMove | null>(null)
170
+ const [quickAddPendingStatusId, setQuickAddPendingStatusId] = React.useState<string | null>(null)
171
+ const [composingStatus, setComposingStatus] = React.useState(false)
172
+ const [statusDraft, setStatusDraft] = React.useState('')
173
+
174
+ const ownMoveKeysRef = React.useRef(new Map<string, number>())
175
+
176
+ // The version the server confirmed for a card in its own move response, kept until
177
+ // a fetched row catches up with it (note 4). "Newest wins" never suppresses a real
178
+ // conflict: somebody else's move produces a version NEWER than the one confirmed
179
+ // here, and the row that carries it takes over — this only stops the board from
180
+ // replaying a version its own last move already spent.
181
+ const confirmedVersionsRef = React.useRef(new Map<string, string>())
182
+
183
+ const resolveTaskVersion = React.useCallback(
184
+ (taskId: string, rowVersion: string | null): string | null => {
185
+ const confirmed = confirmedVersionsRef.current.get(taskId)
186
+ if (!confirmed) return rowVersion
187
+ if (rowVersion && !isOlderVersion(rowVersion, confirmed)) {
188
+ confirmedVersionsRef.current.delete(taskId)
189
+ return rowVersion
190
+ }
191
+ return confirmed
192
+ },
193
+ [],
194
+ )
195
+
196
+ const { runMutation: runMoveMutation, retryLastMutation: retryMoveMutation } = useGuardedMutation<{
197
+ formId: string
198
+ resourceKind: string
199
+ resourceId: string
200
+ retryLastMutation: () => Promise<boolean>
201
+ }>({
202
+ contextId: MOVE_MUTATION_CONTEXT_ID,
203
+ blockedMessage: t('staff.time_tracking.board.move.error', 'Could not move the task.'),
204
+ })
205
+
206
+ const { runMutation: runCreateMutation, retryLastMutation: retryCreateMutation } = useGuardedMutation<{
207
+ formId: string
208
+ resourceKind: string
209
+ resourceId: string
210
+ retryLastMutation: () => Promise<boolean>
211
+ }>({
212
+ contextId: CREATE_MUTATION_CONTEXT_ID,
213
+ blockedMessage: t('staff.time_tracking.board.quickAdd.error', 'Could not create the task.'),
214
+ })
215
+
216
+ const statusesQuery = useBoardStatuses(timeProjectId)
217
+
218
+ const statuses = React.useMemo(() => statusesQuery.data ?? [], [statusesQuery.data])
219
+
220
+ // A status filter is not a request parameter here — it selects which columns exist.
221
+ const visibleStatuses = React.useMemo(
222
+ () => visibleBoardStatusIds(statuses, filters),
223
+ [filters, statuses],
224
+ )
225
+
226
+ const columnQueries = useQueries({
227
+ queries: visibleStatuses.map((status) => {
228
+ const pages = pagesByStatus[status.id] ?? 1
229
+ return {
230
+ queryKey: boardColumnQueryKey(scopeVersion, timeProjectId, status.id, pages, serverFilterKey),
231
+ staleTime: 15_000,
232
+ queryFn: async (): Promise<ColumnCache> =>
233
+ fetchTaskPages(
234
+ (page) =>
235
+ `timeProjectId=${encodeURIComponent(timeProjectId)}&taskStatusId=${encodeURIComponent(status.id)}&topLevelOnly=true&page=${page}&pageSize=${BOARD_PAGE_SIZE}&sortField=position&sortDir=asc${serverFilterParams}`,
236
+ pages,
237
+ ),
238
+ }
239
+ }),
240
+ })
241
+
242
+ const tasksByStatusId = React.useMemo(() => {
243
+ const map = new Map<string, { tasks: BoardTask[]; loaded: number; total: number }>()
244
+ visibleStatuses.forEach((status, index) => {
245
+ const data = columnQueries[index]?.data
246
+ const loaded = (data?.items ?? [])
247
+ .map(toBoardTask)
248
+ .filter((task): task is BoardTask => task !== null)
249
+ map.set(status.id, {
250
+ tasks: sortByPosition(loaded),
251
+ loaded: loaded.length,
252
+ total: data?.total ?? loaded.length,
253
+ })
254
+ })
255
+ return map
256
+ }, [columnQueries, visibleStatuses])
257
+
258
+ const allTasks = React.useMemo(() => {
259
+ const rows: BoardTask[] = []
260
+ for (const entry of tasksByStatusId.values()) rows.push(...entry.tasks)
261
+ return rows
262
+ }, [tasksByStatusId])
263
+
264
+ const hasChildren = React.useMemo(() => allTasks.some((task) => task.childCount > 0), [allTasks])
265
+
266
+ const childrenQuery = useQuery<BoardTask[]>({
267
+ queryKey: [...BOARD_QUERY_ROOT, 'children', `scope:${scopeVersion}`, `project:${timeProjectId}`],
268
+ enabled: hasChildren,
269
+ staleTime: 30_000,
270
+ queryFn: async () => {
271
+ // The list route filters children only by a single `parentTaskId`, so a board-wide
272
+ // sweep is the one request that answers "how many of each card's subtasks are done"
273
+ // without one call per card. Bounded, because the count is decoration, not data.
274
+ const { items } = await fetchTaskPages(
275
+ (page) =>
276
+ `timeProjectId=${encodeURIComponent(timeProjectId)}&page=${page}&pageSize=${BOARD_PAGE_SIZE}&sortField=position&sortDir=asc`,
277
+ BOARD_MAX_CHILD_PAGES,
278
+ )
279
+ return items
280
+ .map(toBoardTask)
281
+ .filter((task): task is BoardTask => task !== null && !!task.parentTaskId)
282
+ },
283
+ })
284
+
285
+ const doneStatusIds = React.useMemo(
286
+ () => new Set(statuses.filter((status) => status.isDone).map((status) => status.id)),
287
+ [statuses],
288
+ )
289
+
290
+ const subtasksByTaskId = React.useMemo(
291
+ () => computeSubtaskProgress(childrenQuery.data ?? [], doneStatusIds),
292
+ [childrenQuery.data, doneStatusIds],
293
+ )
294
+
295
+ const assigneeIds = React.useMemo(() => {
296
+ const ids = new Set<string>()
297
+ for (const task of allTasks) {
298
+ if (task.assigneeStaffMemberId) ids.add(task.assigneeStaffMemberId)
299
+ }
300
+ return Array.from(ids).sort((a, b) => (a < b ? -1 : a > b ? 1 : 0))
301
+ }, [allTasks])
302
+
303
+ const assigneeQuery = useAssigneeNames(assigneeIds)
304
+
305
+ const tagIds = React.useMemo(() => {
306
+ const ids = new Set<string>()
307
+ for (const task of allTasks) {
308
+ for (const id of task.tagIds) ids.add(id)
309
+ }
310
+ return Array.from(ids).sort((a, b) => (a < b ? -1 : a > b ? 1 : 0))
311
+ }, [allTasks])
312
+
313
+ const tagQuery = useTagLabels(tagIds)
314
+
315
+ // A chip carries a tag's name or it is not drawn at all. The label lookup is a
316
+ // separate request from the card's own row, so falling back to the id painted a
317
+ // raw uuid on the card for as long as that request was in flight.
318
+ const tagsByTaskId = React.useMemo(() => {
319
+ const labels = tagQuery.data ?? new Map<string, string>()
320
+ const map = new Map<string, KanbanTagOption[]>()
321
+ for (const task of allTasks) {
322
+ if (task.tagIds.length === 0) continue
323
+ const named: KanbanTagOption[] = []
324
+ for (const id of task.tagIds) {
325
+ const label = labels.get(id)
326
+ if (label) named.push({ id, label })
327
+ }
328
+ if (named.length === 0) continue
329
+ map.set(task.id, named)
330
+ }
331
+ return map
332
+ }, [allTasks, tagQuery.data])
333
+
334
+ const selfQuery = useBoardSelf()
335
+
336
+ const selfStaffMemberId = selfQuery.data?.id ?? null
337
+
338
+ const runningTimerQuery = useQuery<{ entryId: string | null; taskId: string | null }>({
339
+ queryKey: [...BOARD_QUERY_ROOT, 'running-timer', `scope:${scopeVersion}`, selfStaffMemberId ?? 'none'],
340
+ enabled: !!selfStaffMemberId,
341
+ staleTime: 15_000,
342
+ refetchInterval: 30_000,
343
+ queryFn: async () => {
344
+ if (!selfStaffMemberId) return { entryId: null, taskId: null }
345
+ const call = await apiCall<Record<string, unknown>>(
346
+ `/api/staff/timesheets/time-entries?staffMemberId=${encodeURIComponent(selfStaffMemberId)}&running=true&pageSize=${BOARD_PAGE_SIZE}`,
347
+ )
348
+ if (!call.ok) return { entryId: null, taskId: null }
349
+ for (const row of readItems(call.result)) {
350
+ const endedAt = readString(row, 'ended_at', 'endedAt')
351
+ const startedAt = readString(row, 'started_at', 'startedAt')
352
+ if (!startedAt || endedAt) continue
353
+ return { entryId: readString(row, 'id'), taskId: readString(row, 'task_id', 'taskId') }
354
+ }
355
+ return { entryId: null, taskId: null }
356
+ },
357
+ })
358
+
359
+ const runningTimerTaskId = runningTimerQuery.data?.taskId ?? null
360
+ const runningTimerEntryId = runningTimerQuery.data?.entryId ?? null
361
+
362
+ const invalidateBoard = React.useCallback(() => {
363
+ queryClient.invalidateQueries({ queryKey: [...BOARD_QUERY_ROOT, 'column'] }).catch(() => {})
364
+ queryClient.invalidateQueries({ queryKey: [...BOARD_QUERY_ROOT, 'children'] }).catch(() => {})
365
+ }, [queryClient])
366
+
367
+ const invalidateStatuses = React.useCallback(() => {
368
+ queryClient.invalidateQueries({ queryKey: [...BOARD_QUERY_ROOT, 'statuses'] }).catch(() => {})
369
+ }, [queryClient])
370
+
371
+ const markOwnMove = React.useCallback((taskId: string, statusId: string) => {
372
+ ownMoveKeysRef.current.set(`${taskId}|${statusId}`, Date.now())
373
+ }, [])
374
+
375
+ const consumeOwnMove = React.useCallback((taskId: string, statusId: string): boolean => {
376
+ const keys = ownMoveKeysRef.current
377
+ const key = `${taskId}|${statusId}`
378
+ const stamped = keys.get(key)
379
+ // Drop stale entries so a broadcast that never arrived cannot suppress a later,
380
+ // genuinely remote move of the same card into the same column.
381
+ for (const [entry, at] of keys) {
382
+ if (Date.now() - at > OWN_MOVE_ECHO_TTL_MS) keys.delete(entry)
383
+ }
384
+ if (stamped === undefined) return false
385
+ keys.delete(key)
386
+ return true
387
+ }, [])
388
+
389
+ useAppEvent(
390
+ TASK_STATUS_CHANGED_EVENT,
391
+ (event) => {
392
+ const eventPayload = event.payload as Record<string, unknown> | null | undefined
393
+ if (!eventPayload) return
394
+ const projectId = typeof eventPayload.timeProjectId === 'string' ? eventPayload.timeProjectId : null
395
+ if (projectId && projectId !== timeProjectId) return
396
+ const taskId =
397
+ typeof eventPayload.taskId === 'string'
398
+ ? eventPayload.taskId
399
+ : typeof eventPayload.id === 'string'
400
+ ? eventPayload.id
401
+ : null
402
+ const statusId = typeof eventPayload.taskStatusId === 'string' ? eventPayload.taskStatusId : null
403
+ if (taskId && statusId && consumeOwnMove(taskId, statusId)) return
404
+ invalidateBoard()
405
+ },
406
+ [consumeOwnMove, invalidateBoard, timeProjectId],
407
+ )
408
+
409
+ const setTaskPending = React.useCallback((taskId: string, pending: boolean) => {
410
+ setPendingTaskIds((previous) => {
411
+ const next = new Set(previous)
412
+ if (pending) next.add(taskId)
413
+ else next.delete(taskId)
414
+ return next
415
+ })
416
+ }, [])
417
+
418
+ const moveTask = React.useCallback(
419
+ (taskId: string, targetStatusId: string) => {
420
+ const columnCaches = queryClient.getQueriesData<ColumnCache>({
421
+ queryKey: [...BOARD_QUERY_ROOT, 'column'],
422
+ })
423
+ // The full pre-move picture. Restoring it wholesale is what puts the card back in
424
+ // its origin column on failure, regardless of how many columns the drag touched.
425
+ const snapshot = columnCaches.map(([key, data]) => [key, data] as const)
426
+
427
+ let sourceStatusId: string | null = null
428
+ let movingRow: BoardApiRow | null = null
429
+ for (const [key, data] of columnCaches) {
430
+ if (!data) continue
431
+ const statusId = statusIdFromColumnKey(key as readonly unknown[])
432
+ if (!statusId) continue
433
+ const index = data.items.findIndex((item) => readString(item, 'id') === taskId)
434
+ if (index < 0) continue
435
+ sourceStatusId = statusId
436
+ movingRow = { ...data.items[index], task_status_id: targetStatusId, taskStatusId: targetStatusId }
437
+ queryClient.setQueryData(key, {
438
+ items: data.items.filter((item) => readString(item, 'id') !== taskId),
439
+ total: Math.max(0, data.total - 1),
440
+ })
441
+ break
442
+ }
443
+
444
+ if (!movingRow || !sourceStatusId || sourceStatusId === targetStatusId) {
445
+ for (const [key, data] of snapshot) queryClient.setQueryData(key, data)
446
+ return
447
+ }
448
+
449
+ const targetTasks = tasksByStatusId.get(targetStatusId)?.tasks ?? []
450
+ const position = nextPositionAtColumnTop(targetTasks)
451
+ const landed: BoardApiRow = { ...movingRow, position }
452
+
453
+ for (const [key, data] of columnCaches) {
454
+ if (!data) continue
455
+ if (statusIdFromColumnKey(key as readonly unknown[]) !== targetStatusId) continue
456
+ queryClient.setQueryData(key, {
457
+ items: [landed, ...data.items],
458
+ total: data.total + 1,
459
+ })
460
+ }
461
+
462
+ const taskVersion = resolveTaskVersion(taskId, readString(movingRow, 'updated_at', 'updatedAt'))
463
+ setTaskPending(taskId, true)
464
+ setFailedMove(null)
465
+ markOwnMove(taskId, targetStatusId)
466
+
467
+ runMoveMutation({
468
+ operation: async () => {
469
+ const call = await withScopedApiRequestHeaders(buildOptimisticLockHeader(taskVersion), () =>
470
+ apiCallOrThrow<Record<string, unknown>>(
471
+ `/api/staff/timesheets/tasks/${encodeURIComponent(taskId)}/status`,
472
+ {
473
+ method: 'PATCH',
474
+ headers: { 'content-type': 'application/json' },
475
+ body: JSON.stringify({ taskStatusId: targetStatusId, position }),
476
+ },
477
+ { errorMessage: t('staff.time_tracking.board.move.error', 'Could not move the task.') },
478
+ ),
479
+ )
480
+ return readString(call.result ?? {}, 'updatedAt', 'updated_at')
481
+ },
482
+ context: {
483
+ formId: MOVE_MUTATION_CONTEXT_ID,
484
+ resourceKind: 'staff.timesheets.task',
485
+ resourceId: taskId,
486
+ retryLastMutation: retryMoveMutation,
487
+ },
488
+ })
489
+ .then((confirmedVersion) => {
490
+ // Note 4: the card the optimistic write left in the target column still
491
+ // carries the version this move just spent, so the response's version has
492
+ // to take over until a fetched row carries a newer one.
493
+ if (confirmedVersion) confirmedVersionsRef.current.set(taskId, confirmedVersion)
494
+ invalidateBoard()
495
+ })
496
+ .catch((error: unknown) => {
497
+ // Card first: it goes back where it came from before anything else is decided.
498
+ for (const [key, data] of snapshot) queryClient.setQueryData(key, data)
499
+ consumeOwnMove(taskId, targetStatusId)
500
+ if (surfaceRecordConflict(error, t)) {
501
+ // Somebody else moved this card. Refetch and let the conflict bar speak —
502
+ // retrying here would silently overwrite their move. The version this board
503
+ // remembered is behind the server's, so the refetched row takes over.
504
+ confirmedVersionsRef.current.delete(taskId)
505
+ invalidateBoard()
506
+ return
507
+ }
508
+ setFailedMove({ taskId, targetStatusId })
509
+ const message =
510
+ error instanceof Error && error.message
511
+ ? error.message
512
+ : t('staff.time_tracking.board.move.error', 'Could not move the task.')
513
+ flash(message, 'error')
514
+ })
515
+ .finally(() => {
516
+ setTaskPending(taskId, false)
517
+ })
518
+ },
519
+ [
520
+ consumeOwnMove,
521
+ invalidateBoard,
522
+ markOwnMove,
523
+ queryClient,
524
+ resolveTaskVersion,
525
+ retryMoveMutation,
526
+ runMoveMutation,
527
+ setTaskPending,
528
+ t,
529
+ tasksByStatusId,
530
+ ],
531
+ )
532
+
533
+ // Pointer only. dnd-kit's keyboard sensor would have to own Enter/Space on the card —
534
+ // the key that opens the drawer — and it has no sortable context to traverse anyway;
535
+ // keyboard moves go through the card's explicit "Move" menu, which calls `moveTask`
536
+ // directly (U3).
537
+ const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 4 } }))
538
+
539
+ const collisionDetection = React.useCallback<CollisionDetection>((args) => pointerWithin(args), [])
540
+
541
+ const measuringConfig = React.useMemo(
542
+ () => ({ droppable: { strategy: MeasuringStrategy.BeforeDragging } }),
543
+ [],
544
+ )
545
+
546
+ const statusIdByTaskId = React.useMemo(() => {
547
+ const map = new Map<string, string>()
548
+ for (const [statusId, entry] of tasksByStatusId) {
549
+ for (const task of entry.tasks) map.set(task.id, statusId)
550
+ }
551
+ return map
552
+ }, [tasksByStatusId])
553
+
554
+ const activeDragTask = React.useMemo(
555
+ () => (activeDragTaskId ? allTasks.find((task) => task.id === activeDragTaskId) ?? null : null),
556
+ [activeDragTaskId, allTasks],
557
+ )
558
+
559
+ const handleDragStart = React.useCallback((event: DragStartEvent) => {
560
+ setActiveDragTaskId(typeof event.active.id === 'string' ? event.active.id : null)
561
+ }, [])
562
+
563
+ const handleDragCancel = React.useCallback(() => setActiveDragTaskId(null), [])
564
+
565
+ const targetStatusIdFor = React.useCallback(
566
+ (overId: UniqueIdentifier | null | undefined): string | null => {
567
+ if (typeof overId !== 'string') return null
568
+ return overId.startsWith(KANBAN_COLUMN_DROPPABLE_PREFIX)
569
+ ? overId.slice(KANBAN_COLUMN_DROPPABLE_PREFIX.length)
570
+ : statusIdByTaskId.get(overId) ?? null
571
+ },
572
+ [statusIdByTaskId],
573
+ )
574
+
575
+ const handleDragEnd = React.useCallback(
576
+ (event: DragEndEvent) => {
577
+ setActiveDragTaskId(null)
578
+ const taskId = typeof event.active.id === 'string' ? event.active.id : null
579
+ if (!taskId) return
580
+ const targetStatusId = targetStatusIdFor(event.over?.id)
581
+ if (!targetStatusId) return
582
+ if (statusIdByTaskId.get(taskId) === targetStatusId) return
583
+ moveTask(taskId, targetStatusId)
584
+ },
585
+ [moveTask, statusIdByTaskId, targetStatusIdFor],
586
+ )
587
+
588
+ const moveTargets = React.useMemo<KanbanMoveTarget[]>(
589
+ () => visibleStatuses.map((status) => ({ id: status.id, name: status.name })),
590
+ [visibleStatuses],
591
+ )
592
+
593
+ const describeTask = React.useCallback(
594
+ (taskId: UniqueIdentifier | null | undefined): string => {
595
+ if (typeof taskId !== 'string') return ''
596
+ return allTasks.find((task) => task.id === taskId)?.title ?? taskId
597
+ },
598
+ [allTasks],
599
+ )
600
+
601
+ const describeColumn = React.useCallback(
602
+ (overId: UniqueIdentifier | null | undefined): string | null => {
603
+ const statusId = targetStatusIdFor(overId)
604
+ if (!statusId) return null
605
+ return statuses.find((status) => status.id === statusId)?.name ?? statusId
606
+ },
607
+ [statuses, targetStatusIdFor],
608
+ )
609
+
610
+ // dnd-kit narrates every drag through a live region. Left unset it narrates in English
611
+ // regardless of locale (U9), so the board owns the strings.
612
+ const announcements = React.useMemo<Announcements>(
613
+ () => ({
614
+ onDragStart: ({ active }) =>
615
+ t('staff.time_tracking.board.dnd.dragStart', 'Picked up the task {task}.', {
616
+ task: describeTask(active.id),
617
+ }),
618
+ onDragOver: ({ active, over }) => {
619
+ const column = describeColumn(over?.id)
620
+ return column
621
+ ? t('staff.time_tracking.board.dnd.dragOver', 'The task {task} is over the column {column}.', {
622
+ task: describeTask(active.id),
623
+ column,
624
+ })
625
+ : t('staff.time_tracking.board.dnd.dragOverNothing', 'The task {task} is not over a column.', {
626
+ task: describeTask(active.id),
627
+ })
628
+ },
629
+ onDragEnd: ({ active, over }) => {
630
+ const column = describeColumn(over?.id)
631
+ return column
632
+ ? t('staff.time_tracking.board.dnd.dragEnd', 'The task {task} was dropped into the column {column}.', {
633
+ task: describeTask(active.id),
634
+ column,
635
+ })
636
+ : t('staff.time_tracking.board.dnd.dragEndOutside', 'The task {task} was dropped outside the columns.', {
637
+ task: describeTask(active.id),
638
+ })
639
+ },
640
+ onDragCancel: ({ active }) =>
641
+ t('staff.time_tracking.board.dnd.dragCancel', 'Moving the task {task} was cancelled.', {
642
+ task: describeTask(active.id),
643
+ }),
644
+ }),
645
+ [describeColumn, describeTask, t],
646
+ )
647
+
648
+ const screenReaderInstructions = React.useMemo<ScreenReaderInstructions>(
649
+ () => ({
650
+ draggable: t(
651
+ 'staff.time_tracking.board.dnd.instructions',
652
+ 'Drag a card with the pointer to move it between columns. With the keyboard, press Enter on a card to open it, or use the Move button in the card actions to choose a target column.',
653
+ ),
654
+ }),
655
+ [t],
656
+ )
657
+
658
+ const accessibility = React.useMemo(
659
+ () => ({ announcements, screenReaderInstructions }),
660
+ [announcements, screenReaderInstructions],
661
+ )
662
+
663
+ const handleQuickAdd = React.useCallback(
664
+ async (statusId: string, title: string) => {
665
+ setQuickAddPendingStatusId(statusId)
666
+ try {
667
+ await runCreateMutation({
668
+ operation: () =>
669
+ apiCallOrThrow(
670
+ '/api/staff/timesheets/tasks',
671
+ {
672
+ method: 'POST',
673
+ headers: { 'content-type': 'application/json' },
674
+ body: JSON.stringify({
675
+ timeProjectId,
676
+ taskStatusId: statusId,
677
+ title,
678
+ // US-C1: the creator owns what they just typed until somebody reassigns it.
679
+ assigneeStaffMemberId: selfStaffMemberId,
680
+ }),
681
+ },
682
+ { errorMessage: t('staff.time_tracking.board.quickAdd.error', 'Could not create the task.') },
683
+ ),
684
+ context: {
685
+ formId: CREATE_MUTATION_CONTEXT_ID,
686
+ resourceKind: 'staff.timesheets.task',
687
+ resourceId: statusId,
688
+ retryLastMutation: retryCreateMutation,
689
+ },
690
+ })
691
+ invalidateBoard()
692
+ } catch (error) {
693
+ if (surfaceRecordConflict(error, t)) return
694
+ logger.error('staff.time_tracking board quick add failed', { err: error })
695
+ flash(
696
+ error instanceof Error && error.message
697
+ ? error.message
698
+ : t('staff.time_tracking.board.quickAdd.error', 'Could not create the task.'),
699
+ 'error',
700
+ )
701
+ } finally {
702
+ setQuickAddPendingStatusId(null)
703
+ }
704
+ },
705
+ [invalidateBoard, retryCreateMutation, runCreateMutation, selfStaffMemberId, t, timeProjectId],
706
+ )
707
+
708
+ const handleCreateStatus = React.useCallback(async () => {
709
+ const name = statusDraft.trim()
710
+ if (!name) {
711
+ setComposingStatus(false)
712
+ setStatusDraft('')
713
+ return
714
+ }
715
+ try {
716
+ await runCreateMutation({
717
+ operation: () =>
718
+ apiCallOrThrow(
719
+ '/api/staff/timesheets/task-statuses',
720
+ {
721
+ method: 'POST',
722
+ headers: { 'content-type': 'application/json' },
723
+ body: JSON.stringify({ timeProjectId, name }),
724
+ },
725
+ { errorMessage: t('staff.time_tracking.board.addStatus.error', 'Could not add the column.') },
726
+ ),
727
+ context: {
728
+ formId: CREATE_MUTATION_CONTEXT_ID,
729
+ resourceKind: 'staff.timesheets.task_status',
730
+ resourceId: timeProjectId,
731
+ retryLastMutation: retryCreateMutation,
732
+ },
733
+ })
734
+ setStatusDraft('')
735
+ setComposingStatus(false)
736
+ invalidateStatuses()
737
+ } catch (error) {
738
+ if (surfaceRecordConflict(error, t)) return
739
+ flash(
740
+ error instanceof Error && error.message
741
+ ? error.message
742
+ : t('staff.time_tracking.board.addStatus.error', 'Could not add the column.'),
743
+ 'error',
744
+ )
745
+ }
746
+ }, [invalidateStatuses, retryCreateMutation, runCreateMutation, statusDraft, t, timeProjectId])
747
+
748
+ const handleLoadMore = React.useCallback((statusId: string) => {
749
+ setPagesByStatus((previous) => ({ ...previous, [statusId]: (previous[statusId] ?? 1) + 1 }))
750
+ }, [])
751
+
752
+ const openTask = React.useCallback(
753
+ (taskId: string) => {
754
+ // Seam for the task drawer (T3.7): the drawer reads `?task=` and the board keeps
755
+ // rendering underneath it.
756
+ const params = new URLSearchParams(searchParams?.toString() ?? '')
757
+ params.set('task', taskId)
758
+ router.push(`?${params.toString()}`, { scroll: false })
759
+ },
760
+ [router, searchParams],
761
+ )
762
+
763
+ const handleAddTime = React.useCallback(
764
+ (taskId: string) => {
765
+ const params = new URLSearchParams(searchParams?.toString() ?? '')
766
+ params.set('task', taskId)
767
+ params.set('panel', 'time')
768
+ router.push(`?${params.toString()}`, { scroll: false })
769
+ },
770
+ [router, searchParams],
771
+ )
772
+
773
+ const drawerTaskId = searchParams?.get('task') ?? null
774
+ const drawerPanel = searchParams?.get('panel') ?? null
775
+
776
+ const closeTaskDrawer = React.useCallback(() => {
777
+ const params = new URLSearchParams(searchParams?.toString() ?? '')
778
+ params.delete('task')
779
+ params.delete('panel')
780
+ const query = params.toString()
781
+ router.push(query.length > 0 ? `?${query}` : '?', { scroll: false })
782
+ }, [router, searchParams])
783
+
784
+ const handleStartTimer = React.useCallback(
785
+ async (taskId: string) => {
786
+ if (!selfStaffMemberId) return
787
+ const task = allTasks.find((entry) => entry.id === taskId) ?? null
788
+ try {
789
+ const started = await runCreateMutation({
790
+ operation: () =>
791
+ apiCallOrThrow<Record<string, unknown>>(
792
+ '/api/staff/timesheets/time-entries/start-timer',
793
+ {
794
+ method: 'POST',
795
+ headers: { 'content-type': 'application/json' },
796
+ body: JSON.stringify({
797
+ staffMemberId: selfStaffMemberId,
798
+ timeProjectId,
799
+ date: new Date().toISOString().slice(0, 10),
800
+ notes: task?.title ?? null,
801
+ }),
802
+ },
803
+ { errorMessage: t('staff.time_tracking.board.timer.startError', 'Could not start the timer.') },
804
+ ),
805
+ context: {
806
+ formId: CREATE_MUTATION_CONTEXT_ID,
807
+ resourceKind: 'staff.timesheets.time_entry',
808
+ resourceId: taskId,
809
+ retryLastMutation: retryCreateMutation,
810
+ },
811
+ })
812
+ const entryId = typeof started?.result?.id === 'string' ? started.result.id : null
813
+ if (entryId) {
814
+ // `start-timer` creates the entry atomically but takes no task, so the link is a
815
+ // second write. A failure here leaves a running, project-level timer rather than
816
+ // no timer at all — the lesser of the two wrong states.
817
+ const linked = await apiCall(`/api/staff/timesheets/time-entries`, {
818
+ method: 'PUT',
819
+ headers: { 'content-type': 'application/json' },
820
+ body: JSON.stringify({ id: entryId, taskId }),
821
+ })
822
+ if (!linked.ok) {
823
+ flash(
824
+ t(
825
+ 'staff.time_tracking.board.timer.linkWarning',
826
+ 'The timer is running but is not linked to this task yet.',
827
+ ),
828
+ 'warning',
829
+ )
830
+ }
831
+ }
832
+ await runningTimerQuery.refetch()
833
+ } catch (error) {
834
+ if (surfaceRecordConflict(error, t)) return
835
+ flash(
836
+ error instanceof Error && error.message
837
+ ? error.message
838
+ : t('staff.time_tracking.board.timer.startError', 'Could not start the timer.'),
839
+ 'error',
840
+ )
841
+ }
842
+ },
843
+ [allTasks, retryCreateMutation, runCreateMutation, runningTimerQuery, selfStaffMemberId, t, timeProjectId],
844
+ )
845
+
846
+ const handleStopTimer = React.useCallback(async () => {
847
+ if (!runningTimerEntryId) return
848
+ try {
849
+ await runCreateMutation({
850
+ operation: () =>
851
+ apiCallOrThrow(
852
+ `/api/staff/timesheets/time-entries/${encodeURIComponent(runningTimerEntryId)}/timer-stop`,
853
+ { method: 'POST' },
854
+ { errorMessage: t('staff.time_tracking.board.timer.stopError', 'Could not stop the timer.') },
855
+ ),
856
+ context: {
857
+ formId: CREATE_MUTATION_CONTEXT_ID,
858
+ resourceKind: 'staff.timesheets.time_entry',
859
+ resourceId: runningTimerEntryId,
860
+ retryLastMutation: retryCreateMutation,
861
+ },
862
+ })
863
+ await runningTimerQuery.refetch()
864
+ invalidateBoard()
865
+ } catch (error) {
866
+ if (surfaceRecordConflict(error, t)) return
867
+ flash(
868
+ error instanceof Error && error.message
869
+ ? error.message
870
+ : t('staff.time_tracking.board.timer.stopError', 'Could not stop the timer.'),
871
+ 'error',
872
+ )
873
+ }
874
+ }, [invalidateBoard, retryCreateMutation, runCreateMutation, runningTimerEntryId, runningTimerQuery, t])
875
+
876
+ // Never fold `loggedMinutes` inline: a parent's figure already contains its children's,
877
+ // so the shared helper — which drops any row whose parent is in the same set — is the
878
+ // only thing standing between this number and risk R10.
879
+ const summary = React.useMemo<BoardSummary>(
880
+ () => ({ taskCount: allTasks.length, loggedMinutes: sumTaskLoggedMinutes(allTasks) }),
881
+ [allTasks],
882
+ )
883
+
884
+ React.useEffect(() => {
885
+ onSummaryChange?.(summary)
886
+ }, [onSummaryChange, summary])
887
+
888
+ if (statusesQuery.isLoading) {
889
+ return <LoadingMessage label={t('staff.time_tracking.board.loading', 'Loading the board…')} />
890
+ }
891
+
892
+ if (statusesQuery.isError) {
893
+ return (
894
+ <ErrorMessage
895
+ label={t('staff.time_tracking.board.loadError', 'Could not load the board.')}
896
+ action={
897
+ <Button type="button" variant="outline" size="sm" onClick={() => void statusesQuery.refetch()}>
898
+ {t('staff.time_tracking.board.reload', 'Try again')}
899
+ </Button>
900
+ }
901
+ />
902
+ )
903
+ }
904
+
905
+ if (statuses.length === 0) {
906
+ return (
907
+ <EmptyState
908
+ title={t('staff.time_tracking.board.empty.title', 'This project has no columns yet')}
909
+ description={t(
910
+ 'staff.time_tracking.board.empty.description',
911
+ 'A Team Leader configures the board columns for each project.',
912
+ )}
913
+ />
914
+ )
915
+ }
916
+
917
+ return (
918
+ <div className="flex flex-col gap-4">
919
+ <InjectionSpot
920
+ spotId={extensionPoints.hosts.taskBoardToolbar.spotId}
921
+ context={{ timeProjectId, projectName: projectName ?? null, statusIds: visibleStatuses.map((status) => status.id) }}
922
+ data={summary}
923
+ />
924
+
925
+ {failedMove ? (
926
+ <Alert status="error" data-testid="kanban-move-retry">
927
+ <AlertDescription className="flex flex-wrap items-center gap-3">
928
+ <span>
929
+ {t('staff.time_tracking.board.move.rolledBack', 'The task was moved back — the change was not saved.')}
930
+ </span>
931
+ <Button
932
+ type="button"
933
+ variant="outline"
934
+ size="sm"
935
+ onClick={() => {
936
+ const retry = failedMove
937
+ setFailedMove(null)
938
+ moveTask(retry.taskId, retry.targetStatusId)
939
+ }}
940
+ >
941
+ <RotateCcw className="size-3.5" aria-hidden="true" />
942
+ {t('staff.time_tracking.board.move.retry', 'Retry')}
943
+ </Button>
944
+ </AlertDescription>
945
+ </Alert>
946
+ ) : null}
947
+
948
+ <DndContext
949
+ sensors={sensors}
950
+ accessibility={accessibility}
951
+ collisionDetection={collisionDetection}
952
+ measuring={measuringConfig}
953
+ onDragStart={handleDragStart}
954
+ onDragEnd={handleDragEnd}
955
+ onDragCancel={handleDragCancel}
956
+ >
957
+ <div
958
+ className={`flex min-w-0 gap-3.5 overflow-x-auto pb-6 ${
959
+ activeDragTaskId ? 'cursor-grabbing select-none' : ''
960
+ }`}
961
+ >
962
+ {visibleStatuses.map((status, index) => {
963
+ const entry = tasksByStatusId.get(status.id)
964
+ const serverTotal = entry?.total ?? 0
965
+ const loaded = entry?.loaded ?? 0
966
+ return (
967
+ <KanbanColumn
968
+ key={status.id}
969
+ status={status}
970
+ tasks={entry?.tasks ?? []}
971
+ total={serverTotal}
972
+ remaining={Math.max(0, serverTotal - loaded)}
973
+ canQuickAdd={canManageTasks}
974
+ loadingMore={columnQueries[index]?.isFetching ?? false}
975
+ activeDragTaskId={activeDragTaskId}
976
+ pendingTaskIds={pendingTaskIds}
977
+ runningTimerTaskId={runningTimerTaskId}
978
+ assigneeNames={assigneeQuery.data ?? new Map<string, string>()}
979
+ tagsByTaskId={tagsByTaskId}
980
+ subtasksByTaskId={subtasksByTaskId}
981
+ quickAddPending={quickAddPendingStatusId === status.id}
982
+ moveTargets={moveTargets}
983
+ onQuickAdd={handleQuickAdd}
984
+ onLoadMore={handleLoadMore}
985
+ onOpenTask={openTask}
986
+ onStartTimer={handleStartTimer}
987
+ onStopTimer={handleStopTimer}
988
+ onAddTime={handleAddTime}
989
+ onMoveTask={moveTask}
990
+ />
991
+ )
992
+ })}
993
+
994
+ {canManageColumns ? (
995
+ <div className="flex w-72 flex-none flex-col gap-3">
996
+ {composingStatus ? (
997
+ <Input
998
+ autoFocus
999
+ value={statusDraft}
1000
+ onChange={(event) => setStatusDraft(event.target.value)}
1001
+ onKeyDown={(event) => {
1002
+ if (event.key === 'Enter') {
1003
+ event.preventDefault()
1004
+ void handleCreateStatus()
1005
+ }
1006
+ if (event.key === 'Escape') {
1007
+ event.preventDefault()
1008
+ setComposingStatus(false)
1009
+ setStatusDraft('')
1010
+ }
1011
+ }}
1012
+ placeholder={t('staff.time_tracking.board.addStatus.placeholder', 'Column name')}
1013
+ aria-label={t('staff.time_tracking.board.addStatus.cta', 'Add status')}
1014
+ data-testid="kanban-add-status-input"
1015
+ />
1016
+ ) : (
1017
+ <Button
1018
+ type="button"
1019
+ variant="ghost"
1020
+ onClick={() => setComposingStatus(true)}
1021
+ data-testid="kanban-add-status"
1022
+ className="flex h-24 flex-col items-center justify-center gap-2 rounded-lg border border-dashed border-muted-foreground/60 bg-muted/40 text-sm font-semibold leading-normal text-muted-foreground hover:text-foreground"
1023
+ >
1024
+ <Plus className="size-5" aria-hidden="true" />
1025
+ {t('staff.time_tracking.board.addStatus.cta', 'Add status')}
1026
+ </Button>
1027
+ )}
1028
+ </div>
1029
+ ) : null}
1030
+ </div>
1031
+
1032
+ <DragOverlay dropAnimation={null}>
1033
+ {activeDragTask ? (
1034
+ <div
1035
+ data-testid="kanban-drag-overlay"
1036
+ className="pointer-events-none w-72 rotate-2 cursor-grabbing select-none rounded-lg border border-border bg-card px-4 py-3 opacity-80 shadow-xl"
1037
+ >
1038
+ <div className="text-sm font-semibold leading-normal text-foreground">
1039
+ {activeDragTask.title}
1040
+ </div>
1041
+ <div className="mt-2 text-xs font-semibold tabular-nums leading-normal text-muted-foreground">
1042
+ {formatBoardMinutes(activeDragTask.loggedMinutes)}
1043
+ </div>
1044
+ </div>
1045
+ ) : null}
1046
+ </DragOverlay>
1047
+ </DndContext>
1048
+
1049
+ {drawerTaskId ? (
1050
+ <TaskDrawer
1051
+ taskId={drawerTaskId}
1052
+ open
1053
+ onOpenChange={(next) => {
1054
+ if (!next) closeTaskDrawer()
1055
+ }}
1056
+ projectName={projectName ?? null}
1057
+ focusPanel={drawerPanel}
1058
+ onChanged={invalidateBoard}
1059
+ />
1060
+ ) : null}
1061
+ </div>
1062
+ )
1063
+ }
1064
+
1065
+ export default KanbanBoard