@hed-hog/operations 0.0.330 → 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 (281) 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 +34 -9
  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 +182 -268
  146. package/dist/operations.service.d.ts.map +1 -1
  147. package/dist/operations.service.js +2147 -1337
  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_item.yaml +25 -25
  153. package/hedhog/data/route.yaml +61 -0
  154. package/hedhog/frontend/app/_components/collaborator-form-screen.tsx.ejs +39 -99
  155. package/hedhog/frontend/app/_components/collaborator-picker.tsx.ejs +158 -0
  156. package/hedhog/frontend/app/_components/my-project-summary-screen.tsx.ejs +314 -116
  157. package/hedhog/frontend/app/_components/project-assignments-tab.tsx.ejs +434 -449
  158. package/hedhog/frontend/app/_components/project-details-screen.tsx.ejs +289 -412
  159. package/hedhog/frontend/app/_components/project-file-attachments.tsx.ejs +371 -0
  160. package/hedhog/frontend/app/_components/project-form-screen.tsx.ejs +426 -374
  161. package/hedhog/frontend/app/_components/task-detail-sheet.tsx.ejs +803 -581
  162. package/hedhog/frontend/app/_components/task-file-attachments.tsx.ejs +4 -1
  163. package/hedhog/frontend/app/_components/task-form-fields.tsx.ejs +406 -0
  164. package/hedhog/frontend/app/_components/task-form-sheet.tsx.ejs +629 -784
  165. package/hedhog/frontend/app/_components/task-info-display.tsx.ejs +137 -0
  166. package/hedhog/frontend/app/_components/timesheet-entry-create-sheet.tsx.ejs +306 -0
  167. package/hedhog/frontend/app/_lib/api.ts.ejs +480 -476
  168. package/hedhog/frontend/app/_lib/types.ts.ejs +66 -5
  169. package/hedhog/frontend/app/_lib/utils/format.ts.ejs +0 -2
  170. package/hedhog/frontend/app/_lib/utils/task-ui.ts.ejs +43 -0
  171. package/hedhog/frontend/app/approvals/page.tsx.ejs +6 -1
  172. package/hedhog/frontend/app/collaborator-types/page.tsx.ejs +6 -1
  173. package/hedhog/frontend/app/collaborators/page.tsx.ejs +59 -8
  174. package/hedhog/frontend/app/contracts/page.tsx.ejs +29 -8
  175. package/hedhog/frontend/app/dashboard/widgets/CapacityDistribution.tsx.ejs +84 -0
  176. package/hedhog/frontend/app/dashboard/widgets/EffortByProject.tsx.ejs +85 -0
  177. package/hedhog/frontend/app/dashboard/widgets/HeadcountByArea.tsx.ejs +101 -0
  178. package/hedhog/frontend/app/dashboard/widgets/ManagedProjectsStatus.tsx.ejs +113 -0
  179. package/hedhog/frontend/app/dashboard/widgets/MyHoursPeriodKpi.tsx.ejs +87 -0
  180. package/hedhog/frontend/app/dashboard/widgets/MyOpenRequestsKpi.tsx.ejs +97 -0
  181. package/hedhog/frontend/app/dashboard/widgets/MyPendingRequestsList.tsx.ejs +99 -0
  182. package/hedhog/frontend/app/dashboard/widgets/MyProjectAllocationsKpi.tsx.ejs +78 -0
  183. package/hedhog/frontend/app/dashboard/widgets/MyQuickActions.tsx.ejs +130 -0
  184. package/hedhog/frontend/app/dashboard/widgets/MyRelevantDeadlines.tsx.ejs +144 -0
  185. package/hedhog/frontend/app/dashboard/widgets/MyTimesheetStatusKpi.tsx.ejs +78 -0
  186. package/hedhog/frontend/app/dashboard/widgets/MyWeeklyJourney.tsx.ejs +99 -0
  187. package/hedhog/frontend/app/dashboard/widgets/PortfolioCostsKpi.tsx.ejs +112 -0
  188. package/hedhog/frontend/app/dashboard/widgets/PortfolioEffortKpi.tsx.ejs +93 -0
  189. package/hedhog/frontend/app/dashboard/widgets/PortfolioProjectsKpi.tsx.ejs +96 -0
  190. package/hedhog/frontend/app/dashboard/widgets/PortfolioRiskKpi.tsx.ejs +115 -0
  191. package/hedhog/frontend/app/dashboard/widgets/ProjectStatusOverview.tsx.ejs +120 -0
  192. package/hedhog/frontend/app/dashboard/widgets/StrategicDeadlines.tsx.ejs +146 -0
  193. package/hedhog/frontend/app/dashboard/widgets/TeamApprovalQueue.tsx.ejs +108 -0
  194. package/hedhog/frontend/app/dashboard/widgets/TeamCapacityKpi.tsx.ejs +97 -0
  195. package/hedhog/frontend/app/dashboard/widgets/TeamHeadcountKpi.tsx.ejs +100 -0
  196. package/hedhog/frontend/app/dashboard/widgets/TeamHoursKpi.tsx.ejs +104 -0
  197. package/hedhog/frontend/app/dashboard/widgets/TeamPendingApprovalsKpi.tsx.ejs +110 -0
  198. package/hedhog/frontend/app/dashboard/widgets/TeamUtilizationOverview.tsx.ejs +115 -0
  199. package/hedhog/frontend/app/dashboard/widgets/TeamWorkloadAlerts.tsx.ejs +117 -0
  200. package/hedhog/frontend/app/dashboard/widgets/index.ts.ejs +26 -0
  201. package/hedhog/frontend/app/departments/page.tsx.ejs +6 -1
  202. package/hedhog/frontend/app/my-projects/page.tsx.ejs +14 -10
  203. package/hedhog/frontend/app/my-tasks/page.tsx.ejs +328 -105
  204. package/hedhog/frontend/app/project-cost-categories/page.tsx.ejs +58 -52
  205. package/hedhog/frontend/app/project-cost-types/page.tsx.ejs +58 -51
  206. package/hedhog/frontend/app/projects/page.tsx.ejs +376 -30
  207. package/hedhog/frontend/app/schedule-adjustments/page.tsx.ejs +6 -1
  208. package/hedhog/frontend/app/time-off/page.tsx.ejs +6 -1
  209. package/hedhog/frontend/app/timesheets/page.tsx.ejs +10 -4
  210. package/hedhog/frontend/messages/en.json +238 -46
  211. package/hedhog/frontend/messages/operations/en.json +61 -52
  212. package/hedhog/frontend/messages/operations/pt.json +59 -43
  213. package/hedhog/frontend/messages/pt.json +238 -46
  214. package/hedhog/frontend/widgets/capacity-distribution.tsx.ejs +17 -0
  215. package/hedhog/frontend/widgets/effort-by-project.tsx.ejs +17 -0
  216. package/hedhog/frontend/widgets/headcount-by-area.tsx.ejs +17 -0
  217. package/hedhog/frontend/widgets/index.ts.ejs +25 -0
  218. package/hedhog/frontend/widgets/managed-projects-status.tsx.ejs +17 -0
  219. package/hedhog/frontend/widgets/my-hours-period-kpi.tsx.ejs +17 -0
  220. package/hedhog/frontend/widgets/my-open-requests-kpi.tsx.ejs +17 -0
  221. package/hedhog/frontend/widgets/my-pending-requests-list.tsx.ejs +17 -0
  222. package/hedhog/frontend/widgets/my-project-allocations-kpi.tsx.ejs +17 -0
  223. package/hedhog/frontend/widgets/my-quick-actions.tsx.ejs +17 -0
  224. package/hedhog/frontend/widgets/my-relevant-deadlines.tsx.ejs +17 -0
  225. package/hedhog/frontend/widgets/my-timesheet-status-kpi.tsx.ejs +17 -0
  226. package/hedhog/frontend/widgets/my-weekly-journey.tsx.ejs +17 -0
  227. package/hedhog/frontend/widgets/portfolio-costs-kpi.tsx.ejs +17 -0
  228. package/hedhog/frontend/widgets/portfolio-effort-kpi.tsx.ejs +17 -0
  229. package/hedhog/frontend/widgets/portfolio-projects-kpi.tsx.ejs +17 -0
  230. package/hedhog/frontend/widgets/portfolio-risk-kpi.tsx.ejs +17 -0
  231. package/hedhog/frontend/widgets/project-status-overview.tsx.ejs +17 -0
  232. package/hedhog/frontend/widgets/shared-operations-widget.tsx.ejs +170 -0
  233. package/hedhog/frontend/widgets/strategic-deadlines.tsx.ejs +17 -0
  234. package/hedhog/frontend/widgets/team-approval-queue.tsx.ejs +17 -0
  235. package/hedhog/frontend/widgets/team-capacity-kpi.tsx.ejs +17 -0
  236. package/hedhog/frontend/widgets/team-headcount-kpi.tsx.ejs +17 -0
  237. package/hedhog/frontend/widgets/team-hours-kpi.tsx.ejs +17 -0
  238. package/hedhog/frontend/widgets/team-pending-approvals-kpi.tsx.ejs +17 -0
  239. package/hedhog/frontend/widgets/team-utilization-overview.tsx.ejs +17 -0
  240. package/hedhog/frontend/widgets/team-workload-alerts.tsx.ejs +17 -0
  241. package/hedhog/table/operations_collaborator.yaml +8 -13
  242. package/hedhog/table/operations_project.yaml +1 -1
  243. package/hedhog/table/operations_project_file.yaml +23 -0
  244. package/hedhog/table/operations_task.yaml +76 -69
  245. package/hedhog/table/operations_task_activity.yaml +51 -0
  246. package/package.json +6 -5
  247. package/src/controllers/operations-projects.controller.ts +41 -8
  248. package/src/controllers/operations-tasks.controller.ts +156 -166
  249. package/src/dashboard/README.md +214 -0
  250. package/src/dashboard/components/DashboardLayout.tsx +131 -0
  251. package/src/dashboard/components/widget-registry.ts +255 -0
  252. package/src/dashboard/hooks/useDashboardData.ts +29 -0
  253. package/src/dashboard/types/widgets.types.ts +237 -0
  254. package/src/dashboard/widgets/CapacityDistribution.tsx +56 -0
  255. package/src/dashboard/widgets/EffortByProject.tsx +51 -0
  256. package/src/dashboard/widgets/HeadcountByArea.tsx +57 -0
  257. package/src/dashboard/widgets/ManagedProjectsStatus.tsx +53 -0
  258. package/src/dashboard/widgets/MyHoursPeriodKpi.tsx +87 -0
  259. package/src/dashboard/widgets/MyOpenRequestsKpi.tsx +51 -0
  260. package/src/dashboard/widgets/MyPendingRequestsList.tsx +63 -0
  261. package/src/dashboard/widgets/MyProjectAllocationsKpi.tsx +57 -0
  262. package/src/dashboard/widgets/MyQuickActions.tsx +62 -0
  263. package/src/dashboard/widgets/MyRelevantDeadlines.tsx +84 -0
  264. package/src/dashboard/widgets/MyTimesheetStatusKpi.tsx +65 -0
  265. package/src/dashboard/widgets/MyWeeklyJourney.tsx +57 -0
  266. package/src/dashboard/widgets/PortfolioCostsKpi.tsx +48 -0
  267. package/src/dashboard/widgets/PortfolioEffortKpi.tsx +41 -0
  268. package/src/dashboard/widgets/PortfolioRiskKpi.tsx +50 -0
  269. package/src/dashboard/widgets/ProjectStatusOverview.tsx +52 -0
  270. package/src/dashboard/widgets/StrategicDeadlines.tsx +93 -0
  271. package/src/dashboard/widgets/TeamApprovalQueue.tsx +70 -0
  272. package/src/dashboard/widgets/TeamCapacityKpi.tsx +50 -0
  273. package/src/dashboard/widgets/TeamHoursKpi.tsx +51 -0
  274. package/src/dashboard/widgets/TeamPendingApprovalsKpi.tsx +53 -0
  275. package/src/dashboard/widgets/TeamUtilizationOverview.tsx +62 -0
  276. package/src/dashboard/widgets/TeamWorkloadAlerts.tsx +81 -0
  277. package/src/dashboard/widgets/index.ts +26 -0
  278. package/src/dto/create-collaborator.dto.ts +4 -11
  279. package/src/index.ts +3 -0
  280. package/src/operations.service.spec.ts +988 -764
  281. package/src/operations.service.ts +4277 -2535
