@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
@@ -115,9 +115,10 @@ function getFileOpenUrl(fileId: number) {
115
115
 
116
116
  type Props = {
117
117
  taskId: number | null;
118
+ onChanged?: () => void;
118
119
  };
119
120
 
120
- export function TaskFileAttachments({ taskId }: Props) {
121
+ export function TaskFileAttachments({ taskId, onChanged }: Props) {
121
122
  const t = useTranslations('operations.TaskFileAttachments');
122
123
  const { request } = useApp();
123
124
 
@@ -183,6 +184,7 @@ export function TaskFileAttachments({ taskId }: Props) {
183
184
 
184
185
  if (res?.data) {
185
186
  await fetchFiles();
187
+ onChanged?.();
186
188
  }
187
189
  } catch {
188
190
  setUploading((prev) =>
@@ -222,6 +224,7 @@ export function TaskFileAttachments({ taskId }: Props) {
222
224
  method: 'DELETE',
223
225
  });
224
226
  setFiles((prev) => prev.filter((f) => f.id !== relationId));
227
+ onChanged?.();
225
228
  } catch {
226
229
  // ignore
227
230
  } finally {
@@ -0,0 +1,406 @@
1
+ 'use client';
2
+
3
+ import {
4
+ RichTextEditor,
5
+ type MentionItem,
6
+ } from '@/components/rich-text-editor';
7
+ import { Button } from '@/components/ui/button';
8
+ import { Input } from '@/components/ui/input';
9
+ import { Label } from '@/components/ui/label';
10
+ import {
11
+ Select,
12
+ SelectContent,
13
+ SelectItem,
14
+ SelectTrigger,
15
+ SelectValue,
16
+ } from '@/components/ui/select';
17
+ import { History, Paperclip, Plus, X } from 'lucide-react';
18
+ import { useTranslations } from 'next-intl';
19
+ import { useState, type Dispatch, type SetStateAction } from 'react';
20
+ import { Badge } from '@/components/ui/badge';
21
+ import type { OperationsProjectOption } from '../_lib/types';
22
+ import {
23
+ formatDurationMinutes,
24
+ getTaskPriorityLabel,
25
+ } from '../_lib/utils/task-ui';
26
+ import { CollaboratorPicker } from './collaborator-picker';
27
+ import { TaskFileAttachments } from './task-file-attachments';
28
+
29
+ type BaseTaskFormState = {
30
+ projectId: string;
31
+ name: string;
32
+ description: string;
33
+ priority: 'low' | 'medium' | 'high';
34
+ status: string;
35
+ dueDate: string;
36
+ estimateHours: string;
37
+ tags: string;
38
+ assigneeCollaboratorId: string;
39
+ };
40
+
41
+ type TaskFormFieldsProps<TFormState extends BaseTaskFormState> = {
42
+ formData: TFormState;
43
+ setFormData: Dispatch<SetStateAction<TFormState>>;
44
+ statusOptions: Array<{ id: TFormState['status']; label: string }>;
45
+ mentionItems: MentionItem[];
46
+ projectOptions: OperationsProjectOption[];
47
+ showProject?: boolean;
48
+ projectRequired?: boolean;
49
+ allowUnassignedProject?: boolean;
50
+ onOpenCreateProject?: () => void;
51
+ showDoingTime?: boolean;
52
+ doingMinutes?: number | null;
53
+ showAttachments?: boolean;
54
+ attachmentTaskId?: number | null;
55
+ /** Which field group to render. 'main' = project/name/description. 'aside' = metadata+files. 'all' = everything (default). */
56
+ section?: 'all' | 'main' | 'aside';
57
+ /** Hide the assignee field entirely. */
58
+ showAssignee?: boolean;
59
+ /** When provided, renders a static Select instead of the CollaboratorPicker API widget. */
60
+ assigneeOptions?: Array<{ id: string; label: string }>;
61
+ /** Called immediately when a tag is added or removed (for auto-save in edit mode). */
62
+ onTagsChange?: (tags: string) => void;
63
+ /** Called after a file is uploaded or deleted (for refreshing external counts). */
64
+ onFileChanged?: () => void;
65
+ };
66
+
67
+ export function TaskFormFields<TFormState extends BaseTaskFormState>({
68
+ formData,
69
+ setFormData,
70
+ statusOptions,
71
+ mentionItems,
72
+ projectOptions,
73
+ showProject = true,
74
+ projectRequired = false,
75
+ allowUnassignedProject = false,
76
+ onOpenCreateProject,
77
+ showDoingTime = false,
78
+ doingMinutes,
79
+ showAttachments = false,
80
+ attachmentTaskId,
81
+ section = 'all',
82
+ showAssignee = true,
83
+ assigneeOptions,
84
+ onTagsChange,
85
+ onFileChanged,
86
+ }: TaskFormFieldsProps<TFormState>) {
87
+ const commonT = useTranslations('operations.Common');
88
+ const taskT = useTranslations('operations.ProjectDetailsPage');
89
+ const projectsT = useTranslations('operations.ProjectsPage');
90
+
91
+ const [tagInput, setTagInput] = useState('');
92
+
93
+ const showMain = section === 'all' || section === 'main';
94
+ const showAside = section === 'all' || section === 'aside';
95
+
96
+ const tagList = formData.tags
97
+ ? formData.tags.split(',').map((t) => t.trim()).filter(Boolean)
98
+ : [];
99
+
100
+ const commitTag = (value: string) => {
101
+ const trimmed = value.trim();
102
+ if (!trimmed || tagList.includes(trimmed)) {
103
+ setTagInput('');
104
+ return;
105
+ }
106
+ const next = [...tagList, trimmed].join(',');
107
+ setFormData((prev) => ({ ...prev, tags: next }));
108
+ onTagsChange?.(next);
109
+ setTagInput('');
110
+ };
111
+
112
+ const removeTag = (tag: string) => {
113
+ const next = tagList.filter((t) => t !== tag).join(',');
114
+ setFormData((prev) => ({ ...prev, tags: next }));
115
+ onTagsChange?.(next);
116
+ };
117
+
118
+ const doingTimeClass = (() => {
119
+ const estimate = formData.estimateHours
120
+ ? Number(formData.estimateHours) * 60
121
+ : null;
122
+ if (!estimate || doingMinutes == null)
123
+ return 'border bg-muted/30';
124
+ if (doingMinutes > estimate * 1.1)
125
+ return 'border-red-500/30 bg-red-500/10 text-red-600 dark:text-red-400';
126
+ if (doingMinutes > estimate)
127
+ return 'border-amber-500/30 bg-amber-500/10 text-amber-600 dark:text-amber-400';
128
+ return 'border-emerald-500/30 bg-emerald-500/10 text-emerald-600 dark:text-emerald-400';
129
+ })();
130
+
131
+ return (
132
+ <>
133
+ {showMain && showProject ? (
134
+ <div className="space-y-1.5">
135
+ <Label htmlFor="task-form-project">
136
+ {commonT('labels.project')}
137
+ {projectRequired ? ' *' : ''}
138
+ </Label>
139
+ <div className="flex gap-2">
140
+ <Select
141
+ value={formData.projectId}
142
+ onValueChange={(value) =>
143
+ setFormData((prev) => ({ ...prev, projectId: value }))
144
+ }
145
+ >
146
+ <SelectTrigger className="w-full" id="task-form-project">
147
+ <SelectValue placeholder={commonT('labels.project')} />
148
+ </SelectTrigger>
149
+ <SelectContent>
150
+ {allowUnassignedProject ? (
151
+ <SelectItem value="none">
152
+ {commonT('labels.notAssigned')}
153
+ </SelectItem>
154
+ ) : null}
155
+ {projectOptions.map((project) => (
156
+ <SelectItem key={project.id} value={String(project.id)}>
157
+ {project.label}
158
+ </SelectItem>
159
+ ))}
160
+ </SelectContent>
161
+ </Select>
162
+ {onOpenCreateProject ? (
163
+ <Button
164
+ type="button"
165
+ variant="outline"
166
+ size="icon"
167
+ className="shrink-0"
168
+ title={projectsT('sheet.createTitle')}
169
+ onClick={onOpenCreateProject}
170
+ >
171
+ <Plus className="size-4" />
172
+ </Button>
173
+ ) : null}
174
+ </div>
175
+ </div>
176
+ ) : null}
177
+
178
+ {showMain ? (
179
+ <div className="space-y-1.5">
180
+ <Label htmlFor="task-form-name">{taskT('taskForm.nameLabel')} *</Label>
181
+ <Input
182
+ id="task-form-name"
183
+ placeholder={taskT('taskForm.namePlaceholder')}
184
+ value={formData.name}
185
+ onChange={(event) =>
186
+ setFormData((prev) => ({ ...prev, name: event.target.value }))
187
+ }
188
+ />
189
+ </div>
190
+ ) : null}
191
+
192
+ {showMain ? (
193
+ <div className="space-y-1.5">
194
+ <Label htmlFor="task-form-description">
195
+ {taskT('taskForm.descriptionLabel')}
196
+ </Label>
197
+ <RichTextEditor
198
+ value={formData.description}
199
+ onChange={(value) =>
200
+ setFormData((prev) => ({ ...prev, description: value }))
201
+ }
202
+ mentions={mentionItems}
203
+ />
204
+ </div>
205
+ ) : null}
206
+
207
+ {showAside ? (
208
+ <div className="space-y-1.5">
209
+ <Label>{taskT('taskForm.priorityLabel')}</Label>
210
+ <Select
211
+ value={formData.priority}
212
+ onValueChange={(value) =>
213
+ setFormData((prev) => ({
214
+ ...prev,
215
+ priority: value as TFormState['priority'],
216
+ }))
217
+ }
218
+ >
219
+ <SelectTrigger className="w-full">
220
+ <SelectValue />
221
+ </SelectTrigger>
222
+ <SelectContent>
223
+ <SelectItem value="low">{getTaskPriorityLabel('low')}</SelectItem>
224
+ <SelectItem value="medium">
225
+ {getTaskPriorityLabel('medium')}
226
+ </SelectItem>
227
+ <SelectItem value="high">
228
+ {getTaskPriorityLabel('high')}
229
+ </SelectItem>
230
+ </SelectContent>
231
+ </Select>
232
+ </div>
233
+ ) : null}
234
+
235
+ {showAside ? (
236
+ <div className="space-y-1.5">
237
+ <Label>{taskT('taskForm.columnLabel')}</Label>
238
+ <Select
239
+ value={formData.status}
240
+ onValueChange={(value) =>
241
+ setFormData((prev) => ({
242
+ ...prev,
243
+ status: value as TFormState['status'],
244
+ }))
245
+ }
246
+ >
247
+ <SelectTrigger className="w-full">
248
+ <SelectValue />
249
+ </SelectTrigger>
250
+ <SelectContent>
251
+ {statusOptions.map((column) => (
252
+ <SelectItem key={column.id} value={column.id}>
253
+ {column.label}
254
+ </SelectItem>
255
+ ))}
256
+ </SelectContent>
257
+ </Select>
258
+ </div>
259
+ ) : null}
260
+
261
+ {showAside && showAssignee ? (
262
+ <div className="space-y-1.5">
263
+ <Label htmlFor="task-form-assignee">
264
+ {taskT('taskForm.assigneeLabel')}
265
+ </Label>
266
+ {assigneeOptions ? (
267
+ <Select
268
+ value={formData.assigneeCollaboratorId || 'none'}
269
+ onValueChange={(value) =>
270
+ setFormData((prev) => ({
271
+ ...prev,
272
+ assigneeCollaboratorId: value === 'none' ? '' : value,
273
+ }))
274
+ }
275
+ >
276
+ <SelectTrigger className="w-full" id="task-form-assignee">
277
+ <SelectValue placeholder={taskT('taskForm.assigneeLabel')} />
278
+ </SelectTrigger>
279
+ <SelectContent>
280
+ <SelectItem value="none">
281
+ {commonT('labels.notAssigned')}
282
+ </SelectItem>
283
+ {assigneeOptions.map((option) => (
284
+ <SelectItem key={option.id} value={option.id}>
285
+ {option.label}
286
+ </SelectItem>
287
+ ))}
288
+ </SelectContent>
289
+ </Select>
290
+ ) : (
291
+ <CollaboratorPicker
292
+ value={formData.assigneeCollaboratorId ?? ''}
293
+ placeholder={taskT('taskForm.assigneeLabel')}
294
+ onChange={(newValue) =>
295
+ setFormData((prev) => ({
296
+ ...prev,
297
+ assigneeCollaboratorId: newValue,
298
+ }))
299
+ }
300
+ setFormData={setFormData}
301
+ />
302
+ )}
303
+ </div>
304
+ ) : null}
305
+
306
+ {showAside ? (
307
+ <div className="grid grid-cols-2 gap-3">
308
+ <div className="space-y-1.5">
309
+ <Label htmlFor="task-form-due-date">
310
+ {taskT('taskForm.deadlineLabel')}
311
+ </Label>
312
+ <Input
313
+ id="task-form-due-date"
314
+ type="date"
315
+ value={formData.dueDate}
316
+ onChange={(event) =>
317
+ setFormData((prev) => ({ ...prev, dueDate: event.target.value }))
318
+ }
319
+ />
320
+ </div>
321
+
322
+ <div className="space-y-1.5">
323
+ <Label htmlFor="task-form-estimate">
324
+ {taskT('taskForm.estimateLabel')}
325
+ </Label>
326
+ <Input
327
+ id="task-form-estimate"
328
+ type="number"
329
+ min="0"
330
+ step="0.5"
331
+ placeholder="0"
332
+ value={formData.estimateHours}
333
+ onChange={(event) =>
334
+ setFormData((prev) => ({
335
+ ...prev,
336
+ estimateHours: event.target.value,
337
+ }))
338
+ }
339
+ />
340
+ </div>
341
+ </div>
342
+ ) : null}
343
+
344
+ {showAside && showDoingTime ? (
345
+ <div className="space-y-1.5">
346
+ <Label className="flex items-center gap-1.5">
347
+ <History className="size-3.5" />
348
+ {taskT('taskForm.doingTimeLabel')}
349
+ </Label>
350
+ <div
351
+ className={`rounded-md px-3 py-2 text-sm font-medium ${doingTimeClass}`}
352
+ >
353
+ {formatDurationMinutes(doingMinutes)}
354
+ </div>
355
+ </div>
356
+ ) : null}
357
+
358
+ {showAside ? (
359
+ <div className="space-y-1.5">
360
+ <Label>{taskT('taskForm.tagsLabel')}</Label>
361
+ {tagList.length > 0 ? (
362
+ <div className="flex flex-wrap gap-1.5">
363
+ {tagList.map((tag) => (
364
+ <Badge key={tag} variant="secondary" className="gap-1 pr-1">
365
+ {tag}
366
+ <button
367
+ type="button"
368
+ onClick={() => removeTag(tag)}
369
+ className="rounded-sm opacity-60 hover:opacity-100 focus:outline-none"
370
+ >
371
+ <X className="size-3" />
372
+ </button>
373
+ </Badge>
374
+ ))}
375
+ </div>
376
+ ) : null}
377
+ <Input
378
+ id="task-form-tags"
379
+ placeholder={taskT('taskForm.tagsPlaceholder')}
380
+ value={tagInput}
381
+ onChange={(e) => setTagInput(e.target.value)}
382
+ onKeyDown={(e) => {
383
+ if (e.key === 'Enter' || e.key === ',') {
384
+ e.preventDefault();
385
+ commitTag(tagInput);
386
+ } else if (e.key === 'Backspace' && !tagInput && tagList.length > 0) {
387
+ removeTag(tagList.at(-1)!);
388
+ }
389
+ }}
390
+ onBlur={() => { if (tagInput.trim()) commitTag(tagInput); }}
391
+ />
392
+ </div>
393
+ ) : null}
394
+
395
+ {showAside && showAttachments && attachmentTaskId ? (
396
+ <div className="space-y-1.5">
397
+ <Label className="flex items-center gap-1.5">
398
+ <Paperclip className="size-3.5" />
399
+ {taskT('taskForm.attachmentsLabel')}
400
+ </Label>
401
+ <TaskFileAttachments taskId={attachmentTaskId} onChanged={onFileChanged} />
402
+ </div>
403
+ ) : null}
404
+ </>
405
+ );
406
+ }