@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
@@ -48,6 +48,7 @@ import {
48
48
  TrendingUp,
49
49
  } from 'lucide-react';
50
50
  import Link from 'next/link';
51
+ import { useTranslations } from 'next-intl';
51
52
  import { useState } from 'react';
52
53
  import { OperationsHeader } from '../../_components/operations-header';
53
54
  import { fetchOperations } from '../../_lib/api';
@@ -57,13 +58,6 @@ import type {
57
58
  } from '../../_lib/types';
58
59
  import { formatCurrency, formatHours } from '../../_lib/utils/format';
59
60
 
60
- const statusLabels: Record<string, string> = {
61
- on_track: 'No prazo',
62
- attention: 'Atenção',
63
- late: 'Atrasado',
64
- paused: 'Pausado',
65
- };
66
-
67
61
  const statusClasses: Record<string, string> = {
68
62
  on_track: 'border-emerald-200 bg-emerald-50 text-emerald-700',
69
63
  attention: 'border-amber-200 bg-amber-50 text-amber-700',
@@ -71,12 +65,6 @@ const statusClasses: Record<string, string> = {
71
65
  paused: 'border-slate-200 bg-slate-100 text-slate-700',
72
66
  };
73
67
 
74
- const contractLabels: Record<string, string> = {
75
- fixed_price: 'Preço fechado',
76
- time_materials: 'Hora / Material',
77
- retainer: 'Recorrente',
78
- };
79
-
80
68
  const scenarioLabels: Record<OperationsReportScenario, string> = {
81
69
  base: 'Base',
82
70
  growth: 'Crescimento',
@@ -92,20 +80,12 @@ function formatPercent(value: number) {
92
80
  return `${Math.round(Number(value || 0))}%`;
93
81
  }
94
82
 
95
- function getStatusLabel(status: string) {
96
- return statusLabels[status] ?? status;
97
- }
98
-
99
83
  function getStatusClass(status: string) {
100
84
  return (
101
85
  statusClasses[status] ?? 'border-slate-200 bg-slate-100 text-slate-700'
102
86
  );
103
87
  }
104
88
 
105
- function getContractLabel(contractType: string) {
106
- return contractLabels[contractType] ?? contractType.replace(/_/g, ' ');
107
- }
108
-
109
89
  function KpiLabel({ text, tooltip }: { text: string; tooltip: string }) {
110
90
  return (
111
91
  <span className="flex items-center gap-1">
@@ -123,6 +103,7 @@ function KpiLabel({ text, tooltip }: { text: string; tooltip: string }) {
123
103
  }
124
104
 
125
105
  export default function OperationsProjectReportsPage() {
106
+ const t = useTranslations('operations.ReportsProjectsPage');
126
107
  const defaults = getDefaultRange();
127
108
  const { request } = useApp();
128
109
  const [startDate, setStartDate] = useState(defaults.from);
@@ -182,15 +163,15 @@ export default function OperationsProjectReportsPage() {
182
163
  return (
183
164
  <Page>
184
165
  <OperationsHeader
185
- title="Relatório de Projetos"
186
- description="Dashboard executivo com receita, custo, lucro, prazo, risco e planejamento dos próximos 12 meses."
187
- current="Relatórios / Projetos"
166
+ title={t('title')}
167
+ description={t('description')}
168
+ current={t('breadcrumb')}
188
169
  />
189
170
 
190
171
  <div className="grid gap-3 rounded-lg border bg-card p-3 md:grid-cols-[1fr_1fr_180px_180px_180px_auto]">
191
172
  <div className="space-y-1">
192
173
  <span className="text-xs font-medium text-muted-foreground">
193
- Início
174
+ {t('filters.start')}
194
175
  </span>
195
176
  <Input
196
177
  type="date"
@@ -199,7 +180,7 @@ export default function OperationsProjectReportsPage() {
199
180
  />
200
181
  </div>
201
182
  <div className="space-y-1">
202
- <span className="text-xs font-medium text-muted-foreground">Fim</span>
183
+ <span className="text-xs font-medium text-muted-foreground">{t('filters.end')}</span>
203
184
  <Input
204
185
  type="date"
205
186
  value={endDate}
@@ -208,31 +189,31 @@ export default function OperationsProjectReportsPage() {
208
189
  </div>
209
190
  <div className="space-y-1">
210
191
  <span className="text-xs font-medium text-muted-foreground">
211
- Status
192
+ {t('filters.status')}
212
193
  </span>
213
194
  <Select value={statusFilter} onValueChange={setStatusFilter}>
214
195
  <SelectTrigger className="w-full">
215
196
  <SelectValue />
216
197
  </SelectTrigger>
217
198
  <SelectContent>
218
- <SelectItem value="all">Todos</SelectItem>
219
- <SelectItem value="on_track">No prazo</SelectItem>
220
- <SelectItem value="attention">Atenção</SelectItem>
221
- <SelectItem value="late">Atrasado</SelectItem>
222
- <SelectItem value="paused">Pausado</SelectItem>
199
+ <SelectItem value="all">{t('filters.all')}</SelectItem>
200
+ <SelectItem value="on_track">{t('status.on_track')}</SelectItem>
201
+ <SelectItem value="attention">{t('status.attention')}</SelectItem>
202
+ <SelectItem value="late">{t('status.late')}</SelectItem>
203
+ <SelectItem value="paused">{t('status.paused')}</SelectItem>
223
204
  </SelectContent>
224
205
  </Select>
225
206
  </div>
226
207
  <div className="space-y-1">
227
208
  <span className="text-xs font-medium text-muted-foreground">
228
- Cliente
209
+ {t('filters.client')}
229
210
  </span>
230
211
  <Select value={clientFilter} onValueChange={setClientFilter}>
231
212
  <SelectTrigger className="w-full">
232
213
  <SelectValue />
233
214
  </SelectTrigger>
234
215
  <SelectContent>
235
- <SelectItem value="all">Todos</SelectItem>
216
+ <SelectItem value="all">{t('filters.all')}</SelectItem>
236
217
  {clients.map((client) => (
237
218
  <SelectItem key={client} value={client}>
238
219
  {client}
@@ -243,7 +224,7 @@ export default function OperationsProjectReportsPage() {
243
224
  </div>
244
225
  <div className="space-y-1">
245
226
  <span className="text-xs font-medium text-muted-foreground">
246
- Cenário
227
+ {t('filters.scenario')}
247
228
  </span>
248
229
  <Select
249
230
  value={scenario}
@@ -255,9 +236,9 @@ export default function OperationsProjectReportsPage() {
255
236
  <SelectValue />
256
237
  </SelectTrigger>
257
238
  <SelectContent>
258
- <SelectItem value="base">Base</SelectItem>
259
- <SelectItem value="growth">Crescimento</SelectItem>
260
- <SelectItem value="conservative">Conservador</SelectItem>
239
+ <SelectItem value="base">{t('scenario.base')}</SelectItem>
240
+ <SelectItem value="growth">{t('scenario.growth')}</SelectItem>
241
+ <SelectItem value="conservative">{t('scenario.conservative')}</SelectItem>
261
242
  </SelectContent>
262
243
  </Select>
263
244
  </div>
@@ -273,7 +254,7 @@ export default function OperationsProjectReportsPage() {
273
254
  setScenario('base');
274
255
  }}
275
256
  >
276
- Limpar
257
+ {t('actions.clear')}
277
258
  </Button>
278
259
  </div>
279
260
  </div>
@@ -286,12 +267,12 @@ export default function OperationsProjectReportsPage() {
286
267
  key: 'active',
287
268
  title: (
288
269
  <KpiLabel
289
- text="Projetos"
290
- tooltip="Número total de projetos ativos no período filtrado, incluindo os que estão em risco operacional."
270
+ text={t('cards.projects.title')}
271
+ tooltip={t('cards.projects.tooltip')}
291
272
  />
292
273
  ),
293
274
  value: data?.rows.length ?? 0,
294
- description: `${summary.atRisk} em risco operacional`,
275
+ description: t('cards.projects.description', { count: summary.atRisk }),
295
276
  icon: FolderKanban,
296
277
  accentClassName: 'from-sky-500/20 via-cyan-400/10 to-transparent',
297
278
  iconContainerClassName: 'bg-sky-50 text-sky-700',
@@ -300,12 +281,12 @@ export default function OperationsProjectReportsPage() {
300
281
  key: 'budget',
301
282
  title: (
302
283
  <KpiLabel
303
- text="Orçamento"
304
- tooltip="Soma dos campos de orçamento (budget_amount) de todos os projetos no período filtrado."
284
+ text={t('cards.budget.title')}
285
+ tooltip={t('cards.budget.tooltip')}
305
286
  />
306
287
  ),
307
288
  value: formatCurrency(summary.contractedRevenue),
308
- description: `${data?.rows.length ?? 0} projetos no período`,
289
+ description: t('cards.budget.description', { count: data?.rows.length ?? 0 }),
309
290
  icon: Banknote,
310
291
  accentClassName:
311
292
  'from-emerald-500/20 via-green-400/10 to-transparent',
@@ -315,12 +296,14 @@ export default function OperationsProjectReportsPage() {
315
296
  key: 'allocated-cost',
316
297
  title: (
317
298
  <KpiLabel
318
- text="Custo alocado"
319
- tooltip="Custo calculado a partir das alocações: horas semanais planejadas × custo mensal do colaborador × meses do período ÷ capacidade semanal."
299
+ text={t('cards.allocatedCost.title')}
300
+ tooltip={t('cards.allocatedCost.tooltip')}
320
301
  />
321
302
  ),
322
303
  value: formatCurrency(summary.allocatedCost),
323
- description: `vs ${formatCurrency(summary.consumedHoursCost)} consumido`,
304
+ description: t('cards.allocatedCost.description', {
305
+ value: formatCurrency(summary.consumedHoursCost),
306
+ }),
324
307
  icon: Gauge,
325
308
  accentClassName: 'from-rose-500/20 via-red-400/10 to-transparent',
326
309
  iconContainerClassName: 'bg-rose-50 text-rose-700',
@@ -329,12 +312,18 @@ export default function OperationsProjectReportsPage() {
329
312
  key: 'planned-profit',
330
313
  title: (
331
314
  <KpiLabel
332
- text="Lucro planejado"
333
- tooltip="Diferença entre o orçamento total dos projetos e o custo alocado dos colaboradores no período."
315
+ text={t('cards.plannedProfit.title')}
316
+ tooltip={t('cards.plannedProfit.tooltip')}
334
317
  />
335
318
  ),
336
319
  value: formatCurrency(summary.plannedProfit),
337
- description: `${formatPercent(summary.contractedRevenue > 0 ? (summary.plannedProfit / summary.contractedRevenue) * 100 : 0)} de margem`,
320
+ description: t('cards.plannedProfit.description', {
321
+ value: formatPercent(
322
+ summary.contractedRevenue > 0
323
+ ? (summary.plannedProfit / summary.contractedRevenue) * 100
324
+ : 0
325
+ ),
326
+ }),
338
327
  icon: TrendingUp,
339
328
  accentClassName:
340
329
  'from-violet-500/20 via-fuchsia-400/10 to-transparent',
@@ -344,12 +333,14 @@ export default function OperationsProjectReportsPage() {
344
333
  key: 'hours',
345
334
  title: (
346
335
  <KpiLabel
347
- text="Horas consumidas"
348
- tooltip="Total de horas lançadas e aprovadas nos timesheets dentro do período selecionado."
336
+ text={t('cards.hours.title')}
337
+ tooltip={t('cards.hours.tooltip')}
349
338
  />
350
339
  ),
351
340
  value: formatHours(summary.actualHours),
352
- description: `${formatHours(summary.billableHours)} faturáveis`,
341
+ description: t('cards.hours.description', {
342
+ value: formatHours(summary.billableHours),
343
+ }),
353
344
  icon: Clock3,
354
345
  layout: 'compact',
355
346
  accentClassName:
@@ -360,12 +351,14 @@ export default function OperationsProjectReportsPage() {
360
351
  key: 'consumed-hours-cost',
361
352
  title: (
362
353
  <KpiLabel
363
- text="Custo das horas consumidas"
364
- tooltip="Custo real das horas entregues: horas do timesheet × taxa horária do colaborador (custo mensal ÷ capacidade semanal)."
354
+ text={t('cards.consumedHoursCost.title')}
355
+ tooltip={t('cards.consumedHoursCost.tooltip')}
365
356
  />
366
357
  ),
367
358
  value: formatCurrency(summary.consumedHoursCost),
368
- description: `${formatHours(summary.actualHours)} de horas entregues`,
359
+ description: t('cards.consumedHoursCost.description', {
360
+ value: formatHours(summary.actualHours),
361
+ }),
369
362
  icon: Receipt,
370
363
  layout: 'compact',
371
364
  accentClassName:
@@ -376,12 +369,14 @@ export default function OperationsProjectReportsPage() {
376
369
  key: 'idleness-rate',
377
370
  title: (
378
371
  <KpiLabel
379
- text="Taxa de ociosidade"
380
- tooltip="Percentual de tempo ocioso: diferença entre a carga horária semanal planejada (alocada) e as horas efetivamente submetidas via timesheet, em relação à capacidade total."
372
+ text={t('cards.idlenessRate.title')}
373
+ tooltip={t('cards.idlenessRate.tooltip')}
381
374
  />
382
375
  ),
383
376
  value: formatPercent(summary.idlenessRate),
384
- description: `${formatCurrency(summary.idlenessCost)} de custo ocioso`,
377
+ description: t('cards.idlenessRate.description', {
378
+ value: formatCurrency(summary.idlenessCost),
379
+ }),
385
380
  icon: TimerOff,
386
381
  layout: 'compact',
387
382
  accentClassName:
@@ -392,12 +387,14 @@ export default function OperationsProjectReportsPage() {
392
387
  key: 'idleness-cost',
393
388
  title: (
394
389
  <KpiLabel
395
- text="Custo de ociosidade"
396
- tooltip="Quanto foi gasto com tempo ocioso: custo alocado menos o custo das horas efetivamente consumidas nos timesheets aprovados."
390
+ text={t('cards.idlenessCost.title')}
391
+ tooltip={t('cards.idlenessCost.tooltip')}
397
392
  />
398
393
  ),
399
394
  value: formatCurrency(summary.idlenessCost),
400
- description: `${formatPercent(summary.idlenessRate)} de ociosidade`,
395
+ description: t('cards.idlenessCost.description', {
396
+ value: formatPercent(summary.idlenessRate),
397
+ }),
401
398
  icon: CircleDollarSign,
402
399
  layout: 'compact',
403
400
  accentClassName:
@@ -410,27 +407,26 @@ export default function OperationsProjectReportsPage() {
410
407
 
411
408
  <Card>
412
409
  <CardHeader>
413
- <CardTitle>Detalhamento por projeto</CardTitle>
410
+ <CardTitle>{t('table.title')}</CardTitle>
414
411
  <CardDescription>
415
- Orçamento, custo alocado, lucro planejado, horas consumidas,
416
- ociosidade e risco por projeto.
412
+ {t('table.description')}
417
413
  </CardDescription>
418
414
  </CardHeader>
419
415
  <CardContent className="overflow-x-auto">
420
416
  <Table>
421
417
  <TableHeader>
422
418
  <TableRow>
423
- <TableHead>Projeto</TableHead>
424
- <TableHead>Status</TableHead>
425
- <TableHead>Orçamento</TableHead>
426
- <TableHead>Custo alocado</TableHead>
427
- <TableHead>Lucro planejado</TableHead>
428
- <TableHead>Horas consumidas</TableHead>
429
- <TableHead>Custo das horas consumidas</TableHead>
430
- <TableHead>Taxa de ociosidade</TableHead>
431
- <TableHead>Custo de ociosidade</TableHead>
432
- <TableHead>Risco</TableHead>
433
- <TableHead>Recomendação</TableHead>
419
+ <TableHead>{t('table.columns.project')}</TableHead>
420
+ <TableHead>{t('table.columns.status')}</TableHead>
421
+ <TableHead>{t('table.columns.budget')}</TableHead>
422
+ <TableHead>{t('table.columns.allocatedCost')}</TableHead>
423
+ <TableHead>{t('table.columns.plannedProfit')}</TableHead>
424
+ <TableHead>{t('table.columns.hours')}</TableHead>
425
+ <TableHead>{t('table.columns.consumedHoursCost')}</TableHead>
426
+ <TableHead>{t('table.columns.idlenessRate')}</TableHead>
427
+ <TableHead>{t('table.columns.idlenessCost')}</TableHead>
428
+ <TableHead>{t('table.columns.risk')}</TableHead>
429
+ <TableHead>{t('table.columns.recommendation')}</TableHead>
434
430
  <TableHead />
435
431
  </TableRow>
436
432
  </TableHeader>
@@ -452,7 +448,7 @@ export default function OperationsProjectReportsPage() {
452
448
  <div className="font-medium">{row.name}</div>
453
449
  <div className="text-xs text-muted-foreground">
454
450
  {row.client} · {row.manager} · {row.squad} ·{' '}
455
- {getContractLabel(row.contractType)}
451
+ {t(`contractType.${row.contractType}`)}
456
452
  </div>
457
453
  </TableCell>
458
454
  <TableCell>
@@ -460,7 +456,7 @@ export default function OperationsProjectReportsPage() {
460
456
  variant="outline"
461
457
  className={getStatusClass(row.status)}
462
458
  >
463
- {getStatusLabel(row.status)}
459
+ {t(`status.${row.status}`)}
464
460
  </Badge>
465
461
  </TableCell>
466
462
  <TableCell>
@@ -482,7 +478,9 @@ export default function OperationsProjectReportsPage() {
482
478
  <TableCell>
483
479
  <div>{formatHours(row.actualHours)}</div>
484
480
  <div className="text-xs text-muted-foreground">
485
- {formatHours(row.billableHours)} faturáveis
481
+ {t('table.billableHours', {
482
+ value: formatHours(row.billableHours),
483
+ })}
486
484
  </div>
487
485
  </TableCell>
488
486
  <TableCell>
@@ -508,7 +506,7 @@ export default function OperationsProjectReportsPage() {
508
506
  : 'border-emerald-200 bg-emerald-50 text-emerald-700'
509
507
  }
510
508
  >
511
- {row.risk}
509
+ {t(`risk.${row.risk}`)}
512
510
  </Badge>
513
511
  </TableCell>
514
512
  <TableCell className="min-w-72 text-sm text-muted-foreground">
@@ -523,7 +521,7 @@ export default function OperationsProjectReportsPage() {
523
521
  size="icon"
524
522
  variant="ghost"
525
523
  className="size-8"
526
- title="Ver detalhes"
524
+ title={t('actions.viewDetails')}
527
525
  >
528
526
  <ExternalLink className="size-4" />
529
527
  </Button>
@@ -52,6 +52,7 @@ import { OperationsHeader } from '../_components/operations-header';
52
52
  import { StatusBadge } from '../_components/status-badge';
53
53
  import { fetchOperations, mutateOperations } from '../_lib/api';
54
54
  import { useOperationsAccess } from '../_lib/hooks/use-operations-access';
55
+ import { usePersistedPageSize } from '@/hooks/use-persisted-page-size';
55
56
  import type {
56
57
  OperationsScheduleAdjustmentDay,
57
58
  OperationsScheduleAdjustmentRequest,
@@ -163,10 +164,12 @@ function SchedulePanel({
163
164
  days,
164
165
  locale,
165
166
  emptyLabel,
167
+ dayOffLabel,
166
168
  }: {
167
169
  days: ScheduleDay[];
168
170
  locale: string;
169
171
  emptyLabel: string;
172
+ dayOffLabel: string;
170
173
  }) {
171
174
  const lines = groupScheduleLines(days, locale);
172
175
  if (!lines.length)
@@ -182,7 +185,7 @@ function SchedulePanel({
182
185
  {line.dayLabel}
183
186
  </span>
184
187
  {line.isOff ? (
185
- <span className="text-muted-foreground">Folga</span>
188
+ <span className="text-muted-foreground">{dayOffLabel}</span>
186
189
  ) : (
187
190
  <span className="text-foreground">
188
191
  {line.time}
@@ -235,7 +238,11 @@ export default function OperationsScheduleAdjustmentsPage() {
235
238
  const [search, setSearch] = useState('');
236
239
  const [statusFilter, setStatusFilter] = useState('all');
237
240
  const [page, setPage] = useState(1);
238
- const [pageSize, setPageSize] = useState(12);
241
+ const [pageSize, setPageSize] = usePersistedPageSize({
242
+ storageKey: 'pagination:operations-schedule-adjustments:pageSize',
243
+ defaultValue: 12,
244
+ allowedValues: [12, 24, 48],
245
+ });
239
246
  const [viewMode, setViewMode] = useState<'table' | 'cards'>(() => {
240
247
  if (typeof window === 'undefined') return 'cards';
241
248
  const saved = window.localStorage.getItem(
@@ -539,6 +546,7 @@ export default function OperationsScheduleAdjustmentsPage() {
539
546
  days={requestItem.currentSchedule ?? []}
540
547
  locale={currentLocaleCode}
541
548
  emptyLabel={commonT('labels.notAssigned')}
549
+ dayOffLabel={commonT('labels.dayOff')}
542
550
  />
543
551
  </div>
544
552
  <div className="bg-card px-4 py-3">
@@ -552,6 +560,7 @@ export default function OperationsScheduleAdjustmentsPage() {
552
560
  }
553
561
  locale={currentLocaleCode}
554
562
  emptyLabel={commonT('labels.notAssigned')}
563
+ dayOffLabel={commonT('labels.dayOff')}
555
564
  />
556
565
  </div>
557
566
  </div>
@@ -946,6 +955,7 @@ export default function OperationsScheduleAdjustmentsPage() {
946
955
  days={selectedRequest.currentSchedule ?? []}
947
956
  locale={currentLocaleCode}
948
957
  emptyLabel={commonT('labels.notAssigned')}
958
+ dayOffLabel={commonT('labels.dayOff')}
949
959
  />
950
960
  </div>
951
961
  </div>
@@ -961,6 +971,7 @@ export default function OperationsScheduleAdjustmentsPage() {
961
971
  }
962
972
  locale={currentLocaleCode}
963
973
  emptyLabel={commonT('labels.notAssigned')}
974
+ dayOffLabel={commonT('labels.dayOff')}
964
975
  />
965
976
  </div>
966
977
  </div>
@@ -43,6 +43,7 @@ import { OperationsHeader } from '../_components/operations-header';
43
43
  import { StatusBadge } from '../_components/status-badge';
44
44
  import { fetchOperations, mutateOperations } from '../_lib/api';
45
45
  import { useOperationsAccess } from '../_lib/hooks/use-operations-access';
46
+ import { usePersistedPageSize } from '@/hooks/use-persisted-page-size';
46
47
  import type {
47
48
  OperationsTimeOffRequest,
48
49
  PaginatedResponse,
@@ -106,7 +107,11 @@ export default function OperationsTimeOffPage() {
106
107
  const [search, setSearch] = useState('');
107
108
  const [statusFilter, setStatusFilter] = useState('all');
108
109
  const [page, setPage] = useState(1);
109
- const [pageSize, setPageSize] = useState(12);
110
+ const [pageSize, setPageSize] = usePersistedPageSize({
111
+ storageKey: 'pagination:operations-time-off:pageSize',
112
+ defaultValue: 12,
113
+ allowedValues: [12, 24, 48],
114
+ });
110
115
  const [viewMode, setViewMode] = useState<'table' | 'cards'>(() => {
111
116
  if (typeof window === 'undefined') return 'table';
112
117
  const saved = window.localStorage.getItem('operations-time-off-view-mode');
@@ -80,6 +80,7 @@ import {
80
80
  mutateOperations,
81
81
  } from '../_lib/api';
82
82
  import { useOperationsAccess } from '../_lib/hooks/use-operations-access';
83
+ import { usePersistedPageSize } from '@/hooks/use-persisted-page-size';
83
84
  import type {
84
85
  OperationsCollaborator,
85
86
  OperationsProjectOption,
@@ -242,7 +243,11 @@ export default function OperationsTimesheetsPage() {
242
243
  () => new Date().getMonth() + 1
243
244
  );
244
245
  const [page, setPage] = useState(1);
245
- const [pageSize, setPageSize] = useState(12);
246
+ const [pageSize, setPageSize] = usePersistedPageSize({
247
+ storageKey: 'pagination:operations-timesheets:pageSize',
248
+ defaultValue: 12,
249
+ allowedValues: [12, 24, 48],
250
+ });
246
251
  const [viewMode, setViewMode] = useState<'table' | 'cards' | 'calendar'>(
247
252
  () => {
248
253
  if (typeof window === 'undefined') return 'table';
@@ -733,8 +738,7 @@ export default function OperationsTimesheetsPage() {
733
738
  status: entry.status ?? 'submitted',
734
739
  projectId: entry.projectId,
735
740
  projectAssignmentId: entry.projectAssignmentId ?? 0,
736
- projectName:
737
- entry.projectName || commonT('labels.unassigned'),
741
+ projectName: entry.projectName || commonT('labels.unassigned'),
738
742
  },
739
743
  ]
740
744
  : []
@@ -1188,7 +1192,9 @@ export default function OperationsTimesheetsPage() {
1188
1192
  {canSubmitTimesheet(timesheet) ? (
1189
1193
  <Button
1190
1194
  size="icon"
1191
- onClick={() => void submitTimesheet(timesheet.id)}
1195
+ onClick={() =>
1196
+ void submitTimesheet(timesheet.id)
1197
+ }
1192
1198
  >
1193
1199
  <Send className="size-4" />
1194
1200
  </Button>