@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
@@ -3,41 +3,61 @@
3
3
  import { RichTextEditor } from '@/components/rich-text-editor';
4
4
  import { Button } from '@/components/ui/button';
5
5
  import { CommentContent } from '@/components/ui/comment-rich-editor';
6
+ import { KpiCardsGrid, type KpiCardItem } from '@/components/ui/kpi-cards-grid';
6
7
  import {
7
8
  Sheet,
8
9
  SheetContent,
10
+ SheetDescription,
9
11
  SheetHeader,
10
12
  SheetTitle,
11
13
  } from '@/components/ui/sheet';
12
14
  import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
13
15
  import { useApp, useQuery } from '@hed-hog/next-app-provider';
14
16
  import {
17
+ Activity,
15
18
  AlarmClock,
16
19
  Calendar,
20
+ Clock3,
21
+ History,
17
22
  MessageSquare,
18
23
  Pencil,
19
- Tag,
24
+ Timer,
20
25
  Trash2,
21
- User,
26
+ Users,
22
27
  X,
23
28
  } from 'lucide-react';
24
29
  import { useTranslations } from 'next-intl';
25
30
  import type { ReactNode } from 'react';
26
- import { useEffect, useRef, useState } from 'react';
31
+ import { useEffect, useMemo, useRef, useState } from 'react';
27
32
  import {
28
33
  createTaskComment,
29
34
  deleteTaskComment,
35
+ fetchTaskActivities,
30
36
  fetchTaskComments,
31
37
  updateTaskComment,
32
38
  } from '../_lib/api';
33
39
  import { useMentionItems } from '../_lib/hooks/use-mention-items';
34
- import type { OperationsTaskComment } from '../_lib/types';
35
- import { formatDate, getStatusBadgeClass } from '../_lib/utils/format';
40
+ import type {
41
+ OperationsTaskActivity,
42
+ OperationsTaskComment,
43
+ } from '../_lib/types';
44
+ import { formatDate, formatDateTime, getStatusBadgeClass } from '../_lib/utils/format';
45
+ import {
46
+ formatDurationMinutes,
47
+ getElapsedDoingMinutes,
48
+ getInitials,
49
+ getTaskPriorityLabel,
50
+ } from '../_lib/utils/task-ui';
51
+
36
52
  import { StatusBadge } from './status-badge';
53
+ import { TaskFileAttachments } from './task-file-attachments';
54
+ import { TimesheetEntryCreateSheet } from './timesheet-entry-create-sheet';
37
55
 
