@hed-hog/operations 0.0.330 → 0.0.332

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 (320) hide show
  1. package/README.md +5 -5
  2. package/dist/controllers/operations-collaborators.controller.d.ts +58 -213
  3. package/dist/controllers/operations-collaborators.controller.d.ts.map +1 -1
  4. package/dist/controllers/operations-collaborators.controller.js +100 -0
  5. package/dist/controllers/operations-collaborators.controller.js.map +1 -1
  6. package/dist/controllers/operations-contracts.controller.d.ts +6 -6
  7. package/dist/controllers/operations-projects.controller.d.ts +25 -0
  8. package/dist/controllers/operations-projects.controller.d.ts.map +1 -1
  9. package/dist/controllers/operations-projects.controller.js +48 -0
  10. package/dist/controllers/operations-projects.controller.js.map +1 -1
  11. package/dist/controllers/operations-reports.controller.d.ts +1 -1
  12. package/dist/controllers/operations-tasks.controller.d.ts +34 -9
  13. package/dist/controllers/operations-tasks.controller.d.ts.map +1 -1
  14. package/dist/controllers/operations-tasks.controller.js +43 -32
  15. package/dist/controllers/operations-tasks.controller.js.map +1 -1
  16. package/dist/controllers/operations-timesheets.controller.d.ts +9 -9
  17. package/dist/dashboard/components/DashboardLayout.d.ts +30 -0
  18. package/dist/dashboard/components/DashboardLayout.d.ts.map +1 -0
  19. package/dist/dashboard/components/DashboardLayout.js +87 -0
  20. package/dist/dashboard/components/DashboardLayout.js.map +1 -0
  21. package/dist/dashboard/components/widget-registry.d.ts +23 -0
  22. package/dist/dashboard/components/widget-registry.d.ts.map +1 -0
  23. package/dist/dashboard/components/widget-registry.js +245 -0
  24. package/dist/dashboard/components/widget-registry.js.map +1 -0
  25. package/dist/dashboard/hooks/useDashboardData.d.ts +20 -0
  26. package/dist/dashboard/hooks/useDashboardData.d.ts.map +1 -0
  27. package/dist/dashboard/hooks/useDashboardData.js +24 -0
  28. package/dist/dashboard/hooks/useDashboardData.js.map +1 -0
  29. package/dist/dashboard/types/widgets.types.d.ts +233 -0
  30. package/dist/dashboard/types/widgets.types.d.ts.map +1 -0
  31. package/dist/dashboard/types/widgets.types.js +6 -0
  32. package/dist/dashboard/types/widgets.types.js.map +1 -0
  33. package/dist/dashboard/widgets/CapacityDistribution.d.ts +23 -0
  34. package/dist/dashboard/widgets/CapacityDistribution.d.ts.map +1 -0
  35. package/dist/dashboard/widgets/CapacityDistribution.js +11 -0
  36. package/dist/dashboard/widgets/CapacityDistribution.js.map +1 -0
  37. package/dist/dashboard/widgets/EffortByProject.d.ts +22 -0
  38. package/dist/dashboard/widgets/EffortByProject.d.ts.map +1 -0
  39. package/dist/dashboard/widgets/EffortByProject.js +11 -0
  40. package/dist/dashboard/widgets/EffortByProject.js.map +1 -0
  41. package/dist/dashboard/widgets/HeadcountByArea.d.ts +24 -0
  42. package/dist/dashboard/widgets/HeadcountByArea.d.ts.map +1 -0
  43. package/dist/dashboard/widgets/HeadcountByArea.js +11 -0
  44. package/dist/dashboard/widgets/HeadcountByArea.js.map +1 -0
  45. package/dist/dashboard/widgets/ManagedProjectsStatus.d.ts +18 -0
  46. package/dist/dashboard/widgets/ManagedProjectsStatus.d.ts.map +1 -0
  47. package/dist/dashboard/widgets/ManagedProjectsStatus.js +12 -0
  48. package/dist/dashboard/widgets/ManagedProjectsStatus.js.map +1 -0
  49. package/dist/dashboard/widgets/MyHoursPeriodKpi.d.ts +22 -0
  50. package/dist/dashboard/widgets/MyHoursPeriodKpi.d.ts.map +1 -0
  51. package/dist/dashboard/widgets/MyHoursPeriodKpi.js +12 -0
  52. package/dist/dashboard/widgets/MyHoursPeriodKpi.js.map +1 -0
  53. package/dist/dashboard/widgets/MyOpenRequestsKpi.d.ts +19 -0
  54. package/dist/dashboard/widgets/MyOpenRequestsKpi.d.ts.map +1 -0
  55. package/dist/dashboard/widgets/MyOpenRequestsKpi.js +17 -0
  56. package/dist/dashboard/widgets/MyOpenRequestsKpi.js.map +1 -0
  57. package/dist/dashboard/widgets/MyPendingRequestsList.d.ts +23 -0
  58. package/dist/dashboard/widgets/MyPendingRequestsList.d.ts.map +1 -0
  59. package/dist/dashboard/widgets/MyPendingRequestsList.js +14 -0
  60. package/dist/dashboard/widgets/MyPendingRequestsList.js.map +1 -0
  61. package/dist/dashboard/widgets/MyProjectAllocationsKpi.d.ts +22 -0
  62. package/dist/dashboard/widgets/MyProjectAllocationsKpi.d.ts.map +1 -0
  63. package/dist/dashboard/widgets/MyProjectAllocationsKpi.js +11 -0
  64. package/dist/dashboard/widgets/MyProjectAllocationsKpi.js.map +1 -0
  65. package/dist/dashboard/widgets/MyQuickActions.d.ts +23 -0
  66. package/dist/dashboard/widgets/MyQuickActions.d.ts.map +1 -0
  67. package/dist/dashboard/widgets/MyQuickActions.js +18 -0
  68. package/dist/dashboard/widgets/MyQuickActions.js.map +1 -0
  69. package/dist/dashboard/widgets/MyRelevantDeadlines.d.ts +23 -0
  70. package/dist/dashboard/widgets/MyRelevantDeadlines.d.ts.map +1 -0
  71. package/dist/dashboard/widgets/MyRelevantDeadlines.js +22 -0
  72. package/dist/dashboard/widgets/MyRelevantDeadlines.js.map +1 -0
  73. package/dist/dashboard/widgets/MyTimesheetStatusKpi.d.ts +17 -0
  74. package/dist/dashboard/widgets/MyTimesheetStatusKpi.d.ts.map +1 -0
  75. package/dist/dashboard/widgets/MyTimesheetStatusKpi.js +11 -0
  76. package/dist/dashboard/widgets/MyTimesheetStatusKpi.js.map +1 -0
  77. package/dist/dashboard/widgets/MyWeeklyJourney.d.ts +21 -0
  78. package/dist/dashboard/widgets/MyWeeklyJourney.d.ts.map +1 -0
  79. package/dist/dashboard/widgets/MyWeeklyJourney.js +19 -0
  80. package/dist/dashboard/widgets/MyWeeklyJourney.js.map +1 -0
  81. package/dist/dashboard/widgets/PortfolioCostsKpi.d.ts +19 -0
  82. package/dist/dashboard/widgets/PortfolioCostsKpi.d.ts.map +1 -0
  83. package/dist/dashboard/widgets/PortfolioCostsKpi.js +12 -0
  84. package/dist/dashboard/widgets/PortfolioCostsKpi.js.map +1 -0
  85. package/dist/dashboard/widgets/PortfolioEffortKpi.d.ts +18 -0
  86. package/dist/dashboard/widgets/PortfolioEffortKpi.d.ts.map +1 -0
  87. package/dist/dashboard/widgets/PortfolioEffortKpi.js +8 -0
  88. package/dist/dashboard/widgets/PortfolioEffortKpi.js.map +1 -0
  89. package/dist/dashboard/widgets/PortfolioProjectsKpi.d.ts +22 -0
  90. package/dist/dashboard/widgets/PortfolioProjectsKpi.d.ts.map +1 -0
  91. package/dist/dashboard/widgets/PortfolioProjectsKpi.js +56 -0
  92. package/dist/dashboard/widgets/PortfolioProjectsKpi.js.map +1 -0
  93. package/dist/dashboard/widgets/PortfolioRiskKpi.d.ts +19 -0
  94. package/dist/dashboard/widgets/PortfolioRiskKpi.d.ts.map +1 -0
  95. package/dist/dashboard/widgets/PortfolioRiskKpi.js +11 -0
  96. package/dist/dashboard/widgets/PortfolioRiskKpi.js.map +1 -0
  97. package/dist/dashboard/widgets/ProjectStatusOverview.d.ts +19 -0
  98. package/dist/dashboard/widgets/ProjectStatusOverview.d.ts.map +1 -0
  99. package/dist/dashboard/widgets/ProjectStatusOverview.js +18 -0
  100. package/dist/dashboard/widgets/ProjectStatusOverview.js.map +1 -0
  101. package/dist/dashboard/widgets/StrategicDeadlines.d.ts +24 -0
  102. package/dist/dashboard/widgets/StrategicDeadlines.d.ts.map +1 -0
  103. package/dist/dashboard/widgets/StrategicDeadlines.js +22 -0
  104. package/dist/dashboard/widgets/StrategicDeadlines.js.map +1 -0
  105. package/dist/dashboard/widgets/TeamApprovalQueue.d.ts +24 -0
  106. package/dist/dashboard/widgets/TeamApprovalQueue.d.ts.map +1 -0
  107. package/dist/dashboard/widgets/TeamApprovalQueue.js +12 -0
  108. package/dist/dashboard/widgets/TeamApprovalQueue.js.map +1 -0
  109. package/dist/dashboard/widgets/TeamCapacityKpi.d.ts +18 -0
  110. package/dist/dashboard/widgets/TeamCapacityKpi.d.ts.map +1 -0
  111. package/dist/dashboard/widgets/TeamCapacityKpi.js +19 -0
  112. package/dist/dashboard/widgets/TeamCapacityKpi.js.map +1 -0
  113. package/dist/dashboard/widgets/TeamHeadcountKpi.d.ts +22 -0
  114. package/dist/dashboard/widgets/TeamHeadcountKpi.d.ts.map +1 -0
  115. package/dist/dashboard/widgets/TeamHeadcountKpi.js +56 -0
  116. package/dist/dashboard/widgets/TeamHeadcountKpi.js.map +1 -0
  117. package/dist/dashboard/widgets/TeamHoursKpi.d.ts +19 -0
  118. package/dist/dashboard/widgets/TeamHoursKpi.d.ts.map +1 -0
  119. package/dist/dashboard/widgets/TeamHoursKpi.js +13 -0
  120. package/dist/dashboard/widgets/TeamHoursKpi.js.map +1 -0
  121. package/dist/dashboard/widgets/TeamPendingApprovalsKpi.d.ts +20 -0
  122. package/dist/dashboard/widgets/TeamPendingApprovalsKpi.d.ts.map +1 -0
  123. package/dist/dashboard/widgets/TeamPendingApprovalsKpi.js +11 -0
  124. package/dist/dashboard/widgets/TeamPendingApprovalsKpi.js.map +1 -0
  125. package/dist/dashboard/widgets/TeamUtilizationOverview.d.ts +18 -0
  126. package/dist/dashboard/widgets/TeamUtilizationOverview.d.ts.map +1 -0
  127. package/dist/dashboard/widgets/TeamUtilizationOverview.js +17 -0
  128. package/dist/dashboard/widgets/TeamUtilizationOverview.js.map +1 -0
  129. package/dist/dashboard/widgets/TeamWorkloadAlerts.d.ts +24 -0
  130. package/dist/dashboard/widgets/TeamWorkloadAlerts.d.ts.map +1 -0
  131. package/dist/dashboard/widgets/TeamWorkloadAlerts.js +19 -0
  132. package/dist/dashboard/widgets/TeamWorkloadAlerts.js.map +1 -0
  133. package/dist/dashboard/widgets/index.d.ts +24 -0
  134. package/dist/dashboard/widgets/index.d.ts.map +1 -0
  135. package/dist/dashboard/widgets/index.js +54 -0
  136. package/dist/dashboard/widgets/index.js.map +1 -0
  137. package/dist/dto/create-collaborator-invoice.dto.d.ts +11 -0
  138. package/dist/dto/create-collaborator-invoice.dto.d.ts.map +1 -0
  139. package/dist/dto/create-collaborator-invoice.dto.js +55 -0
  140. package/dist/dto/create-collaborator-invoice.dto.js.map +1 -0
  141. package/dist/dto/create-collaborator-payment.dto.d.ts +10 -0
  142. package/dist/dto/create-collaborator-payment.dto.d.ts.map +1 -0
  143. package/dist/dto/create-collaborator-payment.dto.js +50 -0
  144. package/dist/dto/create-collaborator-payment.dto.js.map +1 -0
  145. package/dist/dto/create-collaborator.dto.d.ts +0 -1
  146. package/dist/dto/create-collaborator.dto.d.ts.map +1 -1
  147. package/dist/dto/create-collaborator.dto.js +0 -6
  148. package/dist/dto/create-collaborator.dto.js.map +1 -1
  149. package/dist/dto/list-collaborator-invoice.dto.d.ts +4 -0
  150. package/dist/dto/list-collaborator-invoice.dto.d.ts.map +1 -0
  151. package/dist/dto/list-collaborator-invoice.dto.js +8 -0
  152. package/dist/dto/list-collaborator-invoice.dto.js.map +1 -0
  153. package/dist/dto/list-collaborator-payment.dto.d.ts +4 -0
  154. package/dist/dto/list-collaborator-payment.dto.d.ts.map +1 -0
  155. package/dist/dto/list-collaborator-payment.dto.js +8 -0
  156. package/dist/dto/list-collaborator-payment.dto.js.map +1 -0
  157. package/dist/dto/update-collaborator-invoice.dto.d.ts +6 -0
  158. package/dist/dto/update-collaborator-invoice.dto.d.ts.map +1 -0
  159. package/dist/dto/update-collaborator-invoice.dto.js +9 -0
  160. package/dist/dto/update-collaborator-invoice.dto.js.map +1 -0
  161. package/dist/dto/update-collaborator-payment.dto.d.ts +6 -0
  162. package/dist/dto/update-collaborator-payment.dto.d.ts.map +1 -0
  163. package/dist/dto/update-collaborator-payment.dto.js +9 -0
  164. package/dist/dto/update-collaborator-payment.dto.js.map +1 -0
  165. package/dist/index.d.ts +2 -0
  166. package/dist/index.d.ts.map +1 -1
  167. package/dist/index.js +2 -0
  168. package/dist/index.js.map +1 -1
  169. package/dist/operations.controller.d.ts +42 -0
  170. package/dist/operations.controller.d.ts.map +1 -1
  171. package/dist/operations.service.d.ts +258 -268
  172. package/dist/operations.service.d.ts.map +1 -1
  173. package/dist/operations.service.js +2381 -1341
  174. package/dist/operations.service.js.map +1 -1
  175. package/dist/operations.service.spec.js +345 -174
  176. package/dist/operations.service.spec.js.map +1 -1
  177. package/hedhog/data/dashboard_component.yaml +66 -0
  178. package/hedhog/data/dashboard_item.yaml +25 -25
  179. package/hedhog/data/menu.yaml +27 -8
  180. package/hedhog/data/route.yaml +133 -0
  181. package/hedhog/frontend/app/_components/collaborator-form-screen.tsx.ejs +78 -102
  182. package/hedhog/frontend/app/_components/collaborator-invoices-tab.tsx.ejs +443 -0
  183. package/hedhog/frontend/app/_components/collaborator-payment-history-tab.tsx.ejs +429 -0
  184. package/hedhog/frontend/app/_components/collaborator-picker.tsx.ejs +158 -0
  185. package/hedhog/frontend/app/_components/my-project-summary-screen.tsx.ejs +247 -50
  186. package/hedhog/frontend/app/_components/project-assignments-tab.tsx.ejs +643 -450
  187. package/hedhog/frontend/app/_components/project-details-screen.tsx.ejs +992 -431
  188. package/hedhog/frontend/app/_components/project-file-attachments.tsx.ejs +371 -0
  189. package/hedhog/frontend/app/_components/project-form-screen.tsx.ejs +558 -386
  190. package/hedhog/frontend/app/_components/task-detail-sheet.tsx.ejs +383 -157
  191. package/hedhog/frontend/app/_components/task-file-attachments.tsx.ejs +4 -1
  192. package/hedhog/frontend/app/_components/task-form-fields.tsx.ejs +406 -0
  193. package/hedhog/frontend/app/_components/task-form-sheet.tsx.ejs +629 -784
  194. package/hedhog/frontend/app/_components/task-info-display.tsx.ejs +137 -0
  195. package/hedhog/frontend/app/_components/timesheet-entry-create-sheet.tsx.ejs +306 -0
  196. package/hedhog/frontend/app/_lib/api.ts.ejs +155 -0
  197. package/hedhog/frontend/app/_lib/types.ts.ejs +62 -0
  198. package/hedhog/frontend/app/_lib/utils/format.ts.ejs +0 -2
  199. package/hedhog/frontend/app/_lib/utils/task-ui.ts.ejs +61 -0
  200. package/hedhog/frontend/app/approvals/page.tsx.ejs +6 -1
  201. package/hedhog/frontend/app/collaborator-types/page.tsx.ejs +6 -1
  202. package/hedhog/frontend/app/collaborators/page.tsx.ejs +59 -8
  203. package/hedhog/frontend/app/contracts/page.tsx.ejs +29 -8
  204. package/hedhog/frontend/app/dashboard/widgets/CapacityDistribution.tsx.ejs +84 -0
  205. package/hedhog/frontend/app/dashboard/widgets/EffortByProject.tsx.ejs +85 -0
  206. package/hedhog/frontend/app/dashboard/widgets/HeadcountByArea.tsx.ejs +101 -0
  207. package/hedhog/frontend/app/dashboard/widgets/ManagedProjectsStatus.tsx.ejs +113 -0
  208. package/hedhog/frontend/app/dashboard/widgets/MyHoursPeriodKpi.tsx.ejs +87 -0
  209. package/hedhog/frontend/app/dashboard/widgets/MyOpenRequestsKpi.tsx.ejs +97 -0
  210. package/hedhog/frontend/app/dashboard/widgets/MyPendingRequestsList.tsx.ejs +99 -0
  211. package/hedhog/frontend/app/dashboard/widgets/MyProjectAllocationsKpi.tsx.ejs +78 -0
  212. package/hedhog/frontend/app/dashboard/widgets/MyQuickActions.tsx.ejs +130 -0
  213. package/hedhog/frontend/app/dashboard/widgets/MyRelevantDeadlines.tsx.ejs +144 -0
  214. package/hedhog/frontend/app/dashboard/widgets/MyTimesheetStatusKpi.tsx.ejs +78 -0
  215. package/hedhog/frontend/app/dashboard/widgets/MyWeeklyJourney.tsx.ejs +99 -0
  216. package/hedhog/frontend/app/dashboard/widgets/PortfolioCostsKpi.tsx.ejs +112 -0
  217. package/hedhog/frontend/app/dashboard/widgets/PortfolioEffortKpi.tsx.ejs +93 -0
  218. package/hedhog/frontend/app/dashboard/widgets/PortfolioProjectsKpi.tsx.ejs +96 -0
  219. package/hedhog/frontend/app/dashboard/widgets/PortfolioRiskKpi.tsx.ejs +115 -0
  220. package/hedhog/frontend/app/dashboard/widgets/ProjectStatusOverview.tsx.ejs +120 -0
  221. package/hedhog/frontend/app/dashboard/widgets/StrategicDeadlines.tsx.ejs +146 -0
  222. package/hedhog/frontend/app/dashboard/widgets/TeamApprovalQueue.tsx.ejs +108 -0
  223. package/hedhog/frontend/app/dashboard/widgets/TeamCapacityKpi.tsx.ejs +97 -0
  224. package/hedhog/frontend/app/dashboard/widgets/TeamHeadcountKpi.tsx.ejs +100 -0
  225. package/hedhog/frontend/app/dashboard/widgets/TeamHoursKpi.tsx.ejs +104 -0
  226. package/hedhog/frontend/app/dashboard/widgets/TeamPendingApprovalsKpi.tsx.ejs +110 -0
  227. package/hedhog/frontend/app/dashboard/widgets/TeamUtilizationOverview.tsx.ejs +115 -0
  228. package/hedhog/frontend/app/dashboard/widgets/TeamWorkloadAlerts.tsx.ejs +117 -0
  229. package/hedhog/frontend/app/dashboard/widgets/index.ts.ejs +26 -0
  230. package/hedhog/frontend/app/departments/page.tsx.ejs +6 -1
  231. package/hedhog/frontend/app/my-projects/page.tsx.ejs +30 -12
  232. package/hedhog/frontend/app/my-tasks/page.tsx.ejs +286 -125
  233. package/hedhog/frontend/app/project-cost-categories/page.tsx.ejs +58 -52
  234. package/hedhog/frontend/app/project-cost-types/page.tsx.ejs +58 -51
  235. package/hedhog/frontend/app/projects/page.tsx.ejs +415 -33
  236. package/hedhog/frontend/app/schedule-adjustments/page.tsx.ejs +6 -1
  237. package/hedhog/frontend/app/tasks-gantt/page.tsx.ejs +953 -0
  238. package/hedhog/frontend/app/time-off/page.tsx.ejs +6 -1
  239. package/hedhog/frontend/app/timesheets/page.tsx.ejs +10 -4
  240. package/hedhog/frontend/messages/en.json +332 -46
  241. package/hedhog/frontend/messages/operations/en.json +61 -52
  242. package/hedhog/frontend/messages/operations/pt.json +59 -43
  243. package/hedhog/frontend/messages/pt.json +332 -46
  244. package/hedhog/frontend/widgets/capacity-distribution.tsx.ejs +17 -0
  245. package/hedhog/frontend/widgets/effort-by-project.tsx.ejs +17 -0
  246. package/hedhog/frontend/widgets/headcount-by-area.tsx.ejs +17 -0
  247. package/hedhog/frontend/widgets/index.ts.ejs +25 -0
  248. package/hedhog/frontend/widgets/managed-projects-status.tsx.ejs +17 -0
  249. package/hedhog/frontend/widgets/my-hours-period-kpi.tsx.ejs +17 -0
  250. package/hedhog/frontend/widgets/my-open-requests-kpi.tsx.ejs +17 -0
  251. package/hedhog/frontend/widgets/my-pending-requests-list.tsx.ejs +17 -0
  252. package/hedhog/frontend/widgets/my-project-allocations-kpi.tsx.ejs +17 -0
  253. package/hedhog/frontend/widgets/my-quick-actions.tsx.ejs +17 -0
  254. package/hedhog/frontend/widgets/my-relevant-deadlines.tsx.ejs +17 -0
  255. package/hedhog/frontend/widgets/my-timesheet-status-kpi.tsx.ejs +17 -0
  256. package/hedhog/frontend/widgets/my-weekly-journey.tsx.ejs +17 -0
  257. package/hedhog/frontend/widgets/portfolio-costs-kpi.tsx.ejs +17 -0
  258. package/hedhog/frontend/widgets/portfolio-effort-kpi.tsx.ejs +17 -0
  259. package/hedhog/frontend/widgets/portfolio-projects-kpi.tsx.ejs +17 -0
  260. package/hedhog/frontend/widgets/portfolio-risk-kpi.tsx.ejs +17 -0
  261. package/hedhog/frontend/widgets/project-status-overview.tsx.ejs +17 -0
  262. package/hedhog/frontend/widgets/shared-operations-widget.tsx.ejs +170 -0
  263. package/hedhog/frontend/widgets/strategic-deadlines.tsx.ejs +17 -0
  264. package/hedhog/frontend/widgets/team-approval-queue.tsx.ejs +17 -0
  265. package/hedhog/frontend/widgets/team-capacity-kpi.tsx.ejs +17 -0
  266. package/hedhog/frontend/widgets/team-headcount-kpi.tsx.ejs +17 -0
  267. package/hedhog/frontend/widgets/team-hours-kpi.tsx.ejs +17 -0
  268. package/hedhog/frontend/widgets/team-pending-approvals-kpi.tsx.ejs +17 -0
  269. package/hedhog/frontend/widgets/team-utilization-overview.tsx.ejs +17 -0
  270. package/hedhog/frontend/widgets/team-workload-alerts.tsx.ejs +17 -0
  271. package/hedhog/table/operations_collaborator.yaml +8 -13
  272. package/hedhog/table/operations_collaborator_invoice.yaml +35 -0
  273. package/hedhog/table/operations_collaborator_payment.yaml +32 -0
  274. package/hedhog/table/operations_project.yaml +1 -1
  275. package/hedhog/table/operations_project_file.yaml +23 -0
  276. package/hedhog/table/operations_task.yaml +76 -69
  277. package/hedhog/table/operations_task_activity.yaml +51 -0
  278. package/package.json +6 -5
  279. package/src/controllers/operations-collaborators.controller.ts +117 -8
  280. package/src/controllers/operations-projects.controller.ts +41 -8
  281. package/src/controllers/operations-tasks.controller.ts +156 -166
  282. package/src/dashboard/README.md +214 -0
  283. package/src/dashboard/components/DashboardLayout.tsx +131 -0
  284. package/src/dashboard/components/widget-registry.ts +255 -0
  285. package/src/dashboard/hooks/useDashboardData.ts +29 -0
  286. package/src/dashboard/types/widgets.types.ts +237 -0
  287. package/src/dashboard/widgets/CapacityDistribution.tsx +56 -0
  288. package/src/dashboard/widgets/EffortByProject.tsx +51 -0
  289. package/src/dashboard/widgets/HeadcountByArea.tsx +57 -0
  290. package/src/dashboard/widgets/ManagedProjectsStatus.tsx +53 -0
  291. package/src/dashboard/widgets/MyHoursPeriodKpi.tsx +87 -0
  292. package/src/dashboard/widgets/MyOpenRequestsKpi.tsx +51 -0
  293. package/src/dashboard/widgets/MyPendingRequestsList.tsx +63 -0
  294. package/src/dashboard/widgets/MyProjectAllocationsKpi.tsx +57 -0
  295. package/src/dashboard/widgets/MyQuickActions.tsx +62 -0
  296. package/src/dashboard/widgets/MyRelevantDeadlines.tsx +84 -0
  297. package/src/dashboard/widgets/MyTimesheetStatusKpi.tsx +65 -0
  298. package/src/dashboard/widgets/MyWeeklyJourney.tsx +57 -0
  299. package/src/dashboard/widgets/PortfolioCostsKpi.tsx +48 -0
  300. package/src/dashboard/widgets/PortfolioEffortKpi.tsx +41 -0
  301. package/src/dashboard/widgets/PortfolioRiskKpi.tsx +50 -0
  302. package/src/dashboard/widgets/ProjectStatusOverview.tsx +52 -0
  303. package/src/dashboard/widgets/StrategicDeadlines.tsx +93 -0
  304. package/src/dashboard/widgets/TeamApprovalQueue.tsx +70 -0
  305. package/src/dashboard/widgets/TeamCapacityKpi.tsx +50 -0
  306. package/src/dashboard/widgets/TeamHoursKpi.tsx +51 -0
  307. package/src/dashboard/widgets/TeamPendingApprovalsKpi.tsx +53 -0
  308. package/src/dashboard/widgets/TeamUtilizationOverview.tsx +62 -0
  309. package/src/dashboard/widgets/TeamWorkloadAlerts.tsx +81 -0
  310. package/src/dashboard/widgets/index.ts +26 -0
  311. package/src/dto/create-collaborator-invoice.dto.ts +39 -0
  312. package/src/dto/create-collaborator-payment.dto.ts +35 -0
  313. package/src/dto/create-collaborator.dto.ts +4 -11
  314. package/src/dto/list-collaborator-invoice.dto.ts +3 -0
  315. package/src/dto/list-collaborator-payment.dto.ts +3 -0
  316. package/src/dto/update-collaborator-invoice.dto.ts +6 -0
  317. package/src/dto/update-collaborator-payment.dto.ts +6 -0
  318. package/src/index.ts +3 -0
  319. package/src/operations.service.spec.ts +988 -764
  320. package/src/operations.service.ts +4689 -2624
