@hed-hog/operations 0.0.329 → 0.0.331

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 (290) hide show
  1. package/README.md +5 -5
  2. package/dist/controllers/operations-collaborators.controller.d.ts +7 -216
  3. package/dist/controllers/operations-collaborators.controller.d.ts.map +1 -1
  4. package/dist/controllers/operations-contracts.controller.d.ts +6 -6
  5. package/dist/controllers/operations-projects.controller.d.ts +25 -0
  6. package/dist/controllers/operations-projects.controller.d.ts.map +1 -1
  7. package/dist/controllers/operations-projects.controller.js +48 -0
  8. package/dist/controllers/operations-projects.controller.js.map +1 -1
  9. package/dist/controllers/operations-reports.controller.d.ts +1 -1
  10. package/dist/controllers/operations-tasks.controller.d.ts +30 -5
  11. package/dist/controllers/operations-tasks.controller.d.ts.map +1 -1
  12. package/dist/controllers/operations-tasks.controller.js +43 -32
  13. package/dist/controllers/operations-tasks.controller.js.map +1 -1
  14. package/dist/controllers/operations-timesheets.controller.d.ts +9 -9
  15. package/dist/dashboard/components/DashboardLayout.d.ts +30 -0
  16. package/dist/dashboard/components/DashboardLayout.d.ts.map +1 -0
  17. package/dist/dashboard/components/DashboardLayout.js +87 -0
  18. package/dist/dashboard/components/DashboardLayout.js.map +1 -0
  19. package/dist/dashboard/components/widget-registry.d.ts +23 -0
  20. package/dist/dashboard/components/widget-registry.d.ts.map +1 -0
  21. package/dist/dashboard/components/widget-registry.js +245 -0
  22. package/dist/dashboard/components/widget-registry.js.map +1 -0
  23. package/dist/dashboard/hooks/useDashboardData.d.ts +20 -0
  24. package/dist/dashboard/hooks/useDashboardData.d.ts.map +1 -0
  25. package/dist/dashboard/hooks/useDashboardData.js +24 -0
  26. package/dist/dashboard/hooks/useDashboardData.js.map +1 -0
  27. package/dist/dashboard/types/widgets.types.d.ts +233 -0
  28. package/dist/dashboard/types/widgets.types.d.ts.map +1 -0
  29. package/dist/dashboard/types/widgets.types.js +6 -0
  30. package/dist/dashboard/types/widgets.types.js.map +1 -0
  31. package/dist/dashboard/widgets/CapacityDistribution.d.ts +23 -0
  32. package/dist/dashboard/widgets/CapacityDistribution.d.ts.map +1 -0
  33. package/dist/dashboard/widgets/CapacityDistribution.js +11 -0
  34. package/dist/dashboard/widgets/CapacityDistribution.js.map +1 -0
  35. package/dist/dashboard/widgets/EffortByProject.d.ts +22 -0
  36. package/dist/dashboard/widgets/EffortByProject.d.ts.map +1 -0
  37. package/dist/dashboard/widgets/EffortByProject.js +11 -0
  38. package/dist/dashboard/widgets/EffortByProject.js.map +1 -0
  39. package/dist/dashboard/widgets/HeadcountByArea.d.ts +24 -0
  40. package/dist/dashboard/widgets/HeadcountByArea.d.ts.map +1 -0
  41. package/dist/dashboard/widgets/HeadcountByArea.js +11 -0
  42. package/dist/dashboard/widgets/HeadcountByArea.js.map +1 -0
  43. package/dist/dashboard/widgets/ManagedProjectsStatus.d.ts +18 -0
  44. package/dist/dashboard/widgets/ManagedProjectsStatus.d.ts.map +1 -0
  45. package/dist/dashboard/widgets/ManagedProjectsStatus.js +12 -0
  46. package/dist/dashboard/widgets/ManagedProjectsStatus.js.map +1 -0
  47. package/dist/dashboard/widgets/MyHoursPeriodKpi.d.ts +22 -0
  48. package/dist/dashboard/widgets/MyHoursPeriodKpi.d.ts.map +1 -0
  49. package/dist/dashboard/widgets/MyHoursPeriodKpi.js +12 -0
  50. package/dist/dashboard/widgets/MyHoursPeriodKpi.js.map +1 -0
  51. package/dist/dashboard/widgets/MyOpenRequestsKpi.d.ts +19 -0
  52. package/dist/dashboard/widgets/MyOpenRequestsKpi.d.ts.map +1 -0
  53. package/dist/dashboard/widgets/MyOpenRequestsKpi.js +17 -0
  54. package/dist/dashboard/widgets/MyOpenRequestsKpi.js.map +1 -0
  55. package/dist/dashboard/widgets/MyPendingRequestsList.d.ts +23 -0
  56. package/dist/dashboard/widgets/MyPendingRequestsList.d.ts.map +1 -0
  57. package/dist/dashboard/widgets/MyPendingRequestsList.js +14 -0
  58. package/dist/dashboard/widgets/MyPendingRequestsList.js.map +1 -0
  59. package/dist/dashboard/widgets/MyProjectAllocationsKpi.d.ts +22 -0
  60. package/dist/dashboard/widgets/MyProjectAllocationsKpi.d.ts.map +1 -0
  61. package/dist/dashboard/widgets/MyProjectAllocationsKpi.js +11 -0
  62. package/dist/dashboard/widgets/MyProjectAllocationsKpi.js.map +1 -0
  63. package/dist/dashboard/widgets/MyQuickActions.d.ts +23 -0
  64. package/dist/dashboard/widgets/MyQuickActions.d.ts.map +1 -0
  65. package/dist/dashboard/widgets/MyQuickActions.js +18 -0
  66. package/dist/dashboard/widgets/MyQuickActions.js.map +1 -0
  67. package/dist/dashboard/widgets/MyRelevantDeadlines.d.ts +23 -0
  68. package/dist/dashboard/widgets/MyRelevantDeadlines.d.ts.map +1 -0
  69. package/dist/dashboard/widgets/MyRelevantDeadlines.js +22 -0
  70. package/dist/dashboard/widgets/MyRelevantDeadlines.js.map +1 -0
  71. package/dist/dashboard/widgets/MyTimesheetStatusKpi.d.ts +17 -0
  72. package/dist/dashboard/widgets/MyTimesheetStatusKpi.d.ts.map +1 -0
  73. package/dist/dashboard/widgets/MyTimesheetStatusKpi.js +11 -0
  74. package/dist/dashboard/widgets/MyTimesheetStatusKpi.js.map +1 -0
  75. package/dist/dashboard/widgets/MyWeeklyJourney.d.ts +21 -0
  76. package/dist/dashboard/widgets/MyWeeklyJourney.d.ts.map +1 -0
  77. package/dist/dashboard/widgets/MyWeeklyJourney.js +19 -0
  78. package/dist/dashboard/widgets/MyWeeklyJourney.js.map +1 -0
  79. package/dist/dashboard/widgets/PortfolioCostsKpi.d.ts +19 -0
  80. package/dist/dashboard/widgets/PortfolioCostsKpi.d.ts.map +1 -0
  81. package/dist/dashboard/widgets/PortfolioCostsKpi.js +12 -0
  82. package/dist/dashboard/widgets/PortfolioCostsKpi.js.map +1 -0
  83. package/dist/dashboard/widgets/PortfolioEffortKpi.d.ts +18 -0
  84. package/dist/dashboard/widgets/PortfolioEffortKpi.d.ts.map +1 -0
  85. package/dist/dashboard/widgets/PortfolioEffortKpi.js +8 -0
  86. package/dist/dashboard/widgets/PortfolioEffortKpi.js.map +1 -0
  87. package/dist/dashboard/widgets/PortfolioProjectsKpi.d.ts +22 -0
  88. package/dist/dashboard/widgets/PortfolioProjectsKpi.d.ts.map +1 -0
  89. package/dist/dashboard/widgets/PortfolioProjectsKpi.js +56 -0
  90. package/dist/dashboard/widgets/PortfolioProjectsKpi.js.map +1 -0
  91. package/dist/dashboard/widgets/PortfolioRiskKpi.d.ts +19 -0
  92. package/dist/dashboard/widgets/PortfolioRiskKpi.d.ts.map +1 -0
  93. package/dist/dashboard/widgets/PortfolioRiskKpi.js +11 -0
  94. package/dist/dashboard/widgets/PortfolioRiskKpi.js.map +1 -0
  95. package/dist/dashboard/widgets/ProjectStatusOverview.d.ts +19 -0
  96. package/dist/dashboard/widgets/ProjectStatusOverview.d.ts.map +1 -0
  97. package/dist/dashboard/widgets/ProjectStatusOverview.js +18 -0
  98. package/dist/dashboard/widgets/ProjectStatusOverview.js.map +1 -0
  99. package/dist/dashboard/widgets/StrategicDeadlines.d.ts +24 -0
  100. package/dist/dashboard/widgets/StrategicDeadlines.d.ts.map +1 -0
  101. package/dist/dashboard/widgets/StrategicDeadlines.js +22 -0
  102. package/dist/dashboard/widgets/StrategicDeadlines.js.map +1 -0
  103. package/dist/dashboard/widgets/TeamApprovalQueue.d.ts +24 -0
  104. package/dist/dashboard/widgets/TeamApprovalQueue.d.ts.map +1 -0
  105. package/dist/dashboard/widgets/TeamApprovalQueue.js +12 -0
  106. package/dist/dashboard/widgets/TeamApprovalQueue.js.map +1 -0
  107. package/dist/dashboard/widgets/TeamCapacityKpi.d.ts +18 -0
  108. package/dist/dashboard/widgets/TeamCapacityKpi.d.ts.map +1 -0
  109. package/dist/dashboard/widgets/TeamCapacityKpi.js +19 -0
  110. package/dist/dashboard/widgets/TeamCapacityKpi.js.map +1 -0
  111. package/dist/dashboard/widgets/TeamHeadcountKpi.d.ts +22 -0
  112. package/dist/dashboard/widgets/TeamHeadcountKpi.d.ts.map +1 -0
  113. package/dist/dashboard/widgets/TeamHeadcountKpi.js +56 -0
  114. package/dist/dashboard/widgets/TeamHeadcountKpi.js.map +1 -0
  115. package/dist/dashboard/widgets/TeamHoursKpi.d.ts +19 -0
  116. package/dist/dashboard/widgets/TeamHoursKpi.d.ts.map +1 -0
  117. package/dist/dashboard/widgets/TeamHoursKpi.js +13 -0
  118. package/dist/dashboard/widgets/TeamHoursKpi.js.map +1 -0
  119. package/dist/dashboard/widgets/TeamPendingApprovalsKpi.d.ts +20 -0
  120. package/dist/dashboard/widgets/TeamPendingApprovalsKpi.d.ts.map +1 -0
  121. package/dist/dashboard/widgets/TeamPendingApprovalsKpi.js +11 -0
  122. package/dist/dashboard/widgets/TeamPendingApprovalsKpi.js.map +1 -0
  123. package/dist/dashboard/widgets/TeamUtilizationOverview.d.ts +18 -0
  124. package/dist/dashboard/widgets/TeamUtilizationOverview.d.ts.map +1 -0
  125. package/dist/dashboard/widgets/TeamUtilizationOverview.js +17 -0
  126. package/dist/dashboard/widgets/TeamUtilizationOverview.js.map +1 -0
  127. package/dist/dashboard/widgets/TeamWorkloadAlerts.d.ts +24 -0
  128. package/dist/dashboard/widgets/TeamWorkloadAlerts.d.ts.map +1 -0
  129. package/dist/dashboard/widgets/TeamWorkloadAlerts.js +19 -0
  130. package/dist/dashboard/widgets/TeamWorkloadAlerts.js.map +1 -0
  131. package/dist/dashboard/widgets/index.d.ts +24 -0
  132. package/dist/dashboard/widgets/index.d.ts.map +1 -0
  133. package/dist/dashboard/widgets/index.js +54 -0
  134. package/dist/dashboard/widgets/index.js.map +1 -0
  135. package/dist/dto/create-collaborator.dto.d.ts +0 -1
  136. package/dist/dto/create-collaborator.dto.d.ts.map +1 -1
  137. package/dist/dto/create-collaborator.dto.js +0 -6
  138. package/dist/dto/create-collaborator.dto.js.map +1 -1
  139. package/dist/index.d.ts +2 -0
  140. package/dist/index.d.ts.map +1 -1
  141. package/dist/index.js +2 -0
  142. package/dist/index.js.map +1 -1
  143. package/dist/operations.controller.d.ts +42 -0
  144. package/dist/operations.controller.d.ts.map +1 -1
  145. package/dist/operations.service.d.ts +178 -264
  146. package/dist/operations.service.d.ts.map +1 -1
  147. package/dist/operations.service.js +2170 -1340
  148. package/dist/operations.service.js.map +1 -1
  149. package/dist/operations.service.spec.js +345 -174
  150. package/dist/operations.service.spec.js.map +1 -1
  151. package/hedhog/data/dashboard_component.yaml +66 -0
  152. package/hedhog/data/dashboard_component_role.yaml +8 -8
  153. package/hedhog/data/dashboard_item.yaml +25 -25
  154. package/hedhog/data/dashboard_role.yaml +1 -1
  155. package/hedhog/data/menu.yaml +6 -16
  156. package/hedhog/data/role.yaml +1 -1
  157. package/hedhog/data/route.yaml +116 -55
  158. package/hedhog/frontend/app/_components/async-options-combobox.tsx.ejs +15 -9
  159. package/hedhog/frontend/app/_components/collaborator-form-screen.tsx.ejs +39 -99
  160. package/hedhog/frontend/app/_components/collaborator-picker.tsx.ejs +158 -0
  161. package/hedhog/frontend/app/_components/my-project-summary-screen.tsx.ejs +314 -116
  162. package/hedhog/frontend/app/_components/project-assignments-tab.tsx.ejs +434 -449
  163. package/hedhog/frontend/app/_components/project-costs-section.tsx.ejs +51 -81
  164. package/hedhog/frontend/app/_components/project-details-screen.tsx.ejs +328 -423
  165. package/hedhog/frontend/app/_components/project-file-attachments.tsx.ejs +371 -0
  166. package/hedhog/frontend/app/_components/project-form-screen.tsx.ejs +446 -377
  167. package/hedhog/frontend/app/_components/task-detail-sheet.tsx.ejs +803 -581
  168. package/hedhog/frontend/app/_components/task-file-attachments.tsx.ejs +14 -9
  169. package/hedhog/frontend/app/_components/task-form-fields.tsx.ejs +406 -0
  170. package/hedhog/frontend/app/_components/task-form-sheet.tsx.ejs +629 -784
  171. package/hedhog/frontend/app/_components/task-info-display.tsx.ejs +137 -0
  172. package/hedhog/frontend/app/_components/timesheet-entry-create-sheet.tsx.ejs +306 -0
  173. package/hedhog/frontend/app/_lib/api.ts.ejs +480 -476
  174. package/hedhog/frontend/app/_lib/hooks/use-values-visibility.ts.ejs +61 -0
  175. package/hedhog/frontend/app/_lib/types.ts.ejs +66 -5
  176. package/hedhog/frontend/app/_lib/utils/format.ts.ejs +0 -2
  177. package/hedhog/frontend/app/_lib/utils/task-ui.ts.ejs +43 -0
  178. package/hedhog/frontend/app/approvals/page.tsx.ejs +11 -2
  179. package/hedhog/frontend/app/collaborator-types/page.tsx.ejs +6 -1
  180. package/hedhog/frontend/app/collaborators/page.tsx.ejs +127 -42
  181. package/hedhog/frontend/app/contracts/page.tsx.ejs +29 -8
  182. package/hedhog/frontend/app/dashboard/widgets/CapacityDistribution.tsx.ejs +84 -0
  183. package/hedhog/frontend/app/dashboard/widgets/EffortByProject.tsx.ejs +85 -0
  184. package/hedhog/frontend/app/dashboard/widgets/HeadcountByArea.tsx.ejs +101 -0
  185. package/hedhog/frontend/app/dashboard/widgets/ManagedProjectsStatus.tsx.ejs +113 -0
  186. package/hedhog/frontend/app/dashboard/widgets/MyHoursPeriodKpi.tsx.ejs +87 -0
  187. package/hedhog/frontend/app/dashboard/widgets/MyOpenRequestsKpi.tsx.ejs +97 -0
  188. package/hedhog/frontend/app/dashboard/widgets/MyPendingRequestsList.tsx.ejs +99 -0
  189. package/hedhog/frontend/app/dashboard/widgets/MyProjectAllocationsKpi.tsx.ejs +78 -0
  190. package/hedhog/frontend/app/dashboard/widgets/MyQuickActions.tsx.ejs +130 -0
  191. package/hedhog/frontend/app/dashboard/widgets/MyRelevantDeadlines.tsx.ejs +144 -0
  192. package/hedhog/frontend/app/dashboard/widgets/MyTimesheetStatusKpi.tsx.ejs +78 -0
  193. package/hedhog/frontend/app/dashboard/widgets/MyWeeklyJourney.tsx.ejs +99 -0
  194. package/hedhog/frontend/app/dashboard/widgets/PortfolioCostsKpi.tsx.ejs +112 -0
  195. package/hedhog/frontend/app/dashboard/widgets/PortfolioEffortKpi.tsx.ejs +93 -0
  196. package/hedhog/frontend/app/dashboard/widgets/PortfolioProjectsKpi.tsx.ejs +96 -0
  197. package/hedhog/frontend/app/dashboard/widgets/PortfolioRiskKpi.tsx.ejs +115 -0
  198. package/hedhog/frontend/app/dashboard/widgets/ProjectStatusOverview.tsx.ejs +120 -0
  199. package/hedhog/frontend/app/dashboard/widgets/StrategicDeadlines.tsx.ejs +146 -0
  200. package/hedhog/frontend/app/dashboard/widgets/TeamApprovalQueue.tsx.ejs +108 -0
  201. package/hedhog/frontend/app/dashboard/widgets/TeamCapacityKpi.tsx.ejs +97 -0
  202. package/hedhog/frontend/app/dashboard/widgets/TeamHeadcountKpi.tsx.ejs +100 -0
  203. package/hedhog/frontend/app/dashboard/widgets/TeamHoursKpi.tsx.ejs +104 -0
  204. package/hedhog/frontend/app/dashboard/widgets/TeamPendingApprovalsKpi.tsx.ejs +110 -0
  205. package/hedhog/frontend/app/dashboard/widgets/TeamUtilizationOverview.tsx.ejs +115 -0
  206. package/hedhog/frontend/app/dashboard/widgets/TeamWorkloadAlerts.tsx.ejs +117 -0
  207. package/hedhog/frontend/app/dashboard/widgets/index.ts.ejs +26 -0
  208. package/hedhog/frontend/app/departments/page.tsx.ejs +6 -1
  209. package/hedhog/frontend/app/my-projects/page.tsx.ejs +59 -16
  210. package/hedhog/frontend/app/my-tasks/page.tsx.ejs +329 -106
  211. package/hedhog/frontend/app/project-cost-categories/page.tsx.ejs +58 -52
  212. package/hedhog/frontend/app/project-cost-types/page.tsx.ejs +58 -51
  213. package/hedhog/frontend/app/projects/page.tsx.ejs +436 -35
  214. package/hedhog/frontend/app/reports/collaborators/page.tsx.ejs +65 -52
  215. package/hedhog/frontend/app/reports/projects/page.tsx.ejs +80 -82
  216. package/hedhog/frontend/app/schedule-adjustments/page.tsx.ejs +13 -2
  217. package/hedhog/frontend/app/time-off/page.tsx.ejs +6 -1
  218. package/hedhog/frontend/app/timesheets/page.tsx.ejs +10 -4
  219. package/hedhog/frontend/messages/en.json +460 -61
  220. package/hedhog/frontend/messages/operations/en.json +61 -52
  221. package/hedhog/frontend/messages/operations/pt.json +59 -43
  222. package/hedhog/frontend/messages/pt.json +460 -61
  223. package/hedhog/frontend/widgets/capacity-distribution.tsx.ejs +17 -0
  224. package/hedhog/frontend/widgets/effort-by-project.tsx.ejs +17 -0
  225. package/hedhog/frontend/widgets/headcount-by-area.tsx.ejs +17 -0
  226. package/hedhog/frontend/widgets/index.ts.ejs +25 -0
  227. package/hedhog/frontend/widgets/managed-projects-status.tsx.ejs +17 -0
  228. package/hedhog/frontend/widgets/my-hours-period-kpi.tsx.ejs +17 -0
  229. package/hedhog/frontend/widgets/my-open-requests-kpi.tsx.ejs +17 -0
  230. package/hedhog/frontend/widgets/my-pending-requests-list.tsx.ejs +17 -0
  231. package/hedhog/frontend/widgets/my-project-allocations-kpi.tsx.ejs +17 -0
  232. package/hedhog/frontend/widgets/my-quick-actions.tsx.ejs +17 -0
  233. package/hedhog/frontend/widgets/my-relevant-deadlines.tsx.ejs +17 -0
  234. package/hedhog/frontend/widgets/my-timesheet-status-kpi.tsx.ejs +17 -0
  235. package/hedhog/frontend/widgets/my-weekly-journey.tsx.ejs +17 -0
  236. package/hedhog/frontend/widgets/portfolio-costs-kpi.tsx.ejs +17 -0
  237. package/hedhog/frontend/widgets/portfolio-effort-kpi.tsx.ejs +17 -0
  238. package/hedhog/frontend/widgets/portfolio-projects-kpi.tsx.ejs +17 -0
  239. package/hedhog/frontend/widgets/portfolio-risk-kpi.tsx.ejs +17 -0
  240. package/hedhog/frontend/widgets/project-status-overview.tsx.ejs +17 -0
  241. package/hedhog/frontend/widgets/shared-operations-widget.tsx.ejs +170 -0
  242. package/hedhog/frontend/widgets/strategic-deadlines.tsx.ejs +17 -0
  243. package/hedhog/frontend/widgets/team-approval-queue.tsx.ejs +17 -0
  244. package/hedhog/frontend/widgets/team-capacity-kpi.tsx.ejs +17 -0
  245. package/hedhog/frontend/widgets/team-headcount-kpi.tsx.ejs +17 -0
  246. package/hedhog/frontend/widgets/team-hours-kpi.tsx.ejs +17 -0
  247. package/hedhog/frontend/widgets/team-pending-approvals-kpi.tsx.ejs +17 -0
  248. package/hedhog/frontend/widgets/team-utilization-overview.tsx.ejs +17 -0
  249. package/hedhog/frontend/widgets/team-workload-alerts.tsx.ejs +17 -0
  250. package/hedhog/table/operations_collaborator.yaml +8 -13
  251. package/hedhog/table/operations_project.yaml +1 -1
  252. package/hedhog/table/operations_project_file.yaml +23 -0
  253. package/hedhog/table/operations_task.yaml +76 -69
  254. package/hedhog/table/operations_task_activity.yaml +51 -0
  255. package/package.json +7 -6
  256. package/src/controllers/operations-projects.controller.ts +41 -8
  257. package/src/controllers/operations-tasks.controller.ts +156 -166
  258. package/src/dashboard/README.md +214 -0
  259. package/src/dashboard/components/DashboardLayout.tsx +131 -0
  260. package/src/dashboard/components/widget-registry.ts +255 -0
  261. package/src/dashboard/hooks/useDashboardData.ts +29 -0
  262. package/src/dashboard/types/widgets.types.ts +237 -0
  263. package/src/dashboard/widgets/CapacityDistribution.tsx +56 -0
  264. package/src/dashboard/widgets/EffortByProject.tsx +51 -0
  265. package/src/dashboard/widgets/HeadcountByArea.tsx +57 -0
  266. package/src/dashboard/widgets/ManagedProjectsStatus.tsx +53 -0
  267. package/src/dashboard/widgets/MyHoursPeriodKpi.tsx +87 -0
  268. package/src/dashboard/widgets/MyOpenRequestsKpi.tsx +51 -0
  269. package/src/dashboard/widgets/MyPendingRequestsList.tsx +63 -0
  270. package/src/dashboard/widgets/MyProjectAllocationsKpi.tsx +57 -0
  271. package/src/dashboard/widgets/MyQuickActions.tsx +62 -0
  272. package/src/dashboard/widgets/MyRelevantDeadlines.tsx +84 -0
  273. package/src/dashboard/widgets/MyTimesheetStatusKpi.tsx +65 -0
  274. package/src/dashboard/widgets/MyWeeklyJourney.tsx +57 -0
  275. package/src/dashboard/widgets/PortfolioCostsKpi.tsx +48 -0
  276. package/src/dashboard/widgets/PortfolioEffortKpi.tsx +41 -0
  277. package/src/dashboard/widgets/PortfolioRiskKpi.tsx +50 -0
  278. package/src/dashboard/widgets/ProjectStatusOverview.tsx +52 -0
  279. package/src/dashboard/widgets/StrategicDeadlines.tsx +93 -0
  280. package/src/dashboard/widgets/TeamApprovalQueue.tsx +70 -0
  281. package/src/dashboard/widgets/TeamCapacityKpi.tsx +50 -0
  282. package/src/dashboard/widgets/TeamHoursKpi.tsx +51 -0
  283. package/src/dashboard/widgets/TeamPendingApprovalsKpi.tsx +53 -0
  284. package/src/dashboard/widgets/TeamUtilizationOverview.tsx +62 -0
  285. package/src/dashboard/widgets/TeamWorkloadAlerts.tsx +81 -0
  286. package/src/dashboard/widgets/index.ts +26 -0
  287. package/src/dto/create-collaborator.dto.ts +4 -11
  288. package/src/index.ts +3 -0
  289. package/src/operations.service.spec.ts +988 -764
  290. package/src/operations.service.ts +4300 -2538