38
56
  export type TaskDetailSheetData = {
39
57
  id: number;
40
58
  name: string;
59
+ projectId?: number | null;
60
+ projectAssignmentId?: number | null;
41
61
  description?: string | null;
42
62
  status: string;
43
63
  priority?: string | null;
@@ -49,6 +69,8 @@ export type TaskDetailSheetData = {
49
69
  assigneeName?: string | null;
50
70
  assigneeUserPhotoId?: number | null;
51
71
  assigneePersonAvatarId?: number | null;
72
+ doingStartedAt?: string | null;
73
+ totalDoingMinutes?: number | null;
52
74
  };
53
75
 
54
76
  type Props = {
@@ -57,18 +79,9 @@ type Props = {
57
79
  onOpenChange: (open: boolean) => void;
58
80
  statusLabel?: (status: string) => string;
59
81
  footer?: ReactNode;
60
- defaultTab?: 'info' | 'comments';
82
+ defaultTab?: 'comments' | 'activities';
61
83
  };
62
84
 
63
- function getPriorityLabel(value?: string | null) {
64
- const labels: Record<string, string> = {
65
- low: 'Baixa',
66
- medium: 'Média',
67
- high: 'Alta',
68
- };
69
- return labels[value ?? ''] ?? value ?? '—';
70
- }
71
-
72
85
  function getPriorityClassName(value?: string | null) {
73
86
  if (value === 'high') return 'bg-rose-100 text-rose-700 border-rose-200';
74
87
  if (value === 'medium') return 'bg-amber-100 text-amber-700 border-amber-200';
@@ -104,16 +117,6 @@ function getCommentAvatarSrc(comment: OperationsTaskComment) {
104
117
  return null;
105
118
  }
106
119
 
107
- function getInitials(value?: string | null) {
108
- const parts = String(value ?? '')
109
- .trim()
110
- .split(/\s+/)
111
- .filter(Boolean)
112
- .slice(0, 2);
113
- if (!parts.length) return '??';
114
- return parts.map((p) => p[0]?.toUpperCase() ?? '').join('');
115
- }
116
-
117
120
  function formatCommentDate(value?: string | null) {
118
121
  if (!value) return '';
119
122
  const date = new Date(value);
@@ -132,9 +135,10 @@ function formatCommentDate(value?: string | null) {
132
135
 
133
136
  export type TaskCommentsSectionProps = {
134
137
  taskId: number;
138
+ onChanged?: () => void;
135
139
  };
136
140
 
137
- export function TaskCommentsSection({ taskId }: TaskCommentsSectionProps) {
141
+ export function TaskCommentsSection({ taskId, onChanged }: TaskCommentsSectionProps) {
138
142
  const { request, showToastHandler, getSettingValue } = useApp();
139
143
  const ct = useTranslations('operations.ProjectDetailsPage.commentsSection');
140
144
  const editWindowMinutes = Number(
@@ -156,9 +160,7 @@ export function TaskCommentsSection({ taskId }: TaskCommentsSectionProps) {
156
160
 
157
161
  const mentionItems = useMentionItems(request);
158
162
 
159
- const { data: fetchedComments = [], refetch } = useQuery<
160
- OperationsTaskComment[]
161
- >({
163
+ const { data: fetchedComments = [] } = useQuery<OperationsTaskComment[]>({
162
164
  queryKey: ['task-comments', taskId],
163
165
  queryFn: () =>
164
166
  fetchTaskComments(request, taskId) as Promise<OperationsTaskComment[]>,
@@ -183,8 +185,6 @@ export function TaskCommentsSection({ taskId }: TaskCommentsSectionProps) {
183
185
  }
184
186
  }
185
187
  return () => timers.forEach(clearTimeout);
186
- // Re-schedule whenever the comment list or the window setting changes
187
- // eslint-disable-next-line react-hooks/exhaustive-deps
188
188
  }, [comments, editWindowMinutes]);
189
189
 
190
190
  const handleSubmit = async () => {
@@ -199,6 +199,7 @@ export function TaskCommentsSection({ taskId }: TaskCommentsSectionProps) {
199
199
  )) as OperationsTaskComment;
200
200
  setLocalComments([...(localComments ?? fetchedComments), created]);
201
201
  setNewComment('');
202
+ onChanged?.();
202
203
  } catch {
203
204
  showToastHandler?.('error', ct('errors.addComment'));
204
205
  } finally {
@@ -250,6 +251,7 @@ export function TaskCommentsSection({ taskId }: TaskCommentsSectionProps) {
250
251
  setLocalComments(
251
252
  (localComments ?? fetchedComments).filter((c) => c.id !== comment.id)
252
253
  );
254
+ onChanged?.();
253
255
  } catch {
254
256
  showToastHandler?.('error', ct('errors.deleteComment'));
255
257
  } finally {
@@ -380,12 +382,19 @@ export function TaskCommentsSection({ taskId }: TaskCommentsSectionProps) {
380
382
  value={newComment}
381
383
  onChange={setNewComment}
382
384
  mentions={mentionItems}
385
+ onCtrlEnter={() => void handleSubmit()}
383
386
  />
384
- <div className="flex justify-end">
387
+ <div className="flex items-center justify-end gap-2">
388
+ <span className="text-[10px] text-muted-foreground select-none">
389
+ <kbd className="rounded border bg-muted px-1 py-0.5 font-mono text-[10px]">Ctrl</kbd>
390
+ {' + '}
391
+ <kbd className="rounded border bg-muted px-1 py-0.5 font-mono text-[10px]">↵</kbd>
392
+ </span>
385
393
  <Button
386
394
  size="sm"
387
395
  className="gap-1.5"
388
396
  disabled={submitting || !newComment.replace(/<[^>]*>/g, '').trim()}
397
+ onMouseDown={(e) => e.preventDefault()}
389
398
  onClick={() => void handleSubmit()}
390
399
  >
391
400
  {ct('submitButton')}
@@ -396,6 +405,177 @@ export function TaskCommentsSection({ taskId }: TaskCommentsSectionProps) {
396
405
  );
397
406
  }
398
407
 
408
+ export type TaskActivitiesSectionProps = {
409
+ taskId: number;
410
+ task?: {
411
+ status?: string | null;
412
+ doingStartedAt?: string | null;
413
+ totalDoingMinutes?: number | null;
414
+ } | null;
415
+ statusLabel?: (status: string) => string;
416
+ };
417
+
418
+ function getActivityAvatarSrc(activity: OperationsTaskActivity) {
419
+ if (
420
+ typeof activity.actorUserPhotoId === 'number' &&
421
+ activity.actorUserPhotoId > 0
422
+ )
423
+ return `${process.env.NEXT_PUBLIC_API_BASE_URL}/file/open/${activity.actorUserPhotoId}`;
424
+ if (
425
+ typeof activity.actorPersonAvatarId === 'number' &&
426
+ activity.actorPersonAvatarId > 0
427
+ )
428
+ return `${process.env.NEXT_PUBLIC_API_BASE_URL}/person/avatar/${activity.actorPersonAvatarId}`;
429
+ return null;
430
+ }
431
+
432
+ export function TaskActivitiesSection({
433
+ taskId,
434
+ task,
435
+ statusLabel,
436
+ }: TaskActivitiesSectionProps) {
437
+ const { request, getSettingValue, currentLocaleCode } = useApp();
438
+ const at = useTranslations('operations.ProjectDetailsPage.activitiesSection');
439
+ const [doingTick, setDoingTick] = useState(0);
440
+
441
+ const { data: activities = [] } = useQuery<OperationsTaskActivity[]>({
442
+ queryKey: ['task-activities', taskId],
443
+ queryFn: () =>
444
+ fetchTaskActivities(request, taskId) as Promise<OperationsTaskActivity[]>,
445
+ });
446
+
447
+ useEffect(() => {
448
+ if (task?.status !== 'doing' || !task.doingStartedAt) return;
449
+ const timer = setInterval(() => setDoingTick((value) => value + 1), 30000);
450
+ return () => clearInterval(timer);
451
+ }, [task?.doingStartedAt, task?.status]);
452
+
453
+ const uniqueCollaborators = useMemo(() => {
454
+ const collaborators = new Set<string>();
455
+ for (const activity of activities) {
456
+ if (activity.actorCollaboratorId != null) {
457
+ collaborators.add(`id:${activity.actorCollaboratorId}`);
458
+ } else if (activity.actorName) {
459
+ collaborators.add(`name:${activity.actorName}`);
460
+ }
461
+ }
462
+ return collaborators.size;
463
+ }, [activities]);
464
+
465
+ const totalDoingMinutes = getElapsedDoingMinutes(task, doingTick);
466
+
467
+ const kpiItems: KpiCardItem[] = [
468
+ {
469
+ key: 'activities',
470
+ title: at('kpis.activities'),
471
+ value: activities.length,
472
+ description: at('kpis.activitiesDescription'),
473
+ icon: Activity,
474
+ layout: 'compact',
475
+ accentClassName: 'from-sky-500/30 via-cyan-500/10 to-transparent',
476
+ iconContainerClassName: 'bg-sky-500/10 text-sky-700',
477
+ },
478
+ {
479
+ key: 'collaborators',
480
+ title: at('kpis.collaborators'),
481
+ value: uniqueCollaborators,
482
+ description: at('kpis.collaboratorsDescription'),
483
+ icon: Users,
484
+ layout: 'compact',
485
+ accentClassName: 'from-violet-500/30 via-fuchsia-500/10 to-transparent',
486
+ iconContainerClassName: 'bg-violet-500/10 text-violet-700',
487
+ },
488
+ {
489
+ key: 'doing-time',
490
+ title: at('kpis.executionTime'),
491
+ value: formatDurationMinutes(totalDoingMinutes),
492
+ description: at('kpis.executionTimeDescription'),
493
+ icon: Clock3,
494
+ layout: 'compact',
495
+ accentClassName: 'from-emerald-500/30 via-teal-500/10 to-transparent',
496
+ iconContainerClassName: 'bg-emerald-500/10 text-emerald-700',
497
+ },
498
+ ];
499
+
500
+ const getActivityDescription = (activity: OperationsTaskActivity) => {
501
+ if (activity.action === 'status_changed') {
502
+ return at('statusChanged', {
503
+ from: statusLabel?.(activity.fromStatus) ?? activity.fromStatus,
504
+ to: statusLabel?.(activity.toStatus) ?? activity.toStatus,
505
+ });
506
+ }
507
+ return at('fallbackAction');
508
+ };
509
+
510
+ return (
511
+ <div className="flex flex-col gap-4">
512
+ <div className="flex items-center gap-1.5 text-xs font-medium uppercase tracking-wide text-muted-foreground">
513
+ <History className="size-3" />
514
+ {at('title')}
515
+ {activities.length > 0 ? (
516
+ <span className="ml-0.5 rounded-full bg-muted px-1.5 py-0.5 text-[10px] font-semibold text-muted-foreground">
517
+ {activities.length}
518
+ </span>
519
+ ) : null}
520
+ </div>
521
+
522
+ <KpiCardsGrid items={kpiItems} columns={3} className="gap-2" />
523
+
524
+ {activities.length > 0 ? (
525
+ <div className="relative flex flex-col">
526
+ {activities.map((activity, index) => {
527
+ const avatarSrc = getActivityAvatarSrc(activity);
528
+ const isLast = index === activities.length - 1;
529
+ return (
530
+ <div
531
+ key={activity.id}
532
+ className="relative flex gap-3 pb-4 last:pb-0"
533
+ >
534
+ <div className="relative flex shrink-0 justify-center">
535
+ {!isLast ? (
536
+ <span className="absolute bottom-0 top-9 w-px bg-border" />
537
+ ) : null}
538
+ <div className="relative z-10 flex size-9 shrink-0 items-center justify-center overflow-hidden rounded-full border-2 border-background bg-muted text-[11px] font-semibold uppercase text-muted-foreground shadow-sm ring-1 ring-border">
539
+ {avatarSrc ? (
540
+ // eslint-disable-next-line @next/next/no-img-element
541
+ <img
542
+ src={avatarSrc}
543
+ alt={activity.actorName ?? ''}
544
+ className="size-full object-cover"
545
+ />
546
+ ) : (
547
+ getInitials(activity.actorName)
548
+ )}
549
+ </div>
550
+ </div>
551
+ <div className="min-w-0 flex-1 rounded-md border bg-background px-3 py-2 shadow-sm">
552
+ <div className="flex flex-wrap items-start justify-between gap-x-2 gap-y-1">
553
+ <span className="min-w-0 truncate text-xs font-semibold">
554
+ {activity.actorName ?? at('defaultUser')}
555
+ </span>
556
+ <span className="shrink-0 text-[10px] font-medium text-muted-foreground">
557
+ {formatDateTime(
558
+ activity.createdAt,
559
+ getSettingValue,
560
+ currentLocaleCode
561
+ )}
562
+ </span>
563
+ </div>
564
+ <p className="mt-1.5 text-xs leading-relaxed text-muted-foreground">
565
+ {getActivityDescription(activity)}
566
+ </p>
567
+ </div>
568
+ </div>
569
+ );
570
+ })}
571
+ </div>
572
+ ) : (
573
+ <p className="text-xs text-muted-foreground">{at('noActivities')}</p>
574
+ )}
575
+ </div>
576
+ );
577
+ }
578
+
399
579
  export function TaskDetailSheet({
400
580
  task,
401
581
  open,
@@ -404,133 +584,143 @@ export function TaskDetailSheet({
404
584
  footer,
405
585
  defaultTab = 'comments',
406
586
  }: Props) {
407
- const commonT = useTranslations('operations.Common');
408
587
  const detailT = useTranslations('operations.ProjectDetailsPage');
588
+ const commonT = useTranslations('operations.Common');
409
589
  const { getSettingValue, currentLocaleCode } = useApp();
410
590
 
411
- const [tab, setTab] = useState<'info' | 'comments'>(defaultTab);
591
+ const [tab, setTab] = useState<'comments' | 'activities'>(defaultTab);
592
+ const [isTimesheetOpen, setIsTimesheetOpen] = useState(false);
412
593
 
413
594
  useEffect(() => {
414
- if (open) setTab(defaultTab);
595
+ if (!open) {
596
+ setIsTimesheetOpen(false);
597
+ return;
598
+ }
599
+ const timer = setTimeout(() => setTab(defaultTab), 0);
600
+ return () => clearTimeout(timer);
415
601
  }, [open, defaultTab]);
416
602
 
417
603
  const avatarSrc = getAvatarSrc(task);
418
604
 
605
+ const timesheetProject =
606
+ task && task.projectId
607
+ ? {
608
+ id: task.projectId,
609
+ label:
610
+ [task.projectName, task.projectCode].filter(Boolean).join(' • ') ||
611
+ commonT('labels.notAssigned'),
612
+ projectAssignmentId: task.projectAssignmentId,
613
+ }
614
+ : null;
615
+
419
616
  return (
420
- <Sheet open={open} onOpenChange={onOpenChange}>
421
- <SheetContent className="flex w-full flex-col gap-0 overflow-hidden sm:max-w-md">
422
- {task ? (
423
- <>
424
- <SheetHeader className="shrink-0 px-4 pb-2 pt-4">
425
- <SheetTitle className="pr-6 text-base font-semibold leading-snug">
426
- {task.name}
427
- </SheetTitle>
428
- <div className="flex flex-wrap gap-2 pt-1">
429
- <StatusBadge
430
- label={statusLabel?.(task.status) ?? task.status}
431
- className={getStatusBadgeClass(task.status)}
432
- />
433
- {task.priority ? (
434
- <span
435
- className={`inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold ${getPriorityClassName(task.priority)}`}
436
- >
437
- {getPriorityLabel(task.priority)}
438
- </span>
439
- ) : null}
440
- </div>
441
- </SheetHeader>
442
-
443
- <Tabs
444
- value={tab}
445
- onValueChange={(v) => setTab(v as 'info' | 'comments')}
446
- className="flex min-h-0 flex-1 flex-col"
447
- >
448
- <TabsList className="mx-4 mb-1 mt-2 grid shrink-0 grid-cols-2">
449
- <TabsTrigger value="info">
450
- {detailT('taskForm.tabInfo')}
451
- </TabsTrigger>
452
- <TabsTrigger value="comments">
453
- <MessageSquare className="mr-1.5 size-3.5" />
454
- {detailT('taskForm.tabComments')}
455
- </TabsTrigger>
456
- </TabsList>
457
-
458
- <TabsContent
459
- value="info"
460
- className="flex-1 overflow-y-auto px-4 pb-4 pt-2 data-[state=inactive]:hidden"
461
- >
462
- <div className="flex flex-col gap-5">
617
+ <>
618
+ <Sheet open={open} onOpenChange={onOpenChange}>
619
+ <SheetContent className="flex w-full flex-col gap-0 overflow-hidden sm:max-w-5xl">
620
+ {task ? (
621
+ <>
622
+ <SheetHeader className="shrink-0 border-b px-5 pb-3 pt-4">
623
+ <div className="flex items-start justify-between gap-3 pr-8">
624
+ <SheetTitle className="text-base font-semibold leading-snug">
625
+ {task.name}
626
+ </SheetTitle>
627
+ {task.projectId ? (
628
+ <Button
629
+ variant="outline"
630
+ size="sm"
631
+ className="h-7 shrink-0 gap-1.5 text-xs"
632
+ onClick={() => setIsTimesheetOpen(true)}
633
+ >
634
+ <Timer className="size-3.5" />
635
+ {commonT('actions.logHours')}
636
+ </Button>
637
+ ) : null}
638
+ </div>
639
+ <SheetDescription className="sr-only">
640
+ {task.description || task.name}
641
+ </SheetDescription>
642
+ </SheetHeader>
643
+
644
+ <div className="flex flex-1 min-h-0 overflow-hidden">
645
+ {/* ── Coluna esquerda ── */}
646
+ <div className="flex flex-1 flex-col min-h-0 overflow-hidden">
463
647
  {task.description ? (
464
- <div>
648
+ <div className="shrink-0 border-b px-5 py-4">
465
649
  <p className="mb-1 text-xs font-medium uppercase tracking-wide text-muted-foreground">
466
650
  {detailT('taskForm.descriptionLabel')}
467
651
  </p>
468
- <p className="text-sm leading-relaxed">
469
- {task.description}
470
- </p>
652
+ <CommentContent content={task.description} />
471
653
  </div>
472
654
  ) : null}
473
655
 
474
- {task.dueDate || task.estimateHours != null ? (
475
- <div className="grid grid-cols-2 gap-3">
476
- {task.dueDate ? (
477
- <div className="flex items-start gap-2 text-sm">
478
- <Calendar className="mt-0.5 size-4 shrink-0 text-muted-foreground" />
479
- <div>
480
- <p className="text-[11px] text-muted-foreground">
481
- {detailT('taskForm.deadlineLabel')}
482
- </p>
483
- <p className="font-medium">
484
- {formatDate(
485
- task.dueDate,
486
- getSettingValue,
487
- currentLocaleCode
488
- )}
489
- </p>
490
- </div>
491
- </div>
492
- ) : null}
493
- {task.estimateHours != null ? (
494
- <div className="flex items-start gap-2 text-sm">
495
- <AlarmClock className="mt-0.5 size-4 shrink-0 text-muted-foreground" />
496
- <div>
497
- <p className="text-[11px] text-muted-foreground">
498
- {detailT('taskForm.estimateLabel')}
499
- </p>
500
- <p className="font-medium">{task.estimateHours}h</p>
501
- </div>
502
- </div>
503
- ) : null}
656
+ <Tabs
657
+ value={tab}
658
+ onValueChange={(v) => setTab(v as 'comments' | 'activities')}
659
+ className="flex flex-1 min-h-0 flex-col"
660
+ >
661
+ <div className="shrink-0 border-b px-5 py-2">
662
+ <TabsList className="grid grid-cols-2">
663
+ <TabsTrigger value="comments">
664
+ <MessageSquare className="mr-1.5 size-3.5" />
665
+ {detailT('taskForm.tabComments')}
666
+ </TabsTrigger>
667
+ <TabsTrigger value="activities">
668
+ <History className="mr-1.5 size-3.5" />
669
+ {detailT('taskForm.tabActivities')}
670
+ </TabsTrigger>
671
+ </TabsList>
504
672
  </div>
505
- ) : null}
673
+ <TabsContent
674
+ value="comments"
675
+ className="mt-0 flex-1 overflow-y-auto px-5 py-4 data-[state=inactive]:hidden"
676
+ >
677
+ <TaskCommentsSection taskId={task.id} />
678
+ </TabsContent>
679
+ <TabsContent
680
+ value="activities"
681
+ className="mt-0 flex-1 overflow-y-auto px-5 py-4 data-[state=inactive]:hidden"
682
+ >
683
+ <TaskActivitiesSection
684
+ taskId={task.id}
685
+ task={task}
686
+ statusLabel={statusLabel}
687
+ />
688
+ </TabsContent>
689
+ </Tabs>
690
+ </div>
506
691
 
507
- {task.tags ? (
692
+ {/* ── Aside direito ── */}
693
+ <aside className="flex w-72 shrink-0 flex-col gap-4 overflow-y-auto border-l px-4 py-4">
694
+ <div>
695
+ <p className="mb-1.5 text-xs font-medium uppercase tracking-wide text-muted-foreground">
696
+ {detailT('taskForm.columnLabel')}
697
+ </p>
698
+ <StatusBadge
699
+ label={statusLabel?.(task.status) ?? task.status}
700
+ className={getStatusBadgeClass(task.status)}
701
+ />
702
+ </div>
703
+
704
+ {task.priority ? (
508
705
  <div>
509
- <div className="mb-1.5 flex items-center gap-1.5 text-xs font-medium uppercase tracking-wide text-muted-foreground">
510
- <Tag className="size-3" />
511
- {detailT('taskForm.tagsLabel')}
512
- </div>
513
- <div className="flex flex-wrap gap-1.5">
514
- {task.tags.split(',').map((tag) => (
515
- <span
516
- key={tag.trim()}
517
- className="rounded-md bg-muted px-2 py-0.5 text-xs text-muted-foreground"
518
- >
519
- {tag.trim()}
520
- </span>
521
- ))}
522
- </div>
706
+ <p className="mb-1.5 text-xs font-medium uppercase tracking-wide text-muted-foreground">
707
+ {detailT('taskForm.priorityLabel')}
708
+ </p>
709
+ <span
710
+ className={`inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold ${getPriorityClassName(task.priority)}`}
711
+ >
712
+ {getTaskPriorityLabel(task.priority)}
713
+ </span>
523
714
  </div>
524
715
  ) : null}
525
716
 
526
717
  {task.assigneeName ? (
527
718
  <div>
528
- <div className="mb-1.5 flex items-center gap-1.5 text-xs font-medium uppercase tracking-wide text-muted-foreground">
529
- <User className="size-3" />
530
- {commonT('labels.collaborator')}
531
- </div>
532
- <div className="flex items-center gap-2.5">
533
- <div className="flex size-8 shrink-0 items-center justify-center overflow-hidden rounded-full bg-muted text-xs font-semibold uppercase text-muted-foreground ring-1 ring-border">
719
+ <p className="mb-1.5 text-xs font-medium uppercase tracking-wide text-muted-foreground">
720
+ {detailT('taskForm.assigneeLabel')}
721
+ </p>
722
+ <div className="flex items-center gap-2">
723
+ <div className="flex size-7 shrink-0 items-center justify-center overflow-hidden rounded-full bg-muted text-[10px] font-semibold uppercase text-muted-foreground ring-1 ring-border">
534
724
  {avatarSrc ? (
535
725
  // eslint-disable-next-line @next/next/no-img-element
536
726
  <img
@@ -547,35 +737,71 @@ export function TaskDetailSheet({
547
737
  </div>
548
738
  ) : null}
549
739
 
550
- {task.projectName || task.projectCode ? (
551
- <div className="rounded-lg border bg-muted/20 px-3 py-2.5">
552
- <p className="text-[11px] font-medium uppercase tracking-wide text-muted-foreground">
553
- {commonT('labels.project')}
740
+ {task.dueDate ? (
741
+ <div>
742
+ <p className="mb-1.5 text-xs font-medium uppercase tracking-wide text-muted-foreground">
743
+ {detailT('taskForm.deadlineLabel')}
554
744
  </p>
555
- <p className="mt-0.5 text-sm font-medium">
556
- {[task.projectName, task.projectCode]
557
- .filter(Boolean)
558
- .join(' ')}
745
+ <div className="flex items-center gap-1.5 text-sm">
746
+ <Calendar className="size-3.5 shrink-0 text-muted-foreground" />
747
+ <span>
748
+ {formatDate(task.dueDate, getSettingValue, currentLocaleCode)}
749
+ </span>
750
+ </div>
751
+ </div>
752
+ ) : null}
753
+
754
+ {task.estimateHours != null ? (
755
+ <div>
756
+ <p className="mb-1.5 text-xs font-medium uppercase tracking-wide text-muted-foreground">
757
+ {detailT('taskForm.estimateLabel')}
559
758
  </p>
759
+ <div className="flex items-center gap-1.5 text-sm">
760
+ <AlarmClock className="size-3.5 shrink-0 text-muted-foreground" />
761
+ <span>{task.estimateHours}h</span>
762
+ </div>
560
763
  </div>
561
764
  ) : null}
562
765
 
563
- {footer ? (
564
- <div className="border-t pt-5">{footer}</div>
766
+ {task.tags ? (
767
+ <div>
768
+ <p className="mb-1.5 text-xs font-medium uppercase tracking-wide text-muted-foreground">
769
+ {detailT('taskForm.tagsLabel')}
770
+ </p>
771
+ <div className="flex flex-wrap gap-1.5">
772
+ {task.tags.split(',').map((tag) => (
773
+ <span
774
+ key={tag.trim()}
775
+ className="rounded-md bg-muted px-2 py-0.5 text-xs text-muted-foreground"
776
+ >
777
+ {tag.trim()}
778
+ </span>
779
+ ))}
780
+ </div>
781
+ </div>
565
782
  ) : null}
566
- </div>
567
- </TabsContent>
568
783
 
569
- <TabsContent
570
- value="comments"
571
- className="flex-1 overflow-y-auto px-4 pb-4 pt-2 data-[state=inactive]:hidden"
572
- >
573
- <TaskCommentsSection taskId={task.id} />
574
- </TabsContent>
575
- </Tabs>
576
- </>
577
- ) : null}
578
- </SheetContent>
579
- </Sheet>
784
+ <div className="border-t pt-2">
785
+ <p className="mb-2 text-xs font-medium uppercase tracking-wide text-muted-foreground">
786
+ {detailT('taskForm.attachmentsLabel')}
787
+ </p>
788
+ <TaskFileAttachments taskId={task.id} />
789
+ </div>
790
+ </aside>
791
+ </div>
792
+ {footer ? (
793
+ <div className="shrink-0 border-t px-5 py-4">{footer}</div>
794
+ ) : null}
795
+ </>
796
+ ) : null}
797
+ </SheetContent>
798
+ </Sheet>
799
+ <TimesheetEntryCreateSheet
800
+ open={isTimesheetOpen}
801
+ onOpenChange={setIsTimesheetOpen}
802
+ project={timesheetProject}
803
+ task={task ? { id: task.id, label: task.name } : null}
804
+ />
805
+ </>
580
806
  );
581
807
  }