@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
@@ -1,7 +1,6 @@
1
1
  'use client';
2
2
 
3
3
  import { EmptyState, Page } from '@/components/entity-list';
4
- import { RichTextEditor } from '@/components/rich-text-editor';
5
4
  import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
6
5
  import { Button } from '@/components/ui/button';
7
6
  import { Card, CardContent } from '@/components/ui/card';
@@ -14,6 +13,7 @@ import {
14
13
  import {
15
14
  Dialog,
16
15
  DialogContent,
16
+ DialogDescription,
17
17
  DialogFooter,
18
18
  DialogHeader,
19
19
  DialogTitle,
@@ -87,6 +87,7 @@ import {
87
87
  Gauge,
88
88
  GitCommitHorizontal,
89
89
  HeartPulse,
90
+ History,
90
91
  LineChart as LineChartIcon,
91
92
  Loader2,
92
93
  MessageSquare,
@@ -95,6 +96,7 @@ import {
95
96
  Plus,
96
97
  Rocket,
97
98
  Search,
99
+ Send,
98
100
  SlidersHorizontal,
99
101
  Timer,
100
102
  Trash2,
@@ -125,7 +127,6 @@ import {
125
127
  YAxis,
126
128
  } from 'recharts';
127
129
  import { fetchOperations, mutateOperations } from '../_lib/api';
128
- import { useMentionItems } from '../_lib/hooks/use-mention-items';
129
130
  import { useOperationsAccess } from '../_lib/hooks/use-operations-access';
130
131
  import {
131
132
  MASKED_VALUE,
@@ -150,11 +151,13 @@ import { ProjectFormScreen } from './project-form-screen';
150
151
  import { SectionCard } from './section-card';
151
152
  import { StatusBadge } from './status-badge';
152
153
  import {
153
- TaskCommentsSection,
154
154
  TaskDetailSheet,
155
155
  type TaskDetailSheetData,
156
156
  } from './task-detail-sheet';
157
- import { TaskFileAttachments } from './task-file-attachments';
157
+ import { ProjectFileAttachments } from './project-file-attachments';
158
+ import { TaskFileAttachments } from './task-file-attachments';
159
+ import { TaskFormSheet } from './task-form-sheet';
160
+ import { TimesheetEntryCreateSheet } from './timesheet-entry-create-sheet';
158
161
 
159
162
  type BoardColumnId = 'todo' | 'doing' | 'review' | 'done';
160
163
 
@@ -175,6 +178,8 @@ type BoardTask = {
175
178
  createdAt: string | null;
176
179
  commentCount: number;
177
180
  fileCount: number;
181
+ doingStartedAt: string | null;
182
+ totalDoingMinutes: number;
178
183
  };
179
184
 
180
185
  type ApiBoardTask = Partial<BoardTask> & {
@@ -184,28 +189,6 @@ type ApiBoardTask = Partial<BoardTask> & {
184
189
  priority?: BoardTask['priority'] | null;
185
190
  };
186
191
 
187
- type TaskFormState = {
188
- name: string;
189
- description: string;
190
- priority: 'low' | 'medium' | 'high';
191
- status: BoardColumnId;
192
- assigneeCollaboratorId: string;
193
- dueDate: string;
194
- estimateHours: string;
195
- tags: string;
196
- };
197
-
198
- const EMPTY_TASK_FORM: TaskFormState = {
199
- name: '',
200
- description: '',
201
- priority: 'medium',
202
- status: 'todo',
203
- assigneeCollaboratorId: 'none',
204
- dueDate: '',
205
- estimateHours: '',
206
- tags: '',
207
- };
208
-
209
192
  type BoardColumns = Record<BoardColumnId, BoardTask[]>;
210
193
 
211
194
  type BoardState = {
@@ -213,6 +196,14 @@ type BoardState = {
213
196
  columns: BoardColumns;
214
197
  };
215
198
 
199
+ type TimesheetEntryPrefill = {
200
+ projectId: number;
201
+ projectAssignmentId?: number | null;
202
+ projectLabel: string;
203
+ taskId: number;
204
+ taskLabel: string;
205
+ };
206
+
216
207
  const KANBAN_COLUMNS: Array<{ id: BoardColumnId; label: string }> = [
217
208
  { id: 'todo', label: 'Backlog' },
218
209
  { id: 'doing', label: 'Em execução' },
@@ -252,6 +243,8 @@ function apiTaskToBoardTask(row: ApiBoardTask): BoardTask {
252
243
  0,
253
244
  fileCount:
254
245
  ((row as BoardTask & Record<string, unknown>).fileCount as number) ?? 0,
246
+ doingStartedAt: row.doingStartedAt ?? null,
247
+ totalDoingMinutes: row.totalDoingMinutes ?? 0,
255
248
  };
256
249
  }
257
250
 
@@ -264,6 +257,23 @@ function splitTasksByColumn(tasks: BoardTask[]): BoardColumns {
264
257
  };
265
258
  }
266
259
 
260
+ function replaceTaskInColumns(
261
+ columns: BoardColumns,
262
+ task: BoardTask
263
+ ): BoardColumns {
264
+ const nextColumns = {
265
+ todo: columns.todo.filter((item) => item.id !== task.id),
266
+ doing: columns.doing.filter((item) => item.id !== task.id),
267
+ review: columns.review.filter((item) => item.id !== task.id),
268
+ done: columns.done.filter((item) => item.id !== task.id),
269
+ };
270
+
271
+ return {
272
+ ...nextColumns,
273
+ [task.status]: [task, ...nextColumns[task.status]],
274
+ };
275
+ }
276
+
267
277
  const boardChartConfig = {
268
278
  allocation: { label: 'Alocacao', color: 'hsl(201 96% 32%)' },
269
279
  loggedHours: { label: 'Horas', color: 'hsl(166 72% 28%)' },
@@ -385,26 +395,6 @@ function getUserPhotoUrl(photoId?: number | null) {
385
395
  : null;
386
396
  }
387
397
 
388
- function normalizeDateInputValue(value?: string | null) {
389
- if (!value) {
390
- return '';
391
- }
392
-
393
- const normalizedValue = String(value).trim();
394
- const directMatch = normalizedValue.match(/^\d{4}-\d{2}-\d{2}/);
395
-
396
- if (directMatch?.[0]) {
397
- return directMatch[0];
398
- }
399
-
400
- const parsedDate = new Date(normalizedValue);
401
- if (Number.isNaN(parsedDate.getTime())) {
402
- return '';
403
- }
404
-
405
- return parsedDate.toISOString().slice(0, 10);
406
- }
407
-
408
398
  function clampPercent(value?: number | null) {
409
399
  if (typeof value !== 'number' || Number.isNaN(value)) {
410
400
  return 0;
@@ -1104,7 +1094,6 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
1104
1094
  const contractT = useTranslations('operations.ContractFormPage');
1105
1095
  const { request, currentLocaleCode, getSettingValue } = useApp();
1106
1096
  const access = useOperationsAccess();
1107
- const mentionItems = useMentionItems(request);
1108
1097
  const isLimitedView = !access.isDirector && !access.isSupervisor;
1109
1098
  const { valuesVisible, toggleValuesVisible } = useValuesVisibility();
1110
1099
  const router = useRouter();
@@ -1281,11 +1270,13 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
1281
1270
  );
1282
1271
  const [taskFormOpen, setTaskFormOpen] = useState(false);
1283
1272
  const [editingTaskId, setEditingTaskId] = useState<number | null>(null);
1284
- const [taskFormData, setTaskFormData] =
1285
- useState<TaskFormState>(EMPTY_TASK_FORM);
1286
- const [taskFormLoading, setTaskFormLoading] = useState(false);
1273
+ const [createDefaultStatus, setCreateDefaultStatus] =
1274
+ useState<BoardColumnId>('todo');
1287
1275
  const [deletePromptTask, setDeletePromptTask] =
1288
1276
  useState<TaskDetailSheetData | null>(null);
1277
+ const [deleteProjectDialogOpen, setDeleteProjectDialogOpen] = useState(false);
1278
+ const [deleteProjectConfirmText, setDeleteProjectConfirmText] = useState('');
1279
+ const [deletingProject, setDeletingProject] = useState(false);
1289
1280
  const [inlineCreateColumn, setInlineCreateColumn] =
1290
1281
  useState<BoardColumnId | null>(null);
1291
1282
  const [inlineCreateName, setInlineCreateName] = useState('');
@@ -1303,6 +1294,10 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
1303
1294
  const [restoringTaskId, setRestoringTaskId] = useState<number | null>(null);
1304
1295
  const [deletingTaskId, setDeletingTaskId] = useState<number | null>(null);
1305
1296
  const [activeDragTask, setActiveDragTask] = useState<BoardTask | null>(null);
1297
+ const [isTimesheetEntrySheetOpen, setIsTimesheetEntrySheetOpen] =
1298
+ useState(false);
1299
+ const [timesheetPrefill, setTimesheetPrefill] =
1300
+ useState<TimesheetEntryPrefill | null>(null);
1306
1301
 
1307
1302
  const apiTasks = useMemo(() => rawTasks.map(apiTaskToBoardTask), [rawTasks]);
1308
1303
  const archivedTasks = useMemo(
@@ -1320,6 +1315,29 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
1320
1315
  return splitTasksByColumn(apiTasks);
1321
1316
  }, [project, boardState, apiTasks]);
1322
1317
 
1318
+ const editingTask = useMemo(() => {
1319
+ if (!editingTaskId) return null;
1320
+ return (
1321
+ Object.values(taskColumns)
1322
+ .flat()
1323
+ .find((task) => task.id === editingTaskId) ??
1324
+ apiTasks.find((task) => task.id === editingTaskId) ??
1325
+ archivedTasks.find((task) => task.id === editingTaskId) ??
1326
+ null
1327
+ );
1328
+ }, [apiTasks, archivedTasks, editingTaskId, taskColumns]);
1329
+
1330
+ const editingTaskAsOption = useMemo(() => {
1331
+ if (!editingTask) return null;
1332
+ return {
1333
+ ...editingTask,
1334
+ label: editingTask.name,
1335
+ projectId,
1336
+ projectName: project?.name ?? '',
1337
+ projectCode: project?.code,
1338
+ };
1339
+ }, [editingTask, projectId, project]);
1340
+
1323
1341
  const filteredTaskColumns: BoardColumns = useMemo(() => {
1324
1342
  const normalizedSearch = boardSearch.trim().toLocaleLowerCase();
1325
1343
  const filterTask = (task: BoardTask) => {
@@ -1373,8 +1391,8 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
1373
1391
 
1374
1392
  const openCreateTaskForm = useCallback(
1375
1393
  (defaultStatus: BoardColumnId = 'todo') => {
1394
+ setCreateDefaultStatus(defaultStatus);
1376
1395
  setEditingTaskId(null);
1377
- setTaskFormData({ ...EMPTY_TASK_FORM, status: defaultStatus });
1378
1396
  setTaskFormOpen(true);
1379
1397
  },
1380
1398
  []
@@ -1382,73 +1400,10 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
1382
1400
 
1383
1401
  const openEditTaskForm = useCallback((task: BoardTask) => {
1384
1402
  setEditingTaskId(task.id);
1385
- setTaskFormData({
1386
- name: task.name,
1387
- description: task.description ?? '',
1388
- priority: task.priority,
1389
- status: task.status,
1390
- assigneeCollaboratorId: task.assigneeCollaboratorId
1391
- ? String(task.assigneeCollaboratorId)
1392
- : 'none',
1393
- dueDate: normalizeDateInputValue(task.dueDate),
1394
- estimateHours:
1395
- task.estimateHours != null ? String(task.estimateHours) : '',
1396
- tags: task.tags ?? '',
1397
- });
1398
1403
  setSelectedTask(null);
1399
1404
  setTaskFormOpen(true);
1400
1405
  }, []);
1401
1406
 
1402
- const handleTaskFormSubmit = useCallback(async () => {
1403
- if (!taskFormData.name.trim()) return;
1404
- setTaskFormLoading(true);
1405
- try {
1406
- const payload: Record<string, unknown> = {
1407
- name: taskFormData.name.trim(),
1408
- description: taskFormData.description || null,
1409
- priority: taskFormData.priority,
1410
- status: taskFormData.status,
1411
- assigneeCollaboratorId:
1412
- taskFormData.assigneeCollaboratorId !== 'none'
1413
- ? Number(taskFormData.assigneeCollaboratorId)
1414
- : null,
1415
- dueDate: taskFormData.dueDate || null,
1416
- estimateHours: taskFormData.estimateHours
1417
- ? Number(taskFormData.estimateHours)
1418
- : null,
1419
- tags: taskFormData.tags || null,
1420
- };
1421
- if (editingTaskId) {
1422
- await mutateOperations(
1423
- request,
1424
- `/operations/tasks/${editingTaskId}`,
1425
- 'PATCH',
1426
- payload
1427
- );
1428
- } else {
1429
- await mutateOperations(request, '/operations/tasks', 'POST', {
1430
- projectId,
1431
- ...payload,
1432
- });
1433
- }
1434
- setBoardState(null);
1435
- await refetchTasks();
1436
- await refetchArchivedTasks();
1437
- setTaskFormOpen(false);
1438
- setEditingTaskId(null);
1439
- setTaskFormData(EMPTY_TASK_FORM);
1440
- } finally {
1441
- setTaskFormLoading(false);
1442
- }
1443
- }, [
1444
- taskFormData,
1445
- editingTaskId,
1446
- projectId,
1447
- request,
1448
- refetchTasks,
1449
- refetchArchivedTasks,
1450
- ]);
1451
-
1452
1407
  const handleArchiveTask = useCallback(
1453
1408
  async (taskId: number) => {
1454
1409
  setArchivingTaskId(taskId);
@@ -1548,6 +1503,43 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
1548
1503
  [request, refetchTasks, refetchArchivedTasks]
1549
1504
  );
1550
1505
 
1506
+ const handleDeleteProject = useCallback(async () => {
1507
+ setDeletingProject(true);
1508
+ try {
1509
+ await mutateOperations(request, `/operations/projects/${projectId}`, 'DELETE');
1510
+ router.push('/operations/projects');
1511
+ } catch {
1512
+ // ignore
1513
+ } finally {
1514
+ setDeletingProject(false);
1515
+ setDeleteProjectDialogOpen(false);
1516
+ setDeleteProjectConfirmText('');
1517
+ }
1518
+ }, [request, projectId, router]);
1519
+
1520
+ const openTimesheetEntrySheet = useCallback(
1521
+ (task: TaskDetailSheetData) => {
1522
+ if (!project) {
1523
+ return;
1524
+ }
1525
+
1526
+ const projectLabel = [project.name, project.code]
1527
+ .filter(Boolean)
1528
+ .join(' • ');
1529
+
1530
+ setTimesheetPrefill({
1531
+ projectId,
1532
+ projectAssignmentId: task.projectAssignmentId ?? null,
1533
+ projectLabel: projectLabel || commonT('labels.notAssigned'),
1534
+ taskId: task.id,
1535
+ taskLabel: task.name,
1536
+ });
1537
+ setSelectedTask(null);
1538
+ setIsTimesheetEntrySheetOpen(true);
1539
+ },
1540
+ [commonT, project, projectId]
1541
+ );
1542
+
1551
1543
  const allocationChartData = useMemo(() => {
1552
1544
  if (projectStats?.allocationByCollaborator?.length) {
1553
1545
  return projectStats.allocationByCollaborator;
@@ -1621,14 +1613,33 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
1621
1613
  },
1622
1614
  });
1623
1615
 
1624
- // Persist to API
1625
- mutateOperations(request, `/operations/tasks/${taskId}`, 'PATCH', {
1626
- status: targetColumn,
1627
- }).catch(() => {
1628
- // Rollback optimistic update on error
1629
- setBoardState(null);
1630
- void refetchTasks();
1631
- });
1616
+ mutateOperations<ApiBoardTask>(
1617
+ request,
1618
+ `/operations/tasks/${taskId}`,
1619
+ 'PATCH',
1620
+ {
1621
+ status: targetColumn,
1622
+ }
1623
+ )
1624
+ .then((updatedTask) => {
1625
+ if (!updatedTask) {
1626
+ return;
1627
+ }
1628
+
1629
+ const updatedBoardTask = apiTaskToBoardTask(updatedTask);
1630
+ setBoardState((current) => ({
1631
+ projectId: current?.projectId ?? project.id,
1632
+ columns: replaceTaskInColumns(
1633
+ current?.columns ?? taskColumns,
1634
+ updatedBoardTask
1635
+ ),
1636
+ }));
1637
+ })
1638
+ .catch(() => {
1639
+ // Rollback optimistic update on error
1640
+ setBoardState(null);
1641
+ void refetchTasks();
1642
+ });
1632
1643
  },
1633
1644
  [findColumnByTask, taskColumns, project, request, refetchTasks]
1634
1645
  );
@@ -2109,6 +2120,17 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
2109
2120
  {commonT('actions.edit')}
2110
2121
  </Button>
2111
2122
  ) : null}
2123
+ {access.isDirector && project?.status === 'archived' ? (
2124
+ <Button
2125
+ size="sm"
2126
+ variant="destructive"
2127
+ className="cursor-pointer gap-1.5"
2128
+ onClick={() => setDeleteProjectDialogOpen(true)}
2129
+ >
2130
+ <Trash2 className="size-3.5" />
2131
+ {t('deleteProjectButton')}
2132
+ </Button>
2133
+ ) : null}
2112
2134
  </div>
2113
2135
  </div>
2114
2136
 
@@ -2406,6 +2428,7 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
2406
2428
  <TabsTrigger value="team">{t('tabs.team')}</TabsTrigger>
2407
2429
  <TabsTrigger value="timeline">{t('tabs.timeline')}</TabsTrigger>
2408
2430
  <TabsTrigger value="archive">{t('tabs.archive')}</TabsTrigger>
2431
+ <TabsTrigger value="files">{t('tabs.files')}</TabsTrigger>
2409
2432
  <TabsTrigger value="costs">{t('tabs.costs')}</TabsTrigger>
2410
2433
  </TabsList>
2411
2434
 
@@ -4152,6 +4175,16 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
4152
4175
  />
4153
4176
  </SectionCard>
4154
4177
  </TabsContent>
4178
+
4179
+ <TabsContent value="files">
4180
+ <SectionCard
4181
+ title={t('sections.files')}
4182
+ description={t('sections.filesDescription')}
4183
+ className="rounded-2xl border bg-card p-4 shadow-sm"
4184
+ >
4185
+ <ProjectFileAttachments projectId={projectId} />
4186
+ </SectionCard>
4187
+ </TabsContent>
4155
4188
  </Tabs>
4156
4189
 
4157
4190
  <TaskDetailSheet
@@ -4168,13 +4201,13 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
4168
4201
  }
4169
4202
  footer={
4170
4203
  selectedTask && !isLimitedView ? (
4171
- <div className="grid grid-cols-2 gap-3">
4204
+ <div className="grid grid-cols-1 gap-3 sm:grid-cols-3">
4172
4205
  {archivedTasks.some((task) => task.id === selectedTask.id) ? (
4173
4206
  <>
4174
4207
  <Button
4175
4208
  variant="outline"
4176
4209
  size="sm"
4177
- className="h-10 gap-2"
4210
+ className="h-10 gap-2 sm:col-span-2"
4178
4211
  disabled={restoringTaskId === selectedTask.id}
4179
4212
  onClick={() => void handleRestoreTask(selectedTask.id)}
4180
4213
  >
@@ -4196,26 +4229,67 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
4196
4229
  </Button>
4197
4230
  </>
4198
4231
  ) : (
4199
- <Button
4200
- variant="outline"
4201
- size="sm"
4202
- className="col-span-2 h-10 gap-2"
4203
- disabled={archivingTaskId === selectedTask.id}
4204
- onClick={() => void handleArchiveTask(selectedTask.id)}
4205
- >
4206
- {archivingTaskId === selectedTask.id ? (
4207
- <Loader2 className="size-3.5 animate-spin" />
4208
- ) : (
4209
- <Archive className="size-3.5" />
4210
- )}
4211
- {commonT('actions.archive')}
4212
- </Button>
4232
+ <>
4233
+ <Button
4234
+ variant="outline"
4235
+ size="sm"
4236
+ className="h-10 gap-2"
4237
+ onClick={() => openTimesheetEntrySheet(selectedTask)}
4238
+ >
4239
+ <Send className="size-3.5" />
4240
+ {commonT('actions.logHours')}
4241
+ </Button>
4242
+ <Button
4243
+ variant="outline"
4244
+ size="sm"
4245
+ className="h-10 gap-2 sm:col-span-2"
4246
+ disabled={archivingTaskId === selectedTask.id}
4247
+ onClick={() => void handleArchiveTask(selectedTask.id)}
4248
+ >
4249
+ {archivingTaskId === selectedTask.id ? (
4250
+ <Loader2 className="size-3.5 animate-spin" />
4251
+ ) : (
4252
+ <Archive className="size-3.5" />
4253
+ )}
4254
+ {commonT('actions.archive')}
4255
+ </Button>
4256
+ </>
4213
4257
  )}
4214
4258
  </div>
4215
4259
  ) : null
4216
4260
  }
4217
4261
  />
4218
4262
 
4263
+ <TimesheetEntryCreateSheet
4264
+ open={isTimesheetEntrySheetOpen}
4265
+ onOpenChange={(open) => {
4266
+ setIsTimesheetEntrySheetOpen(open);
4267
+ if (!open) {
4268
+ setTimesheetPrefill(null);
4269
+ }
4270
+ }}
4271
+ project={
4272
+ timesheetPrefill
4273
+ ? {
4274
+ id: timesheetPrefill.projectId,
4275
+ label: timesheetPrefill.projectLabel,
4276
+ projectAssignmentId: timesheetPrefill.projectAssignmentId,
4277
+ }
4278
+ : null
4279
+ }
4280
+ task={
4281
+ timesheetPrefill
4282
+ ? {
4283
+ id: timesheetPrefill.taskId,
4284
+ label: timesheetPrefill.taskLabel,
4285
+ }
4286
+ : null
4287
+ }
4288
+ onCreated={() => {
4289
+ void refetchTasks();
4290
+ }}
4291
+ />
4292
+
4219
4293
  {!isLimitedView ? (
4220
4294
  <Sheet
4221
4295
  open={isEditSheetOpen}
@@ -4244,284 +4318,36 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
4244
4318
  ) : null}
4245
4319
 
4246
4320
  {!isLimitedView ? (
4247
- <Sheet
4321
+ <TaskFormSheet
4248
4322
  open={taskFormOpen}
4249
4323
  onOpenChange={(open) => {
4250
4324
  if (!open) {
4251
4325
  setTaskFormOpen(false);
4252
4326
  setEditingTaskId(null);
4253
- setTaskFormData(EMPTY_TASK_FORM);
4254
4327
  }
4255
4328
  }}
4256
- >
4257
- <SheetContent className="flex w-full flex-col overflow-hidden sm:max-w-xl">
4258
- <SheetHeader className="shrink-0">
4259
- <SheetTitle>
4260
- {editingTaskId
4261
- ? t('taskForm.titleEdit')
4262
- : t('taskForm.titleNew')}
4263
- </SheetTitle>
4264
- </SheetHeader>
4265
-
4266
- <Tabs defaultValue="info" className="flex min-h-0 flex-1 flex-col">
4267
- <TabsList className="mx-4 grid w-[calc(100%-2rem)] shrink-0 grid-cols-2">
4268
- <TabsTrigger value="info">{t('taskForm.tabInfo')}</TabsTrigger>
4269
- {editingTaskId ? (
4270
- <TabsTrigger value="comments">
4271
- {t('taskForm.tabComments')}
4272
- </TabsTrigger>
4273
- ) : null}
4274
- </TabsList>
4275
- <TabsContent
4276
- value="info"
4277
- className="flex min-h-0 flex-1 flex-col data-[state=inactive]:hidden"
4278
- >
4279
- <div className="flex-1 space-y-4 overflow-y-auto px-4 py-2">
4280
- <div className="space-y-1.5">
4281
- <Label htmlFor="task-name">
4282
- {t('taskForm.nameLabel')} *
4283
- </Label>
4284
- <Input
4285
- id="task-name"
4286
- placeholder={t('taskForm.namePlaceholder')}
4287
- value={taskFormData.name}
4288
- onChange={(e) =>
4289
- setTaskFormData((prev) => ({
4290
- ...prev,
4291
- name: e.target.value,
4292
- }))
4293
- }
4294
- />
4295
- </div>
4296
-
4297
- <div className="space-y-1.5">
4298
- <Label htmlFor="task-description">
4299
- {t('taskForm.descriptionLabel')}
4300
- </Label>
4301
- <RichTextEditor
4302
- value={taskFormData.description}
4303
- onChange={(val) =>
4304
- setTaskFormData((prev) => ({
4305
- ...prev,
4306
- description: val,
4307
- }))
4308
- }
4309
- mentions={mentionItems}
4310
- />
4311
- </div>
4312
-
4313
- <div className="grid grid-cols-2 gap-3">
4314
- <div className="space-y-1.5">
4315
- <Label>{t('taskForm.priorityLabel')}</Label>
4316
- <Select
4317
- value={taskFormData.priority}
4318
- onValueChange={(v) =>
4319
- setTaskFormData((prev) => ({
4320
- ...prev,
4321
- priority: v as TaskFormState['priority'],
4322
- }))
4323
- }
4324
- >
4325
- <SelectTrigger className="w-full">
4326
- <SelectValue />
4327
- </SelectTrigger>
4328
- <SelectContent>
4329
- <SelectItem value="low">
4330
- {getTaskPriorityLabel('low')}
4331
- </SelectItem>
4332
- <SelectItem value="medium">
4333
- {getTaskPriorityLabel('medium')}
4334
- </SelectItem>
4335
- <SelectItem value="high">
4336
- {getTaskPriorityLabel('high')}
4337
- </SelectItem>
4338
- </SelectContent>
4339
- </Select>
4340
- </div>
4341
-
4342
- <div className="space-y-1.5">
4343
- <Label>{t('taskForm.columnLabel')}</Label>
4344
- <Select
4345
- value={taskFormData.status}
4346
- onValueChange={(v) =>
4347
- setTaskFormData((prev) => ({
4348
- ...prev,
4349
- status: v as BoardColumnId,
4350
- }))
4351
- }
4352
- >
4353
- <SelectTrigger className="w-full">
4354
- <SelectValue />
4355
- </SelectTrigger>
4356
- <SelectContent>
4357
- {KANBAN_COLUMNS.map((col) => (
4358
- <SelectItem key={col.id} value={col.id}>
4359
- {col.label}
4360
- </SelectItem>
4361
- ))}
4362
- </SelectContent>
4363
- </Select>
4364
- </div>
4365
- </div>
4366
-
4367
- <div className="space-y-1.5">
4368
- <Label>Responsável</Label>
4369
- <Select
4370
- value={taskFormData.assigneeCollaboratorId}
4371
- onValueChange={(value) =>
4372
- setTaskFormData((prev) => ({
4373
- ...prev,
4374
- assigneeCollaboratorId: value,
4375
- }))
4376
- }
4377
- >
4378
- <SelectTrigger className="w-full">
4379
- <SelectValue
4380
- placeholder={commonT('labels.notAssigned')}
4381
- />
4382
- </SelectTrigger>
4383
- <SelectContent>
4384
- <SelectItem value="none">
4385
- {commonT('labels.notAssigned')}
4386
- </SelectItem>
4387
- {taskAssigneeOptions.map((option) => (
4388
- <SelectItem key={option.id} value={option.id}>
4389
- {option.label}
4390
- </SelectItem>
4391
- ))}
4392
- </SelectContent>
4393
- </Select>
4394
- </div>
4395
-
4396
- <div className="grid grid-cols-2 gap-3">
4397
- <div className="space-y-1.5">
4398
- <Label htmlFor="task-due-date">
4399
- {t('taskForm.deadlineLabel')}
4400
- </Label>
4401
- <Input
4402
- id="task-due-date"
4403
- type="date"
4404
- value={taskFormData.dueDate}
4405
- onChange={(e) =>
4406
- setTaskFormData((prev) => ({
4407
- ...prev,
4408
- dueDate: e.target.value,
4409
- }))
4410
- }
4411
- />
4412
- </div>
4413
-
4414
- <div className="space-y-1.5">
4415
- <Label htmlFor="task-estimate">
4416
- {t('taskForm.estimateLabel')}
4417
- </Label>
4418
- <Input
4419
- id="task-estimate"
4420
- type="number"
4421
- min="0"
4422
- step="0.5"
4423
- placeholder="0"
4424
- value={taskFormData.estimateHours}
4425
- onChange={(e) =>
4426
- setTaskFormData((prev) => ({
4427
- ...prev,
4428
- estimateHours: e.target.value,
4429
- }))
4430
- }
4431
- />
4432
- </div>
4433
- </div>
4434
-
4435
- <div className="space-y-1.5">
4436
- <Label htmlFor="task-tags">{t('taskForm.tagsLabel')}</Label>
4437
- <Input
4438
- id="task-tags"
4439
- placeholder={t('taskForm.tagsPlaceholder')}
4440
- value={taskFormData.tags}
4441
- onChange={(e) =>
4442
- setTaskFormData((prev) => ({
4443
- ...prev,
4444
- tags: e.target.value,
4445
- }))
4446
- }
4447
- />
4448
- </div>
4449
-
4450
- {editingTaskId ? (
4451
- <div className="space-y-1.5">
4452
- <Label className="flex items-center gap-1.5">
4453
- <Paperclip className="size-3.5" />
4454
- {t('taskForm.attachmentsLabel')}
4455
- </Label>
4456
- <TaskFileAttachments taskId={editingTaskId} />
4457
- </div>
4458
- ) : null}
4459
- </div>
4460
-
4461
- <div className="mt-4 flex flex-wrap items-center justify-between gap-2 border-t px-4 pb-4 pt-4">
4462
- <div className="flex gap-2">
4463
- {editingTaskId ? (
4464
- <Button
4465
- type="button"
4466
- variant="outline"
4467
- disabled={
4468
- taskFormLoading || archivingTaskId === editingTaskId
4469
- }
4470
- onClick={() => {
4471
- if (!editingTaskId) return;
4472
- const id = editingTaskId;
4473
- setTaskFormOpen(false);
4474
- setEditingTaskId(null);
4475
- setTaskFormData(EMPTY_TASK_FORM);
4476
- void handleArchiveTask(id);
4477
- }}
4478
- >
4479
- {archivingTaskId === editingTaskId ? (
4480
- <Loader2 className="mr-2 size-4 animate-spin" />
4481
- ) : (
4482
- <Archive className="mr-2 size-4" />
4483
- )}
4484
- {commonT('actions.archive')}
4485
- </Button>
4486
- ) : null}
4487
- </div>
4488
- <div className="flex gap-2">
4489
- <Button
4490
- variant="outline"
4491
- onClick={() => {
4492
- setTaskFormOpen(false);
4493
- setEditingTaskId(null);
4494
- setTaskFormData(EMPTY_TASK_FORM);
4495
- }}
4496
- disabled={taskFormLoading}
4497
- >
4498
- {commonT('actions.cancel')}
4499
- </Button>
4500
- <Button
4501
- onClick={() => void handleTaskFormSubmit()}
4502
- disabled={taskFormLoading || !taskFormData.name.trim()}
4503
- >
4504
- {taskFormLoading
4505
- ? t('taskForm.saving')
4506
- : editingTaskId
4507
- ? commonT('actions.save')
4508
- : commonT('actions.create')}
4509
- </Button>
4510
- </div>
4511
- </div>
4512
- </TabsContent>
4513
-
4514
- {editingTaskId ? (
4515
- <TabsContent
4516
- value="comments"
4517
- className="min-h-0 flex-1 overflow-y-auto px-4 py-2 data-[state=inactive]:hidden"
4518
- >
4519
- <TaskCommentsSection taskId={editingTaskId} />
4520
- </TabsContent>
4521
- ) : null}
4522
- </Tabs>
4523
- </SheetContent>
4524
- </Sheet>
4329
+ request={request}
4330
+ editingTask={editingTaskAsOption}
4331
+ showProject={false}
4332
+ defaultProjectId={projectId}
4333
+ defaultStatus={createDefaultStatus}
4334
+ assigneeOptions={taskAssigneeOptions}
4335
+ onArchive={handleArchiveTask}
4336
+ onLogHours={
4337
+ editingTask
4338
+ ? () =>
4339
+ openTimesheetEntrySheet(
4340
+ editingTask as unknown as TaskDetailSheetData
4341
+ )
4342
+ : undefined
4343
+ }
4344
+ onCountChanged={() => { setBoardState(null); void refetchTasks(); }}
4345
+ onSaved={() => {
4346
+ setBoardState(null);
4347
+ void refetchTasks();
4348
+ void refetchArchivedTasks();
4349
+ }}
4350
+ />
4525
4351
  ) : null}
4526
4352
 
4527
4353
  {!isLimitedView ? (
@@ -4535,10 +4361,10 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
4535
4361
  <DialogContent className="sm:max-w-sm">
4536
4362
  <DialogHeader>
4537
4363
  <DialogTitle>{t('dialogs.deleteTitle')}</DialogTitle>
4364
+ <DialogDescription>
4365
+ {t('dialogs.deleteDescription')}
4366
+ </DialogDescription>
4538
4367
  </DialogHeader>
4539
- <p className="text-sm text-muted-foreground">
4540
- {t('dialogs.deleteDescription')}
4541
- </p>
4542
4368
  <DialogFooter className="mt-4">
4543
4369
  <Button
4544
4370
  variant="outline"
@@ -4561,6 +4387,57 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
4561
4387
  </DialogFooter>
4562
4388
  </DialogContent>
4563
4389
  </Dialog>
4390
+
4391
+ <Dialog
4392
+ open={deleteProjectDialogOpen}
4393
+ onOpenChange={(open) => {
4394
+ if (!open) {
4395
+ setDeleteProjectDialogOpen(false);
4396
+ setDeleteProjectConfirmText('');
4397
+ }
4398
+ }}
4399
+ >
4400
+ <DialogContent className="sm:max-w-md">
4401
+ <DialogHeader>
4402
+ <DialogTitle>{t('deleteConfirmTitle')}</DialogTitle>
4403
+ </DialogHeader>
4404
+ <p className="text-sm text-muted-foreground">
4405
+ {t('deleteConfirmDescription')}
4406
+ </p>
4407
+ <div className="space-y-1.5">
4408
+ <Label className="text-xs">{t('deleteConfirmLabel')}</Label>
4409
+ <Input
4410
+ value={deleteProjectConfirmText}
4411
+ onChange={(e) => setDeleteProjectConfirmText(e.target.value)}
4412
+ placeholder={project?.name ?? ''}
4413
+ />
4414
+ </div>
4415
+ <DialogFooter className="mt-2">
4416
+ <Button
4417
+ variant="outline"
4418
+ onClick={() => {
4419
+ setDeleteProjectDialogOpen(false);
4420
+ setDeleteProjectConfirmText('');
4421
+ }}
4422
+ >
4423
+ {commonT('actions.cancel')}
4424
+ </Button>
4425
+ <Button
4426
+ variant="destructive"
4427
+ disabled={
4428
+ deletingProject ||
4429
+ deleteProjectConfirmText !== (project?.name ?? '')
4430
+ }
4431
+ onClick={() => void handleDeleteProject()}
4432
+ >
4433
+ {deletingProject ? (
4434
+ <Loader2 className="mr-2 size-3.5 animate-spin" />
4435
+ ) : null}
4436
+ {t('deleteConfirmButton')}
4437
+ </Button>
4438
+ </DialogFooter>
4439
+ </DialogContent>
4440
+ </Dialog>
4564
4441
  </>
4565
4442
  ) : null}
4566
4443
  </Page>