@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,1499 @@
1
+ "use client";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { z } from "zod";
5
+ import { useQuery, useQueryClient } from "@tanstack/react-query";
6
+ import { Lock, X } from "lucide-react";
7
+ import {
8
+ Dialog,
9
+ DialogContent,
10
+ DialogFooter,
11
+ DialogHeader,
12
+ DialogTitle
13
+ } from "@open-mercato/ui/primitives/dialog";
14
+ import { Alert, AlertDescription, AlertTitle } from "@open-mercato/ui/primitives/alert";
15
+ import { Badge } from "@open-mercato/ui/primitives/badge";
16
+ import { Button } from "@open-mercato/ui/primitives/button";
17
+ import { Input } from "@open-mercato/ui/primitives/input";
18
+ import { Kbd } from "@open-mercato/ui/primitives/kbd";
19
+ import { Label } from "@open-mercato/ui/primitives/label";
20
+ import { Separator } from "@open-mercato/ui/primitives/separator";
21
+ import { Spinner } from "@open-mercato/ui/primitives/spinner";
22
+ import { Switch } from "@open-mercato/ui/primitives/switch";
23
+ import { ErrorMessage, LoadingMessage } from "@open-mercato/ui/backend/detail";
24
+ import { apiCall, apiCallOrThrow, withScopedApiRequestHeaders } from "@open-mercato/ui/backend/utils/apiCall";
25
+ import { TagPicker, tagChipStyle } from "./TagPicker.js";
26
+ import { TaskPicker } from "./TaskPicker.js";
27
+ import { slugifyProjectName } from "../time-tracking/projectCode.js";
28
+ import { autoColorFromName } from "../timesheets-ui/colors.js";
29
+ import { buildOptimisticLockHeader } from "@open-mercato/ui/backend/utils/optimisticLock";
30
+ import { surfaceRecordConflict } from "@open-mercato/ui/backend/conflicts";
31
+ import { normalizeCrudServerError } from "@open-mercato/ui/backend/utils/serverErrors";
32
+ import { useBackendChrome } from "@open-mercato/ui/backend/BackendChromeProvider";
33
+ import { useConfirmDialog } from "@open-mercato/ui/backend/confirm-dialog";
34
+ import { useGuardedMutation } from "@open-mercato/ui/backend/injection/useGuardedMutation";
35
+ import { InjectionSpot, useInjectionSpotEvents } from "@open-mercato/ui/backend/injection/InjectionSpot";
36
+ import { extensionPoints } from "@open-mercato/core/modules/staff/extension-points";
37
+ import { registerComponent } from "@open-mercato/shared/modules/widgets/component-registry";
38
+ import { useRegisteredComponent } from "@open-mercato/ui/backend/injection/useRegisteredComponent";
39
+ import {
40
+ callbackProp,
41
+ opaqueProp,
42
+ optionalCallbackProp
43
+ } from "../time-tracking/componentContracts.js";
44
+ import { extensionSpotChildId } from "@open-mercato/shared/modules/widgets/extension-points";
45
+ import { flash } from "@open-mercato/ui/backend/FlashMessages";
46
+ import { formatCurrency } from "@open-mercato/ui/utils/format";
47
+ import { hasFeature } from "@open-mercato/shared/security/features";
48
+ import { useT } from "@open-mercato/shared/lib/i18n/context";
49
+ import { useOrganizationScopeVersion } from "@open-mercato/shared/lib/frontend/useOrganizationScope";
50
+ import { createLogger } from "@open-mercato/shared/lib/logger";
51
+ import { DurationInput } from "./DurationInput.js";
52
+ import { formatDuration } from "../time-tracking/duration.js";
53
+ import { parseClock } from "../time-tracking/interval.js";
54
+ import { applicableRate, entryAmount } from "../time-tracking/cost.js";
55
+ import { roundMinutes, DEFAULT_ROUNDING_SETTINGS } from "../time-tracking/rounding.js";
56
+ import {
57
+ combineDateAndClock,
58
+ createIntervalState,
59
+ reduceIntervalState,
60
+ resetIntervalState,
61
+ readRowItems,
62
+ readRowString,
63
+ shiftIsoDate,
64
+ todayIsoDate,
65
+ toOverlapEntry,
66
+ toProjectOption,
67
+ toTaskOption,
68
+ toTimeEntryRecord
69
+ } from "./timeEntryDialogState.js";
70
+ const logger = createLogger("staff").child({ component: "TimeEntryDialog" });
71
+ const TIME_ENTRY_DIALOG_MUTATION_CONTEXT_ID = "staff.time_tracking.entryDialog";
72
+ const TIME_ENTRY_ENTITY_ID = "staff:staff_time_entry";
73
+ function fieldValueSnapshot(value) {
74
+ return JSON.stringify(value ?? null);
75
+ }
76
+ const RATES_FEATURE = "staff.timesheets.rates.view";
77
+ const ENTRIES_LIST_PATH = "/backend/staff/time-tracking/entries";
78
+ const REPORTS_PATH = "/backend/staff/time-tracking/reports";
79
+ const DIALOG_QUERY_ROOT = ["staff", "time-tracking", "entry-dialog"];
80
+ const DIRECTORY_PAGE_SIZE = 100;
81
+ const TASK_SEARCH_PAGE_SIZE = 50;
82
+ const TASK_SEARCH_DEBOUNCE_MS = 250;
83
+ const FALLBACK_SETTINGS = {
84
+ rounding: { ...DEFAULT_ROUNDING_SETTINGS },
85
+ defaults: { billable: true, chainStartFromPreviousEnd: true },
86
+ warnings: { overlap: true }
87
+ };
88
+ function readSettings(payload) {
89
+ const source = payload ?? {};
90
+ const rounding = source.rounding ?? {};
91
+ const defaults = source.defaults ?? {};
92
+ const warnings = source.warnings ?? {};
93
+ return {
94
+ rounding: {
95
+ unitMinutes: rounding.unitMinutes === 5 || rounding.unitMinutes === 10 || rounding.unitMinutes === 15 ? rounding.unitMinutes : 0,
96
+ direction: rounding.direction === "nearest" ? "nearest" : "up"
97
+ },
98
+ defaults: {
99
+ billable: typeof defaults.billable === "boolean" ? defaults.billable : true,
100
+ chainStartFromPreviousEnd: typeof defaults.chainStartFromPreviousEnd === "boolean" ? defaults.chainStartFromPreviousEnd : true
101
+ },
102
+ warnings: { overlap: typeof warnings.overlap === "boolean" ? warnings.overlap : true }
103
+ };
104
+ }
105
+ function parseRateText(value) {
106
+ const trimmed = value.trim().replace(",", ".");
107
+ if (!trimmed) return null;
108
+ const parsed = Number(trimmed);
109
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : null;
110
+ }
111
+ const NO_FIELD_ISSUES = { task: null, duration: null };
112
+ function readFieldIssues(error) {
113
+ const { fieldErrors } = normalizeCrudServerError(error);
114
+ if (!fieldErrors) return null;
115
+ const task = fieldErrors.taskId ?? fieldErrors.task ?? null;
116
+ const duration = fieldErrors.durationMinutes ?? fieldErrors.duration ?? null;
117
+ if (!task && !duration) return null;
118
+ return { task, duration };
119
+ }
120
+ function formSnapshot(values) {
121
+ return JSON.stringify([
122
+ values.taskId,
123
+ values.description.trim(),
124
+ values.date,
125
+ values.startText,
126
+ values.endText,
127
+ values.durationMinutes,
128
+ values.isBillable,
129
+ values.rateOverrideEnabled,
130
+ values.rateOverrideAmount,
131
+ [...values.tagIds].sort((a, b) => a < b ? -1 : a > b ? 1 : 0)
132
+ ]);
133
+ }
134
+ async function fetchTaskOptions(term) {
135
+ const params = new URLSearchParams({
136
+ page: "1",
137
+ pageSize: String(TASK_SEARCH_PAGE_SIZE),
138
+ sortField: "title",
139
+ sortDir: "asc"
140
+ });
141
+ const responses = await Promise.all(
142
+ [
143
+ `/api/staff/timesheets/tasks?${params.toString()}&q=${encodeURIComponent(term)}`,
144
+ `/api/staff/timesheets/tasks?${params.toString()}&reference=${encodeURIComponent(term)}`
145
+ ].map((url) => apiCall(url).catch(() => null))
146
+ );
147
+ const merged = /* @__PURE__ */ new Map();
148
+ for (const response of responses) {
149
+ if (!response?.ok) continue;
150
+ for (const row of readRowItems(response.result)) {
151
+ const option = toTaskOption(row);
152
+ if (option && !merged.has(option.id)) merged.set(option.id, option);
153
+ }
154
+ }
155
+ return [...merged.values()];
156
+ }
157
+ async function fetchTaskById(id) {
158
+ const call = await apiCall(
159
+ `/api/staff/timesheets/tasks?ids=${encodeURIComponent(id)}&pageSize=1`
160
+ );
161
+ if (!call.ok) return null;
162
+ const row = readRowItems(call.result)[0];
163
+ return row ? toTaskOption(row) : null;
164
+ }
165
+ function readErrorCode(error) {
166
+ const body = error?.body;
167
+ if (body && typeof body.code === "string") return body.code;
168
+ if (body && typeof body.error === "string") return body.error;
169
+ return null;
170
+ }
171
+ function DefaultTimeEntryDialog({
172
+ open,
173
+ onOpenChange,
174
+ entryId,
175
+ defaults,
176
+ headerHint,
177
+ onSaved,
178
+ onShowEntry
179
+ }) {
180
+ const t = useT();
181
+ const scopeVersion = useOrganizationScopeVersion();
182
+ const { payload } = useBackendChrome();
183
+ const { confirm, ConfirmDialogElement } = useConfirmDialog();
184
+ const canSeeMoney = hasFeature(payload?.grantedFeatures, RATES_FEATURE);
185
+ const isEdit = typeof entryId === "string" && entryId.length > 0;
186
+ const [taskId, setTaskId] = React.useState(null);
187
+ const [description, setDescription] = React.useState("");
188
+ const [date, setDate] = React.useState(() => todayIsoDate());
189
+ const [interval, setIntervalState] = React.useState(() => createIntervalState({}));
190
+ const [isBillable, setIsBillable] = React.useState(true);
191
+ const [rateOverrideEnabled, setRateOverrideEnabled] = React.useState(false);
192
+ const [rateText, setRateText] = React.useState("");
193
+ const [tagIds, setTagIds] = React.useState([]);
194
+ const [overlaps, setOverlaps] = React.useState([]);
195
+ const [savingMode, setSavingMode] = React.useState(null);
196
+ const [fieldIssues, setFieldIssues] = React.useState(NO_FIELD_ISSUES);
197
+ const saving = savingMode !== null;
198
+ const [taskSearch, setTaskSearch] = React.useState("");
199
+ const [debouncedTaskSearch, setDebouncedTaskSearch] = React.useState("");
200
+ const [pinnedTask, setPinnedTask] = React.useState(null);
201
+ const [baseline, setBaseline] = React.useState(null);
202
+ const taskTriggerRef = React.useRef(null);
203
+ const focusTaskPicker = React.useCallback(() => {
204
+ const host = taskTriggerRef.current;
205
+ if (!host) return false;
206
+ const focusable = host.querySelector("input, select, button");
207
+ if (!focusable) return false;
208
+ focusable.focus();
209
+ return true;
210
+ }, []);
211
+ const seedKeyRef = React.useRef(null);
212
+ const billableDefaultRef = React.useRef(null);
213
+ const versionRef = React.useRef(null);
214
+ const { runMutation, retryLastMutation } = useGuardedMutation({
215
+ contextId: TIME_ENTRY_DIALOG_MUTATION_CONTEXT_ID,
216
+ blockedMessage: t("staff.time_tracking.entryDialog.errors.save", "Could not save the time entry.")
217
+ });
218
+ const settingsQuery = useQuery({
219
+ queryKey: [...DIALOG_QUERY_ROOT, "settings", `scope:${scopeVersion}`],
220
+ enabled: open,
221
+ staleTime: 3e5,
222
+ queryFn: async () => {
223
+ const call = await apiCall("/api/staff/timesheets/settings");
224
+ return call.ok ? readSettings(call.result) : FALLBACK_SETTINGS;
225
+ }
226
+ });
227
+ const settings = settingsQuery.data ?? FALLBACK_SETTINGS;
228
+ const selfQuery = useQuery({
229
+ queryKey: [...DIALOG_QUERY_ROOT, "self", `scope:${scopeVersion}`],
230
+ enabled: open,
231
+ staleTime: 3e5,
232
+ queryFn: async () => {
233
+ const call = await apiCall(
234
+ "/api/staff/team-members/self"
235
+ );
236
+ const member = call.ok ? call.result?.member ?? null : null;
237
+ if (!member?.id) return null;
238
+ return { id: member.id, displayName: member.displayName ?? "" };
239
+ }
240
+ });
241
+ const tasksQuery = useQuery({
242
+ queryKey: [...DIALOG_QUERY_ROOT, "tasks", `scope:${scopeVersion}`],
243
+ enabled: open,
244
+ staleTime: 6e4,
245
+ queryFn: async () => {
246
+ const call = await apiCall(
247
+ `/api/staff/timesheets/tasks?page=1&pageSize=${DIRECTORY_PAGE_SIZE}&sortField=title&sortDir=asc`
248
+ );
249
+ if (!call.ok) return [];
250
+ return readRowItems(call.result).map(toTaskOption).filter((task) => task !== null);
251
+ }
252
+ });
253
+ React.useEffect(() => {
254
+ if (!open) {
255
+ setDebouncedTaskSearch("");
256
+ return;
257
+ }
258
+ const handle = setTimeout(() => setDebouncedTaskSearch(taskSearch.trim()), TASK_SEARCH_DEBOUNCE_MS);
259
+ return () => clearTimeout(handle);
260
+ }, [open, taskSearch]);
261
+ const taskSearchQuery = useQuery({
262
+ queryKey: [...DIALOG_QUERY_ROOT, "task-search", `scope:${scopeVersion}`, debouncedTaskSearch],
263
+ enabled: open && debouncedTaskSearch.length > 0,
264
+ staleTime: 6e4,
265
+ queryFn: () => fetchTaskOptions(debouncedTaskSearch)
266
+ });
267
+ const projectsQuery = useQuery({
268
+ queryKey: [...DIALOG_QUERY_ROOT, "projects", `scope:${scopeVersion}`],
269
+ enabled: open,
270
+ staleTime: 6e4,
271
+ queryFn: async () => {
272
+ const call = await apiCall(
273
+ `/api/staff/timesheets/time-projects?page=1&pageSize=${DIRECTORY_PAGE_SIZE}&sortField=name&sortDir=asc`
274
+ );
275
+ if (!call.ok) return [];
276
+ return readRowItems(call.result).map(toProjectOption).filter((project2) => project2 !== null);
277
+ }
278
+ });
279
+ const staffMemberId = selfQuery.data?.id ?? null;
280
+ const statusesQuery = useQuery({
281
+ queryKey: [...DIALOG_QUERY_ROOT, "task-statuses", `scope:${scopeVersion}`],
282
+ enabled: open,
283
+ staleTime: 3e5,
284
+ queryFn: async () => {
285
+ const call = await apiCall(
286
+ `/api/staff/timesheets/task-statuses?page=1&pageSize=${DIRECTORY_PAGE_SIZE}`
287
+ );
288
+ if (!call.ok) return {};
289
+ const map = {};
290
+ for (const row of readRowItems(call.result)) {
291
+ const id = readRowString(row, "id");
292
+ if (!id) continue;
293
+ map[id] = {
294
+ id,
295
+ name: readRowString(row, "name") ?? "",
296
+ color: readRowString(row, "color"),
297
+ isDone: row.is_done === true || row.isDone === true
298
+ };
299
+ }
300
+ return map;
301
+ }
302
+ });
303
+ const recentQuery = useQuery({
304
+ queryKey: [...DIALOG_QUERY_ROOT, "recent-tasks", `scope:${scopeVersion}`, staffMemberId ?? "none"],
305
+ enabled: open && !!staffMemberId,
306
+ staleTime: 6e4,
307
+ queryFn: async () => {
308
+ const since = shiftIsoDate(todayIsoDate(), -21);
309
+ const call = await apiCall(
310
+ `/api/staff/timesheets/time-entries?page=1&pageSize=100&sortField=date&sortDir=desc&staffMemberId=${encodeURIComponent(staffMemberId)}&from=${since}`
311
+ );
312
+ if (!call.ok) return [];
313
+ const seen = [];
314
+ for (const row of readRowItems(call.result)) {
315
+ const taskId2 = readRowString(row, "task_id", "taskId");
316
+ if (taskId2 && !seen.includes(taskId2)) seen.push(taskId2);
317
+ if (seen.length >= 6) break;
318
+ }
319
+ return seen;
320
+ }
321
+ });
322
+ const tagsQuery = useQuery({
323
+ queryKey: [...DIALOG_QUERY_ROOT, "tags", `scope:${scopeVersion}`],
324
+ enabled: open,
325
+ staleTime: 3e5,
326
+ queryFn: async () => {
327
+ const call = await apiCall(
328
+ `/api/staff/timesheets/tags?page=1&pageSize=${DIRECTORY_PAGE_SIZE}&sortField=label&sortDir=asc`
329
+ );
330
+ if (!call.ok) return [];
331
+ return readRowItems(call.result).map((row) => {
332
+ const id = readRowString(row, "id");
333
+ return id ? { id, label: readRowString(row, "label") ?? id, color: readRowString(row, "color") } : null;
334
+ }).filter((tag) => tag !== null);
335
+ }
336
+ });
337
+ const entryQuery = useQuery({
338
+ queryKey: [...DIALOG_QUERY_ROOT, "entry", `scope:${scopeVersion}`, entryId ?? "new"],
339
+ enabled: open && isEdit,
340
+ staleTime: 0,
341
+ queryFn: async () => {
342
+ const call = await apiCall(
343
+ `/api/staff/timesheets/time-entries?ids=${encodeURIComponent(entryId)}&pageSize=1`
344
+ );
345
+ if (!call.ok) throw new Error("[internal] time entry request failed");
346
+ const row = readRowItems(call.result)[0];
347
+ if (!row) return null;
348
+ const record = toTimeEntryRecord(row);
349
+ if (!record) return null;
350
+ if (record.id !== entryId) {
351
+ throw new Error("[internal] time entry response did not match the requested id");
352
+ }
353
+ return record;
354
+ }
355
+ });
356
+ const entry = entryQuery.data ?? null;
357
+ const locked = !!entry?.isLocked;
358
+ const lockedReportId = entry?.lockedReportId ?? null;
359
+ const directoryTasks = React.useMemo(() => tasksQuery.data ?? [], [tasksQuery.data]);
360
+ const searchedTasks = React.useMemo(() => taskSearchQuery.data ?? [], [taskSearchQuery.data]);
361
+ React.useEffect(() => {
362
+ if (!taskId) {
363
+ setPinnedTask(null);
364
+ return;
365
+ }
366
+ const found = directoryTasks.find((task) => task.id === taskId) ?? searchedTasks.find((task) => task.id === taskId) ?? null;
367
+ if (found) setPinnedTask(found);
368
+ }, [directoryTasks, searchedTasks, taskId]);
369
+ const unresolvedTaskId = taskId && !pinnedTask && !tasksQuery.isPending && !taskSearchQuery.isFetching ? taskId : null;
370
+ const unresolvedTaskQuery = useQuery({
371
+ queryKey: [...DIALOG_QUERY_ROOT, "task", `scope:${scopeVersion}`, unresolvedTaskId ?? "none"],
372
+ enabled: open && !!unresolvedTaskId,
373
+ staleTime: 6e4,
374
+ queryFn: () => fetchTaskById(unresolvedTaskId)
375
+ });
376
+ const tasks = React.useMemo(() => {
377
+ const merged = /* @__PURE__ */ new Map();
378
+ for (const task of directoryTasks) merged.set(task.id, task);
379
+ for (const task of searchedTasks) if (!merged.has(task.id)) merged.set(task.id, task);
380
+ for (const task of [pinnedTask, unresolvedTaskQuery.data ?? null]) {
381
+ if (task && !merged.has(task.id)) merged.set(task.id, task);
382
+ }
383
+ return [...merged.values()];
384
+ }, [directoryTasks, pinnedTask, searchedTasks, unresolvedTaskQuery.data]);
385
+ const projects = React.useMemo(() => projectsQuery.data ?? [], [projectsQuery.data]);
386
+ const tagOptions = React.useMemo(() => tagsQuery.data ?? [], [tagsQuery.data]);
387
+ const projectById = React.useMemo(() => {
388
+ const map = /* @__PURE__ */ new Map();
389
+ for (const project2 of projects) map.set(project2.id, project2);
390
+ return map;
391
+ }, [projects]);
392
+ const selectedTask = React.useMemo(
393
+ () => tasks.find((task) => task.id === taskId) ?? null,
394
+ [taskId, tasks]
395
+ );
396
+ const projectId = selectedTask?.timeProjectId ?? entry?.timeProjectId ?? defaults?.timeProjectId ?? null;
397
+ const project = projectId ? projectById.get(projectId) ?? null : null;
398
+ const resetForm = React.useCallback(
399
+ (seed) => {
400
+ setTaskId(seed.taskId);
401
+ setDescription(seed.description);
402
+ setDate(seed.date || todayIsoDate());
403
+ setIntervalState(
404
+ (current) => resetIntervalState(current, {
405
+ start: seed.start,
406
+ end: seed.end,
407
+ durationMinutes: seed.durationMinutes
408
+ })
409
+ );
410
+ setIsBillable(seed.isBillable);
411
+ setRateOverrideEnabled(seed.rateOverrideAmount !== null);
412
+ setRateText(seed.rateOverrideAmount !== null ? String(seed.rateOverrideAmount) : "");
413
+ setTagIds(seed.tagIds);
414
+ setOverlaps([]);
415
+ setFieldIssues(NO_FIELD_ISSUES);
416
+ const derived = createIntervalState({
417
+ start: seed.start,
418
+ end: seed.end,
419
+ durationMinutes: seed.durationMinutes
420
+ });
421
+ setBaseline({
422
+ taskId: seed.taskId,
423
+ description: seed.description,
424
+ date: seed.date || todayIsoDate(),
425
+ startText: derived.startText,
426
+ endText: derived.endText,
427
+ durationMinutes: derived.durationMinutes,
428
+ isBillable: seed.isBillable,
429
+ rateOverrideEnabled: seed.rateOverrideAmount !== null,
430
+ rateOverrideAmount: seed.rateOverrideAmount,
431
+ tagIds: seed.tagIds
432
+ });
433
+ },
434
+ []
435
+ );
436
+ React.useEffect(() => {
437
+ if (!open) {
438
+ seedKeyRef.current = null;
439
+ billableDefaultRef.current = null;
440
+ setBaseline(null);
441
+ setTaskSearch("");
442
+ setPinnedTask(null);
443
+ return;
444
+ }
445
+ if (isEdit && !entry) return;
446
+ const key = isEdit ? `edit:${entry?.id ?? entryId}:${entry?.updatedAt ?? ""}` : "create";
447
+ if (seedKeyRef.current === key) return;
448
+ seedKeyRef.current = key;
449
+ versionRef.current = entry?.updatedAt ?? null;
450
+ if (entry) {
451
+ resetForm({
452
+ taskId: entry.taskId,
453
+ description: entry.description,
454
+ date: entry.date,
455
+ start: entry.startText,
456
+ end: entry.endText,
457
+ durationMinutes: entry.durationMinutes,
458
+ isBillable: entry.isBillable,
459
+ rateOverrideAmount: entry.rateOverrideAmount,
460
+ tagIds: entry.tagIds
461
+ });
462
+ return;
463
+ }
464
+ resetForm({
465
+ taskId: defaults?.taskId ?? null,
466
+ description: defaults?.description ?? "",
467
+ date: defaults?.date ?? todayIsoDate(),
468
+ start: defaults?.startClock ?? null,
469
+ end: defaults?.endClock ?? null,
470
+ durationMinutes: defaults?.durationMinutes ?? null,
471
+ isBillable: defaults?.isBillable ?? true,
472
+ rateOverrideAmount: null,
473
+ tagIds: defaults?.tagIds ?? []
474
+ });
475
+ }, [defaults, entry, entryId, isEdit, open, resetForm]);
476
+ React.useEffect(() => {
477
+ if (!open || isEdit || settingsQuery.isPending) return;
478
+ if (defaults?.isBillable !== void 0 && defaults?.isBillable !== null) return;
479
+ if (billableDefaultRef.current === seedKeyRef.current) return;
480
+ billableDefaultRef.current = seedKeyRef.current;
481
+ setIsBillable(settings.defaults.billable);
482
+ setBaseline(
483
+ (current) => current ? { ...current, isBillable: settings.defaults.billable } : current
484
+ );
485
+ }, [defaults?.isBillable, isEdit, open, settings.defaults.billable, settingsQuery.isPending]);
486
+ const durationMinutes = interval.durationMinutes;
487
+ const startClock = interval.startText;
488
+ const endClock = interval.endText;
489
+ React.useEffect(() => {
490
+ if (!open || locked || !settings.warnings.overlap) {
491
+ setOverlaps([]);
492
+ return;
493
+ }
494
+ if (!date || parseClock(startClock) === null || !durationMinutes) {
495
+ setOverlaps([]);
496
+ return;
497
+ }
498
+ let cancelled = false;
499
+ const params = new URLSearchParams({
500
+ date,
501
+ startedAt: startClock,
502
+ durationMinutes: String(durationMinutes)
503
+ });
504
+ if (parseClock(endClock) !== null) params.set("endedAt", endClock);
505
+ if (isEdit && entryId) params.set("excludeId", entryId);
506
+ void apiCall(`/api/staff/timesheets/time-entries/overlaps?${params.toString()}`).then((call) => {
507
+ if (cancelled) return;
508
+ if (!call.ok) {
509
+ setOverlaps([]);
510
+ return;
511
+ }
512
+ setOverlaps(
513
+ readRowItems(call.result).map(toOverlapEntry).filter((item) => item !== null)
514
+ );
515
+ }).catch(() => {
516
+ if (!cancelled) setOverlaps([]);
517
+ });
518
+ return () => {
519
+ cancelled = true;
520
+ };
521
+ }, [date, durationMinutes, endClock, entryId, isEdit, locked, open, settings.warnings.overlap, startClock]);
522
+ const rateOverrideAmount = rateOverrideEnabled ? parseRateText(rateText) : null;
523
+ const currencyCode = project?.currencyCode ?? entry?.currencyCode ?? null;
524
+ const entryFormValues = React.useMemo(
525
+ () => ({
526
+ taskId,
527
+ description,
528
+ date,
529
+ startText: startClock,
530
+ endText: endClock,
531
+ durationMinutes,
532
+ isBillable,
533
+ rateOverrideEnabled,
534
+ rateOverrideAmount,
535
+ tagIds
536
+ }),
537
+ [
538
+ date,
539
+ description,
540
+ durationMinutes,
541
+ endClock,
542
+ isBillable,
543
+ rateOverrideAmount,
544
+ rateOverrideEnabled,
545
+ startClock,
546
+ tagIds,
547
+ taskId
548
+ ]
549
+ );
550
+ const entryInjectionContext = React.useMemo(
551
+ () => ({
552
+ formId: TIME_ENTRY_DIALOG_MUTATION_CONTEXT_ID,
553
+ entityId: TIME_ENTRY_ENTITY_ID,
554
+ recordId: entryId ?? null,
555
+ resourceKind: "staff.timesheets.time_entry",
556
+ resourceId: entryId ?? "new",
557
+ operation: isEdit ? "update" : "create",
558
+ locked,
559
+ retryLastMutation
560
+ }),
561
+ [entryId, isEdit, locked, retryLastMutation]
562
+ );
563
+ const { triggerEvent: triggerEntryFormEvent } = useInjectionSpotEvents(extensionPoints.hosts.timeEntryForm.spotId);
564
+ const injectedFieldWritesRef = React.useRef(/* @__PURE__ */ new Set());
565
+ const applyInjectedFieldValue = React.useCallback((fieldId, value) => {
566
+ switch (fieldId) {
567
+ case "taskId":
568
+ injectedFieldWritesRef.current.add(fieldId);
569
+ setTaskId(typeof value === "string" && value.length > 0 ? value : null);
570
+ return;
571
+ case "description":
572
+ injectedFieldWritesRef.current.add(fieldId);
573
+ setDescription(typeof value === "string" ? value : "");
574
+ return;
575
+ case "date":
576
+ if (typeof value !== "string") return;
577
+ injectedFieldWritesRef.current.add(fieldId);
578
+ setDate(value);
579
+ return;
580
+ case "isBillable":
581
+ injectedFieldWritesRef.current.add(fieldId);
582
+ setIsBillable(value === true);
583
+ return;
584
+ case "rateOverrideEnabled":
585
+ injectedFieldWritesRef.current.add(fieldId);
586
+ setRateOverrideEnabled(value === true);
587
+ return;
588
+ case "rateOverrideAmount":
589
+ injectedFieldWritesRef.current.add(fieldId);
590
+ setRateText(typeof value === "number" || typeof value === "string" ? String(value) : "");
591
+ return;
592
+ case "tagIds":
593
+ if (!Array.isArray(value)) return;
594
+ injectedFieldWritesRef.current.add(fieldId);
595
+ setTagIds(value.filter((id) => typeof id === "string"));
596
+ return;
597
+ case "startText":
598
+ if (typeof value !== "string") return;
599
+ injectedFieldWritesRef.current.add(fieldId);
600
+ setIntervalState((current) => reduceIntervalState(current, { field: "start", value }));
601
+ return;
602
+ case "endText":
603
+ if (typeof value !== "string") return;
604
+ injectedFieldWritesRef.current.add(fieldId);
605
+ setIntervalState((current) => reduceIntervalState(current, { field: "end", value }));
606
+ return;
607
+ case "durationMinutes":
608
+ injectedFieldWritesRef.current.add(fieldId);
609
+ setIntervalState(
610
+ (current) => reduceIntervalState(current, {
611
+ field: "duration",
612
+ minutes: typeof value === "number" ? value : null,
613
+ invalid: false
614
+ })
615
+ );
616
+ return;
617
+ default:
618
+ }
619
+ }, []);
620
+ const previousEntryFormValuesRef = React.useRef(null);
621
+ React.useEffect(() => {
622
+ if (!open || locked) {
623
+ previousEntryFormValuesRef.current = null;
624
+ injectedFieldWritesRef.current.clear();
625
+ return;
626
+ }
627
+ const previous = previousEntryFormValuesRef.current;
628
+ previousEntryFormValuesRef.current = entryFormValues;
629
+ if (!previous) return;
630
+ const injectedWrites = injectedFieldWritesRef.current;
631
+ const changedFieldIds = Object.keys(entryFormValues).filter(
632
+ (fieldId) => fieldValueSnapshot(previous[fieldId]) !== fieldValueSnapshot(entryFormValues[fieldId])
633
+ );
634
+ for (const fieldId of changedFieldIds) {
635
+ if (injectedWrites.delete(fieldId)) continue;
636
+ void triggerEntryFormEvent("onFieldChange", entryFormValues, entryInjectionContext, {
637
+ fieldId,
638
+ fieldValue: entryFormValues[fieldId]
639
+ }).then((result) => {
640
+ if (!result.ok || !result.fieldChange) return;
641
+ const change = result.fieldChange;
642
+ if (change.value !== void 0) applyInjectedFieldValue(fieldId, change.value);
643
+ for (const [sideFieldId, sideValue] of Object.entries(change.sideEffects ?? {})) {
644
+ applyInjectedFieldValue(sideFieldId, sideValue);
645
+ }
646
+ for (const message of change.messages ?? []) flash(message.text, message.severity);
647
+ }).catch((err) => {
648
+ logger.error("staff.time_tracking entry dialog onFieldChange failed", { err });
649
+ });
650
+ }
651
+ }, [
652
+ applyInjectedFieldValue,
653
+ entryFormValues,
654
+ entryInjectionContext,
655
+ locked,
656
+ open,
657
+ triggerEntryFormEvent
658
+ ]);
659
+ const roundedMinutes = roundMinutes(durationMinutes ?? 0, settings.rounding);
660
+ const rate = applicableRate({ rateOverrideAmount }, { hourlyRate: project?.hourlyRate ?? null });
661
+ const cost = entryAmount(
662
+ { isBillable, roundedMinutes, rateOverrideAmount },
663
+ { hourlyRate: project?.hourlyRate ?? null }
664
+ );
665
+ const computedBadge = (field) => /* @__PURE__ */ jsx(Badge, { variant: "neutral", "data-testid": `entry-dialog-computed-${field}`, children: t("staff.time_tracking.entryDialog.computed", "computed") });
666
+ const midnightEndDate = interval.crossesMidnight ? shiftIsoDate(date, 1) : null;
667
+ const canSubmit = !locked && !interval.durationInvalid && !saving;
668
+ React.useEffect(() => {
669
+ if (!taskId) return;
670
+ setFieldIssues((current) => current.task ? { ...current, task: null } : current);
671
+ }, [taskId]);
672
+ React.useEffect(() => {
673
+ if (!durationMinutes) return;
674
+ setFieldIssues((current) => current.duration ? { ...current, duration: null } : current);
675
+ }, [durationMinutes]);
676
+ const validateRequired = React.useCallback(() => {
677
+ const next = {
678
+ task: taskId ? null : t("staff.time_tracking.entryDialog.errors.taskRequired", "Pick the task this time belongs to."),
679
+ duration: durationMinutes ? null : t("staff.time_tracking.entryDialog.errors.durationRequired", "Enter how long the work took.")
680
+ };
681
+ setFieldIssues(next);
682
+ return !next.task && !next.duration;
683
+ }, [durationMinutes, t, taskId]);
684
+ const isDirty = React.useMemo(() => {
685
+ if (locked || !baseline) return false;
686
+ return formSnapshot(baseline) !== formSnapshot({
687
+ taskId,
688
+ description,
689
+ date,
690
+ startText: startClock,
691
+ endText: endClock,
692
+ durationMinutes,
693
+ isBillable,
694
+ rateOverrideEnabled,
695
+ rateOverrideAmount,
696
+ tagIds
697
+ });
698
+ }, [
699
+ baseline,
700
+ date,
701
+ description,
702
+ durationMinutes,
703
+ endClock,
704
+ isBillable,
705
+ locked,
706
+ rateOverrideAmount,
707
+ rateOverrideEnabled,
708
+ startClock,
709
+ tagIds,
710
+ taskId
711
+ ]);
712
+ const requestClose = React.useCallback(async () => {
713
+ if (saving) return;
714
+ if (!isDirty) {
715
+ onOpenChange(false);
716
+ return;
717
+ }
718
+ const confirmed = await confirm({
719
+ title: t("staff.time_tracking.entryDialog.discard.title", "Discard this time entry?"),
720
+ text: t(
721
+ "staff.time_tracking.entryDialog.discard.text",
722
+ "It has not been saved yet, so closing now loses what you entered."
723
+ ),
724
+ confirmText: t("staff.time_tracking.entryDialog.discard.confirm", "Discard"),
725
+ cancelText: t("staff.time_tracking.entryDialog.discard.cancel", "Keep editing"),
726
+ variant: "destructive"
727
+ });
728
+ if (confirmed) onOpenChange(false);
729
+ }, [confirm, isDirty, onOpenChange, saving, t]);
730
+ const buildPayload = React.useCallback(() => {
731
+ const startedAt = parseClock(startClock) !== null ? combineDateAndClock(date, startClock) : null;
732
+ const endedAt = parseClock(endClock) !== null ? combineDateAndClock(date, endClock, interval.crossesMidnight ? 1 : 0) : null;
733
+ return {
734
+ date,
735
+ durationMinutes: durationMinutes ?? 0,
736
+ startedAt,
737
+ endedAt,
738
+ taskId,
739
+ timeProjectId: projectId,
740
+ description: description.trim() ? description.trim() : null,
741
+ isBillable,
742
+ tagIds,
743
+ rateOverrideAmount: canSeeMoney ? rateOverrideAmount : void 0
744
+ };
745
+ }, [
746
+ canSeeMoney,
747
+ date,
748
+ description,
749
+ durationMinutes,
750
+ endClock,
751
+ interval.crossesMidnight,
752
+ isBillable,
753
+ projectId,
754
+ rateOverrideAmount,
755
+ startClock,
756
+ tagIds,
757
+ taskId
758
+ ]);
759
+ const reportFailure = React.useCallback(
760
+ (error) => {
761
+ if (surfaceRecordConflict(error, t)) return;
762
+ if (readErrorCode(error) === "entry_locked") {
763
+ flash(
764
+ t(
765
+ "staff.time_tracking.entryDialog.locked.notice",
766
+ "This entry is closed in a report, so it can no longer be edited."
767
+ ),
768
+ "error"
769
+ );
770
+ return;
771
+ }
772
+ const issues = readFieldIssues(error);
773
+ if (issues) {
774
+ setFieldIssues(issues);
775
+ return;
776
+ }
777
+ logger.error("staff.time_tracking entry dialog save failed", { err: error });
778
+ flash(
779
+ error instanceof Error && error.message ? error.message : t("staff.time_tracking.entryDialog.errors.save", "Could not save the time entry."),
780
+ "error"
781
+ );
782
+ },
783
+ [t]
784
+ );
785
+ const submit = React.useCallback(
786
+ async (mode) => {
787
+ if (!canSubmit) return;
788
+ if (!validateRequired()) return;
789
+ if (!isEdit && !staffMemberId) {
790
+ flash(
791
+ t(
792
+ "staff.time_tracking.entryDialog.errors.noStaffMember",
793
+ "Your account is not linked to a staff member, so time cannot be logged."
794
+ ),
795
+ "error"
796
+ );
797
+ return;
798
+ }
799
+ const body2 = buildPayload();
800
+ const beforeSave = await triggerEntryFormEvent("onBeforeSave", entryFormValues, entryInjectionContext);
801
+ if (!beforeSave.ok) {
802
+ if (beforeSave.fieldErrors) {
803
+ const taskIssue = beforeSave.fieldErrors.taskId ?? beforeSave.fieldErrors.task ?? null;
804
+ const durationIssue = beforeSave.fieldErrors.durationMinutes ?? beforeSave.fieldErrors.duration ?? null;
805
+ if (taskIssue || durationIssue) setFieldIssues({ task: taskIssue, duration: durationIssue });
806
+ }
807
+ flash(
808
+ beforeSave.message || t("ui.forms.flash.saveBlocked", "Save blocked by validation"),
809
+ "error"
810
+ );
811
+ return;
812
+ }
813
+ const injectedRequestHeaders = beforeSave.requestHeaders ?? {};
814
+ setSavingMode(mode);
815
+ try {
816
+ const saved = await runMutation({
817
+ operation: () => withScopedApiRequestHeaders(
818
+ {
819
+ ...injectedRequestHeaders,
820
+ ...isEdit ? buildOptimisticLockHeader(versionRef.current) : {}
821
+ },
822
+ () => apiCallOrThrow(
823
+ "/api/staff/timesheets/time-entries",
824
+ {
825
+ method: isEdit ? "PUT" : "POST",
826
+ headers: { "content-type": "application/json" },
827
+ body: JSON.stringify(
828
+ isEdit ? { id: entryId, ...body2 } : { staffMemberId, source: "manual", ...body2 }
829
+ )
830
+ },
831
+ {
832
+ errorMessage: t(
833
+ "staff.time_tracking.entryDialog.errors.save",
834
+ "Could not save the time entry."
835
+ )
836
+ }
837
+ )
838
+ ),
839
+ context: {
840
+ formId: TIME_ENTRY_DIALOG_MUTATION_CONTEXT_ID,
841
+ resourceKind: "staff.timesheets.time_entry",
842
+ resourceId: entryId ?? "new",
843
+ retryLastMutation
844
+ }
845
+ });
846
+ const savedId = typeof saved?.result?.id === "string" ? saved.result.id : entryId ?? null;
847
+ try {
848
+ await triggerEntryFormEvent("onAfterSave", entryFormValues, entryInjectionContext);
849
+ } catch (err) {
850
+ logger.error("staff.time_tracking entry dialog onAfterSave failed", { err });
851
+ }
852
+ flash(t("staff.time_tracking.entryDialog.saved", "Time entry saved."), "success");
853
+ if (mode === "again") {
854
+ const chain = settings.defaults.chainStartFromPreviousEnd;
855
+ const nextDate = interval.crossesMidnight && midnightEndDate ? midnightEndDate : date;
856
+ const nextStart = chain ? endClock || null : null;
857
+ seedKeyRef.current = "create";
858
+ setDescription("");
859
+ setDate(nextDate);
860
+ setIntervalState((current) => resetIntervalState(current, { start: nextStart }));
861
+ setOverlaps([]);
862
+ setBaseline({
863
+ taskId,
864
+ description: "",
865
+ date: nextDate,
866
+ startText: nextStart ?? "",
867
+ endText: "",
868
+ durationMinutes: null,
869
+ isBillable,
870
+ rateOverrideEnabled,
871
+ rateOverrideAmount,
872
+ tagIds
873
+ });
874
+ onSaved?.({ id: savedId, keptOpen: true });
875
+ focusTaskPicker();
876
+ return;
877
+ }
878
+ onSaved?.({ id: savedId, keptOpen: false });
879
+ onOpenChange(false);
880
+ } catch (error) {
881
+ reportFailure(error);
882
+ } finally {
883
+ setSavingMode(null);
884
+ }
885
+ },
886
+ [
887
+ buildPayload,
888
+ canSubmit,
889
+ date,
890
+ endClock,
891
+ entryFormValues,
892
+ entryId,
893
+ entryInjectionContext,
894
+ interval.crossesMidnight,
895
+ isBillable,
896
+ isEdit,
897
+ midnightEndDate,
898
+ onOpenChange,
899
+ onSaved,
900
+ rateOverrideAmount,
901
+ rateOverrideEnabled,
902
+ reportFailure,
903
+ retryLastMutation,
904
+ runMutation,
905
+ settings.defaults.chainStartFromPreviousEnd,
906
+ staffMemberId,
907
+ t,
908
+ tagIds,
909
+ taskId,
910
+ triggerEntryFormEvent,
911
+ validateRequired
912
+ ]
913
+ );
914
+ const handleShowOverlap = React.useCallback(
915
+ (overlap2) => {
916
+ if (onShowEntry) {
917
+ onShowEntry(overlap2.id);
918
+ return;
919
+ }
920
+ window.open(`${ENTRIES_LIST_PATH}?ids=${encodeURIComponent(overlap2.id)}`, "_blank", "noopener");
921
+ },
922
+ [onShowEntry]
923
+ );
924
+ const pickerItems = React.useMemo(
925
+ () => tasks.map((task) => {
926
+ const project2 = task.timeProjectId ? projectById.get(task.timeProjectId) : void 0;
927
+ return {
928
+ id: task.id,
929
+ reference: task.reference,
930
+ title: task.title,
931
+ projectId: task.timeProjectId,
932
+ projectName: project2?.name ?? null,
933
+ customerName: project2?.customerName ?? null,
934
+ statusId: task.taskStatusId ?? null,
935
+ assigneeInitials: null,
936
+ assigneeName: null,
937
+ loggedMinutes: task.loggedMinutes ?? null
938
+ };
939
+ }),
940
+ [projectById, tasks]
941
+ );
942
+ const queryClient = useQueryClient();
943
+ const createTag = React.useCallback(
944
+ async (label) => {
945
+ const color = autoColorFromName(label).key;
946
+ try {
947
+ const created = await apiCallOrThrow(
948
+ "/api/staff/timesheets/tags",
949
+ {
950
+ method: "POST",
951
+ headers: { "content-type": "application/json" },
952
+ body: JSON.stringify({ label, slug: slugifyProjectName(label).toLowerCase(), color })
953
+ },
954
+ { errorMessage: t("staff.time_tracking.entryDialog.errors.createTag", "Could not create the tag.") }
955
+ );
956
+ const id = typeof created?.result?.id === "string" ? created.result.id : null;
957
+ if (!id) return null;
958
+ const option = { id, label, color };
959
+ queryClient.setQueryData(
960
+ [...DIALOG_QUERY_ROOT, "tags", `scope:${scopeVersion}`],
961
+ (current) => [...current ?? [], option]
962
+ );
963
+ await queryClient.invalidateQueries({ queryKey: [...DIALOG_QUERY_ROOT, "tags"] });
964
+ return option;
965
+ } catch (error) {
966
+ logger.warn("staff.time_tracking.entryDialog tag create failed", { err: error });
967
+ flash(t("staff.time_tracking.entryDialog.errors.createTag", "Could not create the tag."), "error");
968
+ return null;
969
+ }
970
+ },
971
+ [queryClient, scopeVersion, t]
972
+ );
973
+ const overlap = overlaps[0] ?? null;
974
+ const availableTags = React.useMemo(
975
+ () => tagOptions.filter((tag) => !tagIds.includes(tag.id)),
976
+ [tagIds, tagOptions]
977
+ );
978
+ const assignedTags = React.useMemo(() => {
979
+ const byId = new Map(tagOptions.map((tag) => [tag.id, tag]));
980
+ return tagIds.map((id) => byId.get(id) ?? { id, label: id, color: null });
981
+ }, [tagIds, tagOptions]);
982
+ const rateLabel = rate !== null ? formatCurrency(rate, currencyCode) ?? String(rate) : null;
983
+ const projectRateLabel = project?.hourlyRate !== null && project?.hourlyRate !== void 0 ? formatCurrency(project.hourlyRate, currencyCode) ?? String(project.hourlyRate) : null;
984
+ const body = (() => {
985
+ if (isEdit && entryQuery.isLoading) {
986
+ return /* @__PURE__ */ jsx(LoadingMessage, { label: t("staff.time_tracking.entryDialog.loading", "Loading the time entry\u2026") });
987
+ }
988
+ if (isEdit && entryQuery.isError) {
989
+ return /* @__PURE__ */ jsx(ErrorMessage, { label: t("staff.time_tracking.entryDialog.errors.load", "Could not load the time entry.") });
990
+ }
991
+ if (isEdit && !entry) {
992
+ return /* @__PURE__ */ jsx(ErrorMessage, { label: t("staff.time_tracking.entryDialog.errors.notFound", "Time entry not found or not accessible.") });
993
+ }
994
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4", children: [
995
+ /* @__PURE__ */ jsx(
996
+ InjectionSpot,
997
+ {
998
+ spotId: extensionSpotChildId(extensionPoints.hosts.timeEntryForm.spotId, "before-fields"),
999
+ context: entryInjectionContext,
1000
+ data: entryFormValues
1001
+ }
1002
+ ),
1003
+ locked ? /* @__PURE__ */ jsxs(Alert, { status: "warning", "data-testid": "entry-dialog-locked", children: [
1004
+ /* @__PURE__ */ jsxs(AlertTitle, { className: "flex items-center gap-2", children: [
1005
+ /* @__PURE__ */ jsx(Lock, { className: "size-3.5", "aria-hidden": "true" }),
1006
+ t("staff.time_tracking.entryDialog.locked.badge", "Locked")
1007
+ ] }),
1008
+ /* @__PURE__ */ jsx(AlertDescription, { children: t(
1009
+ "staff.time_tracking.entryDialog.locked.notice",
1010
+ "This entry is closed in a report, so it can no longer be edited."
1011
+ ) }),
1012
+ lockedReportId ? /* @__PURE__ */ jsx(
1013
+ Button,
1014
+ {
1015
+ type: "button",
1016
+ variant: "outline",
1017
+ size: "sm",
1018
+ className: "mt-2",
1019
+ "data-testid": "entry-dialog-locked-report",
1020
+ onClick: () => window.open(`${REPORTS_PATH}?id=${encodeURIComponent(lockedReportId)}`, "_blank", "noopener"),
1021
+ children: t("staff.time_tracking.entryDialog.locked.showReport", "Show the report")
1022
+ }
1023
+ ) : null
1024
+ ] }) : null,
1025
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
1026
+ /* @__PURE__ */ jsxs(Label, { htmlFor: "entry-dialog-task", children: [
1027
+ t("staff.time_tracking.entryDialog.task", "Task"),
1028
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: " *" })
1029
+ ] }),
1030
+ /* @__PURE__ */ jsx(
1031
+ "div",
1032
+ {
1033
+ "data-testid": "entry-dialog-task",
1034
+ ref: taskTriggerRef,
1035
+ "aria-invalid": fieldIssues.task ? true : void 0,
1036
+ "aria-describedby": fieldIssues.task ? "entry-dialog-task-message" : void 0,
1037
+ children: /* @__PURE__ */ jsx(
1038
+ TaskPicker,
1039
+ {
1040
+ value: taskId,
1041
+ onChange: (next) => setTaskId(next || null),
1042
+ items: pickerItems,
1043
+ recentTaskIds: recentQuery.data ?? [],
1044
+ statuses: statusesQuery.data ?? {},
1045
+ onQueryChange: setTaskSearch,
1046
+ searching: taskSearchQuery.isFetching,
1047
+ loading: tasksQuery.isLoading,
1048
+ disabled: locked
1049
+ }
1050
+ )
1051
+ }
1052
+ ),
1053
+ fieldIssues.task ? /* @__PURE__ */ jsx(
1054
+ "p",
1055
+ {
1056
+ id: "entry-dialog-task-message",
1057
+ className: "text-xs text-status-error-text",
1058
+ role: "alert",
1059
+ "data-testid": "entry-dialog-task-error",
1060
+ children: fieldIssues.task
1061
+ }
1062
+ ) : /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t(
1063
+ "staff.time_tracking.entryDialog.taskHint",
1064
+ "The project and the customer follow from the task \u2014 you do not pick them separately."
1065
+ ) })
1066
+ ] }),
1067
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
1068
+ /* @__PURE__ */ jsx(Label, { htmlFor: "entry-dialog-description", children: t("staff.time_tracking.entryDialog.description", "Description") }),
1069
+ /* @__PURE__ */ jsx(
1070
+ Input,
1071
+ {
1072
+ id: "entry-dialog-description",
1073
+ value: description,
1074
+ disabled: locked,
1075
+ onChange: (event) => setDescription(event.target.value),
1076
+ "data-testid": "entry-dialog-description"
1077
+ }
1078
+ )
1079
+ ] }),
1080
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 gap-4 sm:grid-cols-2", children: [
1081
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
1082
+ /* @__PURE__ */ jsx(Label, { htmlFor: "entry-dialog-date", children: t("staff.time_tracking.entryDialog.date", "Date") }),
1083
+ /* @__PURE__ */ jsx(
1084
+ Input,
1085
+ {
1086
+ id: "entry-dialog-date",
1087
+ type: "date",
1088
+ value: date,
1089
+ disabled: locked,
1090
+ onChange: (event) => setDate(event.target.value),
1091
+ "data-testid": "entry-dialog-date"
1092
+ }
1093
+ )
1094
+ ] }),
1095
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
1096
+ /* @__PURE__ */ jsxs(Label, { htmlFor: "entry-dialog-duration", className: "flex items-center gap-2", children: [
1097
+ t("staff.time_tracking.entryDialog.duration", "Duration"),
1098
+ interval.computed === "duration" ? computedBadge("duration") : null
1099
+ ] }),
1100
+ /* @__PURE__ */ jsx(
1101
+ DurationInput,
1102
+ {
1103
+ id: "entry-dialog-duration",
1104
+ value: durationMinutes,
1105
+ disabled: locked,
1106
+ error: fieldIssues.duration ?? void 0,
1107
+ ariaLabel: t("staff.time_tracking.entryDialog.duration", "Duration"),
1108
+ onChange: (minutes, state) => setIntervalState(
1109
+ (current) => reduceIntervalState(current, {
1110
+ field: "duration",
1111
+ minutes,
1112
+ invalid: state.status === "invalid"
1113
+ })
1114
+ )
1115
+ },
1116
+ `entry-dialog-duration-${interval.durationEpoch}`
1117
+ )
1118
+ ] })
1119
+ ] }),
1120
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 gap-4 sm:grid-cols-2", children: [
1121
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
1122
+ /* @__PURE__ */ jsxs(Label, { htmlFor: "entry-dialog-start", className: "flex items-center gap-2", children: [
1123
+ t("staff.time_tracking.entryDialog.start", "Start"),
1124
+ interval.computed === "start" ? computedBadge("start") : null
1125
+ ] }),
1126
+ /* @__PURE__ */ jsx(
1127
+ Input,
1128
+ {
1129
+ id: "entry-dialog-start",
1130
+ value: startClock,
1131
+ disabled: locked,
1132
+ inputClassName: "font-mono tabular-nums",
1133
+ placeholder: "15:10",
1134
+ onChange: (event) => setIntervalState(
1135
+ (current) => reduceIntervalState(current, { field: "start", value: event.target.value })
1136
+ ),
1137
+ "data-testid": "entry-dialog-start"
1138
+ }
1139
+ )
1140
+ ] }),
1141
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
1142
+ /* @__PURE__ */ jsxs(Label, { htmlFor: "entry-dialog-end", className: "flex items-center gap-2", children: [
1143
+ t("staff.time_tracking.entryDialog.end", "End"),
1144
+ interval.computed === "end" ? computedBadge("end") : null
1145
+ ] }),
1146
+ /* @__PURE__ */ jsx(
1147
+ Input,
1148
+ {
1149
+ id: "entry-dialog-end",
1150
+ value: endClock,
1151
+ disabled: locked,
1152
+ inputClassName: "font-mono tabular-nums",
1153
+ placeholder: "16:25",
1154
+ onChange: (event) => setIntervalState(
1155
+ (current) => reduceIntervalState(current, { field: "end", value: event.target.value })
1156
+ ),
1157
+ "data-testid": "entry-dialog-end"
1158
+ }
1159
+ ),
1160
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t(
1161
+ "staff.time_tracking.entryDialog.endHint",
1162
+ "Change this field and the duration recalculates itself."
1163
+ ) })
1164
+ ] })
1165
+ ] }),
1166
+ midnightEndDate ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", "data-testid": "entry-dialog-midnight", children: t("staff.time_tracking.entryDialog.midnight", "This entry ends the next day, {date}.", {
1167
+ date: midnightEndDate
1168
+ }) }) : null,
1169
+ overlap ? /* @__PURE__ */ jsxs(Alert, { status: "warning", "data-testid": "entry-dialog-overlap", children: [
1170
+ /* @__PURE__ */ jsx(AlertTitle, { children: t("staff.time_tracking.entryDialog.overlap.title", "This time overlaps another entry") }),
1171
+ /* @__PURE__ */ jsx(AlertDescription, { children: t(
1172
+ "staff.time_tracking.entryDialog.overlap.body",
1173
+ "{start} \u2013 {end} \xB7 \u201E{task}\u201D \xB7 {project} ({duration}). Overlapping is allowed \u2014 if it is intentional, just save.",
1174
+ {
1175
+ start: overlap.startText,
1176
+ end: overlap.endText,
1177
+ task: overlap.taskTitle ?? overlap.description ?? t("staff.time_tracking.entryDialog.overlap.noTask", "no task"),
1178
+ project: overlap.projectName ?? t("staff.time_tracking.entryDialog.overlap.noProject", "no project"),
1179
+ duration: formatDuration(overlap.durationMinutes, "clock")
1180
+ }
1181
+ ) }),
1182
+ /* @__PURE__ */ jsxs("div", { className: "mt-2 flex flex-wrap gap-2", children: [
1183
+ /* @__PURE__ */ jsx(
1184
+ Button,
1185
+ {
1186
+ type: "button",
1187
+ variant: "outline",
1188
+ size: "sm",
1189
+ onClick: () => handleShowOverlap(overlap),
1190
+ "data-testid": "entry-dialog-overlap-show",
1191
+ children: t("staff.time_tracking.entryDialog.overlap.show", "Show that entry")
1192
+ }
1193
+ ),
1194
+ overlap.endText ? /* @__PURE__ */ jsx(
1195
+ Button,
1196
+ {
1197
+ type: "button",
1198
+ variant: "outline",
1199
+ size: "sm",
1200
+ disabled: locked,
1201
+ onClick: () => setIntervalState(
1202
+ (current) => reduceIntervalState(current, { field: "snapStart", value: overlap.endText })
1203
+ ),
1204
+ "data-testid": "entry-dialog-overlap-snap",
1205
+ children: t("staff.time_tracking.entryDialog.overlap.snap", "Snap start to {time}", {
1206
+ time: overlap.endText
1207
+ })
1208
+ }
1209
+ ) : null
1210
+ ] })
1211
+ ] }) : null,
1212
+ /* @__PURE__ */ jsx(Separator, {}),
1213
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
1214
+ /* @__PURE__ */ jsx(
1215
+ Switch,
1216
+ {
1217
+ checked: isBillable,
1218
+ disabled: locked,
1219
+ onCheckedChange: (next) => setIsBillable(next),
1220
+ "aria-label": t("staff.time_tracking.entryDialog.billable", "Billable"),
1221
+ "data-testid": "entry-dialog-billable"
1222
+ }
1223
+ ),
1224
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col", children: [
1225
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-foreground", children: t("staff.time_tracking.entryDialog.billable", "Billable") }),
1226
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: t("staff.time_tracking.entryDialog.billableHint", "On by default for this project.") })
1227
+ ] }),
1228
+ canSeeMoney && rateLabel ? /* @__PURE__ */ jsx(Badge, { variant: "success", "data-testid": "entry-dialog-rate-badge", children: t("staff.time_tracking.entryDialog.ratePerHour", "{amount}/h", { amount: rateLabel }) }) : null
1229
+ ] }),
1230
+ canSeeMoney ? /* @__PURE__ */ jsxs(Fragment, { children: [
1231
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
1232
+ /* @__PURE__ */ jsx(
1233
+ Switch,
1234
+ {
1235
+ checked: rateOverrideEnabled,
1236
+ disabled: locked,
1237
+ onCheckedChange: (next) => setRateOverrideEnabled(next),
1238
+ "aria-label": t("staff.time_tracking.entryDialog.rateOverride", "Custom rate for this entry"),
1239
+ "data-testid": "entry-dialog-rate-override"
1240
+ }
1241
+ ),
1242
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col", children: [
1243
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-foreground", children: t("staff.time_tracking.entryDialog.rateOverride", "Custom rate for this entry") }),
1244
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: t(
1245
+ "staff.time_tracking.entryDialog.rateOverrideHint",
1246
+ "Overrides the project rate here only."
1247
+ ) })
1248
+ ] })
1249
+ ] }),
1250
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 gap-4 sm:grid-cols-2", children: [
1251
+ rateOverrideEnabled ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
1252
+ /* @__PURE__ */ jsx(Label, { htmlFor: "entry-dialog-rate", children: t("staff.time_tracking.entryDialog.rate", "Rate") }),
1253
+ /* @__PURE__ */ jsx(
1254
+ Input,
1255
+ {
1256
+ id: "entry-dialog-rate",
1257
+ value: rateText,
1258
+ disabled: locked,
1259
+ inputMode: "decimal",
1260
+ onChange: (event) => setRateText(event.target.value),
1261
+ "data-testid": "entry-dialog-rate"
1262
+ }
1263
+ ),
1264
+ projectRateLabel ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t(
1265
+ "staff.time_tracking.entryDialog.rateHint",
1266
+ "Defaults to {amount} (the project rate).",
1267
+ { amount: projectRateLabel }
1268
+ ) }) : null
1269
+ ] }) : null,
1270
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
1271
+ /* @__PURE__ */ jsxs(Label, { htmlFor: "entry-dialog-cost", className: "flex items-center gap-2", children: [
1272
+ t("staff.time_tracking.entryDialog.cost", "Cost"),
1273
+ computedBadge("cost")
1274
+ ] }),
1275
+ /* @__PURE__ */ jsx(
1276
+ Input,
1277
+ {
1278
+ id: "entry-dialog-cost",
1279
+ readOnly: true,
1280
+ disabled: true,
1281
+ value: cost === null ? "" : formatCurrency(cost, currencyCode) ?? String(cost),
1282
+ inputClassName: "font-mono tabular-nums",
1283
+ "data-testid": "entry-dialog-cost"
1284
+ }
1285
+ ),
1286
+ rateLabel && durationMinutes ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", "data-testid": "entry-dialog-cost-hint", children: t(
1287
+ "staff.time_tracking.entryDialog.costHint",
1288
+ "{raw} rounded to {rounded} \xD7 {rate}",
1289
+ {
1290
+ raw: formatDuration(durationMinutes, "clock"),
1291
+ rounded: formatDuration(roundedMinutes, "clock"),
1292
+ rate: rateLabel
1293
+ }
1294
+ ) }) : null
1295
+ ] })
1296
+ ] })
1297
+ ] }) : /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", "data-testid": "entry-dialog-money-hidden", children: t(
1298
+ "staff.time_tracking.entryDialog.moneyHidden",
1299
+ "Rates and cost are hidden \u2014 your permissions do not cover money on time entries."
1300
+ ) }),
1301
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
1302
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: t("staff.time_tracking.entryDialog.tags", "Tags") }),
1303
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
1304
+ assignedTags.map((tag) => /* @__PURE__ */ jsxs(
1305
+ Badge,
1306
+ {
1307
+ variant: "neutral",
1308
+ className: "gap-1",
1309
+ style: tagChipStyle(tag),
1310
+ children: [
1311
+ tag.label,
1312
+ locked ? null : /* @__PURE__ */ jsx(
1313
+ "button",
1314
+ {
1315
+ type: "button",
1316
+ "aria-label": t("staff.time_tracking.entryDialog.removeTag", "Remove tag {label}", {
1317
+ label: tag.label
1318
+ }),
1319
+ onClick: () => setTagIds((current) => current.filter((id) => id !== tag.id)),
1320
+ children: /* @__PURE__ */ jsx(X, { className: "size-3", "aria-hidden": "true" })
1321
+ }
1322
+ )
1323
+ ]
1324
+ },
1325
+ tag.id
1326
+ )),
1327
+ !locked ? /* @__PURE__ */ jsx(
1328
+ TagPicker,
1329
+ {
1330
+ available: availableTags,
1331
+ onAssign: (id) => setTagIds((current) => current.includes(id) ? current : [...current, id]),
1332
+ onCreate: createTag,
1333
+ labels: {
1334
+ add: t("staff.time_tracking.entryDialog.addTag", "Add tag"),
1335
+ create: (name) => t("staff.time_tracking.entryDialog.createTag", 'Create "{name}"', { name }),
1336
+ empty: t("staff.time_tracking.entryDialog.noTags", "No matching tag")
1337
+ }
1338
+ }
1339
+ ) : null
1340
+ ] })
1341
+ ] }),
1342
+ /* @__PURE__ */ jsx(
1343
+ InjectionSpot,
1344
+ {
1345
+ spotId: extensionSpotChildId(extensionPoints.hosts.timeEntryForm.spotId, "fields"),
1346
+ context: entryInjectionContext,
1347
+ data: entryFormValues
1348
+ }
1349
+ ),
1350
+ /* @__PURE__ */ jsx(Alert, { status: "information", "data-testid": "entry-dialog-creator", children: /* @__PURE__ */ jsx(AlertDescription, { children: selfQuery.data?.displayName ? t(
1351
+ "staff.time_tracking.entryDialog.creator",
1352
+ "Entry author: {name} \u2014 assigned automatically and not editable.",
1353
+ { name: selfQuery.data.displayName }
1354
+ ) : t(
1355
+ "staff.time_tracking.entryDialog.creatorUnknown",
1356
+ "The entry author is assigned automatically and is not editable."
1357
+ ) }) }),
1358
+ /* @__PURE__ */ jsx(
1359
+ InjectionSpot,
1360
+ {
1361
+ spotId: extensionSpotChildId(extensionPoints.hosts.timeEntryForm.spotId, "after-fields"),
1362
+ context: entryInjectionContext,
1363
+ data: entryFormValues
1364
+ }
1365
+ )
1366
+ ] });
1367
+ })();
1368
+ return /* @__PURE__ */ jsx(
1369
+ Dialog,
1370
+ {
1371
+ open,
1372
+ onOpenChange: (next) => {
1373
+ if (next) {
1374
+ onOpenChange(true);
1375
+ return;
1376
+ }
1377
+ void requestClose();
1378
+ },
1379
+ children: /* @__PURE__ */ jsxs(
1380
+ DialogContent,
1381
+ {
1382
+ size: "lg",
1383
+ "data-testid": "entry-dialog",
1384
+ onOpenAutoFocus: (event) => {
1385
+ if (locked || !taskTriggerRef.current) return;
1386
+ event.preventDefault();
1387
+ focusTaskPicker();
1388
+ },
1389
+ onKeyDown: (event) => {
1390
+ if ((event.metaKey || event.ctrlKey) && event.key === "Enter") {
1391
+ event.preventDefault();
1392
+ void submit(event.shiftKey ? "again" : "close");
1393
+ }
1394
+ },
1395
+ children: [
1396
+ /* @__PURE__ */ jsxs(DialogHeader, { children: [
1397
+ /* @__PURE__ */ jsx(DialogTitle, { children: isEdit ? t("staff.time_tracking.entryDialog.titleEdit", "Time entry") : t("staff.time_tracking.entryDialog.titleCreate", "New time entry") }),
1398
+ headerHint ? /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: headerHint }) : null
1399
+ ] }),
1400
+ body,
1401
+ /* @__PURE__ */ jsxs(DialogFooter, { children: [
1402
+ /* @__PURE__ */ jsxs("span", { className: "mr-auto hidden items-center gap-1 whitespace-nowrap text-xs text-muted-foreground sm:flex", children: [
1403
+ /* @__PURE__ */ jsx(Kbd, { children: "\u2318" }),
1404
+ /* @__PURE__ */ jsx(Kbd, { children: "\u21B5" }),
1405
+ t("staff.time_tracking.entryDialog.shortcutSave", "save"),
1406
+ locked ? null : /* @__PURE__ */ jsxs(Fragment, { children: [
1407
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\xB7" }),
1408
+ /* @__PURE__ */ jsx(Kbd, { children: "\u2318" }),
1409
+ /* @__PURE__ */ jsx(Kbd, { children: "\u21E7" }),
1410
+ /* @__PURE__ */ jsx(Kbd, { children: "\u21B5" }),
1411
+ t("staff.time_tracking.entryDialog.shortcutSaveAgain", "save and add another")
1412
+ ] }),
1413
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\xB7" }),
1414
+ /* @__PURE__ */ jsx(Kbd, { children: "esc" }),
1415
+ t("staff.time_tracking.entryDialog.shortcutCancel", "cancel")
1416
+ ] }),
1417
+ /* @__PURE__ */ jsx(
1418
+ InjectionSpot,
1419
+ {
1420
+ spotId: extensionSpotChildId(extensionPoints.hosts.timeEntryForm.spotId, "footer"),
1421
+ context: entryInjectionContext,
1422
+ data: entryFormValues
1423
+ }
1424
+ ),
1425
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", onClick: () => {
1426
+ void requestClose();
1427
+ }, children: locked ? t("staff.time_tracking.entryDialog.close", "Close") : t("staff.time_tracking.entryDialog.cancel", "Cancel") }),
1428
+ locked ? null : /* @__PURE__ */ jsxs(Fragment, { children: [
1429
+ /* @__PURE__ */ jsxs(
1430
+ Button,
1431
+ {
1432
+ type: "button",
1433
+ variant: "outline",
1434
+ disabled: !canSubmit,
1435
+ onClick: () => void submit("again"),
1436
+ "data-testid": "entry-dialog-save-again",
1437
+ children: [
1438
+ savingMode === "again" ? /* @__PURE__ */ jsx(Spinner, { className: "size-4" }) : null,
1439
+ t("staff.time_tracking.entryDialog.saveAndNew", "Save and add another")
1440
+ ]
1441
+ }
1442
+ ),
1443
+ /* @__PURE__ */ jsxs(
1444
+ Button,
1445
+ {
1446
+ type: "button",
1447
+ disabled: !canSubmit,
1448
+ onClick: () => void submit("close"),
1449
+ "data-testid": "entry-dialog-save",
1450
+ children: [
1451
+ savingMode === "close" ? /* @__PURE__ */ jsx(Spinner, { className: "size-4" }) : null,
1452
+ t("staff.time_tracking.entryDialog.save", "Save")
1453
+ ]
1454
+ }
1455
+ )
1456
+ ] })
1457
+ ] }),
1458
+ ConfirmDialogElement
1459
+ ]
1460
+ }
1461
+ )
1462
+ }
1463
+ );
1464
+ }
1465
+ const timeEntryDialogPropsSchema = z.object({
1466
+ open: z.boolean(),
1467
+ onOpenChange: callbackProp(),
1468
+ entryId: z.string().nullable().optional(),
1469
+ defaults: opaqueProp().optional(),
1470
+ headerHint: z.string().nullable().optional(),
1471
+ onSaved: optionalCallbackProp(),
1472
+ onShowEntry: optionalCallbackProp()
1473
+ });
1474
+ registerComponent({
1475
+ id: extensionPoints.hosts.timeEntryDialogComponent.componentId,
1476
+ component: DefaultTimeEntryDialog,
1477
+ metadata: {
1478
+ module: "staff",
1479
+ description: "Create/edit dialog for a single time entry.",
1480
+ propsSchema: timeEntryDialogPropsSchema
1481
+ }
1482
+ });
1483
+ function TimeEntryDialog(props) {
1484
+ const Resolved = useRegisteredComponent(
1485
+ extensionPoints.hosts.timeEntryDialogComponent.componentId,
1486
+ DefaultTimeEntryDialog
1487
+ );
1488
+ return /* @__PURE__ */ jsx(Resolved, { ...props });
1489
+ }
1490
+ var TimeEntryDialog_default = TimeEntryDialog;
1491
+ export {
1492
+ ENTRIES_LIST_PATH,
1493
+ RATES_FEATURE,
1494
+ REPORTS_PATH,
1495
+ TIME_ENTRY_DIALOG_MUTATION_CONTEXT_ID,
1496
+ TimeEntryDialog,
1497
+ TimeEntryDialog_default as default
1498
+ };
1499
+ //# sourceMappingURL=TimeEntryDialog.js.map