@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
@@ -26,25 +26,39 @@ import {
26
26
  TableRow,
27
27
  } from '@/components/ui/table';
28
28
  import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group';
29
+ import { usePersistedPageSize } from '@/hooks/use-persisted-page-size';
30
+ import {
31
+ closestCenter,
32
+ DndContext,
33
+ PointerSensor,
34
+ useDraggable,
35
+ useDroppable,
36
+ useSensor,
37
+ useSensors,
38
+ type DragEndEvent,
39
+ type UniqueIdentifier,
40
+ } from '@dnd-kit/core';
41
+ import { CSS } from '@dnd-kit/utilities';
29
42
  import { useApp, useQuery } from '@hed-hog/next-app-provider';
30
43
  import {
31
44
  Archive,
32
45
  ArchiveRestore,
33
46
  CalendarDays,
47
+ CheckCircle2,
34
48
  Eye,
35
49
  EyeOff,
36
50
  FileText,
37
51
  FolderKanban,
52
+ KanbanSquare,
38
53
  LayoutGrid,
39
54
  List,
40
55
  Pencil,
41
56
  PlayCircle,
42
- ShieldAlert,
43
57
  } from 'lucide-react';
44
58
  import { useTranslations } from 'next-intl';
45
59
  import Link from 'next/link';
46
60
  import { usePathname, useRouter, useSearchParams } from 'next/navigation';
47
- import { useMemo, useState } from 'react';
61
+ import { useMemo, useState, type ReactNode } from 'react';
48
62
  import { OperationsHeader } from '../_components/operations-header';
49
63
  import { ProjectFormScreen } from '../_components/project-form-screen';
50
64
  import { StatusBadge } from '../_components/status-badge';