@@ -12,6 +12,7 @@ import { Card, CardContent } from '@/components/ui/card';
12
12
  import {
13
13
  Dialog,
14
14
  DialogContent,
15
+ DialogDescription,
15
16
  DialogFooter,
16
17
  DialogHeader,
17
18
  DialogTitle,
@@ -43,15 +44,18 @@ import {
43
44
  } from '@/components/ui/table';
44
45
  import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
45
46
  import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group';
47
+ import { usePersistedPageSize } from '@/hooks/use-persisted-page-size';
46
48
  import {
47
49
  closestCenter,
48
50
  DndContext,
51
+ DragOverlay,
49
52
  PointerSensor,
50
53
  useDraggable,
51
54
  useDroppable,
52
55
  useSensor,
53
56
  useSensors,
54
57
  type DragEndEvent,
58
+ type DragStartEvent,
55
59
  type UniqueIdentifier,
56
60
  } from '@dnd-kit/core';
57
61
  import { CSS } from '@dnd-kit/utilities';
@@ -73,6 +77,7 @@ import {
73
77
  Pencil,
74
78
  PlayCircle,
75
79
  Plus,
80
+ Send,
76
81
  Timer,
77
82
  Trash2,
78
83
  } from 'lucide-react';
@@ -87,14 +92,17 @@ import {
87
92
  type TaskDetailSheetData,
88
93
  } from '../_components/task-detail-sheet';
89
94
  import { TaskFileAttachments } from '../_components/task-file-attachments';
95
+ import { TimesheetEntryCreateSheet } from '../_components/timesheet-entry-create-sheet';
90
96
  import { fetchOperations, mutateOperations } from '../_lib/api';
91
97
  import { useMentionItems } from '../_lib/hooks/use-mention-items';
92
98
  import type {
99
+ OperationsCollaborator,
93
100
  OperationsProjectOption,
94
101
  OperationsTaskOption,
95
102
  PaginatedResponse,
96
103
  } from '../_lib/types';
97
104
  import { formatDate, getStatusBadgeClass } from '../_lib/utils/format';
105
+ import { getTaskDescriptionPreview } from '../_lib/utils/task-ui';
98
106
 
99
107
  type TaskViewMode = 'table' | 'cards' | 'board';
100
108
 
@@ -114,11 +122,20 @@ type TaskFormState = {
114
122
  description: string;
115
123
  priority: 'low' | 'medium' | 'high';
116
124
  status: BoardColumnId;
125
+ assigneeCollaboratorId: string;
117
126
  dueDate: string;
118
127
  estimateHours: string;
119
128
  tags: string;
120
129
  };
121
130
 
131
+ type TimesheetEntryPrefill = {
132
+ projectId: number;
133
+ projectAssignmentId?: number | null;
134
+ projectLabel: string;
135
+ taskId: number;
136
+ taskLabel: string;
137
+ };
138
+
122
139
  const KANBAN_COLUMNS: Array<{ id: BoardColumnId; label: string }> = [
123
140
  { id: 'todo', label: 'Backlog' },
124
141
  { id: 'doing', label: 'Em execução' },
@@ -132,6 +149,7 @@ const EMPTY_TASK_FORM: TaskFormState = {
132
149
  description: '',
133
150
  priority: 'medium',
134
151
  status: 'todo',
152
+ assigneeCollaboratorId: 'none',
135
153
  dueDate: '',
136
154
  estimateHours: '',
137
155
  tags: '',
@@ -160,6 +178,14 @@ function parseColumnId(
160
178
  return KANBAN_COLUMNS.some((c) => c.id === id) ? (id as BoardColumnId) : null;
161
179
  }
162
180
 
181
+ function normalizeDateInputValue(value?: string | null) {
182
+ if (!value) return '';
183
+ const match = String(value)
184
+ .trim()
185
+ .match(/^\d{4}-\d{2}-\d{2}/);
186
+ return match?.[0] ?? '';
187
+ }
188
+
163
189
  function splitTasksByColumn(tasks: OperationsTaskOption[]): BoardColumns {
164
190
  return {
165
191
  todo: tasks.filter((t) => t.status === 'todo'),
@@ -169,6 +195,30 @@ function splitTasksByColumn(tasks: OperationsTaskOption[]): BoardColumns {
169
195
  };
170
196
  }
171
197
 
198
+ function getTaskColumnId(status?: string | null): BoardColumnId {
199
+ return KANBAN_COLUMNS.some((column) => column.id === status)
200
+ ? (status as BoardColumnId)
201
+ : 'todo';
202
+ }
203
+
204
+ function replaceTaskInColumns(
205
+ columns: BoardColumns,
206
+ task: OperationsTaskOption
207
+ ): BoardColumns {
208
+ const targetColumn = getTaskColumnId(task.status);
209
+ const nextColumns = {
210
+ todo: columns.todo.filter((item) => item.id !== task.id),
211
+ doing: columns.doing.filter((item) => item.id !== task.id),
212
+ review: columns.review.filter((item) => item.id !== task.id),
213
+ done: columns.done.filter((item) => item.id !== task.id),
214
+ };
215
+
216
+ return {
217
+ ...nextColumns,
218
+ [targetColumn]: [task, ...nextColumns[targetColumn]],
219
+ };
220
+ }
221
+
172
222
  function getTaskPriorityLabel(value?: string | null) {
173
223
  const labels: Record<string, string> = {
174
224
  low: 'Baixa',
@@ -242,26 +292,6 @@ function getUserPhotoUrl(photoId?: number | null) {
242
292
  : null;
243
293
  }
244
294
 
245
- function normalizeDateInputValue(value?: string | null) {
246
- if (!value) {
247
- return '';
248
- }
249
-
250
- const normalizedValue = String(value).trim();
251
- const directMatch = normalizedValue.match(/^\d{4}-\d{2}-\d{2}/);
252
-
253
- if (directMatch?.[0]) {
254
- return directMatch[0];
255
- }
256
-
257
- const parsedDate = new Date(normalizedValue);
258
- if (Number.isNaN(parsedDate.getTime())) {
259
- return '';
260
- }
261
-
262
- return parsedDate.toISOString().slice(0, 10);
263
- }
264
-
265
295
  function DraggableTaskCard({
266
296
  task,
267
297
  children,
@@ -276,10 +306,12 @@ function DraggableTaskCard({
276
306
  return (
277
307
  <div
278
308
  ref={setNodeRef}
279
- style={{ transform: CSS.Translate.toString(transform) }}
309
+ style={{
310
+ transform: CSS.Translate.toString(transform),
311
+ ...(isDragging ? { opacity: 0, pointerEvents: 'none' } : {}),
312
+ }}
280
313
  {...listeners}
281
314
  {...attributes}
282
- className={isDragging ? 'z-20' : undefined}
283
315
  >
284
316
  {children(isDragging)}
285
317
  </div>
@@ -309,7 +341,11 @@ export default function OperationsMyTasksPage() {
309
341
  const [search, setSearch] = useState('');
310
342
  const [statusFilter, setStatusFilter] = useState('all');
311
343
  const [page, setPage] = useState(1);
312
- const [pageSize, setPageSize] = useState(20);
344
+ const [pageSize, setPageSize] = usePersistedPageSize({
345
+ storageKey: 'pagination:operations-my-tasks:pageSize',
346
+ defaultValue: 20,
347
+ allowedValues: [10, 20, 50],
348
+ });
313
349
  const [taskCollection, setTaskCollection] =
314
350
  useState<TaskCollection>('active');
315
351
  const [viewMode, setViewMode] = useState<TaskViewMode>(() => {
@@ -332,10 +368,16 @@ export default function OperationsMyTasksPage() {
332
368
  useState<OperationsTaskOption | null>(null);
333
369
  const [taskFormOpen, setTaskFormOpen] = useState(false);
334
370
  const [editingTaskId, setEditingTaskId] = useState<number | null>(null);
335
- const [taskFormLoading, setTaskFormLoading] = useState(false);
336
371
  const [archivingTaskId, setArchivingTaskId] = useState<number | null>(null);
372
+ const [taskFormLoading, setTaskFormLoading] = useState(false);
337
373
  const [taskFormData, setTaskFormData] =
338
374
  useState<TaskFormState>(EMPTY_TASK_FORM);
375
+ const [activeDragTask, setActiveDragTask] =
376
+ useState<OperationsTaskOption | null>(null);
377
+ const [isTimesheetEntrySheetOpen, setIsTimesheetEntrySheetOpen] =
378
+ useState(false);
379
+ const [timesheetPrefill, setTimesheetPrefill] =
380
+ useState<TimesheetEntryPrefill | null>(null);
339
381
  const [inlineCreateColumn, setInlineCreateColumn] =
340
382
  useState<BoardColumnId | null>(null);
341
383
  const [inlineCreateName, setInlineCreateName] = useState('');
@@ -396,16 +438,57 @@ export default function OperationsMyTasksPage() {
396
438
  ),
397
439
  placeholderData: (previous) => previous,
398
440
  });
441
+ const { data: collaboratorsResponse } = useQuery<
442
+ PaginatedResponse<OperationsCollaborator>
443
+ >({
444
+ queryKey: ['operations-my-task-collaborator-options', currentLocaleCode],
445
+ queryFn: () =>
446
+ fetchOperations<PaginatedResponse<OperationsCollaborator>>(
447
+ request,
448
+ '/operations/collaborators?pageSize=200&sortField=displayName&sortOrder=asc'
449
+ ),
450
+ placeholderData: (previous) => previous,
451
+ });
399
452
 
400
453
  const tasks = tasksResponse?.data ?? [];
401
454
  const boardApiTasks = boardTasksResponse?.data ?? [];
402
455
  const projectOptions = projectOptionsResponse?.data ?? [];
456
+ const assigneeOptions = useMemo(() => {
457
+ const seen = new Set<number>();
458
+ return (collaboratorsResponse?.data ?? [])
459
+ .filter((collaborator) => {
460
+ if (!collaborator.id || seen.has(collaborator.id)) {
461
+ return false;
462
+ }
463
+ seen.add(collaborator.id);
464
+ return true;
465
+ })
466
+ .map((collaborator) => ({
467
+ id: String(collaborator.id),
468
+ label:
469
+ collaborator.displayName ||
470
+ collaborator.personName ||
471
+ collaborator.code,
472
+ }));
473
+ }, [collaboratorsResponse]);
403
474
 
404
475
  const boardColumns: BoardColumns = useMemo(() => {
405
476
  if (boardOverride) return boardOverride.columns;
406
477
  return splitTasksByColumn(boardApiTasks);
407
478
  }, [boardOverride, boardApiTasks]);
408
479
 
480
+ const editingTask = useMemo(() => {
481
+ if (!editingTaskId) return null;
482
+ return (
483
+ Object.values(boardColumns)
484
+ .flat()
485
+ .find((task) => task.id === editingTaskId) ??
486
+ boardApiTasks.find((task) => task.id === editingTaskId) ??
487
+ tasks.find((task) => task.id === editingTaskId) ??
488
+ null
489
+ );
490
+ }, [boardApiTasks, boardColumns, editingTaskId, tasks]);
491
+
409
492
  const statsCards = useMemo(
410
493
  () => [
411
494
  {
@@ -494,23 +577,56 @@ export default function OperationsMyTasksPage() {
494
577
  },
495
578
  });
496
579
 
497
- mutateOperations(request, `/operations/tasks/${taskId}`, 'PATCH', {
498
- status: targetColumn,
499
- }).catch(() => {
500
- setBoardOverride(null);
501
- void refetchBoard();
502
- });
580
+ mutateOperations<OperationsTaskOption>(
581
+ request,
582
+ `/operations/tasks/${taskId}`,
583
+ 'PATCH',
584
+ {
585
+ status: targetColumn,
586
+ }
587
+ )
588
+ .then((updatedTask) => {
589
+ if (!updatedTask) {
590
+ return;
591
+ }
592
+
593
+ setBoardOverride((current) => ({
594
+ columns: replaceTaskInColumns(current?.columns ?? boardColumns, {
595
+ ...sourceTask,
596
+ ...updatedTask,
597
+ }),
598
+ }));
599
+ })
600
+ .catch(() => {
601
+ setBoardOverride(null);
602
+ void refetchBoard();
603
+ });
503
604
  },
504
605
  [boardColumns, findColumnByTask, request, refetchBoard]
505
606
  );
506
607
 
507
608
  const onBoardDragEnd = (event: DragEndEvent) => {
609
+ setActiveDragTask(null);
508
610
  const taskId = parseTaskId(event.active.id);
509
611
  const targetColumn = parseColumnId(event.over?.id);
510
612
  if (!taskId || !targetColumn) return;
511
613
  moveTaskToColumn(taskId, targetColumn);
512
614
  };
513
615
 
616
+ const onBoardDragStart = (event: DragStartEvent) => {
617
+ const taskId = parseTaskId(event.active.id);
618
+ if (!taskId) {
619
+ setActiveDragTask(null);
620
+ return;
621
+ }
622
+
623
+ const task = KANBAN_COLUMNS.map((column) => boardColumns[column.id])
624
+ .find((items) => items.some((item) => item.id === taskId))
625
+ ?.find((item) => item.id === taskId);
626
+
627
+ setActiveDragTask(task ?? null);
628
+ };
629
+
514
630
  const refetchAll = useCallback(async () => {
515
631
  await Promise.all([refetch(), refetchBoard()]);
516
632
  }, [refetch, refetchBoard]);
@@ -590,10 +706,13 @@ export default function OperationsMyTasksPage() {
590
706
  [inlineCreateName, request, refetchBoard]
591
707
  );
592
708
 
593
- const openCreateTaskForm = useCallback(() => {
709
+ const openCreateTaskForm = useCallback((status?: BoardColumnId) => {
594
710
  setEditingTaskId(null);
595
- setTaskFormData(EMPTY_TASK_FORM);
596
711
  setSelectedTask(null);
712
+ setTaskFormData((prev) => ({
713
+ ...EMPTY_TASK_FORM,
714
+ ...(status ? { status } : { status: prev.status }),
715
+ }));
597
716
  setTaskFormOpen(true);
598
717
  }, []);
599
718
 
@@ -607,6 +726,9 @@ export default function OperationsMyTasksPage() {
607
726
  status: KANBAN_COLUMNS.some((column) => column.id === task.status)
608
727
  ? (task.status as BoardColumnId)
609
728
  : 'todo',
729
+ assigneeCollaboratorId: task.assigneeCollaboratorId
730
+ ? String(task.assigneeCollaboratorId)
731
+ : 'none',
610
732
  dueDate: normalizeDateInputValue(task.dueDate),
611
733
  estimateHours:
612
734
  task.estimateHours != null ? String(task.estimateHours) : '',
@@ -628,6 +750,10 @@ export default function OperationsMyTasksPage() {
628
750
  const payload = {
629
751
  projectId: selectedProject.id,
630
752
  projectAssignmentId: selectedProject.projectAssignmentId ?? null,
753
+ assigneeCollaboratorId:
754
+ taskFormData.assigneeCollaboratorId !== 'none'
755
+ ? Number(taskFormData.assigneeCollaboratorId)
756
+ : null,
631
757
  name: normalizedName,
632
758
  description: taskFormData.description.trim() || null,
633
759
  priority: taskFormData.priority,
@@ -661,6 +787,29 @@ export default function OperationsMyTasksPage() {
661
787
  }
662
788
  }, [editingTaskId, projectOptions, request, refetchAll, taskFormData]);
663
789
 
790
+ const openTimesheetEntrySheet = useCallback(
791
+ (task: TaskDetailSheetData) => {
792
+ if (!task.projectId) {
793
+ return;
794
+ }
795
+
796
+ const projectLabel = [task.projectName, task.projectCode]
797
+ .filter(Boolean)
798
+ .join(' • ');
799
+
800
+ setTimesheetPrefill({
801
+ projectId: task.projectId,
802
+ projectAssignmentId: task.projectAssignmentId ?? null,
803
+ projectLabel: projectLabel || commonT('labels.notAssigned'),
804
+ taskId: task.id,
805
+ taskLabel: task.name,
806
+ });
807
+ setSelectedTask(null);
808
+ setIsTimesheetEntrySheetOpen(true);
809
+ },
810
+ [commonT]
811
+ );
812
+
664
813
  return (
665
814
  <Page>
666
815
  <OperationsHeader
@@ -779,6 +928,8 @@ export default function OperationsMyTasksPage() {
779
928
  <DndContext
780
929
  sensors={sensors}
781
930
  collisionDetection={closestCenter}
931
+ onDragStart={onBoardDragStart}
932
+ onDragCancel={() => setActiveDragTask(null)}
782
933
  onDragEnd={onBoardDragEnd}
783
934
  >
784
935
  <div className="grid gap-4 xl:grid-cols-4">
@@ -787,7 +938,7 @@ export default function OperationsMyTasksPage() {
787
938
  {(isOver) => (
788
939
  <div
789
940
  className={[
790
- 'flex min-h-[32rem] flex-col overflow-hidden rounded-3xl border bg-linear-to-b p-3 transition-all',
941
+ 'flex min-h-48 max-h-160 flex-col rounded-3xl border bg-linear-to-b p-3 transition-all',
791
942
  getColumnClassName(column.id),
792
943
  isOver
793
944
  ? 'border-primary shadow-lg ring-2 ring-primary/15'
@@ -817,17 +968,14 @@ export default function OperationsMyTasksPage() {
817
968
  <button
818
969
  type="button"
819
970
  className="flex size-5 cursor-pointer items-center justify-center rounded-full text-muted-foreground transition hover:bg-muted hover:text-foreground"
820
- onClick={() => {
821
- setInlineCreateColumn(column.id);
822
- setInlineCreateName('');
823
- }}
971
+ onClick={() => openCreateTaskForm(column.id)}
824
972
  >
825
973
  <Plus className="size-3.5" />
826
974
  </button>
827
975
  </div>
828
976
  </div>
829
977
 
830
- <div className="flex flex-1 flex-col gap-2">
978
+ <div className="flex min-h-0 flex-1 flex-col gap-2 overflow-y-auto pb-1 pr-0.5">
831
979
  <AnimatePresence initial={false}>
832
980
  {boardColumns[column.id].map((task) => {
833
981
  const taskTags = task.tags
@@ -879,9 +1027,8 @@ export default function OperationsMyTasksPage() {
879
1027
  </p>
880
1028
  {task.description ? (
881
1029
  <p className="line-clamp-2 text-xs leading-5 text-muted-foreground">
882
- {task.description.replace(
883
- /<[^>]*>/g,
884
- ''
1030
+ {getTaskDescriptionPreview(
1031
+ task.description
885
1032
  )}
886
1033
  </p>
887
1034
  ) : null}
@@ -1005,81 +1152,32 @@ export default function OperationsMyTasksPage() {
1005
1152
  </AnimatePresence>
1006
1153
  </div>
1007
1154
 
1008
- {inlineCreateColumn === column.id ? (
1009
- <div className="space-y-1.5 rounded-lg border bg-card p-2">
1010
- <Input
1011
- autoFocus
1012
- placeholder={detailT('taskForm.namePlaceholder')}
1013
- value={inlineCreateName}
1014
- onChange={(e) =>
1015
- setInlineCreateName(e.target.value)
1016
- }
1017
- onKeyDown={(e) => {
1018
- if (e.key === 'Enter') {
1019
- e.preventDefault();
1020
- void handleInlineCreateTask(column.id);
1021
- } else if (e.key === 'Escape') {
1022
- setInlineCreateColumn(null);
1023
- setInlineCreateName('');
1024
- }
1025
- }}
1026
- onBlur={() => {
1027
- if (!inlineCreateName.trim()) {
1028
- setInlineCreateColumn(null);
1029
- setInlineCreateName('');
1030
- }
1031
- }}
1032
- disabled={inlineCreateLoading}
1033
- className="h-8 text-sm"
1034
- />
1035
- <div className="flex gap-1">
1036
- <Button
1037
- type="button"
1038
- size="sm"
1039
- className="h-7 px-2 text-xs"
1040
- disabled={
1041
- !inlineCreateName.trim() || inlineCreateLoading
1042
- }
1043
- onMouseDown={(e) => e.preventDefault()}
1044
- onClick={() =>
1045
- void handleInlineCreateTask(column.id)
1046
- }
1047
- >
1048
- {t('actions.create')}
1049
- </Button>
1050
- <Button
1051
- type="button"
1052
- variant="ghost"
1053
- size="sm"
1054
- className="h-7 px-2 text-xs"
1055
- onMouseDown={(e) => e.preventDefault()}
1056
- onClick={() => {
1057
- setInlineCreateColumn(null);
1058
- setInlineCreateName('');
1059
- }}
1060
- >
1061
- {commonT('actions.cancel')}
1062
- </Button>
1063
- </div>
1064
- </div>
1065
- ) : (
1066
- <button
1067
- type="button"
1068
- className="flex w-full cursor-pointer items-center gap-1 rounded-md px-1 py-1 text-xs text-muted-foreground transition hover:bg-muted hover:text-foreground"
1069
- onClick={() => {
1070
- setInlineCreateColumn(column.id);
1071
- setInlineCreateName('');
1072
- }}
1073
- >
1074
- <Plus className="size-3" />
1075
- {t('actions.create')}
1076
- </button>
1077
- )}
1155
+ <button
1156
+ type="button"
1157
+ className="mt-auto flex w-full cursor-pointer items-center justify-center gap-1 rounded-2xl border border-dashed bg-background/70 px-3 py-2 text-xs text-muted-foreground transition hover:border-primary/40 hover:bg-primary/5 hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50"
1158
+ onClick={() => openCreateTaskForm(column.id)}
1159
+ >
1160
+ <Plus className="size-3" />
1161
+ {t('actions.create')}
1162
+ </button>
1078
1163
  </div>
1079
1164
  )}
1080
1165
  </DroppableColumn>
1081
1166
  ))}
1082
1167
  </div>
1168
+ <DragOverlay zIndex={1200}>
1169
+ {activeDragTask ? (
1170
+ <div className="w-76 cursor-grabbing rounded-2xl border border-primary/60 bg-card p-3 text-left shadow-2xl ring-2 ring-primary/20 opacity-95">
1171
+ <p className="line-clamp-2 text-sm font-semibold leading-snug">
1172
+ {activeDragTask.name}
1173
+ </p>
1174
+ <p className="mt-1 truncate text-xs text-muted-foreground">
1175
+ {activeDragTask.assigneeName ||
1176
+ commonT('labels.notAssigned')}
1177
+ </p>
1178
+ </div>
1179
+ ) : null}
1180
+ </DragOverlay>
1083
1181
  </DndContext>
1084
1182
  ) : (
1085
1183
  <EmptyState
@@ -1117,7 +1215,7 @@ export default function OperationsMyTasksPage() {
1117
1215
 
1118
1216
  {task.description ? (
1119
1217
  <p className="line-clamp-3 text-sm text-muted-foreground">
1120
- {task.description}
1218
+ {getTaskDescriptionPreview(task.description)}
1121
1219
  </p>
1122
1220
  ) : null}
1123
1221
 
@@ -1166,7 +1264,7 @@ export default function OperationsMyTasksPage() {
1166
1264
  <div className="truncate font-medium">{task.name}</div>
1167
1265
  {task.description ? (
1168
1266
  <div className="truncate text-xs text-muted-foreground">
1169
- {task.description}
1267
+ {getTaskDescriptionPreview(task.description)}
1170
1268
  </div>
1171
1269
  ) : null}
1172
1270
  </div>
@@ -1248,12 +1346,12 @@ export default function OperationsMyTasksPage() {
1248
1346
  }}
1249
1347
  footer={
1250
1348
  selectedTask ? (
1251
- <div className="grid grid-cols-2 gap-3">
1349
+ <div className="grid grid-cols-1 gap-3 sm:grid-cols-3">
1252
1350
  {taskCollection === 'archived' ? (
1253
1351
  <>
1254
1352
  <Button
1255
1353
  variant="outline"
1256
- className="gap-2"
1354
+ className="gap-2 sm:col-span-2"
1257
1355
  onClick={() => void handleRestoreTask(selectedTask.id)}
1258
1356
  >
1259
1357
  <ArchiveRestore className="size-4" />
@@ -1272,6 +1370,15 @@ export default function OperationsMyTasksPage() {
1272
1370
  </>
1273
1371
  ) : (
1274
1372
  <>
1373
+ <Button
1374
+ variant="outline"
1375
+ className="gap-2"
1376
+ disabled={!selectedTask.projectId}
1377
+ onClick={() => openTimesheetEntrySheet(selectedTask)}
1378
+ >
1379
+ <Send className="size-4" />
1380
+ {commonT('actions.logHours')}
1381
+ </Button>
1275
1382
  <Button
1276
1383
  variant="outline"
1277
1384
  className="gap-2"
@@ -1297,6 +1404,36 @@ export default function OperationsMyTasksPage() {
1297
1404
  }
1298
1405
  />
1299
1406
 
1407
+ <TimesheetEntryCreateSheet
1408
+ open={isTimesheetEntrySheetOpen}
1409
+ onOpenChange={(open) => {
1410
+ setIsTimesheetEntrySheetOpen(open);
1411
+ if (!open) {
1412
+ setTimesheetPrefill(null);
1413
+ }
1414
+ }}
1415
+ project={
1416
+ timesheetPrefill
1417
+ ? {
1418
+ id: timesheetPrefill.projectId,
1419
+ label: timesheetPrefill.projectLabel,
1420
+ projectAssignmentId: timesheetPrefill.projectAssignmentId,
1421
+ }
1422
+ : null
1423
+ }
1424
+ task={
1425
+ timesheetPrefill
1426
+ ? {
1427
+ id: timesheetPrefill.taskId,
1428
+ label: timesheetPrefill.taskLabel,
1429
+ }
1430
+ : null
1431
+ }
1432
+ onCreated={() => {
1433
+ void refetchAll();
1434
+ }}
1435
+ />
1436
+
1300
1437
  <Dialog
1301
1438
  open={deletePromptTask !== null}
1302
1439
  onOpenChange={(open) => {
@@ -1306,10 +1443,10 @@ export default function OperationsMyTasksPage() {
1306
1443
  <DialogContent className="sm:max-w-sm">
1307
1444
  <DialogHeader>
1308
1445
  <DialogTitle>{t('dialogs.deleteTitle')}</DialogTitle>
1446
+ <DialogDescription>
1447
+ {t('dialogs.deleteDescription')}
1448
+ </DialogDescription>
1309
1449
  </DialogHeader>
1310
- <p className="text-sm text-muted-foreground">
1311
- {t('dialogs.deleteDescription')}
1312
- </p>
1313
1450
  <DialogFooter className="mt-4">
1314
1451
  <Button variant="outline" onClick={() => setDeletePromptTask(null)}>
1315
1452
  {commonT('actions.cancel')}
@@ -1331,13 +1468,8 @@ export default function OperationsMyTasksPage() {
1331
1468
  <Sheet
1332
1469
  open={taskFormOpen}
1333
1470
  onOpenChange={(open) => {
1334
- if (!taskFormLoading) {
1335
- setTaskFormOpen(open);
1336
- if (!open) {
1337
- setEditingTaskId(null);
1338
- setTaskFormData(EMPTY_TASK_FORM);
1339
- }
1340
- }
1471
+ setTaskFormOpen(open);
1472
+ if (!open) setEditingTaskId(null);
1341
1473
  }}
1342
1474
  >
1343
1475
  <SheetContent className="flex w-full flex-col overflow-hidden sm:max-w-xl">
@@ -1481,6 +1613,35 @@ export default function OperationsMyTasksPage() {
1481
1613
  </div>
1482
1614
  </div>
1483
1615
 
1616
+ <div className="space-y-2">
1617
+ <Label>{commonT('responsible')}</Label>
1618
+ <Select
1619
+ value={taskFormData.assigneeCollaboratorId}
1620
+ onValueChange={(value) =>
1621
+ setTaskFormData((prev) => ({
1622
+ ...prev,
1623
+ assigneeCollaboratorId: value,
1624
+ }))
1625
+ }
1626
+ >
1627
+ <SelectTrigger className="w-full">
1628
+ <SelectValue
1629
+ placeholder={commonT('labels.notAssigned')}
1630
+ />
1631
+ </SelectTrigger>
1632
+ <SelectContent>
1633
+ <SelectItem value="none">
1634
+ {commonT('labels.notAssigned')}
1635
+ </SelectItem>
1636
+ {assigneeOptions.map((option) => (
1637
+ <SelectItem key={option.id} value={option.id}>
1638
+ {option.label}
1639
+ </SelectItem>
1640
+ ))}
1641
+ </SelectContent>
1642
+ </Select>
1643
+ </div>
1644
+
1484
1645
  <div className="grid grid-cols-2 gap-3">
1485
1646
  <div className="space-y-2">
1486
1647
  <Label htmlFor="task-due-date">