@@ -20,6 +20,19 @@ export type OperationsTaskComment = {
20
20
  updatedAt: string | null;
21
21
  };
22
22
 
23
+ export type OperationsTaskActivity = {
24
+ id: number;
25
+ taskId: number;
26
+ actorCollaboratorId: number | null;
27
+ actorName: string | null;
28
+ actorUserPhotoId: number | null;
29
+ actorPersonAvatarId: number | null;
30
+ action: string | null;
31
+ fromStatus: string;
32
+ toStatus: string;
33
+ createdAt: string;
34
+ };
35
+
23
36
  export type OperationsDashboard = {
24
37
  actor: {
25
38
  roleScope: 'self' | 'team' | 'full';
@@ -36,6 +49,48 @@ export type OperationsDashboard = {
36
49
  scheduleAdjustmentRequests: number;
37
50
  pendingApprovals: number;
38
51
  };
52
+ windowDays: number;
53
+ taskSummary: {
54
+ total: number;
55
+ backlog: number;
56
+ inProgress: number;
57
+ completed: number;
58
+ };
59
+ avgTaskCompletionHours: number;
60
+ completedTasksInWindow: number;
61
+ hoursInExecution: number;
62
+ timesheetHeatmap: Array<{
63
+ workDate: string;
64
+ weekday: number;
65
+ hours: number;
66
+ }>;
67
+ activeProjects: Array<{
68
+ id: number;
69
+ name: string;
70
+ code?: string | null;
71
+ taskBacklog: number;
72
+ taskInProgress: number;
73
+ taskCompleted: number;
74
+ }>;
75
+ recentTaskActivities: Array<{
76
+ taskId: number;
77
+ taskName: string;
78
+ taskStatus: string;
79
+ taskPriority?: string | null;
80
+ taskDueDate?: string | null;
81
+ taskEstimateHours?: number | null;
82
+ taskTags?: string | null;
83
+ taskAssigneeCollaboratorId?: number | null;
84
+ taskAssigneeName?: string | null;
85
+ projectId: number;
86
+ projectName: string;
87
+ projectCode?: string | null;
88
+ projectAssignmentId?: number | null;
89
+ action?: string | null;
90
+ fromStatus: string;
91
+ toStatus: string;
92
+ createdAt: string;
93
+ }>;
39
94
  recentTimesheets: Array<{
40
95
  id: number;
41
96
  collaboratorName: string;
@@ -260,6 +315,7 @@ export type OperationsCollaborator = {
260
315
  supervisorName?: string | null;
261
316
  contractId?: number | null;
262
317
  contractStatus?: string | null;
318
+ hasUser?: boolean | null;
263
319
  activeAssignments?: number;
264
320
  pendingApprovals?: number;
265
321
  pendingTimeOffRequests?: number;
@@ -354,18 +410,21 @@ export type OperationsTaskOption = {
354
410
  description?: string | null;
355
411
  status: string;
356
412
  priority?: string | null;
413
+ assigneeCollaboratorId?: number | null;
357
414
  projectId: number;
358
415
  projectAssignmentId: number | null;
359
416
  projectName: string;
360
- projectCode?: string | null;
361
- dueDate?: string | null;
362
- estimateHours?: number | null;
363
- tags?: string | null;
364
- assigneeName?: string | null;
417
+ projectCode?: string | null;
418
+ dueDate?: string | null;
419
+ estimateHours?: number | null;
420
+ tags?: string | null;
421
+ assigneeName?: string | null;
365
422
  assigneeUserPhotoId?: number | null;
366
423
  assigneePersonAvatarId?: number | null;
367
424
  commentCount?: number | null;
368
425
  fileCount?: number | null;
426
+ doingStartedAt?: string | null;
427
+ totalDoingMinutes?: number | null;
369
428
  createdAt?: string | null;
370
429
  deletedAt?: string | null;
371
430
  };
@@ -659,6 +718,8 @@ export type OperationsMyProjectSummary = {
659
718
  assigneeUserPhotoId: number | null;
660
719
  assigneePersonAvatarId: number | null;
661
720
  projectAssignmentId: number | null;
721
+ doingStartedAt?: string | null;
722
+ totalDoingMinutes?: number | null;
662
723
  createdAt: string;
663
724
  }>;
664
725
  };
@@ -222,8 +222,6 @@ export function getStatusBadgeClass(value?: string | null) {
222
222
  case 'inactive':
223
223
  case 'on_leave':
224
224
  return 'bg-slate-100 text-slate-800';
225
- case 'at_risk':
226
- return 'bg-orange-100 text-orange-900';
227
225
  default:
228
226
  return 'bg-sky-100 text-sky-800';
229
227
  }
@@ -0,0 +1,43 @@
1
+ export function getTaskPriorityLabel(value?: string | null) {
2
+ const labels: Record<string, string> = {
3
+ low: 'Baixa',
4
+ medium: 'Média',
5
+ high: 'Alta',
6
+ };
7
+ return labels[value ?? ''] ?? String(value ?? '');
8
+ }
9
+
10
+ export function getInitials(value?: string | null) {
11
+ const parts = String(value ?? '')
12
+ .trim()
13
+ .split(/\s+/)
14
+ .filter(Boolean)
15
+ .slice(0, 2);
16
+ if (!parts.length) return '??';
17
+ return parts.map((part) => part[0]?.toUpperCase() ?? '').join('');
18
+ }
19
+
20
+ export function getElapsedDoingMinutes(
21
+ task?: {
22
+ status?: string | null;
23
+ doingStartedAt?: string | null;
24
+ totalDoingMinutes?: number | null;
25
+ } | null,
26
+ tick = 0
27
+ ) {
28
+ void tick;
29
+ const base = Number(task?.totalDoingMinutes ?? 0);
30
+ if (!task || task.status !== 'doing' || !task.doingStartedAt) return base;
31
+ const startedAt = new Date(task.doingStartedAt).getTime();
32
+ if (Number.isNaN(startedAt)) return base;
33
+ return base + Math.max(Math.floor((Date.now() - startedAt) / 60000), 0);
34
+ }
35
+
36
+ export function formatDurationMinutes(minutes?: number | null) {
37
+ const total = Math.max(Number(minutes ?? 0), 0);
38
+ const hours = Math.floor(total / 60);
39
+ const remainingMinutes = total % 60;
40
+ if (hours <= 0) return `${remainingMinutes}min`;
41
+ if (remainingMinutes <= 0) return `${hours}h`;
42
+ return `${hours}h ${remainingMinutes}min`;
43
+ }
@@ -57,6 +57,7 @@ import {
57
57
  import { OperationsHeader } from '../_components/operations-header';
58
58
  import { StatusBadge } from '../_components/status-badge';
59
59
  import { fetchOperations } from '../_lib/api';
60
+ import { usePersistedPageSize } from '@/hooks/use-persisted-page-size';
60
61
  import { useOperationsAccess } from '../_lib/hooks/use-operations-access';
61
62
  import type { OperationsApproval, PaginatedResponse } from '../_lib/types';
62
63
  import {
@@ -232,7 +233,11 @@ export default function OperationsApprovalsPage() {
232
233
  () => new Date().getMonth() + 1
233
234
  );
234
235
  const [page, setPage] = useState(1);
235
- const [pageSize, setPageSize] = useState(12);
236
+ const [pageSize, setPageSize] = usePersistedPageSize({
237
+ storageKey: 'pagination:operations-approvals:pageSize',
238
+ defaultValue: 12,
239
+ allowedValues: [12, 24, 48],
240
+ });
236
241
  const [viewMode, setViewMode] = useState<'table' | 'cards' | 'calendar'>(
237
242
  () => {
238
243
  if (typeof window === 'undefined') return 'table';
@@ -55,6 +55,7 @@ import { useEffect, useMemo, useState } from 'react';
55
55
  import { OperationsHeader } from '../_components/operations-header';
56
56
  import { StatusBadge } from '../_components/status-badge';
57
57
  import { fetchOperations, mutateOperations } from '../_lib/api';
58
+ import { usePersistedPageSize } from '@/hooks/use-persisted-page-size';
58
59
  import { useOperationsAccess } from '../_lib/hooks/use-operations-access';
59
60
  import type {
60
61
  OperationsCollaboratorType,
@@ -195,7 +196,11 @@ export default function OperationsCollaboratorTypesPage() {
195
196
  'all' | 'active' | 'inactive'
196
197
  >('all');
197
198
  const [page, setPage] = useState(1);
198
- const [pageSize, setPageSize] = useState(100);
199
+ const [pageSize, setPageSize] = usePersistedPageSize({
200
+ storageKey: 'pagination:operations-collaborator-types:pageSize',
201
+ defaultValue: 100,
202
+ allowedValues: [25, 50, 100, 200],
203
+ });
199
204
  const [viewMode, setViewMode] = useState<'table' | 'cards'>(() => {
200
205
  if (typeof window === 'undefined') return 'table';
201
206
  const saved = window.localStorage.getItem(
@@ -47,6 +47,7 @@ import {
47
47
  UserCheck,
48
48
  UserRound,
49
49
  Users,
50
+ UserX,
50
51
  Wallet,
51
52
  } from 'lucide-react';
52
53
  import { useTranslations } from 'next-intl';
@@ -57,6 +58,7 @@ import { CollaboratorFormScreen } from '../_components/collaborator-form-screen'
57
58
  import { OperationsHeader } from '../_components/operations-header';
58
59
  import { StatusBadge } from '../_components/status-badge';
59
60
  import { fetchOperations, mutateOperations } from '../_lib/api';
61
+ import { usePersistedPageSize } from '@/hooks/use-persisted-page-size';
60
62
  import { useOperationsAccess } from '../_lib/hooks/use-operations-access';
61
63
  import {
62
64
  MASKED_VALUE,
@@ -121,7 +123,11 @@ export default function OperationsCollaboratorsPage() {
121
123
  const [statusFilter, setStatusFilter] = useState('all');
122
124
  const [typeFilter, setTypeFilter] = useState('all');
123
125
  const [page, setPage] = useState(1);
124
- const [pageSize, setPageSize] = useState(12);
126
+ const [pageSize, setPageSize] = usePersistedPageSize({
127
+ storageKey: 'pagination:operations-collaborators:pageSize',
128
+ defaultValue: 12,
129
+ allowedValues: [12, 24, 48],
130
+ });
125
131
  const [viewMode, setViewMode] = useState<CollaboratorViewMode>(() => {
126
132
  if (typeof window === 'undefined') {
127
133
  return 'table';
@@ -685,6 +691,20 @@ export default function OperationsCollaboratorsPage() {
685
691
  </div>
686
692
  ) : null}
687
693
 
694
+ <div className="flex items-center gap-2 text-xs">
695
+ {collaborator.hasUser ? (
696
+ <span className="inline-flex items-center gap-1 font-medium text-emerald-600 dark:text-emerald-400">
697
+ <UserCheck className="size-3.5" />
698
+ {t('columns.linkedUserYes')}
699
+ </span>
700
+ ) : (
701
+ <span className="inline-flex items-center gap-1 font-medium text-muted-foreground">
702
+ <UserX className="size-3.5" />
703
+ {t('columns.linkedUserNo')}
704
+ </span>
705
+ )}
706
+ </div>
707
+
688
708
  <div className="flex items-center justify-end gap-1.5 border-t border-border/60 pt-3">
689
709
  {access.isDirector ? (
690
710
  <Button
@@ -750,14 +770,32 @@ export default function OperationsCollaboratorsPage() {
750
770
  <TableHead className="hidden lg:table-cell">
751
771
  {commonT('labels.supervisor')}
752
772
  </TableHead>
753
- <TableHead className="hidden xl:table-cell">
754
- {commonT('labels.weeklyCapacity')}
773
+ <TableHead className="hidden 2xl:table-cell">
774
+ <span
775
+ className="block truncate"
776
+ title={commonT('labels.weeklyCapacity')}
777
+ >
778
+ {commonT('labels.weeklyCapacity')}
779
+ </span>
755
780
  </TableHead>
756
781
  <TableHead className="hidden 2xl:table-cell">
757
- {formT('fields.compensationAmount')}
782
+ <span
783
+ className="block truncate"
784
+ title={formT('fields.compensationAmount')}
785
+ >
786
+ {formT('fields.compensationAmount')}
787
+ </span>
758
788
  </TableHead>
759
- <TableHead className="hidden xl:table-cell">
760
- {commonT('labels.startDate')}
789
+ <TableHead className="hidden 2xl:table-cell">
790
+ <span
791
+ className="block truncate"
792
+ title={commonT('labels.startDate')}
793
+ >
794
+ {commonT('labels.startDate')}
795
+ </span>
796
+ </TableHead>
797
+ <TableHead className="hidden md:table-cell">
798
+ {t('columns.linkedUser')}
761
799
  </TableHead>
762
800
  <TableHead className="w-30 text-right sm:w-42.5">
763
801
  {commonT('labels.actions')}
@@ -850,7 +888,7 @@ export default function OperationsCollaboratorsPage() {
850
888
  commonT('labels.notAssigned')}
851
889
  </div>
852
890
  </TableCell>
853
- <TableCell className="hidden xl:table-cell">
891
+ <TableCell className="hidden 2xl:table-cell">
854
892
  {formatHours(collaborator.weeklyCapacityHours)}
855
893
  </TableCell>
856
894
  <TableCell className="hidden 2xl:table-cell">
@@ -864,13 +902,26 @@ export default function OperationsCollaboratorsPage() {
864
902
  : MASKED_VALUE
865
903
  : commonT('labels.notAvailable')}
866
904
  </TableCell>
867
- <TableCell className="hidden xl:table-cell">
905
+ <TableCell className="hidden 2xl:table-cell">
868
906
  {formatDate(
869
907
  collaborator.joinedAt,
870
908
  getSettingValue,
871
909
  currentLocaleCode
872
910
  )}
873
911
  </TableCell>
912
+ <TableCell className="hidden md:table-cell">
913
+ {collaborator.hasUser ? (
914
+ <span className="inline-flex items-center gap-1 text-xs font-medium text-emerald-600 dark:text-emerald-400">
915
+ <UserCheck className="size-3.5" />
916
+ {t('columns.linkedUserYes')}
917
+ </span>
918
+ ) : (
919
+ <span className="inline-flex items-center gap-1 text-xs font-medium text-muted-foreground">
920
+ <UserX className="size-3.5" />
921
+ {t('columns.linkedUserNo')}
922
+ </span>
923
+ )}
924
+ </TableCell>
874
925
  <TableCell>
875
926
  <div className="flex items-center justify-end gap-1.5">
876
927
  {access.isDirector ? (
@@ -58,6 +58,7 @@ import { ContractFormScreen } from '../_components/contract-form-screen';
58
58
  import { OperationsHeader } from '../_components/operations-header';
59
59
  import { StatusBadge } from '../_components/status-badge';
60
60
  import { fetchOperations, mutateOperations } from '../_lib/api';
61
+ import { usePersistedPageSize } from '@/hooks/use-persisted-page-size';
61
62
  import { useOperationsAccess } from '../_lib/hooks/use-operations-access';
62
63
  import type {
63
64
  OperationsContract,
@@ -97,7 +98,11 @@ export default function OperationsContractsPage() {
97
98
  const [search, setSearch] = useState('');
98
99
  const [isActiveFilter, setIsActiveFilter] = useState('all');
99
100
  const [page, setPage] = useState(1);
100
- const [pageSize, setPageSize] = useState(12);
101
+ const [pageSize, setPageSize] = usePersistedPageSize({
102
+ storageKey: 'pagination:operations-contracts:pageSize',
103
+ defaultValue: 12,
104
+ allowedValues: [12, 24, 48],
105
+ });
101
106
  const [isCreateSheetOpen, setIsCreateSheetOpen] = useState(false);
102
107
  const [contractToDelete, setContractToDelete] =
103
108
  useState<OperationsContract | null>(null);
@@ -159,7 +164,10 @@ export default function OperationsContractsPage() {
159
164
  const { data: stats } = useQuery<OperationsContractStats>({
160
165
  queryKey: ['operations-contracts-stats', currentLocaleCode],
161
166
  queryFn: () =>
162
- fetchOperations<OperationsContractStats>(request, '/operations/contracts/stats'),
167
+ fetchOperations<OperationsContractStats>(
168
+ request,
169
+ '/operations/contracts/stats'
170
+ ),
163
171
  });
164
172
 
165
173
  const rows = contractsResponse?.data ?? [];
@@ -206,7 +214,11 @@ export default function OperationsContractsPage() {
206
214
  ) ?? null;
207
215
 
208
216
  if (document?.fileId) {
209
- window.open(buildStoredFileUrl(document.fileId) || '', '_blank', 'noopener,noreferrer');
217
+ window.open(
218
+ buildStoredFileUrl(document.fileId) || '',
219
+ '_blank',
220
+ 'noopener,noreferrer'
221
+ );
210
222
  return;
211
223
  }
212
224
 
@@ -288,7 +300,9 @@ export default function OperationsContractsPage() {
288
300
  </DropdownMenuItem>
289
301
  {access.isDirector ? <DropdownMenuSeparator /> : null}
290
302
  {access.isDirector ? (
291
- <DropdownMenuItem onSelect={() => updateSheetQuery({ editId: contract.id })}>
303
+ <DropdownMenuItem
304
+ onSelect={() => updateSheetQuery({ editId: contract.id })}
305
+ >
292
306
  <Pencil className="size-4" />
293
307
  {commonT('actions.edit')}
294
308
  </DropdownMenuItem>
@@ -300,7 +314,9 @@ export default function OperationsContractsPage() {
300
314
  ) : (
301
315
  <ShieldCheck className="size-4" />
302
316
  )}
303
- {contract.isActive ? detailT('labels.inactive') : detailT('labels.active')}
317
+ {contract.isActive
318
+ ? detailT('labels.inactive')
319
+ : detailT('labels.active')}
304
320
  </DropdownMenuItem>
305
321
  ) : null}
306
322
  {access.isDirector ? <DropdownMenuSeparator /> : null}
@@ -384,7 +400,9 @@ export default function OperationsContractsPage() {
384
400
  <TableRow
385
401
  key={contract.id}
386
402
  className="cursor-pointer hover:bg-muted/30"
387
- onClick={() => router.push(`/operations/contracts/${contract.id}`)}
403
+ onClick={() =>
404
+ router.push(`/operations/contracts/${contract.id}`)
405
+ }
388
406
  >
389
407
  <TableCell>
390
408
  <div className="min-w-0">
@@ -401,7 +419,8 @@ export default function OperationsContractsPage() {
401
419
  </TableCell>
402
420
  <TableCell>
403
421
  <span className="truncate text-sm text-muted-foreground">
404
- {contract.currentPdfFileName || commonT('labels.notAvailable')}
422
+ {contract.currentPdfFileName ||
423
+ commonT('labels.notAvailable')}
405
424
  </span>
406
425
  </TableCell>
407
426
  <TableCell>
@@ -510,7 +529,9 @@ export default function OperationsContractsPage() {
510
529
  <SheetContent className="flex h-full w-full flex-col overflow-hidden p-0 sm:max-w-xl lg:max-w-4xl">
511
530
  <SheetHeader className="sr-only">
512
531
  <SheetTitle>
513
- {editingContractId ? commonT('actions.edit') : commonT('actions.create')}
532
+ {editingContractId
533
+ ? commonT('actions.edit')
534
+ : commonT('actions.create')}
514
535
  </SheetTitle>
515
536
  <SheetDescription>{t('description')}</SheetDescription>
516
537
  </SheetHeader>
@@ -0,0 +1,84 @@
1
+ 'use client';
2
+
3
+ import { PieChart } from 'lucide-react';
4
+ import { useTranslations } from 'next-intl';
5
+ import React from 'react';
6
+
7
+ interface CapacityArea {
8
+ name: string;
9
+ capacity: number;
10
+ percentage: number;
11
+ color: string;
12
+ icon: string;
13
+ }
14
+
15
+ interface CapacityDistributionProps {
16
+ slug: string;
17
+ title: string;
18
+ roleSlug: string;
19
+ width?: number;
20
+ height?: number;
21
+ data?: { areas: CapacityArea[]; totalCapacity: number };
22
+ style?: React.CSSProperties;
23
+ }
24
+
25
+ const CapacityDistribution: React.FC<CapacityDistributionProps> = ({
26
+ title,
27
+ data = { areas: [], totalCapacity: 0 },
28
+ style,
29
+ }) => {
30
+ const t = useTranslations('operations.CapacityDistribution');
31
+ const colorMap = ['blue', 'purple', 'pink', 'green', 'yellow', 'red'];
32
+ return (
33
+ <div
34
+ className="bg-linear-to-br from-white to-teal-50 rounded-lg shadow-sm p-4 border border-teal-100"
35
+ style={style}
36
+ >
37
+ <div className="flex items-center justify-between mb-4">
38
+ <h3 className="text-sm font-medium text-gray-700">{title}</h3>
39
+ <PieChart className="w-4 h-4 text-teal-600" />
40
+ </div>
41
+ <div className="space-y-3">
42
+ {data.areas.length === 0 ? (
43
+ <div className="text-center py-6 text-gray-500">
44
+ <PieChart className="w-8 h-8 mx-auto mb-2 opacity-50" />
45
+ <p className="text-sm">{t('empty')}</p>
46
+ </div>
47
+ ) : (
48
+ <>
49
+ {data.areas.map((area, idx) => (
50
+ <div key={idx} className="space-y-1">
51
+ <div className="flex items-center justify-between">
52
+ <div className="flex items-center gap-2">
53
+ <span className="text-lg">{area.icon}</span>
54
+ <span className="text-xs font-medium text-gray-900 truncate">
55
+ {area.name}
56
+ </span>
57
+ </div>
58
+ <span className="text-xs font-bold text-gray-700">
59
+ {area.percentage}%
60
+ </span>
61
+ </div>
62
+ <div className="relative w-full h-3 bg-gray-100 rounded-full overflow-hidden">
63
+ <div
64
+ className={`h-full bg-${colorMap[idx % colorMap.length]}-500 transition-all`}
65
+ style={{ width: `${area.percentage}%` }}
66
+ />
67
+ </div>
68
+ <div className="text-xs text-gray-600">{area.capacity}h</div>
69
+ </div>
70
+ ))}
71
+ <div className="pt-3 border-t border-teal-100 bg-teal-50 rounded-lg p-3 text-center">
72
+ <p className="text-xs text-teal-700 mb-1">{t('total')}</p>
73
+ <p className="text-2xl font-bold text-teal-600">
74
+ {data.totalCapacity}h
75
+ </p>
76
+ </div>
77
+ </>
78
+ )}
79
+ </div>
80
+ </div>
81
+ );
82
+ };
83
+
84
+ export default CapacityDistribution;
@@ -0,0 +1,85 @@
1
+ 'use client';
2
+
3
+ import { BarChart3 } from 'lucide-react';
4
+ import { useTranslations } from 'next-intl';
5
+ import React from 'react';
6
+
7
+ interface ProjectEffort {
8
+ name: string;
9
+ hours: number;
10
+ percent: number;
11
+ color: string;
12
+ }
13
+
14
+ interface EffortByProjectProps {
15
+ slug: string;
16
+ title: string;
17
+ roleSlug: string;
18
+ width?: number;
19
+ height?: number;
20
+ data?: { projects: ProjectEffort[]; totalHours: number };
21
+ style?: React.CSSProperties;
22
+ }
23
+
24
+ const EffortByProject: React.FC<EffortByProjectProps> = ({
25
+ title,
26
+ data = { projects: [], totalHours: 0 },
27
+ style,
28
+ }) => {
29
+ const t = useTranslations('operations.EffortByProject');
30
+ const colorMap = [
31
+ 'bg-blue-500',
32
+ 'bg-purple-500',
33
+ 'bg-pink-500',
34
+ 'bg-green-500',
35
+ 'bg-orange-500',
36
+ ];
37
+ return (
38
+ <div
39
+ className="bg-linear-to-br from-white to-pink-50 rounded-lg shadow-sm p-4 border border-pink-100"
40
+ style={style}
41
+ >
42
+ <div className="flex items-center justify-between mb-4">
43
+ <h3 className="text-sm font-medium text-gray-700">{title}</h3>
44
+ <BarChart3 className="w-4 h-4 text-pink-600" />
45
+ </div>
46
+ <div className="space-y-3">
47
+ {data.projects.length === 0 ? (
48
+ <div className="text-center py-6 text-gray-500">
49
+ <BarChart3 className="w-8 h-8 mx-auto mb-2 opacity-50" />
50
+ <p className="text-sm">{t('empty')}</p>
51
+ </div>
52
+ ) : (
53
+ <>
54
+ {data.projects.slice(0, 5).map((project, idx) => (
55
+ <div key={idx} className="space-y-1">
56
+ <div className="flex justify-between items-center">
57
+ <span className="text-xs font-medium text-gray-900 truncate">
58
+ {project.name}
59
+ </span>
60
+ <span className="text-xs font-bold text-gray-700">
61
+ {project.hours}h ({project.percent}%)
62
+ </span>
63
+ </div>
64
+ <div className="relative w-full h-4 bg-gray-100 rounded-full overflow-hidden">
65
+ <div
66
+ className={`h-full ${colorMap[idx % colorMap.length]} transition-all`}
67
+ style={{ width: `${project.percent}%` }}
68
+ />
69
+ </div>
70
+ </div>
71
+ ))}
72
+ <div className="pt-2 border-t border-pink-100 text-center">
73
+ <p className="text-xs text-gray-600 mb-1">{t('total')}</p>
74
+ <p className="text-2xl font-bold text-pink-600">
75
+ {data.totalHours}h
76
+ </p>
77
+ </div>
78
+ </>
79
+ )}
80
+ </div>
81
+ </div>
82
+ );
83
+ };
84
+
85
+ export default EffortByProject;