@@ -63,7 +77,122 @@ import {
63
77
 
64
78
  const PROJECT_VIEW_STORAGE_KEY = 'operations-projects-view-mode';
65
79
 
66
- type ProjectViewMode = 'table' | 'cards';
80
+ type ProjectViewMode = 'table' | 'cards' | 'board';
81
+
82
+ type ProjectBoardStatus = 'planning' | 'active' | 'paused' | 'completed';
83
+
84
+ type ProjectBoardColumns = Record<ProjectBoardStatus, OperationsProject[]>;
85
+
86
+ const PROJECT_BOARD_COLUMNS: Array<{ id: ProjectBoardStatus; color: string }> =
87
+ [
88
+ { id: 'planning', color: 'bg-amber-500' },
89
+ { id: 'active', color: 'bg-emerald-500' },
90
+ { id: 'paused', color: 'bg-slate-500' },
91
+ { id: 'completed', color: 'bg-sky-500' },
92
+ ];
93
+
94
+ function projectDragId(projectId: number) {
95
+ return `project-${projectId}`;
96
+ }
97
+
98
+ function columnDropId(columnId: ProjectBoardStatus) {
99
+ return `project-col-${columnId}`;
100
+ }
101
+
102
+ function parseProjectId(value: UniqueIdentifier | null | undefined) {
103
+ if (!value) return null;
104
+ const match = String(value).match(/^project-(\d+)$/);
105
+ return match ? Number(match[1]) : null;
106
+ }
107
+
108
+ function parseBoardColumnId(
109
+ value: UniqueIdentifier | null | undefined
110
+ ): ProjectBoardStatus | null {
111
+ if (!value) return null;
112
+ const match = String(value).match(/^project-col-(.+)$/);
113
+ const id = match?.[1];
114
+ return PROJECT_BOARD_COLUMNS.some((column) => column.id === id)
115
+ ? (id as ProjectBoardStatus)
116
+ : null;
117
+ }
118
+
119
+ function splitProjectsByStatus(
120
+ projects: OperationsProject[]
121
+ ): ProjectBoardColumns {
122
+ return {
123
+ planning: projects.filter((project) => project.status === 'planning'),
124
+ active: projects.filter((project) => project.status === 'active'),
125
+ paused: projects.filter((project) => project.status === 'paused'),
126
+ completed: projects.filter((project) => project.status === 'completed'),
127
+ };
128
+ }
129
+
130
+ function moveProjectToColumn(
131
+ columns: ProjectBoardColumns,
132
+ projectId: number,
133
+ target: ProjectBoardStatus
134
+ ): ProjectBoardColumns {
135
+ const sourceProject =
136
+ columns.planning.find((project) => project.id === projectId) ??
137
+ columns.active.find((project) => project.id === projectId) ??
138
+ columns.paused.find((project) => project.id === projectId) ??
139
+ columns.completed.find((project) => project.id === projectId);
140
+
141
+ if (!sourceProject) {
142
+ return columns;
143
+ }
144
+
145
+ const nextColumns: ProjectBoardColumns = {
146
+ planning: columns.planning.filter((project) => project.id !== projectId),
147
+ active: columns.active.filter((project) => project.id !== projectId),
148
+ paused: columns.paused.filter((project) => project.id !== projectId),
149
+ completed: columns.completed.filter((project) => project.id !== projectId),
150
+ };
151
+
152
+ return {
153
+ ...nextColumns,
154
+ [target]: [{ ...sourceProject, status: target }, ...nextColumns[target]],
155
+ };
156
+ }
157
+
158
+ function DraggableProjectCard({
159
+ project,
160
+ disabled,
161
+ children,
162
+ }: {
163
+ project: OperationsProject;
164
+ disabled?: boolean;
165
+ children: (isDragging: boolean) => ReactNode;
166
+ }) {
167
+ const { attributes, listeners, setNodeRef, transform, isDragging } =
168
+ useDraggable({
169
+ id: projectDragId(project.id),
170
+ disabled,
171
+ });
172
+
173
+ return (
174
+ <div
175
+ ref={setNodeRef}
176
+ style={{ transform: CSS.Translate.toString(transform) }}
177
+ {...listeners}
178
+ {...attributes}
179
+ className={isDragging ? 'z-20 touch-none' : 'touch-none'}
180
+ >
181
+ {children(isDragging)}
182
+ </div>
183
+ );
184
+ }
185
+
186
+ function DroppableProjectColumn({
187
+ columnId,
188
+ children,
189
+ }: {
190
+ columnId: ProjectBoardStatus;
191
+ children: (isOver: boolean) => ReactNode;
192
+ }) {
193
+ const { setNodeRef, isOver } = useDroppable({ id: columnDropId(columnId) });
194
+ return <div ref={setNodeRef}>{children(isOver)}</div>;
195
+ }
67
196
 
68
197
  function getPersonAvatarUrl(avatarId?: number | null): string {
69
198
  return typeof avatarId === 'number' && avatarId > 0
@@ -113,7 +242,11 @@ export default function OperationsProjectsPage() {
113
242
  const [search, setSearch] = useState('');
114
243
  const [statusFilter, setStatusFilter] = useState('all');
115
244
  const [page, setPage] = useState(1);
116
- const [pageSize, setPageSize] = useState(12);
245
+ const [pageSize, setPageSize] = usePersistedPageSize({
246
+ storageKey: 'pagination:operations-projects:pageSize',
247
+ defaultValue: 12,
248
+ allowedValues: [12, 24, 48],
249
+ });
117
250
  const [viewMode, setViewMode] = useState<ProjectViewMode>(() => {
118
251
  if (typeof window === 'undefined') {
119
252
  return 'table';
@@ -121,8 +254,20 @@ export default function OperationsProjectsPage() {
121
254
 
122
255
  const savedViewMode = window.localStorage.getItem(PROJECT_VIEW_STORAGE_KEY);
123
256
 
124
- return savedViewMode === 'cards' ? 'cards' : 'table';
257
+ if (
258
+ savedViewMode === 'table' ||
259
+ savedViewMode === 'cards' ||
260
+ savedViewMode === 'board'
261
+ ) {
262
+ return savedViewMode;
263
+ }
264
+
265
+ return 'table';
125
266
  });
267
+ const [boardOverride, setBoardOverride] =
268
+ useState<ProjectBoardColumns | null>(null);
269
+ const activeViewMode: ProjectViewMode =
270
+ statusFilter === 'archived' && viewMode === 'board' ? 'table' : viewMode;
126
271
 
127
272
  const createParam = searchParams.get('create');
128
273
  const editProjectId = parseEditProjectId(searchParams.get('edit'));
@@ -197,8 +342,57 @@ export default function OperationsProjectsPage() {
197
342
  },
198
343
  placeholderData: (previous) => previous,
199
344
  });
200
- const projects = projectsResponse?.data ?? [];
345
+ const { data: boardProjectsResponse, refetch: refetchBoard } = useQuery<
346
+ PaginatedResponse<OperationsProject>
347
+ >({
348
+ queryKey: [
349
+ 'operations-projects-board',
350
+ currentLocaleCode,
351
+ search,
352
+ statusFilter,
353
+ ],
354
+ queryFn: () => {
355
+ const params = new URLSearchParams({
356
+ page: '1',
357
+ pageSize: '1000',
358
+ });
359
+
360
+ if (search.trim()) {
361
+ params.set('search', search.trim());
362
+ }
363
+
364
+ if (statusFilter !== 'all') {
365
+ params.set('status', statusFilter);
366
+ }
367
+
368
+ return fetchOperations<PaginatedResponse<OperationsProject>>(
369
+ request,
370
+ `/operations/projects?${params.toString()}`
371
+ );
372
+ },
373
+ enabled: activeViewMode === 'board',
374
+ placeholderData: (previous) => previous,
375
+ });
376
+ const projects = useMemo(
377
+ () => projectsResponse?.data ?? [],
378
+ [projectsResponse?.data]
379
+ );
380
+ const boardProjects = useMemo(
381
+ () => boardProjectsResponse?.data ?? [],
382
+ [boardProjectsResponse?.data]
383
+ );
384
+ const boardColumns = useMemo(() => {
385
+ if (boardOverride) {
386
+ return boardOverride;
387
+ }
388
+
389
+ return splitProjectsByStatus(boardProjects);
390
+ }, [boardOverride, boardProjects]);
201
391
  const filteredRows = projects;
392
+ const hasRowsForCurrentView =
393
+ activeViewMode === 'board'
394
+ ? boardProjects.length > 0
395
+ : filteredRows.length > 0;
202
396
 
203
397
  const statsCards = useMemo(
204
398
  () => [
@@ -221,13 +415,13 @@ export default function OperationsProjectsPage() {
221
415
  iconContainerClassName: 'bg-green-50 text-green-600',
222
416
  },
223
417
  {
224
- key: 'atRisk',
225
- title: t('cards.atRisk'),
226
- description: t('cards.atRiskDescription'),
227
- value: projects.filter((item) => item.status === 'at_risk').length,
228
- icon: ShieldAlert,
229
- accentClassName: 'from-amber-500/20 via-orange-500/10 to-transparent',
230
- iconContainerClassName: 'bg-amber-50 text-amber-600',
418
+ key: 'completed',
419
+ title: t('cards.completed'),
420
+ description: t('cards.completedDescription'),
421
+ value: projects.filter((item) => item.status === 'completed').length,
422
+ icon: CheckCircle2,
423
+ accentClassName: 'from-sky-500/20 via-cyan-500/10 to-transparent',
424
+ iconContainerClassName: 'bg-sky-50 text-sky-600',
231
425
  },
232
426
  {
233
427
  key: 'upcomingDeliveries',
@@ -243,7 +437,7 @@ export default function OperationsProjectsPage() {
243
437
  );
244
438
 
245
439
  const handleViewModeChange = (value: string) => {
246
- if (value !== 'table' && value !== 'cards') {
440
+ if (value !== 'table' && value !== 'cards' && value !== 'board') {
247
441
  return;
248
442
  }
249
443
 
@@ -254,6 +448,83 @@ export default function OperationsProjectsPage() {
254
448
  }
255
449
  };
256
450
 
451
+ const sensors = useSensors(
452
+ useSensor(PointerSensor, { activationConstraint: { distance: 6 } })
453
+ );
454
+
455
+ const handleBoardDragEnd = async (event: DragEndEvent) => {
456
+ if (!access.isDirector) {
457
+ return;
458
+ }
459
+
460
+ const projectId = parseProjectId(event.active.id);
461
+ const targetColumn = parseBoardColumnId(event.over?.id);
462
+
463
+ if (!projectId || !targetColumn) {
464
+ return;
465
+ }
466
+
467
+ const sourceColumn = PROJECT_BOARD_COLUMNS.find((column) =>
468
+ boardColumns[column.id].some((project) => project.id === projectId)
469
+ )?.id;
470
+
471
+ if (!sourceColumn || sourceColumn === targetColumn) {
472
+ return;
473
+ }
474
+
475
+ setBoardOverride(
476
+ moveProjectToColumn(boardColumns, projectId, targetColumn)
477
+ );
478
+
479
+ try {
480
+ await mutateOperations(
481
+ request,
482
+ `/operations/projects/${projectId}`,
483
+ 'PATCH',
484
+ {
485
+ status: targetColumn,
486
+ }
487
+ );
488
+ showToastHandler?.('success', t('messages.statusSuccess'));
489
+ await Promise.all([refetch(), refetchBoard()]);
490
+ setBoardOverride(null);
491
+ } catch {
492
+ setBoardOverride(null);
493
+ showToastHandler?.('error', t('messages.statusError'));
494
+ await Promise.all([refetch(), refetchBoard()]);
495
+ }
496
+ };
497
+
498
+ const renderBoardCard = (project: OperationsProject, isDragging = false) => (
499
+ <Card
500
+ className={`cursor-pointer border-border/60 py-0 shadow-sm transition ${
501
+ isDragging
502
+ ? 'opacity-70 shadow-lg'
503
+ : 'hover:-translate-y-0.5 hover:shadow-md'
504
+ }`}
505
+ onDoubleClick={() => router.push(`/operations/projects/${project.id}`)}
506
+ >
507
+ <CardContent className="space-y-2 p-3">
508
+ <div className="truncate text-sm font-semibold">{project.name}</div>
509
+ <div className="truncate text-xs text-muted-foreground">
510
+ {[project.code, project.clientName].filter(Boolean).join(' • ') ||
511
+ commonT('labels.notAvailable')}
512
+ </div>
513
+ <div className="flex items-center justify-between gap-2 pt-1">
514
+ <StatusBadge
515
+ label={tProjectStatus(project.status)}
516
+ className={getStatusBadgeClass(project.status)}
517
+ />
518
+ <Button variant="outline" size="icon" asChild>
519
+ <Link href={`/operations/projects/${project.id}`}>
520
+ <Eye className="size-4" />
521
+ </Link>
522
+ </Button>
523
+ </div>
524
+ </CardContent>
525
+ </Card>
526
+ );
527
+
257
528
  const toggleArchived = async (project: OperationsProject) => {
258
529
  const nextStatus = project.status === 'archived' ? 'active' : 'archived';
259
530
 
@@ -267,7 +538,7 @@ export default function OperationsProjectsPage() {
267
538
  }
268
539
  );
269
540
  showToastHandler?.('success', t('messages.statusSuccess'));
270
- await refetch();
541
+ await Promise.all([refetch(), refetchBoard()]);
271
542
  } catch {
272
543
  showToastHandler?.('error', t('messages.statusError'));
273
544
  }
@@ -332,7 +603,6 @@ export default function OperationsProjectsPage() {
332
603
  { value: 'all', label: commonT('filters.allStatuses') },
333
604
  { value: 'planning', label: tProjectStatus('planning') },
334
605
  { value: 'active', label: tProjectStatus('active') },
335
- { value: 'at_risk', label: tProjectStatus('at_risk') },
336
606
  { value: 'paused', label: tProjectStatus('paused') },
337
607
  { value: 'completed', label: tProjectStatus('completed') },
338
608
  { value: 'archived', label: tProjectStatus('archived') },
@@ -348,7 +618,7 @@ export default function OperationsProjectsPage() {
348
618
  </span>
349
619
  <ToggleGroup
350
620
  type="single"
351
- value={viewMode}
621
+ value={activeViewMode}
352
622
  onValueChange={handleViewModeChange}
353
623
  variant="outline"
354
624
  size="sm"
@@ -370,12 +640,86 @@ export default function OperationsProjectsPage() {
370
640
  <LayoutGrid className="h-4 w-4" />
371
641
  <span className="hidden sm:inline">{t('viewModeCards')}</span>
372
642
  </ToggleGroupItem>
643
+ <ToggleGroupItem
644
+ value="board"
645
+ className="gap-1.5 px-2.5"
646
+ aria-label={t('viewModeBoard')}
647
+ >
648
+ <KanbanSquare className="h-4 w-4" />
649
+ <span className="hidden sm:inline">{t('viewModeBoard')}</span>
650
+ </ToggleGroupItem>
373
651
  </ToggleGroup>
374
652
  </div>
375
653
  </div>
376
654
 
377
- {filteredRows.length > 0 ? (
378
- viewMode === 'cards' ? (
655
+ {hasRowsForCurrentView ? (
656
+ activeViewMode === 'board' ? (
657
+ <>
658
+ {!access.isDirector ? (
659
+ <p className="text-sm text-muted-foreground">
660
+ {t('board.directorOnly')}
661
+ </p>
662
+ ) : null}
663
+ <DndContext
664
+ sensors={access.isDirector ? sensors : undefined}
665
+ collisionDetection={closestCenter}
666
+ onDragEnd={(event) => void handleBoardDragEnd(event)}
667
+ >
668
+ <div className="grid gap-4 lg:grid-cols-2 xl:grid-cols-4">
669
+ {PROJECT_BOARD_COLUMNS.map((column) => (
670
+ <DroppableProjectColumn key={column.id} columnId={column.id}>
671
+ {(isOver) => (
672
+ <div
673
+ className={`flex min-h-72 flex-col gap-3 rounded-md border bg-muted/20 p-3 ${
674
+ isOver && access.isDirector
675
+ ? 'border-primary/60 bg-primary/5'
676
+ : 'border-border/60'
677
+ }`}
678
+ >
679
+ <div className="flex items-center gap-2">
680
+ <span
681
+ className={`size-2.5 rounded-full ${column.color}`}
682
+ />
683
+ <span className="text-sm font-semibold">
684
+ {t(`board.columns.${column.id}`)}
685
+ </span>
686
+ <span className="text-xs text-muted-foreground">
687
+ ({boardColumns[column.id].length})
688
+ </span>
689
+ </div>
690
+
691
+ <div className="space-y-2">
692
+ {boardColumns[column.id].length ? (
693
+ boardColumns[column.id].map((project) =>
694
+ access.isDirector ? (
695
+ <DraggableProjectCard
696
+ key={project.id}
697
+ project={project}
698
+ >
699
+ {(isDragging) =>
700
+ renderBoardCard(project, isDragging)
701
+ }
702
+ </DraggableProjectCard>
703
+ ) : (
704
+ <div key={project.id}>
705
+ {renderBoardCard(project)}
706
+ </div>
707
+ )
708
+ )
709
+ ) : (
710
+ <div className="rounded-md border border-dashed border-border/70 px-3 py-5 text-center text-xs text-muted-foreground">
711
+ {t('board.emptyColumn')}
712
+ </div>
713
+ )}
714
+ </div>
715
+ </div>
716
+ )}
717
+ </DroppableProjectColumn>
718
+ ))}
719
+ </div>
720
+ </DndContext>
721
+ </>
722
+ ) : activeViewMode === 'cards' ? (
379
723
  <div className="grid gap-4 md:grid-cols-2 2xl:grid-cols-3">
380
724
  {filteredRows.map((project) => (
381
725
  <Card
@@ -769,17 +1113,19 @@ export default function OperationsProjectsPage() {
769
1113
  />
770
1114
  )}
771
1115
 
772
- <PaginationFooter
773
- currentPage={projectsResponse?.page ?? page}
774
- pageSize={projectsResponse?.pageSize ?? pageSize}
775
- totalItems={projectsResponse?.total ?? 0}
776
- onPageChange={setPage}
777
- onPageSizeChange={(value) => {
778
- setPageSize(value);
779
- setPage(1);
780
- }}
781
- pageSizeOptions={[12, 24, 48]}
782
- />
1116
+ {activeViewMode !== 'board' ? (
1117
+ <PaginationFooter
1118
+ currentPage={projectsResponse?.page ?? page}
1119
+ pageSize={projectsResponse?.pageSize ?? pageSize}
1120
+ totalItems={projectsResponse?.total ?? 0}
1121
+ onPageChange={setPage}
1122
+ onPageSizeChange={(value) => {
1123
+ setPageSize(value);
1124
+ setPage(1);
1125
+ }}
1126
+ pageSizeOptions={[12, 24, 48]}
1127
+ />
1128
+ ) : null}
783
1129
 
784
1130
  <Sheet
785
1131
  open={isSheetOpen}
@@ -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,
@@ -237,7 +238,11 @@ export default function OperationsScheduleAdjustmentsPage() {
237
238
  const [search, setSearch] = useState('');
238
239
  const [statusFilter, setStatusFilter] = useState('all');
239
240
  const [page, setPage] = useState(1);
240
- 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
+ });
241
246
  const [viewMode, setViewMode] = useState<'table' | 'cards'>(() => {
242
247
  if (typeof window === 'undefined') return 'cards';
243
248
  const saved = window.localStorage.getItem(
@@ -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>