@@ -1,7 +1,6 @@
1
1
  'use client';
2
2
 
3
3
  import { EmptyState, Page } from '@/components/entity-list';
4
- import { RichTextEditor } from '@/components/rich-text-editor';
5
4
  import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
6
5
  import { Button } from '@/components/ui/button';
7
6
  import { Card, CardContent } from '@/components/ui/card';
@@ -14,6 +13,7 @@ import {
14
13
  import {
15
14
  Dialog,
16
15
  DialogContent,
16
+ DialogDescription,
17
17
  DialogFooter,
18
18
  DialogHeader,
19
19
  DialogTitle,
@@ -80,11 +80,14 @@ import {
80
80
  CheckCircle2,
81
81
  ChevronRight,
82
82
  ClipboardList,
83
+ Eye,
84
+ EyeOff,
83
85
  FileText,
84
86
  FolderKanban,
85
87
  Gauge,
86
88
  GitCommitHorizontal,
87
89
  HeartPulse,
90
+ History,
88
91
  LineChart as LineChartIcon,
89
92
  Loader2,
90
93
  MessageSquare,
@@ -93,6 +96,7 @@ import {
93
96
  Plus,
94
97
  Rocket,
95
98
  Search,
99
+ Send,
96
100
  SlidersHorizontal,
97
101
  Timer,
98
102
  Trash2,
@@ -123,8 +127,11 @@ import {
123
127
  YAxis,
124
128
  } from 'recharts';
125
129
  import { fetchOperations, mutateOperations } from '../_lib/api';
126
- import { useMentionItems } from '../_lib/hooks/use-mention-items';
127
130
  import { useOperationsAccess } from '../_lib/hooks/use-operations-access';
131
+ import {
132
+ MASKED_VALUE,
133
+ useValuesVisibility,
134
+ } from '../_lib/hooks/use-values-visibility';
128
135
  import type {
129
136
  OperationsProjectDetails,
130
137
  OperationsTaskOption,
@@ -144,11 +151,13 @@ import { ProjectFormScreen } from './project-form-screen';
144
151
  import { SectionCard } from './section-card';
145
152
  import { StatusBadge } from './status-badge';
146
153
  import {
147
- TaskCommentsSection,
148
154
  TaskDetailSheet,
149
155
  type TaskDetailSheetData,
150
156
  } from './task-detail-sheet';
151
- import { TaskFileAttachments } from './task-file-attachments';
157
+ import { ProjectFileAttachments } from './project-file-attachments';
158
+ import { TaskFileAttachments } from './task-file-attachments';
159
+ import { TaskFormSheet } from './task-form-sheet';
160
+ import { TimesheetEntryCreateSheet } from './timesheet-entry-create-sheet';
152
161
 
153
162
  type BoardColumnId = 'todo' | 'doing' | 'review' | 'done';
154
163
 
@@ -169,6 +178,8 @@ type BoardTask = {
169
178
  createdAt: string | null;
170
179
  commentCount: number;
171
180
  fileCount: number;
181
+ doingStartedAt: string | null;
182
+ totalDoingMinutes: number;
172
183
  };
173
184
 
174
185
  type ApiBoardTask = Partial<BoardTask> & {
@@ -178,28 +189,6 @@ type ApiBoardTask = Partial<BoardTask> & {
178
189
  priority?: BoardTask['priority'] | null;
179
190
  };
180
191
 
181
- type TaskFormState = {
182
- name: string;
183
- description: string;
184
- priority: 'low' | 'medium' | 'high';
185
- status: BoardColumnId;
186
- assigneeCollaboratorId: string;
187
- dueDate: string;
188
- estimateHours: string;
189
- tags: string;
190
- };
191
-
192
- const EMPTY_TASK_FORM: TaskFormState = {
193
- name: '',
194
- description: '',
195
- priority: 'medium',
196
- status: 'todo',
197
- assigneeCollaboratorId: 'none',
198
- dueDate: '',
199
- estimateHours: '',
200
- tags: '',
201
- };
202
-
203
192
  type BoardColumns = Record<BoardColumnId, BoardTask[]>;
204
193
 
205
194
  type BoardState = {
@@ -207,6 +196,14 @@ type BoardState = {
207
196
  columns: BoardColumns;
208
197
  };
209
198
 
199
+ type TimesheetEntryPrefill = {
200
+ projectId: number;
201
+ projectAssignmentId?: number | null;
202
+ projectLabel: string;
203
+ taskId: number;
204
+ taskLabel: string;
205
+ };
206
+
210
207
  const KANBAN_COLUMNS: Array<{ id: BoardColumnId; label: string }> = [
211
208
  { id: 'todo', label: 'Backlog' },
212
209
  { id: 'doing', label: 'Em execução' },
@@ -246,6 +243,8 @@ function apiTaskToBoardTask(row: ApiBoardTask): BoardTask {
246
243
  0,
247
244
  fileCount:
248
245
  ((row as BoardTask & Record<string, unknown>).fileCount as number) ?? 0,
246
+ doingStartedAt: row.doingStartedAt ?? null,
247
+ totalDoingMinutes: row.totalDoingMinutes ?? 0,
249
248
  };
250
249
  }
251
250
 
@@ -258,6 +257,23 @@ function splitTasksByColumn(tasks: BoardTask[]): BoardColumns {
258
257
  };
259
258
  }
260
259
 
260
+ function replaceTaskInColumns(
261
+ columns: BoardColumns,
262
+ task: BoardTask
263
+ ): BoardColumns {
264
+ const nextColumns = {
265
+ todo: columns.todo.filter((item) => item.id !== task.id),
266
+ doing: columns.doing.filter((item) => item.id !== task.id),
267
+ review: columns.review.filter((item) => item.id !== task.id),
268
+ done: columns.done.filter((item) => item.id !== task.id),
269
+ };
270
+
271
+ return {
272
+ ...nextColumns,
273
+ [task.status]: [task, ...nextColumns[task.status]],
274
+ };
275
+ }
276
+
261
277
  const boardChartConfig = {
262
278
  allocation: { label: 'Alocacao', color: 'hsl(201 96% 32%)' },
263
279
  loggedHours: { label: 'Horas', color: 'hsl(166 72% 28%)' },
@@ -379,26 +395,6 @@ function getUserPhotoUrl(photoId?: number | null) {
379
395
  : null;
380
396
  }
381
397
 
382
- function normalizeDateInputValue(value?: string | null) {
383
- if (!value) {
384
- return '';
385
- }
386
-
387
- const normalizedValue = String(value).trim();
388
- const directMatch = normalizedValue.match(/^\d{4}-\d{2}-\d{2}/);
389
-
390
- if (directMatch?.[0]) {
391
- return directMatch[0];
392
- }
393
-
394
- const parsedDate = new Date(normalizedValue);
395
- if (Number.isNaN(parsedDate.getTime())) {
396
- return '';
397
- }
398
-
399
- return parsedDate.toISOString().slice(0, 10);
400
- }
401
-
402
398
  function clampPercent(value?: number | null) {
403
399
  if (typeof value !== 'number' || Number.isNaN(value)) {
404
400
  return 0;
@@ -1098,8 +1094,8 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
1098
1094
  const contractT = useTranslations('operations.ContractFormPage');
1099
1095
  const { request, currentLocaleCode, getSettingValue } = useApp();
1100
1096
  const access = useOperationsAccess();
1101
- const mentionItems = useMentionItems(request);
1102
1097
  const isLimitedView = !access.isDirector && !access.isSupervisor;
1098
+ const { valuesVisible, toggleValuesVisible } = useValuesVisibility();
1103
1099
  const router = useRouter();
1104
1100
  const pathname = usePathname();
1105
1101
  const searchParams = useSearchParams();
@@ -1274,11 +1270,13 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
1274
1270
  );
1275
1271
  const [taskFormOpen, setTaskFormOpen] = useState(false);
1276
1272
  const [editingTaskId, setEditingTaskId] = useState<number | null>(null);
1277
- const [taskFormData, setTaskFormData] =
1278
- useState<TaskFormState>(EMPTY_TASK_FORM);
1279
- const [taskFormLoading, setTaskFormLoading] = useState(false);
1273
+ const [createDefaultStatus, setCreateDefaultStatus] =
1274
+ useState<BoardColumnId>('todo');
1280
1275
  const [deletePromptTask, setDeletePromptTask] =
1281
1276
  useState<TaskDetailSheetData | null>(null);
1277
+ const [deleteProjectDialogOpen, setDeleteProjectDialogOpen] = useState(false);
1278
+ const [deleteProjectConfirmText, setDeleteProjectConfirmText] = useState('');
1279
+ const [deletingProject, setDeletingProject] = useState(false);
1282
1280
  const [inlineCreateColumn, setInlineCreateColumn] =
1283
1281
  useState<BoardColumnId | null>(null);
1284
1282
  const [inlineCreateName, setInlineCreateName] = useState('');
@@ -1296,6 +1294,10 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
1296
1294
  const [restoringTaskId, setRestoringTaskId] = useState<number | null>(null);
1297
1295
  const [deletingTaskId, setDeletingTaskId] = useState<number | null>(null);
1298
1296
  const [activeDragTask, setActiveDragTask] = useState<BoardTask | null>(null);
1297
+ const [isTimesheetEntrySheetOpen, setIsTimesheetEntrySheetOpen] =
1298
+ useState(false);
1299
+ const [timesheetPrefill, setTimesheetPrefill] =
1300
+ useState<TimesheetEntryPrefill | null>(null);
1299
1301
 
1300
1302
  const apiTasks = useMemo(() => rawTasks.map(apiTaskToBoardTask), [rawTasks]);
1301
1303
  const archivedTasks = useMemo(
@@ -1313,6 +1315,29 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
1313
1315
  return splitTasksByColumn(apiTasks);
1314
1316
  }, [project, boardState, apiTasks]);
1315
1317
 
1318
+ const editingTask = useMemo(() => {
1319
+ if (!editingTaskId) return null;
1320
+ return (
1321
+ Object.values(taskColumns)
1322
+ .flat()
1323
+ .find((task) => task.id === editingTaskId) ??
1324
+ apiTasks.find((task) => task.id === editingTaskId) ??
1325
+ archivedTasks.find((task) => task.id === editingTaskId) ??
1326
+ null
1327
+ );
1328
+ }, [apiTasks, archivedTasks, editingTaskId, taskColumns]);
1329
+
1330
+ const editingTaskAsOption = useMemo(() => {
1331
+ if (!editingTask) return null;
1332
+ return {
1333
+ ...editingTask,
1334
+ label: editingTask.name,
1335
+ projectId,
1336
+ projectName: project?.name ?? '',
1337
+ projectCode: project?.code,
1338
+ };
1339
+ }, [editingTask, projectId, project]);
1340
+
1316
1341
  const filteredTaskColumns: BoardColumns = useMemo(() => {
1317
1342
  const normalizedSearch = boardSearch.trim().toLocaleLowerCase();
1318
1343
  const filterTask = (task: BoardTask) => {
@@ -1366,8 +1391,8 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
1366
1391
 
1367
1392
  const openCreateTaskForm = useCallback(
1368
1393
  (defaultStatus: BoardColumnId = 'todo') => {
1394
+ setCreateDefaultStatus(defaultStatus);
1369
1395
  setEditingTaskId(null);
1370
- setTaskFormData({ ...EMPTY_TASK_FORM, status: defaultStatus });
1371
1396
  setTaskFormOpen(true);
1372
1397
  },
1373
1398
  []
@@ -1375,73 +1400,10 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
1375
1400
 
1376
1401
  const openEditTaskForm = useCallback((task: BoardTask) => {
1377
1402
  setEditingTaskId(task.id);
1378
- setTaskFormData({
1379
- name: task.name,
1380
- description: task.description ?? '',
1381
- priority: task.priority,
1382
- status: task.status,
1383
- assigneeCollaboratorId: task.assigneeCollaboratorId
1384
- ? String(task.assigneeCollaboratorId)
1385
- : 'none',
1386
- dueDate: normalizeDateInputValue(task.dueDate),
1387
- estimateHours:
1388
- task.estimateHours != null ? String(task.estimateHours) : '',
1389
- tags: task.tags ?? '',
1390
- });
1391
1403
  setSelectedTask(null);
1392
1404
  setTaskFormOpen(true);
1393
1405
  }, []);
1394
1406
 
1395
- const handleTaskFormSubmit = useCallback(async () => {
1396
- if (!taskFormData.name.trim()) return;
1397
- setTaskFormLoading(true);
1398
- try {
1399
- const payload: Record<string, unknown> = {
1400
- name: taskFormData.name.trim(),
1401
- description: taskFormData.description || null,
1402
- priority: taskFormData.priority,
1403
- status: taskFormData.status,
1404
- assigneeCollaboratorId:
1405
- taskFormData.assigneeCollaboratorId !== 'none'
1406
- ? Number(taskFormData.assigneeCollaboratorId)
1407
- : null,
1408
- dueDate: taskFormData.dueDate || null,
1409
- estimateHours: taskFormData.estimateHours
1410
- ? Number(taskFormData.estimateHours)
1411
- : null,
1412
- tags: taskFormData.tags || null,
1413
- };
1414
- if (editingTaskId) {
1415
- await mutateOperations(
1416
- request,
1417
- `/operations/tasks/${editingTaskId}`,
1418
- 'PATCH',
1419
- payload
1420
- );
1421
- } else {
1422
- await mutateOperations(request, '/operations/tasks', 'POST', {
1423
- projectId,
1424
- ...payload,
1425
- });
1426
- }
1427
- setBoardState(null);
1428
- await refetchTasks();
1429
- await refetchArchivedTasks();
1430
- setTaskFormOpen(false);
1431
- setEditingTaskId(null);
1432
- setTaskFormData(EMPTY_TASK_FORM);
1433
- } finally {
1434
- setTaskFormLoading(false);
1435
- }
1436
- }, [
1437
- taskFormData,
1438
- editingTaskId,
1439
- projectId,
1440
- request,
1441
- refetchTasks,
1442
- refetchArchivedTasks,
1443
- ]);
1444
-
1445
1407
  const handleArchiveTask = useCallback(
1446
1408
  async (taskId: number) => {
1447
1409
  setArchivingTaskId(taskId);
@@ -1541,6 +1503,43 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
1541
1503
  [request, refetchTasks, refetchArchivedTasks]
1542
1504
  );
1543
1505
 
1506
+ const handleDeleteProject = useCallback(async () => {
1507
+ setDeletingProject(true);
1508
+ try {
1509
+ await mutateOperations(request, `/operations/projects/${projectId}`, 'DELETE');
1510
+ router.push('/operations/projects');
1511
+ } catch {
1512
+ // ignore
1513
+ } finally {
1514
+ setDeletingProject(false);
1515
+ setDeleteProjectDialogOpen(false);
1516
+ setDeleteProjectConfirmText('');
1517
+ }
1518
+ }, [request, projectId, router]);
1519
+
1520
+ const openTimesheetEntrySheet = useCallback(
1521
+ (task: TaskDetailSheetData) => {
1522
+ if (!project) {
1523
+ return;
1524
+ }
1525
+
1526
+ const projectLabel = [project.name, project.code]
1527
+ .filter(Boolean)
1528
+ .join(' • ');
1529
+
1530
+ setTimesheetPrefill({
1531
+ projectId,
1532
+ projectAssignmentId: task.projectAssignmentId ?? null,
1533
+ projectLabel: projectLabel || commonT('labels.notAssigned'),
1534
+ taskId: task.id,
1535
+ taskLabel: task.name,
1536
+ });
1537
+ setSelectedTask(null);
1538
+ setIsTimesheetEntrySheetOpen(true);
1539
+ },
1540
+ [commonT, project, projectId]
1541
+ );
1542
+
1544
1543
  const allocationChartData = useMemo(() => {
1545
1544
  if (projectStats?.allocationByCollaborator?.length) {
1546
1545
  return projectStats.allocationByCollaborator;
@@ -1614,14 +1613,33 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
1614
1613
  },
1615
1614
  });
1616
1615
 
1617
- // Persist to API
1618
- mutateOperations(request, `/operations/tasks/${taskId}`, 'PATCH', {
1619
- status: targetColumn,
1620
- }).catch(() => {
1621
- // Rollback optimistic update on error
1622
- setBoardState(null);
1623
- void refetchTasks();
1624
- });
1616
+ mutateOperations<ApiBoardTask>(
1617
+ request,
1618
+ `/operations/tasks/${taskId}`,
1619
+ 'PATCH',
1620
+ {
1621
+ status: targetColumn,
1622
+ }
1623
+ )
1624
+ .then((updatedTask) => {
1625
+ if (!updatedTask) {
1626
+ return;
1627
+ }
1628
+
1629
+ const updatedBoardTask = apiTaskToBoardTask(updatedTask);
1630
+ setBoardState((current) => ({
1631
+ projectId: current?.projectId ?? project.id,
1632
+ columns: replaceTaskInColumns(
1633
+ current?.columns ?? taskColumns,
1634
+ updatedBoardTask
1635
+ ),
1636
+ }));
1637
+ })
1638
+ .catch(() => {
1639
+ // Rollback optimistic update on error
1640
+ setBoardState(null);
1641
+ void refetchTasks();
1642
+ });
1625
1643
  },
1626
1644
  [findColumnByTask, taskColumns, project, request, refetchTasks]
1627
1645
  );
@@ -2078,6 +2096,20 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
2078
2096
  </div>
2079
2097
  </div>
2080
2098
  <div className="flex shrink-0 flex-wrap gap-1.5">
2099
+ <Button
2100
+ size="icon"
2101
+ variant="ghost"
2102
+ onClick={toggleValuesVisible}
2103
+ title={commonT(
2104
+ valuesVisible ? 'actions.hideValues' : 'actions.showValues'
2105
+ )}
2106
+ >
2107
+ {valuesVisible ? (
2108
+ <EyeOff className="h-4 w-4" />
2109
+ ) : (
2110
+ <Eye className="h-4 w-4" />
2111
+ )}
2112
+ </Button>
2081
2113
  {access.isDirector ? (
2082
2114
  <Button
2083
2115
  size="sm"
@@ -2088,6 +2120,17 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
2088
2120
  {commonT('actions.edit')}
2089
2121
  </Button>
2090
2122
  ) : null}
2123
+ {access.isDirector && project?.status === 'archived' ? (
2124
+ <Button
2125
+ size="sm"
2126
+ variant="destructive"
2127
+ className="cursor-pointer gap-1.5"
2128
+ onClick={() => setDeleteProjectDialogOpen(true)}
2129
+ >
2130
+ <Trash2 className="size-3.5" />
2131
+ {t('deleteProjectButton')}
2132
+ </Button>
2133
+ ) : null}
2091
2134
  </div>
2092
2135
  </div>
2093
2136
 
@@ -2385,6 +2428,7 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
2385
2428
  <TabsTrigger value="team">{t('tabs.team')}</TabsTrigger>
2386
2429
  <TabsTrigger value="timeline">{t('tabs.timeline')}</TabsTrigger>
2387
2430
  <TabsTrigger value="archive">{t('tabs.archive')}</TabsTrigger>
2431
+ <TabsTrigger value="files">{t('tabs.files')}</TabsTrigger>
2388
2432
  <TabsTrigger value="costs">{t('tabs.costs')}</TabsTrigger>
2389
2433
  </TabsList>
2390
2434
 
@@ -2492,11 +2536,13 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
2492
2536
  </dt>
2493
2537
  <dd className="font-medium">
2494
2538
  {project.budgetAmount
2495
- ? formatCurrency(
2496
- project.budgetAmount,
2497
- getSettingValue,
2498
- currentLocaleCode
2499
- )
2539
+ ? valuesVisible
2540
+ ? formatCurrency(
2541
+ project.budgetAmount,
2542
+ getSettingValue,
2543
+ currentLocaleCode
2544
+ )
2545
+ : MASKED_VALUE
2500
2546
  : commonT('labels.notAvailable')}
2501
2547
  </dd>
2502
2548
  </div>
@@ -2657,11 +2703,13 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
2657
2703
  </dt>
2658
2704
  <dd className="font-medium">
2659
2705
  {project.relatedContract.budgetAmount
2660
- ? formatCurrency(
2661
- project.relatedContract.budgetAmount,
2662
- getSettingValue,
2663
- currentLocaleCode
2664
- )
2706
+ ? valuesVisible
2707
+ ? formatCurrency(
2708
+ project.relatedContract.budgetAmount,
2709
+ getSettingValue,
2710
+ currentLocaleCode
2711
+ )
2712
+ : MASKED_VALUE
2665
2713
  : commonT('labels.notAvailable')}
2666
2714
  </dd>
2667
2715
  </div>
@@ -4121,7 +4169,20 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
4121
4169
  description={t('sections.costsDescription')}
4122
4170
  className="rounded-2xl border bg-card p-4 shadow-sm"
4123
4171
  >
4124
- <ProjectCostsSection projectId={projectId} />
4172
+ <ProjectCostsSection
4173
+ projectId={projectId}
4174
+ valuesVisible={valuesVisible}
4175
+ />
4176
+ </SectionCard>
4177
+ </TabsContent>
4178
+
4179
+ <TabsContent value="files">
4180
+ <SectionCard
4181
+ title={t('sections.files')}
4182
+ description={t('sections.filesDescription')}
4183
+ className="rounded-2xl border bg-card p-4 shadow-sm"
4184
+ >
4185
+ <ProjectFileAttachments projectId={projectId} />
4125
4186
  </SectionCard>
4126
4187
  </TabsContent>
4127
4188
  </Tabs>
@@ -4140,13 +4201,13 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
4140
4201
  }
4141
4202
  footer={
4142
4203
  selectedTask && !isLimitedView ? (
4143
- <div className="grid grid-cols-2 gap-3">
4204
+ <div className="grid grid-cols-1 gap-3 sm:grid-cols-3">
4144
4205
  {archivedTasks.some((task) => task.id === selectedTask.id) ? (
4145
4206
  <>
4146
4207
  <Button
4147
4208
  variant="outline"
4148
4209
  size="sm"
4149
- className="h-10 gap-2"
4210
+ className="h-10 gap-2 sm:col-span-2"
4150
4211
  disabled={restoringTaskId === selectedTask.id}
4151
4212
  onClick={() => void handleRestoreTask(selectedTask.id)}
4152
4213
  >
@@ -4168,26 +4229,67 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
4168
4229
  </Button>
4169
4230
  </>
4170
4231
  ) : (
4171
- <Button
4172
- variant="outline"
4173
- size="sm"
4174
- className="col-span-2 h-10 gap-2"
4175
- disabled={archivingTaskId === selectedTask.id}
4176
- onClick={() => void handleArchiveTask(selectedTask.id)}
4177
- >
4178
- {archivingTaskId === selectedTask.id ? (
4179
- <Loader2 className="size-3.5 animate-spin" />
4180
- ) : (
4181
- <Archive className="size-3.5" />
4182
- )}
4183
- {commonT('actions.archive')}
4184
- </Button>
4232
+ <>
4233
+ <Button
4234
+ variant="outline"
4235
+ size="sm"
4236
+ className="h-10 gap-2"
4237
+ onClick={() => openTimesheetEntrySheet(selectedTask)}
4238
+ >
4239
+ <Send className="size-3.5" />
4240
+ {commonT('actions.logHours')}
4241
+ </Button>
4242
+ <Button
4243
+ variant="outline"
4244
+ size="sm"
4245
+ className="h-10 gap-2 sm:col-span-2"
4246
+ disabled={archivingTaskId === selectedTask.id}
4247
+ onClick={() => void handleArchiveTask(selectedTask.id)}
4248
+ >
4249
+ {archivingTaskId === selectedTask.id ? (
4250
+ <Loader2 className="size-3.5 animate-spin" />
4251
+ ) : (
4252
+ <Archive className="size-3.5" />
4253
+ )}
4254
+ {commonT('actions.archive')}
4255
+ </Button>
4256
+ </>
4185
4257
  )}
4186
4258
  </div>
4187
4259
  ) : null
4188
4260
  }
4189
4261
  />
4190
4262
 
4263
+ <TimesheetEntryCreateSheet
4264
+ open={isTimesheetEntrySheetOpen}
4265
+ onOpenChange={(open) => {
4266
+ setIsTimesheetEntrySheetOpen(open);
4267
+ if (!open) {
4268
+ setTimesheetPrefill(null);
4269
+ }
4270
+ }}
4271
+ project={
4272
+ timesheetPrefill
4273
+ ? {
4274
+ id: timesheetPrefill.projectId,
4275
+ label: timesheetPrefill.projectLabel,
4276
+ projectAssignmentId: timesheetPrefill.projectAssignmentId,
4277
+ }
4278
+ : null
4279
+ }
4280
+ task={
4281
+ timesheetPrefill
4282
+ ? {
4283
+ id: timesheetPrefill.taskId,
4284
+ label: timesheetPrefill.taskLabel,
4285
+ }
4286
+ : null
4287
+ }
4288
+ onCreated={() => {
4289
+ void refetchTasks();
4290
+ }}
4291
+ />
4292
+
4191
4293
  {!isLimitedView ? (
4192
4294
  <Sheet
4193
4295
  open={isEditSheetOpen}
@@ -4216,284 +4318,36 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
4216
4318
  ) : null}
4217
4319
 
4218
4320
  {!isLimitedView ? (
4219
- <Sheet
4321
+ <TaskFormSheet
4220
4322
  open={taskFormOpen}
4221
4323
  onOpenChange={(open) => {
4222
4324
  if (!open) {
4223
4325
  setTaskFormOpen(false);
4224
4326
  setEditingTaskId(null);
4225
- setTaskFormData(EMPTY_TASK_FORM);
4226
4327
  }
4227
4328
  }}
4228
- >
4229
- <SheetContent className="flex w-full flex-col overflow-hidden sm:max-w-xl">
4230
- <SheetHeader className="shrink-0">
4231
- <SheetTitle>
4232
- {editingTaskId
4233
- ? t('taskForm.titleEdit')
4234
- : t('taskForm.titleNew')}
4235
- </SheetTitle>
4236
- </SheetHeader>
4237
-
4238
- <Tabs defaultValue="info" className="flex min-h-0 flex-1 flex-col">
4239
- <TabsList className="mx-4 grid w-[calc(100%-2rem)] shrink-0 grid-cols-2">
4240
- <TabsTrigger value="info">{t('taskForm.tabInfo')}</TabsTrigger>
4241
- {editingTaskId ? (
4242
- <TabsTrigger value="comments">
4243
- {t('taskForm.tabComments')}
4244
- </TabsTrigger>
4245
- ) : null}
4246
- </TabsList>
4247
- <TabsContent
4248
- value="info"
4249
- className="flex min-h-0 flex-1 flex-col data-[state=inactive]:hidden"
4250
- >
4251
- <div className="flex-1 space-y-4 overflow-y-auto px-4 py-2">
4252
- <div className="space-y-1.5">
4253
- <Label htmlFor="task-name">
4254
- {t('taskForm.nameLabel')} *
4255
- </Label>
4256
- <Input
4257
- id="task-name"
4258
- placeholder={t('taskForm.namePlaceholder')}
4259
- value={taskFormData.name}
4260
- onChange={(e) =>
4261
- setTaskFormData((prev) => ({
4262
- ...prev,
4263
- name: e.target.value,
4264
- }))
4265
- }
4266
- />
4267
- </div>
4268
-
4269
- <div className="space-y-1.5">
4270
- <Label htmlFor="task-description">
4271
- {t('taskForm.descriptionLabel')}
4272
- </Label>
4273
- <RichTextEditor
4274
- value={taskFormData.description}
4275
- onChange={(val) =>
4276
- setTaskFormData((prev) => ({
4277
- ...prev,
4278
- description: val,
4279
- }))
4280
- }
4281
- mentions={mentionItems}
4282
- />
4283
- </div>
4284
-
4285
- <div className="grid grid-cols-2 gap-3">
4286
- <div className="space-y-1.5">
4287
- <Label>{t('taskForm.priorityLabel')}</Label>
4288
- <Select
4289
- value={taskFormData.priority}
4290
- onValueChange={(v) =>
4291
- setTaskFormData((prev) => ({
4292
- ...prev,
4293
- priority: v as TaskFormState['priority'],
4294
- }))
4295
- }
4296
- >
4297
- <SelectTrigger className="w-full">
4298
- <SelectValue />
4299
- </SelectTrigger>
4300
- <SelectContent>
4301
- <SelectItem value="low">
4302
- {getTaskPriorityLabel('low')}
4303
- </SelectItem>
4304
- <SelectItem value="medium">
4305
- {getTaskPriorityLabel('medium')}
4306
- </SelectItem>
4307
- <SelectItem value="high">
4308
- {getTaskPriorityLabel('high')}
4309
- </SelectItem>
4310
- </SelectContent>
4311
- </Select>
4312
- </div>
4313
-
4314
- <div className="space-y-1.5">
4315
- <Label>{t('taskForm.columnLabel')}</Label>
4316
- <Select
4317
- value={taskFormData.status}
4318
- onValueChange={(v) =>
4319
- setTaskFormData((prev) => ({
4320
- ...prev,
4321
- status: v as BoardColumnId,
4322
- }))
4323
- }
4324
- >
4325
- <SelectTrigger className="w-full">
4326
- <SelectValue />
4327
- </SelectTrigger>
4328
- <SelectContent>
4329
- {KANBAN_COLUMNS.map((col) => (
4330
- <SelectItem key={col.id} value={col.id}>
4331
- {col.label}
4332
- </SelectItem>
4333
- ))}
4334
- </SelectContent>
4335
- </Select>
4336
- </div>
4337
- </div>
4338
-
4339
- <div className="space-y-1.5">
4340
- <Label>Responsável</Label>
4341
- <Select
4342
- value={taskFormData.assigneeCollaboratorId}
4343
- onValueChange={(value) =>
4344
- setTaskFormData((prev) => ({
4345
- ...prev,
4346
- assigneeCollaboratorId: value,
4347
- }))
4348
- }
4349
- >
4350
- <SelectTrigger className="w-full">
4351
- <SelectValue
4352
- placeholder={commonT('labels.notAssigned')}
4353
- />
4354
- </SelectTrigger>
4355
- <SelectContent>
4356
- <SelectItem value="none">
4357
- {commonT('labels.notAssigned')}
4358
- </SelectItem>
4359
- {taskAssigneeOptions.map((option) => (
4360
- <SelectItem key={option.id} value={option.id}>
4361
- {option.label}
4362
- </SelectItem>
4363
- ))}
4364
- </SelectContent>
4365
- </Select>
4366
- </div>
4367
-
4368
- <div className="grid grid-cols-2 gap-3">
4369
- <div className="space-y-1.5">
4370
- <Label htmlFor="task-due-date">
4371
- {t('taskForm.deadlineLabel')}
4372
- </Label>
4373
- <Input
4374
- id="task-due-date"
4375
- type="date"
4376
- value={taskFormData.dueDate}
4377
- onChange={(e) =>
4378
- setTaskFormData((prev) => ({
4379
- ...prev,
4380
- dueDate: e.target.value,
4381
- }))
4382
- }
4383
- />
4384
- </div>
4385
-
4386
- <div className="space-y-1.5">
4387
- <Label htmlFor="task-estimate">
4388
- {t('taskForm.estimateLabel')}
4389
- </Label>
4390
- <Input
4391
- id="task-estimate"
4392
- type="number"
4393
- min="0"
4394
- step="0.5"
4395
- placeholder="0"
4396
- value={taskFormData.estimateHours}
4397
- onChange={(e) =>
4398
- setTaskFormData((prev) => ({
4399
- ...prev,
4400
- estimateHours: e.target.value,
4401
- }))
4402
- }
4403
- />
4404
- </div>
4405
- </div>
4406
-
4407
- <div className="space-y-1.5">
4408
- <Label htmlFor="task-tags">{t('taskForm.tagsLabel')}</Label>
4409
- <Input
4410
- id="task-tags"
4411
- placeholder={t('taskForm.tagsPlaceholder')}
4412
- value={taskFormData.tags}
4413
- onChange={(e) =>
4414
- setTaskFormData((prev) => ({
4415
- ...prev,
4416
- tags: e.target.value,
4417
- }))
4418
- }
4419
- />
4420
- </div>
4421
-
4422
- {editingTaskId ? (
4423
- <div className="space-y-1.5">
4424
- <Label className="flex items-center gap-1.5">
4425
- <Paperclip className="size-3.5" />
4426
- {t('taskForm.attachmentsLabel')}
4427
- </Label>
4428
- <TaskFileAttachments taskId={editingTaskId} />
4429
- </div>
4430
- ) : null}
4431
- </div>
4432
-
4433
- <div className="mt-4 flex flex-wrap items-center justify-between gap-2 border-t px-4 pb-4 pt-4">
4434
- <div className="flex gap-2">
4435
- {editingTaskId ? (
4436
- <Button
4437
- type="button"
4438
- variant="outline"
4439
- disabled={
4440
- taskFormLoading || archivingTaskId === editingTaskId
4441
- }
4442
- onClick={() => {
4443
- if (!editingTaskId) return;
4444
- const id = editingTaskId;
4445
- setTaskFormOpen(false);
4446
- setEditingTaskId(null);
4447
- setTaskFormData(EMPTY_TASK_FORM);
4448
- void handleArchiveTask(id);
4449
- }}
4450
- >
4451
- {archivingTaskId === editingTaskId ? (
4452
- <Loader2 className="mr-2 size-4 animate-spin" />
4453
- ) : (
4454
- <Archive className="mr-2 size-4" />
4455
- )}
4456
- {commonT('actions.archive')}
4457
- </Button>
4458
- ) : null}
4459
- </div>
4460
- <div className="flex gap-2">
4461
- <Button
4462
- variant="outline"
4463
- onClick={() => {
4464
- setTaskFormOpen(false);
4465
- setEditingTaskId(null);
4466
- setTaskFormData(EMPTY_TASK_FORM);
4467
- }}
4468
- disabled={taskFormLoading}
4469
- >
4470
- {commonT('actions.cancel')}
4471
- </Button>
4472
- <Button
4473
- onClick={() => void handleTaskFormSubmit()}
4474
- disabled={taskFormLoading || !taskFormData.name.trim()}
4475
- >
4476
- {taskFormLoading
4477
- ? t('taskForm.saving')
4478
- : editingTaskId
4479
- ? commonT('actions.save')
4480
- : commonT('actions.create')}
4481
- </Button>
4482
- </div>
4483
- </div>
4484
- </TabsContent>
4485
-
4486
- {editingTaskId ? (
4487
- <TabsContent
4488
- value="comments"
4489
- className="min-h-0 flex-1 overflow-y-auto px-4 py-2 data-[state=inactive]:hidden"
4490
- >
4491
- <TaskCommentsSection taskId={editingTaskId} />
4492
- </TabsContent>
4493
- ) : null}
4494
- </Tabs>
4495
- </SheetContent>
4496
- </Sheet>
4329
+ request={request}
4330
+ editingTask={editingTaskAsOption}
4331
+ showProject={false}
4332
+ defaultProjectId={projectId}
4333
+ defaultStatus={createDefaultStatus}
4334
+ assigneeOptions={taskAssigneeOptions}
4335
+ onArchive={handleArchiveTask}
4336
+ onLogHours={
4337
+ editingTask
4338
+ ? () =>
4339
+ openTimesheetEntrySheet(
4340
+ editingTask as unknown as TaskDetailSheetData
4341
+ )
4342
+ : undefined
4343
+ }
4344
+ onCountChanged={() => { setBoardState(null); void refetchTasks(); }}
4345
+ onSaved={() => {
4346
+ setBoardState(null);
4347
+ void refetchTasks();
4348
+ void refetchArchivedTasks();
4349
+ }}
4350
+ />
4497
4351
  ) : null}
4498
4352
 
4499
4353
  {!isLimitedView ? (
@@ -4507,10 +4361,10 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
4507
4361
  <DialogContent className="sm:max-w-sm">
4508
4362
  <DialogHeader>
4509
4363
  <DialogTitle>{t('dialogs.deleteTitle')}</DialogTitle>
4364
+ <DialogDescription>
4365
+ {t('dialogs.deleteDescription')}
4366
+ </DialogDescription>
4510
4367
  </DialogHeader>
4511
- <p className="text-sm text-muted-foreground">
4512
- {t('dialogs.deleteDescription')}
4513
- </p>
4514
4368
  <DialogFooter className="mt-4">
4515
4369
  <Button
4516
4370
  variant="outline"
@@ -4533,6 +4387,57 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
4533
4387
  </DialogFooter>
4534
4388
  </DialogContent>
4535
4389
  </Dialog>
4390
+
4391
+ <Dialog
4392
+ open={deleteProjectDialogOpen}
4393
+ onOpenChange={(open) => {
4394
+ if (!open) {
4395
+ setDeleteProjectDialogOpen(false);
4396
+ setDeleteProjectConfirmText('');
4397
+ }
4398
+ }}
4399
+ >
4400
+ <DialogContent className="sm:max-w-md">
4401
+ <DialogHeader>
4402
+ <DialogTitle>{t('deleteConfirmTitle')}</DialogTitle>
4403
+ </DialogHeader>
4404
+ <p className="text-sm text-muted-foreground">
4405
+ {t('deleteConfirmDescription')}
4406
+ </p>
4407
+ <div className="space-y-1.5">
4408
+ <Label className="text-xs">{t('deleteConfirmLabel')}</Label>
4409
+ <Input
4410
+ value={deleteProjectConfirmText}
4411
+ onChange={(e) => setDeleteProjectConfirmText(e.target.value)}
4412
+ placeholder={project?.name ?? ''}
4413
+ />
4414
+ </div>
4415
+ <DialogFooter className="mt-2">
4416
+ <Button
4417
+ variant="outline"
4418
+ onClick={() => {
4419
+ setDeleteProjectDialogOpen(false);
4420
+ setDeleteProjectConfirmText('');
4421
+ }}
4422
+ >
4423
+ {commonT('actions.cancel')}
4424
+ </Button>
4425
+ <Button
4426
+ variant="destructive"
4427
+ disabled={
4428
+ deletingProject ||
4429
+ deleteProjectConfirmText !== (project?.name ?? '')
4430
+ }
4431
+ onClick={() => void handleDeleteProject()}
4432
+ >
4433
+ {deletingProject ? (
4434
+ <Loader2 className="mr-2 size-3.5 animate-spin" />
4435
+ ) : null}
4436
+ {t('deleteConfirmButton')}
4437
+ </Button>
4438
+ </DialogFooter>
4439
+ </DialogContent>
4440
+ </Dialog>
4536
4441
  </>
4537
4442
  ) : null}
4538
4443
  </Page>