@hed-hog/operations 0.0.322 → 0.0.326

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 (694) hide show
  1. package/dist/controllers/operations-collaborators.controller.d.ts +14 -0
  2. package/dist/controllers/operations-collaborators.controller.d.ts.map +1 -1
  3. package/dist/controllers/operations-collaborators.controller.js +25 -0
  4. package/dist/controllers/operations-collaborators.controller.js.map +1 -1
  5. package/dist/controllers/operations-project-costs.controller.d.ts +422 -0
  6. package/dist/controllers/operations-project-costs.controller.d.ts.map +1 -0
  7. package/dist/controllers/operations-project-costs.controller.js +250 -0
  8. package/dist/controllers/operations-project-costs.controller.js.map +1 -0
  9. package/dist/controllers/operations-reports.controller.d.ts +9 -0
  10. package/dist/controllers/operations-reports.controller.d.ts.map +1 -1
  11. package/dist/controllers/operations-tasks.controller.d.ts +42 -0
  12. package/dist/controllers/operations-tasks.controller.d.ts.map +1 -1
  13. package/dist/controllers/operations-tasks.controller.js +48 -0
  14. package/dist/controllers/operations-tasks.controller.js.map +1 -1
  15. package/dist/controllers/operations-timesheets.controller.d.ts +1 -0
  16. package/dist/controllers/operations-timesheets.controller.d.ts.map +1 -1
  17. package/dist/dto/create-collaborator-project-assignment.dto.d.ts +5 -0
  18. package/dist/dto/create-collaborator-project-assignment.dto.d.ts.map +1 -0
  19. package/dist/dto/create-collaborator-project-assignment.dto.js +30 -0
  20. package/dist/dto/create-collaborator-project-assignment.dto.js.map +1 -0
  21. package/dist/dto/create-project-cost-category.dto.d.ts +10 -0
  22. package/dist/dto/create-project-cost-category.dto.d.ts.map +1 -0
  23. package/dist/dto/create-project-cost-category.dto.js +59 -0
  24. package/dist/dto/create-project-cost-category.dto.js.map +1 -0
  25. package/dist/dto/create-project-cost-type.dto.d.ts +14 -0
  26. package/dist/dto/create-project-cost-type.dto.d.ts.map +1 -0
  27. package/dist/dto/create-project-cost-type.dto.js +87 -0
  28. package/dist/dto/create-project-cost-type.dto.js.map +1 -0
  29. package/dist/dto/create-project-cost.dto.d.ts +22 -0
  30. package/dist/dto/create-project-cost.dto.d.ts.map +1 -0
  31. package/dist/dto/create-project-cost.dto.js +135 -0
  32. package/dist/dto/create-project-cost.dto.js.map +1 -0
  33. package/dist/dto/get-project-cost-report.dto.d.ts +10 -0
  34. package/dist/dto/get-project-cost-report.dto.d.ts.map +1 -0
  35. package/dist/dto/get-project-cost-report.dto.js +65 -0
  36. package/dist/dto/get-project-cost-report.dto.js.map +1 -0
  37. package/dist/dto/list-project-cost-categories.dto.d.ts +6 -0
  38. package/dist/dto/list-project-cost-categories.dto.d.ts.map +1 -0
  39. package/dist/dto/list-project-cost-categories.dto.js +34 -0
  40. package/dist/dto/list-project-cost-categories.dto.js.map +1 -0
  41. package/dist/dto/list-project-cost-types.dto.d.ts +8 -0
  42. package/dist/dto/list-project-cost-types.dto.d.ts.map +1 -0
  43. package/dist/dto/list-project-cost-types.dto.js +45 -0
  44. package/dist/dto/list-project-cost-types.dto.js.map +1 -0
  45. package/dist/dto/list-project-costs.dto.d.ts +14 -0
  46. package/dist/dto/list-project-costs.dto.d.ts.map +1 -0
  47. package/dist/dto/list-project-costs.dto.js +81 -0
  48. package/dist/dto/list-project-costs.dto.js.map +1 -0
  49. package/dist/dto/list-tasks.dto.d.ts +1 -0
  50. package/dist/dto/list-tasks.dto.d.ts.map +1 -1
  51. package/dist/dto/list-tasks.dto.js +6 -0
  52. package/dist/dto/list-tasks.dto.js.map +1 -1
  53. package/dist/dto/list-timesheets.dto.d.ts +1 -0
  54. package/dist/dto/list-timesheets.dto.d.ts.map +1 -1
  55. package/dist/dto/list-timesheets.dto.js +7 -0
  56. package/dist/dto/list-timesheets.dto.js.map +1 -1
  57. package/dist/dto/update-collaborator-project-assignment.dto.d.ts +11 -0
  58. package/dist/dto/update-collaborator-project-assignment.dto.d.ts.map +1 -0
  59. package/dist/dto/update-collaborator-project-assignment.dto.js +65 -0
  60. package/dist/dto/update-collaborator-project-assignment.dto.js.map +1 -0
  61. package/dist/dto/update-project-cost-category.dto.d.ts +6 -0
  62. package/dist/dto/update-project-cost-category.dto.d.ts.map +1 -0
  63. package/dist/dto/update-project-cost-category.dto.js +9 -0
  64. package/dist/dto/update-project-cost-category.dto.js.map +1 -0
  65. package/dist/dto/update-project-cost-type.dto.d.ts +6 -0
  66. package/dist/dto/update-project-cost-type.dto.d.ts.map +1 -0
  67. package/dist/dto/update-project-cost-type.dto.js +9 -0
  68. package/dist/dto/update-project-cost-type.dto.js.map +1 -0
  69. package/dist/dto/update-project-cost.dto.d.ts +6 -0
  70. package/dist/dto/update-project-cost.dto.d.ts.map +1 -0
  71. package/dist/dto/update-project-cost.dto.js +9 -0
  72. package/dist/dto/update-project-cost.dto.js.map +1 -0
  73. package/dist/operations.module.d.ts.map +1 -1
  74. package/dist/operations.module.js +2 -0
  75. package/dist/operations.module.js.map +1 -1
  76. package/dist/operations.service.d.ts +571 -1
  77. package/dist/operations.service.d.ts.map +1 -1
  78. package/dist/operations.service.js +1793 -69
  79. package/dist/operations.service.js.map +1 -1
  80. package/hedhog/data/integration_event_catalog.yaml +313 -0
  81. package/hedhog/data/menu.yaml +52 -0
  82. package/hedhog/data/operations_project_cost_category.yaml +80 -0
  83. package/hedhog/data/operations_project_cost_type.yaml +503 -0
  84. package/hedhog/data/route.yaml +274 -0
  85. package/hedhog/data/setting_group.yaml +21 -0
  86. package/hedhog/frontend/app/_components/collaborator-costs-section.tsx.ejs +2 -18
  87. package/hedhog/frontend/app/_components/collaborator-form-screen.tsx.ejs +593 -297
  88. package/hedhog/frontend/app/_components/collaborator-tasks-tab.tsx.ejs +358 -0
  89. package/hedhog/frontend/app/_components/collaborator-timesheets-tab.tsx.ejs +242 -0
  90. package/hedhog/frontend/app/_components/my-project-summary-screen.tsx.ejs +533 -296
  91. package/hedhog/frontend/app/_components/person-select-with-create.tsx.ejs +1 -853
  92. package/hedhog/frontend/app/_components/project-assignments-tab.tsx.ejs +450 -0
  93. package/hedhog/frontend/app/_components/project-cost-report-screen.tsx.ejs +602 -0
  94. package/hedhog/frontend/app/_components/project-costs-section.tsx.ejs +1401 -0
  95. package/hedhog/frontend/app/_components/project-details-screen.tsx.ejs +2248 -2063
  96. package/hedhog/frontend/app/_components/project-form-screen.tsx.ejs +56 -11
  97. package/hedhog/frontend/app/_components/task-detail-sheet.tsx.ejs +454 -96
  98. package/hedhog/frontend/app/_components/task-form-sheet.tsx.ejs +784 -0
  99. package/hedhog/frontend/app/_lib/api.ts.ejs +256 -0
  100. package/hedhog/frontend/app/_lib/hooks/use-mention-items.ts.ejs +28 -0
  101. package/hedhog/frontend/app/_lib/types.ts.ejs +190 -0
  102. package/hedhog/frontend/app/_lib/utils/format.ts.ejs +9 -3
  103. package/hedhog/frontend/app/collaborators/page.tsx.ejs +18 -7
  104. package/hedhog/frontend/app/my-tasks/page.tsx.ejs +536 -328
  105. package/hedhog/frontend/app/project-cost-categories/page.tsx.ejs +674 -0
  106. package/hedhog/frontend/app/project-cost-types/page.tsx.ejs +845 -0
  107. package/hedhog/frontend/app/projects/[id]/costs-report/page.tsx.ejs +10 -0
  108. package/hedhog/frontend/app/reports/collaborators/page.tsx.ejs +20 -349
  109. package/hedhog/frontend/app/reports/projects/page.tsx.ejs +217 -485
  110. package/hedhog/frontend/messages/en.json +257 -5
  111. package/hedhog/frontend/messages/en.json.ejs +2060 -0
  112. package/hedhog/frontend/messages/operations/en.json +2068 -0
  113. package/hedhog/frontend/messages/operations/operations/en.json +2102 -0
  114. package/hedhog/frontend/messages/operations/operations/pt.json +2111 -0
  115. package/hedhog/frontend/messages/operations/pt.json +2072 -0
  116. package/hedhog/frontend/messages/pt.json +256 -4
  117. package/hedhog/frontend/messages/pt.json.ejs +2067 -0
  118. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/async-options-combobox.d.ts +29 -0
  119. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/async-options-combobox.d.ts.map +1 -0
  120. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/async-options-combobox.js +95 -0
  121. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/async-options-combobox.js.map +1 -0
  122. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/async-options-combobox.tsx +233 -0
  123. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/collaborator-costs-section.d.ts +10 -0
  124. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/collaborator-costs-section.d.ts.map +1 -0
  125. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/collaborator-costs-section.js +577 -0
  126. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/collaborator-costs-section.js.map +1 -0
  127. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/collaborator-costs-section.tsx +868 -0
  128. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/collaborator-details-screen.d.ts +4 -0
  129. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/collaborator-details-screen.d.ts.map +1 -0
  130. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/collaborator-details-screen.js +337 -0
  131. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/collaborator-details-screen.js.map +1 -0
  132. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/collaborator-details-screen.tsx +476 -0
  133. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/collaborator-form-screen.d.ts +9 -0
  134. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/collaborator-form-screen.d.ts.map +1 -0
  135. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/collaborator-form-screen.js +1348 -0
  136. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/collaborator-form-screen.js.map +1 -0
  137. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/collaborator-form-screen.tsx +2233 -0
  138. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/collaborator-select-with-create.d.ts +12 -0
  139. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/collaborator-select-with-create.d.ts.map +1 -0
  140. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/collaborator-select-with-create.js +162 -0
  141. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/collaborator-select-with-create.js.map +1 -0
  142. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/collaborator-select-with-create.tsx +261 -0
  143. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/contract-content-editor.d.ts +18 -0
  144. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/contract-content-editor.d.ts.map +1 -0
  145. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/contract-content-editor.js +145 -0
  146. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/contract-content-editor.js.map +1 -0
  147. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/contract-content-editor.tsx +258 -0
  148. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/contract-details-screen.d.ts +4 -0
  149. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/contract-details-screen.d.ts.map +1 -0
  150. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/contract-details-screen.js +223 -0
  151. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/contract-details-screen.js.map +1 -0
  152. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/contract-details-screen.tsx +342 -0
  153. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/contract-form-screen.d.ts +58 -0
  154. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/contract-form-screen.d.ts.map +1 -0
  155. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/contract-form-screen.js +438 -0
  156. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/contract-form-screen.js.map +1 -0
  157. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/contract-form-screen.tsx +698 -0
  158. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/department-select-with-create.d.ts +20 -0
  159. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/department-select-with-create.d.ts.map +1 -0
  160. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/department-select-with-create.js +233 -0
  161. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/department-select-with-create.js.map +1 -0
  162. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/department-select-with-create.tsx +392 -0
  163. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/my-project-summary-screen.d.ts +4 -0
  164. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/my-project-summary-screen.d.ts.map +1 -0
  165. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/my-project-summary-screen.js +814 -0
  166. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/my-project-summary-screen.js.map +1 -0
  167. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/my-project-summary-screen.tsx +1288 -0
  168. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/operations-calendar-view.d.ts +21 -0
  169. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/operations-calendar-view.d.ts.map +1 -0
  170. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/operations-calendar-view.js +174 -0
  171. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/operations-calendar-view.js.map +1 -0
  172. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/operations-calendar-view.tsx +306 -0
  173. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/operations-header.d.ts +10 -0
  174. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/operations-header.d.ts.map +1 -0
  175. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/operations-header.js +12 -0
  176. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/operations-header.js.map +1 -0
  177. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/operations-header.tsx +29 -0
  178. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/person-select-with-create.d.ts +15 -0
  179. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/person-select-with-create.d.ts.map +1 -0
  180. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/person-select-with-create.js +501 -0
  181. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/person-select-with-create.js.map +1 -0
  182. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/person-select-with-create.tsx +853 -0
  183. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/project-costs-section.d.ts +6 -0
  184. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/project-costs-section.d.ts.map +1 -0
  185. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/project-costs-section.js +847 -0
  186. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/project-costs-section.js.map +1 -0
  187. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/project-costs-section.tsx +1340 -0
  188. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/project-details-screen.d.ts +4 -0
  189. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/project-details-screen.d.ts.map +1 -0
  190. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/project-details-screen.js +2930 -0
  191. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/project-details-screen.js.map +1 -0
  192. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/project-details-screen.tsx +4378 -0
  193. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/project-form-screen.d.ts +9 -0
  194. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/project-form-screen.d.ts.map +1 -0
  195. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/project-form-screen.js +1013 -0
  196. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/project-form-screen.js.map +1 -0
  197. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/project-form-screen.tsx +1745 -0
  198. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/section-card.d.ts +13 -0
  199. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/section-card.d.ts.map +1 -0
  200. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/section-card.js +38 -0
  201. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/section-card.js.map +1 -0
  202. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/section-card.tsx +74 -0
  203. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/status-badge.d.ts +7 -0
  204. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/status-badge.d.ts.map +1 -0
  205. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/status-badge.js +11 -0
  206. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/status-badge.js.map +1 -0
  207. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/status-badge.tsx +15 -0
  208. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/system-user-select-with-create.d.ts +18 -0
  209. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/system-user-select-with-create.d.ts.map +1 -0
  210. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/system-user-select-with-create.js +406 -0
  211. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/system-user-select-with-create.js.map +1 -0
  212. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/system-user-select-with-create.tsx +660 -0
  213. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/task-detail-sheet.d.ts +26 -0
  214. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/task-detail-sheet.d.ts.map +1 -0
  215. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/task-detail-sheet.js +332 -0
  216. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/task-detail-sheet.js.map +1 -0
  217. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/task-detail-sheet.tsx +518 -0
  218. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/task-file-attachments.d.ts +6 -0
  219. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/task-file-attachments.d.ts.map +1 -0
  220. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/task-file-attachments.js +255 -0
  221. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/task-file-attachments.js.map +1 -0
  222. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/task-file-attachments.tsx +388 -0
  223. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/timesheet-task-create-sheet.d.ts +10 -0
  224. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/timesheet-task-create-sheet.d.ts.map +1 -0
  225. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/timesheet-task-create-sheet.js +131 -0
  226. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/timesheet-task-create-sheet.js.map +1 -0
  227. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_components/timesheet-task-create-sheet.tsx +214 -0
  228. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_lib/api.d.ts +108 -0
  229. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_lib/api.d.ts.map +1 -0
  230. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_lib/api.js +162 -0
  231. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_lib/api.js.map +1 -0
  232. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_lib/api.ts +428 -0
  233. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_lib/hooks/use-operations-access.d.ts +8 -0
  234. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_lib/hooks/use-operations-access.d.ts.map +1 -0
  235. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_lib/hooks/use-operations-access.js +36 -0
  236. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_lib/hooks/use-operations-access.js.map +1 -0
  237. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_lib/hooks/use-operations-access.ts +44 -0
  238. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_lib/types.d.ts +837 -0
  239. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_lib/types.d.ts.map +1 -0
  240. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_lib/types.js +3 -0
  241. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_lib/types.js.map +1 -0
  242. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_lib/types.ts +861 -0
  243. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_lib/utils/format.d.ts +16 -0
  244. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_lib/utils/format.d.ts.map +1 -0
  245. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_lib/utils/format.js +182 -0
  246. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_lib/utils/format.js.map +1 -0
  247. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_lib/utils/format.ts +250 -0
  248. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_lib/utils/forms.d.ts +4 -0
  249. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_lib/utils/forms.d.ts.map +1 -0
  250. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_lib/utils/forms.js +51 -0
  251. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_lib/utils/forms.js.map +1 -0
  252. package/hedhog/frontend/src/app/(app)/(libraries)/operations/_lib/utils/forms.ts +61 -0
  253. package/hedhog/frontend/src/app/(app)/(libraries)/operations/approvals/page.d.ts +2 -0
  254. package/hedhog/frontend/src/app/(app)/(libraries)/operations/approvals/page.d.ts.map +1 -0
  255. package/hedhog/frontend/src/app/(app)/(libraries)/operations/approvals/page.js +954 -0
  256. package/hedhog/frontend/src/app/(app)/(libraries)/operations/approvals/page.js.map +1 -0
  257. package/hedhog/frontend/src/app/(app)/(libraries)/operations/approvals/page.tsx +1277 -0
  258. package/hedhog/frontend/src/app/(app)/(libraries)/operations/collaborator-types/page.d.ts +2 -0
  259. package/hedhog/frontend/src/app/(app)/(libraries)/operations/collaborator-types/page.d.ts.map +1 -0
  260. package/hedhog/frontend/src/app/(app)/(libraries)/operations/collaborator-types/page.js +488 -0
  261. package/hedhog/frontend/src/app/(app)/(libraries)/operations/collaborator-types/page.js.map +1 -0
  262. package/hedhog/frontend/src/app/(app)/(libraries)/operations/collaborator-types/page.tsx +805 -0
  263. package/hedhog/frontend/src/app/(app)/(libraries)/operations/collaborators/[id]/edit/page.d.ts +6 -0
  264. package/hedhog/frontend/src/app/(app)/(libraries)/operations/collaborators/[id]/edit/page.d.ts.map +1 -0
  265. package/hedhog/frontend/src/app/(app)/(libraries)/operations/collaborators/[id]/edit/page.js +9 -0
  266. package/hedhog/frontend/src/app/(app)/(libraries)/operations/collaborators/[id]/edit/page.js.map +1 -0
  267. package/hedhog/frontend/src/app/(app)/(libraries)/operations/collaborators/[id]/edit/page.tsx +11 -0
  268. package/hedhog/frontend/src/app/(app)/(libraries)/operations/collaborators/[id]/page.d.ts +6 -0
  269. package/hedhog/frontend/src/app/(app)/(libraries)/operations/collaborators/[id]/page.d.ts.map +1 -0
  270. package/hedhog/frontend/src/app/(app)/(libraries)/operations/collaborators/[id]/page.js +9 -0
  271. package/hedhog/frontend/src/app/(app)/(libraries)/operations/collaborators/[id]/page.js.map +1 -0
  272. package/hedhog/frontend/src/app/(app)/(libraries)/operations/collaborators/[id]/page.tsx +11 -0
  273. package/hedhog/frontend/src/app/(app)/(libraries)/operations/collaborators/new/page.d.ts +2 -0
  274. package/hedhog/frontend/src/app/(app)/(libraries)/operations/collaborators/new/page.d.ts.map +1 -0
  275. package/hedhog/frontend/src/app/(app)/(libraries)/operations/collaborators/new/page.js +8 -0
  276. package/hedhog/frontend/src/app/(app)/(libraries)/operations/collaborators/new/page.js.map +1 -0
  277. package/hedhog/frontend/src/app/(app)/(libraries)/operations/collaborators/new/page.tsx +5 -0
  278. package/hedhog/frontend/src/app/(app)/(libraries)/operations/collaborators/page.d.ts +2 -0
  279. package/hedhog/frontend/src/app/(app)/(libraries)/operations/collaborators/page.d.ts.map +1 -0
  280. package/hedhog/frontend/src/app/(app)/(libraries)/operations/collaborators/page.js +612 -0
  281. package/hedhog/frontend/src/app/(app)/(libraries)/operations/collaborators/page.js.map +1 -0
  282. package/hedhog/frontend/src/app/(app)/(libraries)/operations/collaborators/page.tsx +939 -0
  283. package/hedhog/frontend/src/app/(app)/(libraries)/operations/contracts/[id]/edit/page.d.ts +6 -0
  284. package/hedhog/frontend/src/app/(app)/(libraries)/operations/contracts/[id]/edit/page.d.ts.map +1 -0
  285. package/hedhog/frontend/src/app/(app)/(libraries)/operations/contracts/[id]/edit/page.js +9 -0
  286. package/hedhog/frontend/src/app/(app)/(libraries)/operations/contracts/[id]/edit/page.js.map +1 -0
  287. package/hedhog/frontend/src/app/(app)/(libraries)/operations/contracts/[id]/edit/page.tsx +11 -0
  288. package/hedhog/frontend/src/app/(app)/(libraries)/operations/contracts/[id]/page.d.ts +6 -0
  289. package/hedhog/frontend/src/app/(app)/(libraries)/operations/contracts/[id]/page.d.ts.map +1 -0
  290. package/hedhog/frontend/src/app/(app)/(libraries)/operations/contracts/[id]/page.js +9 -0
  291. package/hedhog/frontend/src/app/(app)/(libraries)/operations/contracts/[id]/page.js.map +1 -0
  292. package/hedhog/frontend/src/app/(app)/(libraries)/operations/contracts/[id]/page.tsx +11 -0
  293. package/hedhog/frontend/src/app/(app)/(libraries)/operations/contracts/new/page.d.ts +6 -0
  294. package/hedhog/frontend/src/app/(app)/(libraries)/operations/contracts/new/page.d.ts.map +1 -0
  295. package/hedhog/frontend/src/app/(app)/(libraries)/operations/contracts/new/page.js +9 -0
  296. package/hedhog/frontend/src/app/(app)/(libraries)/operations/contracts/new/page.js.map +1 -0
  297. package/hedhog/frontend/src/app/(app)/(libraries)/operations/contracts/new/page.tsx +17 -0
  298. package/hedhog/frontend/src/app/(app)/(libraries)/operations/contracts/page.d.ts +2 -0
  299. package/hedhog/frontend/src/app/(app)/(libraries)/operations/contracts/page.d.ts.map +1 -0
  300. package/hedhog/frontend/src/app/(app)/(libraries)/operations/contracts/page.js +348 -0
  301. package/hedhog/frontend/src/app/(app)/(libraries)/operations/contracts/page.js.map +1 -0
  302. package/hedhog/frontend/src/app/(app)/(libraries)/operations/contracts/page.tsx +536 -0
  303. package/hedhog/frontend/src/app/(app)/(libraries)/operations/departments/page.d.ts +2 -0
  304. package/hedhog/frontend/src/app/(app)/(libraries)/operations/departments/page.d.ts.map +1 -0
  305. package/hedhog/frontend/src/app/(app)/(libraries)/operations/departments/page.js +401 -0
  306. package/hedhog/frontend/src/app/(app)/(libraries)/operations/departments/page.js.map +1 -0
  307. package/hedhog/frontend/src/app/(app)/(libraries)/operations/departments/page.tsx +607 -0
  308. package/hedhog/frontend/src/app/(app)/(libraries)/operations/layout.d.ts +5 -0
  309. package/hedhog/frontend/src/app/(app)/(libraries)/operations/layout.d.ts.map +1 -0
  310. package/hedhog/frontend/src/app/(app)/(libraries)/operations/layout.js +7 -0
  311. package/hedhog/frontend/src/app/(app)/(libraries)/operations/layout.js.map +1 -0
  312. package/hedhog/frontend/src/app/(app)/(libraries)/operations/layout.tsx +9 -0
  313. package/hedhog/frontend/src/app/(app)/(libraries)/operations/my-projects/[id]/page.d.ts +6 -0
  314. package/hedhog/frontend/src/app/(app)/(libraries)/operations/my-projects/[id]/page.d.ts.map +1 -0
  315. package/hedhog/frontend/src/app/(app)/(libraries)/operations/my-projects/[id]/page.js +9 -0
  316. package/hedhog/frontend/src/app/(app)/(libraries)/operations/my-projects/[id]/page.js.map +1 -0
  317. package/hedhog/frontend/src/app/(app)/(libraries)/operations/my-projects/[id]/page.tsx +11 -0
  318. package/hedhog/frontend/src/app/(app)/(libraries)/operations/my-projects/page.d.ts +2 -0
  319. package/hedhog/frontend/src/app/(app)/(libraries)/operations/my-projects/page.d.ts.map +1 -0
  320. package/hedhog/frontend/src/app/(app)/(libraries)/operations/my-projects/page.js +321 -0
  321. package/hedhog/frontend/src/app/(app)/(libraries)/operations/my-projects/page.js.map +1 -0
  322. package/hedhog/frontend/src/app/(app)/(libraries)/operations/my-projects/page.tsx +440 -0
  323. package/hedhog/frontend/src/app/(app)/(libraries)/operations/my-tasks/page.d.ts +2 -0
  324. package/hedhog/frontend/src/app/(app)/(libraries)/operations/my-tasks/page.d.ts.map +1 -0
  325. package/hedhog/frontend/src/app/(app)/(libraries)/operations/my-tasks/page.js +939 -0
  326. package/hedhog/frontend/src/app/(app)/(libraries)/operations/my-tasks/page.js.map +1 -0
  327. package/hedhog/frontend/src/app/(app)/(libraries)/operations/my-tasks/page.tsx +1499 -0
  328. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/async-options-combobox.d.ts +29 -0
  329. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/async-options-combobox.d.ts.map +1 -0
  330. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/async-options-combobox.js +95 -0
  331. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/async-options-combobox.js.map +1 -0
  332. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/async-options-combobox.tsx +233 -0
  333. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/collaborator-costs-section.d.ts +10 -0
  334. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/collaborator-costs-section.d.ts.map +1 -0
  335. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/collaborator-costs-section.js +577 -0
  336. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/collaborator-costs-section.js.map +1 -0
  337. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/collaborator-costs-section.tsx +868 -0
  338. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/collaborator-details-screen.d.ts +4 -0
  339. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/collaborator-details-screen.d.ts.map +1 -0
  340. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/collaborator-details-screen.js +337 -0
  341. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/collaborator-details-screen.js.map +1 -0
  342. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/collaborator-details-screen.tsx +476 -0
  343. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/collaborator-form-screen.d.ts +9 -0
  344. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/collaborator-form-screen.d.ts.map +1 -0
  345. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/collaborator-form-screen.js +1348 -0
  346. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/collaborator-form-screen.js.map +1 -0
  347. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/collaborator-form-screen.tsx +2233 -0
  348. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/collaborator-select-with-create.d.ts +12 -0
  349. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/collaborator-select-with-create.d.ts.map +1 -0
  350. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/collaborator-select-with-create.js +162 -0
  351. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/collaborator-select-with-create.js.map +1 -0
  352. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/collaborator-select-with-create.tsx +261 -0
  353. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/contract-content-editor.d.ts +18 -0
  354. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/contract-content-editor.d.ts.map +1 -0
  355. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/contract-content-editor.js +145 -0
  356. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/contract-content-editor.js.map +1 -0
  357. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/contract-content-editor.tsx +258 -0
  358. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/contract-details-screen.d.ts +4 -0
  359. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/contract-details-screen.d.ts.map +1 -0
  360. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/contract-details-screen.js +223 -0
  361. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/contract-details-screen.js.map +1 -0
  362. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/contract-details-screen.tsx +342 -0
  363. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/contract-form-screen.d.ts +58 -0
  364. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/contract-form-screen.d.ts.map +1 -0
  365. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/contract-form-screen.js +438 -0
  366. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/contract-form-screen.js.map +1 -0
  367. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/contract-form-screen.tsx +698 -0
  368. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/department-select-with-create.d.ts +20 -0
  369. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/department-select-with-create.d.ts.map +1 -0
  370. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/department-select-with-create.js +233 -0
  371. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/department-select-with-create.js.map +1 -0
  372. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/department-select-with-create.tsx +392 -0
  373. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/my-project-summary-screen.d.ts +4 -0
  374. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/my-project-summary-screen.d.ts.map +1 -0
  375. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/my-project-summary-screen.js +814 -0
  376. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/my-project-summary-screen.js.map +1 -0
  377. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/my-project-summary-screen.tsx +1288 -0
  378. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/operations-calendar-view.d.ts +21 -0
  379. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/operations-calendar-view.d.ts.map +1 -0
  380. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/operations-calendar-view.js +174 -0
  381. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/operations-calendar-view.js.map +1 -0
  382. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/operations-calendar-view.tsx +306 -0
  383. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/operations-header.d.ts +10 -0
  384. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/operations-header.d.ts.map +1 -0
  385. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/operations-header.js +12 -0
  386. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/operations-header.js.map +1 -0
  387. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/operations-header.tsx +29 -0
  388. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/person-select-with-create.d.ts +15 -0
  389. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/person-select-with-create.d.ts.map +1 -0
  390. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/person-select-with-create.js +501 -0
  391. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/person-select-with-create.js.map +1 -0
  392. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/person-select-with-create.tsx +853 -0
  393. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/project-cost-report-screen.d.ts +6 -0
  394. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/project-cost-report-screen.d.ts.map +1 -0
  395. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/project-cost-report-screen.js +459 -0
  396. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/project-cost-report-screen.js.map +1 -0
  397. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/project-cost-report-screen.tsx +598 -0
  398. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/project-costs-section.d.ts +6 -0
  399. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/project-costs-section.d.ts.map +1 -0
  400. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/project-costs-section.js +876 -0
  401. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/project-costs-section.js.map +1 -0
  402. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/project-costs-section.tsx +1368 -0
  403. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/project-details-screen.d.ts +4 -0
  404. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/project-details-screen.d.ts.map +1 -0
  405. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/project-details-screen.js +2930 -0
  406. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/project-details-screen.js.map +1 -0
  407. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/project-details-screen.tsx +4378 -0
  408. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/project-form-screen.d.ts +9 -0
  409. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/project-form-screen.d.ts.map +1 -0
  410. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/project-form-screen.js +1013 -0
  411. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/project-form-screen.js.map +1 -0
  412. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/project-form-screen.tsx +1745 -0
  413. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/section-card.d.ts +13 -0
  414. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/section-card.d.ts.map +1 -0
  415. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/section-card.js +38 -0
  416. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/section-card.js.map +1 -0
  417. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/section-card.tsx +74 -0
  418. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/status-badge.d.ts +7 -0
  419. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/status-badge.d.ts.map +1 -0
  420. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/status-badge.js +11 -0
  421. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/status-badge.js.map +1 -0
  422. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/status-badge.tsx +15 -0
  423. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/system-user-select-with-create.d.ts +18 -0
  424. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/system-user-select-with-create.d.ts.map +1 -0
  425. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/system-user-select-with-create.js +406 -0
  426. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/system-user-select-with-create.js.map +1 -0
  427. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/system-user-select-with-create.tsx +660 -0
  428. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/task-detail-sheet.d.ts +26 -0
  429. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/task-detail-sheet.d.ts.map +1 -0
  430. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/task-detail-sheet.js +332 -0
  431. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/task-detail-sheet.js.map +1 -0
  432. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/task-detail-sheet.tsx +518 -0
  433. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/task-file-attachments.d.ts +6 -0
  434. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/task-file-attachments.d.ts.map +1 -0
  435. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/task-file-attachments.js +255 -0
  436. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/task-file-attachments.js.map +1 -0
  437. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/task-file-attachments.tsx +388 -0
  438. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/timesheet-task-create-sheet.d.ts +10 -0
  439. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/timesheet-task-create-sheet.d.ts.map +1 -0
  440. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/timesheet-task-create-sheet.js +131 -0
  441. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/timesheet-task-create-sheet.js.map +1 -0
  442. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_components/timesheet-task-create-sheet.tsx +214 -0
  443. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_lib/api.d.ts +108 -0
  444. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_lib/api.d.ts.map +1 -0
  445. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_lib/api.js +162 -0
  446. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_lib/api.js.map +1 -0
  447. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_lib/api.ts +428 -0
  448. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_lib/hooks/use-operations-access.d.ts +8 -0
  449. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_lib/hooks/use-operations-access.d.ts.map +1 -0
  450. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_lib/hooks/use-operations-access.js +36 -0
  451. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_lib/hooks/use-operations-access.js.map +1 -0
  452. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_lib/hooks/use-operations-access.ts +44 -0
  453. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_lib/types.d.ts +837 -0
  454. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_lib/types.d.ts.map +1 -0
  455. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_lib/types.js +3 -0
  456. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_lib/types.js.map +1 -0
  457. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_lib/types.ts +861 -0
  458. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_lib/utils/format.d.ts +16 -0
  459. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_lib/utils/format.d.ts.map +1 -0
  460. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_lib/utils/format.js +182 -0
  461. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_lib/utils/format.js.map +1 -0
  462. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_lib/utils/format.ts +250 -0
  463. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_lib/utils/forms.d.ts +4 -0
  464. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_lib/utils/forms.d.ts.map +1 -0
  465. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_lib/utils/forms.js +51 -0
  466. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_lib/utils/forms.js.map +1 -0
  467. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_lib/utils/forms.ts +61 -0
  468. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/approvals/page.d.ts +2 -0
  469. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/approvals/page.d.ts.map +1 -0
  470. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/approvals/page.js +954 -0
  471. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/approvals/page.js.map +1 -0
  472. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/approvals/page.tsx +1277 -0
  473. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/collaborator-types/page.d.ts +2 -0
  474. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/collaborator-types/page.d.ts.map +1 -0
  475. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/collaborator-types/page.js +488 -0
  476. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/collaborator-types/page.js.map +1 -0
  477. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/collaborator-types/page.tsx +805 -0
  478. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/collaborators/[id]/edit/page.d.ts +6 -0
  479. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/collaborators/[id]/edit/page.d.ts.map +1 -0
  480. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/collaborators/[id]/edit/page.js +9 -0
  481. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/collaborators/[id]/edit/page.js.map +1 -0
  482. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/collaborators/[id]/edit/page.tsx +11 -0
  483. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/collaborators/[id]/page.d.ts +6 -0
  484. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/collaborators/[id]/page.d.ts.map +1 -0
  485. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/collaborators/[id]/page.js +9 -0
  486. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/collaborators/[id]/page.js.map +1 -0
  487. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/collaborators/[id]/page.tsx +11 -0
  488. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/collaborators/new/page.d.ts +2 -0
  489. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/collaborators/new/page.d.ts.map +1 -0
  490. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/collaborators/new/page.js +8 -0
  491. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/collaborators/new/page.js.map +1 -0
  492. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/collaborators/new/page.tsx +5 -0
  493. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/collaborators/page.d.ts +2 -0
  494. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/collaborators/page.d.ts.map +1 -0
  495. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/collaborators/page.js +612 -0
  496. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/collaborators/page.js.map +1 -0
  497. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/collaborators/page.tsx +939 -0
  498. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/contracts/[id]/edit/page.d.ts +6 -0
  499. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/contracts/[id]/edit/page.d.ts.map +1 -0
  500. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/contracts/[id]/edit/page.js +9 -0
  501. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/contracts/[id]/edit/page.js.map +1 -0
  502. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/contracts/[id]/edit/page.tsx +11 -0
  503. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/contracts/[id]/page.d.ts +6 -0
  504. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/contracts/[id]/page.d.ts.map +1 -0
  505. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/contracts/[id]/page.js +9 -0
  506. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/contracts/[id]/page.js.map +1 -0
  507. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/contracts/[id]/page.tsx +11 -0
  508. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/contracts/new/page.d.ts +6 -0
  509. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/contracts/new/page.d.ts.map +1 -0
  510. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/contracts/new/page.js +9 -0
  511. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/contracts/new/page.js.map +1 -0
  512. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/contracts/new/page.tsx +17 -0
  513. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/contracts/page.d.ts +2 -0
  514. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/contracts/page.d.ts.map +1 -0
  515. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/contracts/page.js +348 -0
  516. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/contracts/page.js.map +1 -0
  517. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/contracts/page.tsx +536 -0
  518. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/departments/page.d.ts +2 -0
  519. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/departments/page.d.ts.map +1 -0
  520. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/departments/page.js +401 -0
  521. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/departments/page.js.map +1 -0
  522. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/departments/page.tsx +607 -0
  523. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/layout.d.ts +5 -0
  524. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/layout.d.ts.map +1 -0
  525. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/layout.js +7 -0
  526. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/layout.js.map +1 -0
  527. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/layout.tsx +9 -0
  528. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/my-projects/[id]/page.d.ts +6 -0
  529. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/my-projects/[id]/page.d.ts.map +1 -0
  530. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/my-projects/[id]/page.js +9 -0
  531. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/my-projects/[id]/page.js.map +1 -0
  532. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/my-projects/[id]/page.tsx +11 -0
  533. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/my-projects/page.d.ts +2 -0
  534. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/my-projects/page.d.ts.map +1 -0
  535. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/my-projects/page.js +321 -0
  536. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/my-projects/page.js.map +1 -0
  537. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/my-projects/page.tsx +440 -0
  538. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/my-tasks/page.d.ts +2 -0
  539. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/my-tasks/page.d.ts.map +1 -0
  540. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/my-tasks/page.js +939 -0
  541. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/my-tasks/page.js.map +1 -0
  542. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/my-tasks/page.tsx +1499 -0
  543. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/page.d.ts +2 -0
  544. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/page.d.ts.map +1 -0
  545. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/page.js +8 -0
  546. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/page.js.map +1 -0
  547. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/page.tsx +5 -0
  548. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/project-cost-categories/page.d.ts +2 -0
  549. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/project-cost-categories/page.d.ts.map +1 -0
  550. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/project-cost-categories/page.js +436 -0
  551. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/project-cost-categories/page.js.map +1 -0
  552. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/project-cost-categories/page.tsx +675 -0
  553. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/project-cost-types/page.d.ts +2 -0
  554. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/project-cost-types/page.d.ts.map +1 -0
  555. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/project-cost-types/page.js +563 -0
  556. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/project-cost-types/page.js.map +1 -0
  557. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/project-cost-types/page.tsx +846 -0
  558. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/projects/[id]/costs-report/page.d.ts +6 -0
  559. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/projects/[id]/costs-report/page.d.ts.map +1 -0
  560. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/projects/[id]/costs-report/page.js +9 -0
  561. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/projects/[id]/costs-report/page.js.map +1 -0
  562. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/projects/[id]/costs-report/page.tsx +10 -0
  563. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/projects/[id]/edit/page.d.ts +6 -0
  564. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/projects/[id]/edit/page.d.ts.map +1 -0
  565. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/projects/[id]/edit/page.js +9 -0
  566. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/projects/[id]/edit/page.js.map +1 -0
  567. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/projects/[id]/edit/page.tsx +11 -0
  568. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/projects/[id]/page.d.ts +6 -0
  569. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/projects/[id]/page.d.ts.map +1 -0
  570. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/projects/[id]/page.js +9 -0
  571. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/projects/[id]/page.js.map +1 -0
  572. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/projects/[id]/page.tsx +11 -0
  573. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/projects/new/page.d.ts +2 -0
  574. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/projects/new/page.d.ts.map +1 -0
  575. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/projects/new/page.js +8 -0
  576. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/projects/new/page.js.map +1 -0
  577. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/projects/new/page.tsx +5 -0
  578. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/projects/page.d.ts +2 -0
  579. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/projects/page.d.ts.map +1 -0
  580. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/projects/page.js +492 -0
  581. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/projects/page.js.map +1 -0
  582. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/projects/page.tsx +757 -0
  583. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/reports/collaborators/page.d.ts +2 -0
  584. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/reports/collaborators/page.d.ts.map +1 -0
  585. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/reports/collaborators/page.js +342 -0
  586. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/reports/collaborators/page.js.map +1 -0
  587. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/reports/collaborators/page.tsx +430 -0
  588. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/reports/projects/page.d.ts +2 -0
  589. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/reports/projects/page.d.ts.map +1 -0
  590. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/reports/projects/page.js +338 -0
  591. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/reports/projects/page.js.map +1 -0
  592. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/reports/projects/page.tsx +428 -0
  593. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/schedule-adjustments/page.d.ts +2 -0
  594. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/schedule-adjustments/page.d.ts.map +1 -0
  595. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/schedule-adjustments/page.js +660 -0
  596. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/schedule-adjustments/page.js.map +1 -0
  597. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/schedule-adjustments/page.tsx +992 -0
  598. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/time-off/page.d.ts +2 -0
  599. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/time-off/page.d.ts.map +1 -0
  600. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/time-off/page.js +515 -0
  601. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/time-off/page.js.map +1 -0
  602. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/time-off/page.tsx +707 -0
  603. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/timesheets/page.d.ts +2 -0
  604. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/timesheets/page.d.ts.map +1 -0
  605. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/timesheets/page.js +1141 -0
  606. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/timesheets/page.js.map +1 -0
  607. package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/timesheets/page.tsx +1705 -0
  608. package/hedhog/frontend/src/app/(app)/(libraries)/operations/page.d.ts +2 -0
  609. package/hedhog/frontend/src/app/(app)/(libraries)/operations/page.d.ts.map +1 -0
  610. package/hedhog/frontend/src/app/(app)/(libraries)/operations/page.js +8 -0
  611. package/hedhog/frontend/src/app/(app)/(libraries)/operations/page.js.map +1 -0
  612. package/hedhog/frontend/src/app/(app)/(libraries)/operations/page.tsx +5 -0
  613. package/hedhog/frontend/src/app/(app)/(libraries)/operations/project-cost-categories/page.d.ts +2 -0
  614. package/hedhog/frontend/src/app/(app)/(libraries)/operations/project-cost-categories/page.d.ts.map +1 -0
  615. package/hedhog/frontend/src/app/(app)/(libraries)/operations/project-cost-categories/page.js +436 -0
  616. package/hedhog/frontend/src/app/(app)/(libraries)/operations/project-cost-categories/page.js.map +1 -0
  617. package/hedhog/frontend/src/app/(app)/(libraries)/operations/project-cost-categories/page.tsx +675 -0
  618. package/hedhog/frontend/src/app/(app)/(libraries)/operations/project-cost-types/page.d.ts +2 -0
  619. package/hedhog/frontend/src/app/(app)/(libraries)/operations/project-cost-types/page.d.ts.map +1 -0
  620. package/hedhog/frontend/src/app/(app)/(libraries)/operations/project-cost-types/page.js +563 -0
  621. package/hedhog/frontend/src/app/(app)/(libraries)/operations/project-cost-types/page.js.map +1 -0
  622. package/hedhog/frontend/src/app/(app)/(libraries)/operations/project-cost-types/page.tsx +846 -0
  623. package/hedhog/frontend/src/app/(app)/(libraries)/operations/projects/[id]/edit/page.d.ts +6 -0
  624. package/hedhog/frontend/src/app/(app)/(libraries)/operations/projects/[id]/edit/page.d.ts.map +1 -0
  625. package/hedhog/frontend/src/app/(app)/(libraries)/operations/projects/[id]/edit/page.js +9 -0
  626. package/hedhog/frontend/src/app/(app)/(libraries)/operations/projects/[id]/edit/page.js.map +1 -0
  627. package/hedhog/frontend/src/app/(app)/(libraries)/operations/projects/[id]/edit/page.tsx +11 -0
  628. package/hedhog/frontend/src/app/(app)/(libraries)/operations/projects/[id]/page.d.ts +6 -0
  629. package/hedhog/frontend/src/app/(app)/(libraries)/operations/projects/[id]/page.d.ts.map +1 -0
  630. package/hedhog/frontend/src/app/(app)/(libraries)/operations/projects/[id]/page.js +9 -0
  631. package/hedhog/frontend/src/app/(app)/(libraries)/operations/projects/[id]/page.js.map +1 -0
  632. package/hedhog/frontend/src/app/(app)/(libraries)/operations/projects/[id]/page.tsx +11 -0
  633. package/hedhog/frontend/src/app/(app)/(libraries)/operations/projects/new/page.d.ts +2 -0
  634. package/hedhog/frontend/src/app/(app)/(libraries)/operations/projects/new/page.d.ts.map +1 -0
  635. package/hedhog/frontend/src/app/(app)/(libraries)/operations/projects/new/page.js +8 -0
  636. package/hedhog/frontend/src/app/(app)/(libraries)/operations/projects/new/page.js.map +1 -0
  637. package/hedhog/frontend/src/app/(app)/(libraries)/operations/projects/new/page.tsx +5 -0
  638. package/hedhog/frontend/src/app/(app)/(libraries)/operations/projects/page.d.ts +2 -0
  639. package/hedhog/frontend/src/app/(app)/(libraries)/operations/projects/page.d.ts.map +1 -0
  640. package/hedhog/frontend/src/app/(app)/(libraries)/operations/projects/page.js +492 -0
  641. package/hedhog/frontend/src/app/(app)/(libraries)/operations/projects/page.js.map +1 -0
  642. package/hedhog/frontend/src/app/(app)/(libraries)/operations/projects/page.tsx +757 -0
  643. package/hedhog/frontend/src/app/(app)/(libraries)/operations/reports/collaborators/page.d.ts +2 -0
  644. package/hedhog/frontend/src/app/(app)/(libraries)/operations/reports/collaborators/page.d.ts.map +1 -0
  645. package/hedhog/frontend/src/app/(app)/(libraries)/operations/reports/collaborators/page.js +342 -0
  646. package/hedhog/frontend/src/app/(app)/(libraries)/operations/reports/collaborators/page.js.map +1 -0
  647. package/hedhog/frontend/src/app/(app)/(libraries)/operations/reports/collaborators/page.tsx +430 -0
  648. package/hedhog/frontend/src/app/(app)/(libraries)/operations/reports/projects/page.d.ts +2 -0
  649. package/hedhog/frontend/src/app/(app)/(libraries)/operations/reports/projects/page.d.ts.map +1 -0
  650. package/hedhog/frontend/src/app/(app)/(libraries)/operations/reports/projects/page.js +338 -0
  651. package/hedhog/frontend/src/app/(app)/(libraries)/operations/reports/projects/page.js.map +1 -0
  652. package/hedhog/frontend/src/app/(app)/(libraries)/operations/reports/projects/page.tsx +428 -0
  653. package/hedhog/frontend/src/app/(app)/(libraries)/operations/schedule-adjustments/page.d.ts +2 -0
  654. package/hedhog/frontend/src/app/(app)/(libraries)/operations/schedule-adjustments/page.d.ts.map +1 -0
  655. package/hedhog/frontend/src/app/(app)/(libraries)/operations/schedule-adjustments/page.js +660 -0
  656. package/hedhog/frontend/src/app/(app)/(libraries)/operations/schedule-adjustments/page.js.map +1 -0
  657. package/hedhog/frontend/src/app/(app)/(libraries)/operations/schedule-adjustments/page.tsx +992 -0
  658. package/hedhog/frontend/src/app/(app)/(libraries)/operations/time-off/page.d.ts +2 -0
  659. package/hedhog/frontend/src/app/(app)/(libraries)/operations/time-off/page.d.ts.map +1 -0
  660. package/hedhog/frontend/src/app/(app)/(libraries)/operations/time-off/page.js +515 -0
  661. package/hedhog/frontend/src/app/(app)/(libraries)/operations/time-off/page.js.map +1 -0
  662. package/hedhog/frontend/src/app/(app)/(libraries)/operations/time-off/page.tsx +707 -0
  663. package/hedhog/frontend/src/app/(app)/(libraries)/operations/timesheets/page.d.ts +2 -0
  664. package/hedhog/frontend/src/app/(app)/(libraries)/operations/timesheets/page.d.ts.map +1 -0
  665. package/hedhog/frontend/src/app/(app)/(libraries)/operations/timesheets/page.js +1141 -0
  666. package/hedhog/frontend/src/app/(app)/(libraries)/operations/timesheets/page.js.map +1 -0
  667. package/hedhog/frontend/src/app/(app)/(libraries)/operations/timesheets/page.tsx +1705 -0
  668. package/hedhog/table/operations_collaborator.yaml +5 -0
  669. package/hedhog/table/operations_collaborator_compensation_history.yaml +4 -0
  670. package/hedhog/table/operations_project_assignment.yaml +1 -0
  671. package/hedhog/table/operations_project_cost.yaml +93 -0
  672. package/hedhog/table/operations_project_cost_category.yaml +37 -0
  673. package/hedhog/table/operations_project_cost_type.yaml +55 -0
  674. package/hedhog/table/operations_task_comment.yaml +26 -0
  675. package/package.json +6 -6
  676. package/src/controllers/operations-collaborators.controller.ts +26 -0
  677. package/src/controllers/operations-project-costs.controller.ts +249 -0
  678. package/src/controllers/operations-tasks.controller.ts +49 -0
  679. package/src/dto/create-collaborator-project-assignment.dto.ts +14 -0
  680. package/src/dto/create-project-cost-category.dto.ts +37 -0
  681. package/src/dto/create-project-cost-type.dto.ts +64 -0
  682. package/src/dto/create-project-cost.dto.ts +126 -0
  683. package/src/dto/get-project-cost-report.dto.ts +46 -0
  684. package/src/dto/list-project-cost-categories.dto.ts +17 -0
  685. package/src/dto/list-project-cost-types.dto.ts +28 -0
  686. package/src/dto/list-project-costs.dto.ts +59 -0
  687. package/src/dto/list-tasks.dto.ts +7 -0
  688. package/src/dto/list-timesheets.dto.ts +7 -1
  689. package/src/dto/update-collaborator-project-assignment.dto.ts +58 -0
  690. package/src/dto/update-project-cost-category.dto.ts +4 -0
  691. package/src/dto/update-project-cost-type.dto.ts +4 -0
  692. package/src/dto/update-project-cost.dto.ts +4 -0
  693. package/src/operations.module.ts +2 -0
  694. package/src/operations.service.ts +2472 -61
@@ -18,13 +18,6 @@ import {
18
18
  DialogHeader,
19
19
  DialogTitle,
20
20
  } from '@/components/ui/dialog';
21
- import {
22
- DropdownMenu,
23
- DropdownMenuContent,
24
- DropdownMenuItem,
25
- DropdownMenuSeparator,
26
- DropdownMenuTrigger,
27
- } from '@/components/ui/dropdown-menu';
28
21
  import { Input } from '@/components/ui/input';
29
22
  import { Label } from '@/components/ui/label';
30
23
  import { Progress } from '@/components/ui/progress';
@@ -51,6 +44,7 @@ import {
51
44
  TableHeader,
52
45
  TableRow,
53
46
  } from '@/components/ui/table';
47
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
54
48
  import {
55
49
  Tooltip,
56
50
  TooltipContent,
@@ -94,7 +88,6 @@ import {
94
88
  LineChart as LineChartIcon,
95
89
  Loader2,
96
90
  MessageSquare,
97
- MoreHorizontal,
98
91
  Paperclip,
99
92
  Pencil,
100
93
  Plus,
@@ -130,6 +123,7 @@ import {
130
123
  YAxis,
131
124
  } from 'recharts';
132
125
  import { fetchOperations, mutateOperations } from '../_lib/api';
126
+ import { useMentionItems } from '../_lib/hooks/use-mention-items';
133
127
  import { useOperationsAccess } from '../_lib/hooks/use-operations-access';
134
128
  import type {
135
129
  OperationsProjectDetails,
@@ -145,10 +139,15 @@ import {
145
139
  getStatusBadgeClass,
146
140
  } from '../_lib/utils/format';
147
141
  import { OperationsHeader } from './operations-header';
142
+ import { ProjectCostsSection } from './project-costs-section';
148
143
  import { ProjectFormScreen } from './project-form-screen';
149
144
  import { SectionCard } from './section-card';
150
145
  import { StatusBadge } from './status-badge';
151
- import { TaskDetailSheet, type TaskDetailSheetData } from './task-detail-sheet';
146
+ import {
147
+ TaskCommentsSection,
148
+ TaskDetailSheet,
149
+ type TaskDetailSheetData,
150
+ } from './task-detail-sheet';
152
151
  import { TaskFileAttachments } from './task-file-attachments';
153
152
 
154
153
  type BoardColumnId = 'todo' | 'doing' | 'review' | 'done';
@@ -168,6 +167,8 @@ type BoardTask = {
168
167
  assigneePersonAvatarId: number | null;
169
168
  projectAssignmentId: number | null;
170
169
  createdAt: string | null;
170
+ commentCount: number;
171
+ fileCount: number;
171
172
  };
172
173
 
173
174
  type ApiBoardTask = Partial<BoardTask> & {
@@ -240,6 +241,11 @@ function apiTaskToBoardTask(row: ApiBoardTask): BoardTask {
240
241
  assigneePersonAvatarId: row.assigneePersonAvatarId ?? null,
241
242
  projectAssignmentId: row.projectAssignmentId ?? null,
242
243
  createdAt: row.createdAt ?? null,
244
+ commentCount:
245
+ ((row as BoardTask & Record<string, unknown>).commentCount as number) ??
246
+ 0,
247
+ fileCount:
248
+ ((row as BoardTask & Record<string, unknown>).fileCount as number) ?? 0,
243
249
  };
244
250
  }
245
251
 
@@ -470,11 +476,11 @@ function getTaskTags(task: BoardTask) {
470
476
  }
471
477
 
472
478
  function getTaskCommentCount(task: BoardTask) {
473
- return task.description ? 1 : 0;
479
+ return task.commentCount ?? 0;
474
480
  }
475
481
 
476
- function getTaskAttachmentCount() {
477
- return 0;
482
+ function getTaskAttachmentCount(task: BoardTask) {
483
+ return task.fileCount ?? 0;
478
484
  }
479
485
 
480
486
  function getAllocationTone(allocation: number) {
@@ -872,7 +878,7 @@ function ProjectDetailsSkeleton() {
872
878
  <Skeleton className="h-8 w-72" />
873
879
  <Skeleton className="h-4 w-full max-w-md" />
874
880
  </div>
875
- <div className="hidden flex-shrink-0 gap-2 lg:flex">
881
+ <div className="hidden shrink-0 gap-2 lg:flex">
876
882
  <Skeleton className="h-9 w-20 rounded-lg" />
877
883
  <Skeleton className="h-9 w-28 rounded-lg" />
878
884
  <Skeleton className="h-9 w-9 rounded-lg" />
@@ -1092,6 +1098,7 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
1092
1098
  const contractT = useTranslations('operations.ContractFormPage');
1093
1099
  const { request, currentLocaleCode, getSettingValue } = useApp();
1094
1100
  const access = useOperationsAccess();
1101
+ const mentionItems = useMentionItems(request);
1095
1102
  const isLimitedView = !access.isDirector && !access.isSupervisor;
1096
1103
  const router = useRouter();
1097
1104
  const pathname = usePathname();
@@ -1168,6 +1175,14 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
1168
1175
  updateSheetQuery(false);
1169
1176
  };
1170
1177
 
1178
+ const activeTab = searchParams.get('tab') ?? 'tasks';
1179
+
1180
+ const setActiveTab = (tab: string) => {
1181
+ const params = new URLSearchParams(searchParams.toString());
1182
+ params.set('tab', tab);
1183
+ router.replace(`${pathname}?${params.toString()}`, { scroll: false });
1184
+ };
1185
+
1171
1186
  const getDeliveryModelLabel = (value?: string | null) => {
1172
1187
  if (!value) {
1173
1188
  return commonT('labels.notAvailable');
@@ -2011,1522 +2026,1968 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
2011
2026
 
2012
2027
  return (
2013
2028
  <Page>
2014
- <motion.section
2015
- initial={{ opacity: 0, y: 10 }}
2029
+ <motion.div
2030
+ initial={{ opacity: 0, y: 8 }}
2016
2031
  animate={{ opacity: 1, y: 0 }}
2017
- transition={{ duration: 0.25 }}
2018
- className="overflow-hidden rounded-3xl border bg-card shadow-sm"
2032
+ transition={{ duration: 0.2 }}
2033
+ className="flex flex-col gap-0 pt-4"
2019
2034
  >
2020
- <div className="relative overflow-hidden border-b bg-linear-to-br from-muted/70 via-background to-background p-5 sm:p-6">
2021
- <div className="absolute inset-x-0 top-0 h-px bg-linear-to-r from-transparent via-primary/40 to-transparent" />
2022
- <div className="flex flex-col gap-6">
2023
- <div className="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
2024
- <div className="min-w-0 space-y-4">
2025
- <nav
2026
- aria-label="Breadcrumb"
2027
- className="flex min-w-0 flex-wrap items-center gap-1.5 text-xs text-muted-foreground"
2035
+ {/* Row 1: Identity + actions */}
2036
+ <div className="flex flex-wrap items-center gap-3 pb-2">
2037
+ <div className="flex min-w-0 flex-1 items-center gap-3">
2038
+ <div className="flex size-9 shrink-0 items-center justify-center rounded-xl border bg-background shadow-xs">
2039
+ <FolderKanban className="size-5 text-primary" />
2040
+ </div>
2041
+ <div className="min-w-0">
2042
+ <nav
2043
+ aria-label="Breadcrumb"
2044
+ className="mb-0.5 flex items-center gap-1 text-[11px] text-muted-foreground"
2045
+ >
2046
+ <Link
2047
+ href="/operations/projects"
2048
+ className="transition hover:text-foreground"
2028
2049
  >
2029
- <Link
2030
- href="/operations/projects"
2031
- className="transition hover:text-foreground"
2032
- >
2033
- {t('breadcrumbTrail.operations')}
2034
- </Link>
2035
- <ChevronRight className="size-3.5" />
2036
- <Link
2037
- href="/operations/projects"
2038
- className="transition hover:text-foreground"
2039
- >
2040
- {t('breadcrumbTrail.projects')}
2041
- </Link>
2042
- <ChevronRight className="size-3.5" />
2043
- <span className="max-w-[12rem] truncate font-medium text-foreground sm:max-w-md">
2044
- {project.code || project.name}
2045
- </span>
2046
- </nav>
2047
-
2048
- <div className="flex min-w-0 items-start gap-4">
2049
- <div className="flex size-14 shrink-0 items-center justify-center rounded-2xl border bg-background shadow-xs">
2050
- <FolderKanban className="size-7 text-primary" />
2051
- </div>
2052
- <div className="min-w-0 space-y-2">
2053
- <div className="flex flex-wrap items-center gap-2">
2054
- <span className="rounded-full border bg-background px-3 py-1 text-xs font-medium text-muted-foreground">
2055
- {project.code || commonT('labels.notAvailable')}
2056
- </span>
2057
- <StatusBadge
2058
- label={getProjectStatusLabel(project.status)}
2059
- className={getStatusBadgeClass(project.status)}
2060
- />
2061
- </div>
2062
- <div className="space-y-1.5">
2063
- <h1 className="max-w-5xl text-2xl font-semibold tracking-tight text-foreground sm:text-3xl">
2064
- {project.name}
2065
- </h1>
2066
- <p className="max-w-3xl text-sm leading-6 text-muted-foreground">
2067
- {project.summary || t('executive.fallbackDescription')}
2068
- </p>
2069
- </div>
2070
- </div>
2071
- </div>
2072
- </div>
2073
-
2074
- <div className="flex flex-wrap gap-2 lg:justify-end">
2075
- {access.isDirector ? (
2076
- <Button
2077
- size="sm"
2078
- onClick={openEditSheet}
2079
- className="cursor-pointer gap-2"
2080
- >
2081
- <Pencil className="size-4" />
2082
- {commonT('actions.edit')}
2083
- </Button>
2084
- ) : null}
2085
- {!isLimitedView ? (
2086
- <Button
2087
- variant="outline"
2088
- size="sm"
2089
- onClick={() => openCreateTaskForm()}
2090
- className="cursor-pointer gap-2"
2091
- >
2092
- <Plus className="size-4" />
2093
- {t('taskForm.titleNew')}
2094
- </Button>
2095
- ) : null}
2096
- <Button variant="outline" size="sm" asChild>
2097
- <Link href="/operations/timesheets">
2098
- <Timer className="size-4" />
2099
- {t('quickActions.timesheet')}
2100
- </Link>
2101
- </Button>
2102
- <DropdownMenu>
2103
- <DropdownMenuTrigger asChild>
2104
- <Button
2105
- variant="outline"
2106
- size="icon"
2107
- className="size-9 cursor-pointer"
2108
- aria-label={t('quickActions.more')}
2109
- >
2110
- <MoreHorizontal className="size-4" />
2111
- </Button>
2112
- </DropdownMenuTrigger>
2113
- <DropdownMenuContent align="end" className="w-56">
2114
- <DropdownMenuItem asChild>
2115
- <Link href="/operations/reports/projects">
2116
- <BarChart2 className="size-4" />
2117
- {t('quickActions.reports')}
2118
- </Link>
2119
- </DropdownMenuItem>
2120
- {project.contractId ? (
2121
- <DropdownMenuItem asChild>
2122
- <Link
2123
- href={`/operations/contracts?edit=${project.contractId}`}
2124
- >
2125
- <FileText className="size-4" />
2126
- {commonT('actions.openContract')}
2127
- </Link>
2128
- </DropdownMenuItem>
2129
- ) : null}
2130
- <DropdownMenuSeparator />
2131
- <DropdownMenuItem asChild>
2132
- <Link href="/operations/projects">
2133
- <FolderKanban className="size-4" />
2134
- {t('breadcrumbTrail.projects')}
2135
- </Link>
2136
- </DropdownMenuItem>
2137
- </DropdownMenuContent>
2138
- </DropdownMenu>
2050
+ {t('breadcrumbTrail.projects')}
2051
+ </Link>
2052
+ <ChevronRight className="size-3" />
2053
+ <span className="font-medium text-foreground">
2054
+ {project.code || project.name}
2055
+ </span>
2056
+ </nav>
2057
+ <div className="flex flex-wrap items-center gap-2">
2058
+ <h1 className="text-base font-semibold leading-tight">
2059
+ {project.name}
2060
+ </h1>
2061
+ <StatusBadge
2062
+ label={getProjectStatusLabel(project.status)}
2063
+ className={getStatusBadgeClass(project.status)}
2064
+ />
2065
+ <span
2066
+ className={[
2067
+ 'rounded-full px-2 py-0.5 text-xs font-medium',
2068
+ projectHealth.tone === 'good'
2069
+ ? 'bg-emerald-500/15 text-emerald-600 dark:text-emerald-400'
2070
+ : projectHealth.tone === 'warning'
2071
+ ? 'bg-amber-500/15 text-amber-600 dark:text-amber-400'
2072
+ : 'bg-rose-500/15 text-rose-600 dark:text-rose-400',
2073
+ ].join(' ')}
2074
+ >
2075
+ {projectHealthLabel}
2076
+ </span>
2139
2077
  </div>
2140
2078
  </div>
2079
+ </div>
2080
+ <div className="flex shrink-0 flex-wrap gap-1.5">
2081
+ {access.isDirector ? (
2082
+ <Button
2083
+ size="sm"
2084
+ onClick={openEditSheet}
2085
+ className="cursor-pointer gap-1.5"
2086
+ >
2087
+ <Pencil className="size-3.5" />
2088
+ {commonT('actions.edit')}
2089
+ </Button>
2090
+ ) : null}
2091
+ </div>
2092
+ </div>
2141
2093
 
2142
- <div className="grid gap-3 text-sm md:grid-cols-2 xl:grid-cols-7">
2143
- <div className="rounded-xl border bg-background/70 p-3 xl:col-span-2">
2144
- <div className="flex items-center gap-2 text-muted-foreground">
2145
- <Users className="size-4" />
2146
- {commonT('labels.client')}
2147
- </div>
2148
- <div className="mt-2 flex items-center gap-2 font-medium">
2149
- <Avatar className="size-7 border bg-muted">
2150
- <AvatarImage
2151
- src={getPersonAvatarUrl(project.clientAvatarId)}
2152
- alt={project.clientName || commonT('labels.client')}
2153
- />
2154
- <AvatarFallback className="text-[10px]">
2155
- {getInitials(project.clientName)}
2156
- </AvatarFallback>
2157
- </Avatar>
2158
- <span className="truncate">
2159
- {project.clientName || commonT('labels.notAvailable')}
2160
- </span>
2161
- </div>
2094
+ {/* Row 2: Metadata strip */}
2095
+ <div className="flex flex-wrap border-y text-xs">
2096
+ <Tooltip>
2097
+ <TooltipTrigger asChild>
2098
+ <div className="flex cursor-default items-center gap-1.5 border-r px-3 py-2 transition hover:bg-muted/30">
2099
+ <Avatar className="size-5 shrink-0 border bg-muted">
2100
+ <AvatarImage
2101
+ src={getPersonAvatarUrl(project.clientAvatarId)}
2102
+ alt={project.clientName || ''}
2103
+ />
2104
+ <AvatarFallback className="text-[9px]">
2105
+ {getInitials(project.clientName)}
2106
+ </AvatarFallback>
2107
+ </Avatar>
2108
+ <span className="max-w-28 truncate font-medium">
2109
+ {project.clientName || commonT('labels.notAvailable')}
2110
+ </span>
2162
2111
  </div>
2163
- <div className="rounded-xl border bg-background/70 p-3">
2164
- <div className="flex items-center gap-2 text-muted-foreground">
2165
- <Rocket className="size-4" />
2166
- {commonT('labels.deliveryModel')}
2167
- </div>
2168
- <div className="mt-2 truncate font-medium">
2112
+ </TooltipTrigger>
2113
+ <TooltipContent side="bottom">
2114
+ <p className="text-[11px] text-muted-foreground">
2115
+ {commonT('labels.client')}
2116
+ </p>
2117
+ </TooltipContent>
2118
+ </Tooltip>
2119
+
2120
+ <Tooltip>
2121
+ <TooltipTrigger asChild>
2122
+ <div className="flex cursor-default items-center gap-1.5 border-r px-3 py-2 transition hover:bg-muted/30">
2123
+ <Rocket className="size-3.5 shrink-0 text-muted-foreground" />
2124
+ <span className="max-w-32 truncate">
2169
2125
  {project.deliveryModel
2170
2126
  ? getDeliveryModelLabel(project.deliveryModel)
2171
2127
  : commonT('labels.notAvailable')}
2172
- </div>
2128
+ </span>
2173
2129
  </div>
2174
- <div className="rounded-xl border bg-background/70 p-3">
2175
- <div className="flex items-center gap-2 text-muted-foreground">
2176
- <Users className="size-4" />
2177
- {commonT('labels.manager')}
2178
- </div>
2179
- <div className="mt-2 truncate font-medium">
2130
+ </TooltipTrigger>
2131
+ <TooltipContent side="bottom">
2132
+ <p className="text-[11px] text-muted-foreground">
2133
+ {commonT('labels.deliveryModel')}
2134
+ </p>
2135
+ </TooltipContent>
2136
+ </Tooltip>
2137
+
2138
+ <Tooltip>
2139
+ <TooltipTrigger asChild>
2140
+ <div className="flex cursor-default items-center gap-1.5 border-r px-3 py-2 transition hover:bg-muted/30">
2141
+ <Users className="size-3.5 shrink-0 text-muted-foreground" />
2142
+ <span className="max-w-28 truncate">
2180
2143
  {project.managerName || commonT('labels.notAssigned')}
2181
- </div>
2144
+ </span>
2182
2145
  </div>
2183
- <div className="rounded-xl border bg-background/70 p-3">
2184
- <div className="flex items-center gap-2 text-muted-foreground">
2185
- <CalendarDays className="size-4" />
2186
- {commonT('labels.startDate')}
2187
- </div>
2188
- <div className="mt-2 font-medium">
2146
+ </TooltipTrigger>
2147
+ <TooltipContent side="bottom">
2148
+ <p className="text-[11px] text-muted-foreground">
2149
+ {commonT('labels.manager')}
2150
+ </p>
2151
+ </TooltipContent>
2152
+ </Tooltip>
2153
+
2154
+ <Tooltip>
2155
+ <TooltipTrigger asChild>
2156
+ <div className="flex cursor-default items-center gap-1.5 border-r px-3 py-2 transition hover:bg-muted/30">
2157
+ <CalendarDays className="size-3.5 shrink-0 text-muted-foreground" />
2158
+ <span>
2189
2159
  {formatDate(
2190
2160
  project.startDate,
2191
2161
  getSettingValue,
2192
2162
  currentLocaleCode
2193
2163
  )}
2194
- </div>
2164
+ </span>
2195
2165
  </div>
2196
- <div className="rounded-xl border bg-background/70 p-3">
2197
- <div className="flex items-center gap-2 text-muted-foreground">
2198
- <CalendarClock className="size-4" />
2199
- {commonT('labels.endDate')}
2200
- </div>
2201
- <div className="mt-2 font-medium">
2166
+ </TooltipTrigger>
2167
+ <TooltipContent side="bottom">
2168
+ <p className="text-[11px] text-muted-foreground">
2169
+ {commonT('labels.startDate')}
2170
+ </p>
2171
+ </TooltipContent>
2172
+ </Tooltip>
2173
+
2174
+ <Tooltip>
2175
+ <TooltipTrigger asChild>
2176
+ <div
2177
+ className={[
2178
+ 'flex cursor-default items-center gap-1.5 border-r px-3 py-2 transition hover:bg-muted/30',
2179
+ isPastDue(project.endDate) ? 'text-rose-500' : '',
2180
+ ].join(' ')}
2181
+ >
2182
+ <CalendarClock className="size-3.5 shrink-0 text-muted-foreground" />
2183
+ <span>
2202
2184
  {formatDate(
2203
2185
  project.endDate,
2204
2186
  getSettingValue,
2205
2187
  currentLocaleCode
2206
2188
  )}
2207
- </div>
2189
+ </span>
2208
2190
  </div>
2209
- <div className="rounded-xl border bg-background/70 p-3">
2210
- <div className="flex items-center gap-2 text-muted-foreground">
2211
- <Gauge className="size-4" />
2212
- {commonT('labels.progress')}
2213
- </div>
2214
- <div className="mt-2 flex items-center gap-3">
2215
- <Progress value={displayedProgress} className="h-2" />
2216
- <span className="text-sm font-semibold tabular-nums">
2191
+ </TooltipTrigger>
2192
+ <TooltipContent side="bottom">
2193
+ <p className="text-[11px] text-muted-foreground">
2194
+ {commonT('labels.endDate')}
2195
+ </p>
2196
+ </TooltipContent>
2197
+ </Tooltip>
2198
+
2199
+ <Tooltip>
2200
+ <TooltipTrigger asChild>
2201
+ <div className="flex cursor-default items-center gap-2 border-r px-3 py-2 transition hover:bg-muted/30">
2202
+ <Gauge className="size-3.5 shrink-0 text-muted-foreground" />
2203
+ <div className="flex items-center gap-1.5">
2204
+ <div className="h-1.5 w-20 overflow-hidden rounded-full bg-muted">
2205
+ <motion.div
2206
+ initial={{ width: 0 }}
2207
+ animate={{ width: `${displayedProgress}%` }}
2208
+ transition={{ duration: 0.7, ease: 'easeOut' }}
2209
+ className="h-full rounded-full bg-primary"
2210
+ />
2211
+ </div>
2212
+ <span className="w-8 text-right font-semibold tabular-nums">
2217
2213
  {displayedProgress}%
2218
2214
  </span>
2219
2215
  </div>
2220
2216
  </div>
2221
- </div>
2217
+ </TooltipTrigger>
2218
+ <TooltipContent side="bottom">
2219
+ <p className="text-[11px] text-muted-foreground">
2220
+ {commonT('labels.progress')}
2221
+ </p>
2222
+ <p className="font-medium">
2223
+ {completedTasks}/{totalTasks} {t('executive.completedTasks')}
2224
+ </p>
2225
+ </TooltipContent>
2226
+ </Tooltip>
2222
2227
 
2223
- <div className="flex flex-col gap-4 rounded-2xl border bg-background/75 p-4 sm:flex-row sm:items-center sm:justify-between">
2224
- <div className="flex items-center gap-3">
2225
- <div>
2226
- <div className="flex items-center gap-2 text-muted-foreground">
2227
- <Users className="size-4" />
2228
- <span className="text-xs font-medium uppercase tracking-[0.18em]">
2229
- {t('executive.team')}
2230
- </span>
2231
- </div>
2232
- <div className="mt-1 text-sm font-semibold">
2233
- {t('executive.membersCount', {
2234
- count: project.assignments.length,
2235
- })}
2236
- </div>
2237
- </div>
2238
- <div className="flex -space-x-2">
2228
+ <Tooltip>
2229
+ <TooltipTrigger asChild>
2230
+ <div className="flex cursor-default items-center gap-2 px-3 py-2 transition hover:bg-muted/30">
2231
+ <div className="flex -space-x-1.5">
2239
2232
  {teamPreview.map((assignment) => (
2240
- <Tooltip key={assignment.id}>
2241
- <TooltipTrigger asChild>
2242
- <Avatar className="size-10 cursor-default border-2 border-background bg-muted transition-transform hover:z-10 hover:scale-110">
2243
- <AvatarImage
2244
- src={
2245
- getUserPhotoUrl(assignment.userPhotoId) ||
2246
- getPersonAvatarUrl(assignment.personAvatarId)
2247
- }
2248
- alt={assignment.collaboratorName}
2249
- />
2250
- <AvatarFallback className="text-xs">
2251
- {getInitials(assignment.collaboratorName)}
2252
- </AvatarFallback>
2253
- </Avatar>
2254
- </TooltipTrigger>
2255
- <TooltipContent side="bottom" className="text-xs">
2256
- <p className="font-medium">
2257
- {assignment.collaboratorName}
2258
- </p>
2259
- {assignment.roleLabel ? (
2260
- <p className="text-muted-foreground">
2261
- {assignment.roleLabel}
2262
- </p>
2263
- ) : null}
2264
- </TooltipContent>
2265
- </Tooltip>
2233
+ <Avatar
2234
+ key={assignment.id}
2235
+ className="size-6 border-2 border-card bg-muted"
2236
+ >
2237
+ <AvatarImage
2238
+ src={
2239
+ getUserPhotoUrl(assignment.userPhotoId) ||
2240
+ getPersonAvatarUrl(assignment.personAvatarId)
2241
+ }
2242
+ alt={assignment.collaboratorName}
2243
+ />
2244
+ <AvatarFallback className="text-[9px]">
2245
+ {getInitials(assignment.collaboratorName)}
2246
+ </AvatarFallback>
2247
+ </Avatar>
2266
2248
  ))}
2267
2249
  {hiddenTeamCount > 0 ? (
2268
- <div className="flex size-10 items-center justify-center rounded-full border-2 border-background bg-muted text-xs font-semibold text-muted-foreground">
2250
+ <div className="flex size-6 items-center justify-center rounded-full border-2 border-card bg-muted text-[10px] font-semibold text-muted-foreground">
2269
2251
  +{hiddenTeamCount}
2270
2252
  </div>
2271
2253
  ) : null}
2272
2254
  </div>
2255
+ <span className="text-xs text-muted-foreground">
2256
+ {project.assignments.length}
2257
+ </span>
2273
2258
  </div>
2259
+ </TooltipTrigger>
2260
+ <TooltipContent side="bottom" className="max-w-44">
2261
+ <p className="mb-1 font-medium">{t('executive.team')}</p>
2262
+ {teamPreview.map((a) => (
2263
+ <p key={a.id} className="text-xs text-muted-foreground">
2264
+ {a.collaboratorName}
2265
+ {a.roleLabel ? ` · ${a.roleLabel}` : ''}
2266
+ </p>
2267
+ ))}
2268
+ {hiddenTeamCount > 0 ? (
2269
+ <p className="text-xs text-muted-foreground">
2270
+ +{hiddenTeamCount}
2271
+ </p>
2272
+ ) : null}
2273
+ </TooltipContent>
2274
+ </Tooltip>
2275
+ </div>
2274
2276
 
2275
- <div className="grid grid-cols-3 gap-3 text-sm sm:min-w-80">
2276
- <div className="rounded-xl border bg-muted/20 p-3">
2277
- <div className="text-muted-foreground">
2278
- {commonT('labels.status')}
2279
- </div>
2280
- <div className="mt-1">
2281
- <StatusBadge
2282
- label={getProjectStatusLabel(project.status)}
2283
- className={getStatusBadgeClass(project.status)}
2284
- />
2277
+ {/* Row 3: KPI strip */}
2278
+ {!isLimitedView ? (
2279
+ <div className="flex divide-x bg-muted/10">
2280
+ {kpiWidgets.map((kpi, index) => {
2281
+ const kpiToneClass =
2282
+ kpi.tone === 'critical'
2283
+ ? 'text-rose-500'
2284
+ : kpi.tone === 'warning'
2285
+ ? 'text-amber-500'
2286
+ : kpi.tone === 'positive'
2287
+ ? 'text-emerald-500'
2288
+ : 'text-sky-500';
2289
+ const kpiBarClass =
2290
+ kpi.tone === 'critical'
2291
+ ? 'bg-rose-500'
2292
+ : kpi.tone === 'warning'
2293
+ ? 'bg-amber-500'
2294
+ : kpi.tone === 'positive'
2295
+ ? 'bg-emerald-500'
2296
+ : 'bg-sky-500';
2297
+ const isVelocity = kpi.key === 'velocity';
2298
+ const velocityBars = velocityChartData.slice(-5);
2299
+ const velocityMax = Math.max(
2300
+ ...velocityBars.map((d) => d.loggedHours),
2301
+ 1
2302
+ );
2303
+ return (
2304
+ <Tooltip key={kpi.key}>
2305
+ <TooltipTrigger asChild>
2306
+ <motion.div
2307
+ initial={{ opacity: 0 }}
2308
+ animate={{ opacity: 1 }}
2309
+ transition={{ delay: index * 0.05 }}
2310
+ className="flex flex-1 cursor-default select-none items-center gap-2 px-3 py-2 transition hover:bg-muted/20"
2311
+ >
2312
+ <kpi.icon
2313
+ className={['size-4 shrink-0', kpiToneClass].join(' ')}
2314
+ />
2315
+ <div className="min-w-0 flex-1">
2316
+ <div className="text-sm font-semibold leading-none">
2317
+ {kpi.value}
2318
+ </div>
2319
+ {isVelocity && velocityBars.length > 1 ? (
2320
+ <div className="mt-1.5 flex items-end gap-px">
2321
+ {velocityBars.map((d, i) => (
2322
+ <motion.div
2323
+ key={i}
2324
+ className={[
2325
+ 'w-1.5 rounded-sm opacity-70',
2326
+ kpiBarClass,
2327
+ ].join(' ')}
2328
+ initial={{ height: 0 }}
2329
+ animate={{
2330
+ height: `${Math.max(3, Math.round((d.loggedHours / velocityMax) * 16))}px`,
2331
+ }}
2332
+ transition={{
2333
+ delay: index * 0.05 + i * 0.06,
2334
+ type: 'spring',
2335
+ stiffness: 180,
2336
+ }}
2337
+ />
2338
+ ))}
2339
+ </div>
2340
+ ) : (
2341
+ <div className="mt-1.5 h-1 overflow-hidden rounded-full bg-muted">
2342
+ <motion.div
2343
+ initial={{ width: 0 }}
2344
+ animate={{
2345
+ width: `${clampPercent(kpi.indicator)}%`,
2346
+ }}
2347
+ transition={{
2348
+ duration: 0.55,
2349
+ delay: index * 0.07,
2350
+ ease: 'easeOut',
2351
+ }}
2352
+ className={[
2353
+ 'h-full rounded-full',
2354
+ kpiBarClass,
2355
+ ].join(' ')}
2356
+ />
2357
+ </div>
2358
+ )}
2359
+ </div>
2360
+ </motion.div>
2361
+ </TooltipTrigger>
2362
+ <TooltipContent side="bottom" className="max-w-52">
2363
+ <p className="font-medium">{kpi.title}</p>
2364
+ <p className="text-xs text-muted-foreground">
2365
+ {kpi.subtitle}
2366
+ </p>
2367
+ <p className="mt-1 text-xs font-medium">{kpi.trend}</p>
2368
+ </TooltipContent>
2369
+ </Tooltip>
2370
+ );
2371
+ })}
2372
+ </div>
2373
+ ) : null}
2374
+ </motion.div>
2375
+
2376
+ <Tabs value={activeTab} onValueChange={setActiveTab}>
2377
+ <TabsList className="flex-wrap">
2378
+ <TabsTrigger value="tasks">{t('tabs.tasks')}</TabsTrigger>
2379
+ {!isLimitedView ? (
2380
+ <TabsTrigger value="overview">{t('tabs.overview')}</TabsTrigger>
2381
+ ) : null}
2382
+ {!isLimitedView ? (
2383
+ <TabsTrigger value="analysis">{t('tabs.analysis')}</TabsTrigger>
2384
+ ) : null}
2385
+ <TabsTrigger value="team">{t('tabs.team')}</TabsTrigger>
2386
+ <TabsTrigger value="timeline">{t('tabs.timeline')}</TabsTrigger>
2387
+ <TabsTrigger value="archive">{t('tabs.archive')}</TabsTrigger>
2388
+ <TabsTrigger value="costs">{t('tabs.costs')}</TabsTrigger>
2389
+ </TabsList>
2390
+
2391
+ {!isLimitedView ? (
2392
+ <TabsContent value="overview">
2393
+ <div className="grid gap-4 xl:grid-cols-12">
2394
+ <SectionCard
2395
+ title={t('sections.overview')}
2396
+ className="rounded-xl border bg-card p-4 shadow-sm xl:col-span-12"
2397
+ >
2398
+ <dl className="grid gap-3 text-sm sm:grid-cols-2 xl:grid-cols-3">
2399
+ <div>
2400
+ <dt className="text-muted-foreground">
2401
+ {commonT('labels.project')}
2402
+ </dt>
2403
+ <dd className="font-medium">{project.name}</dd>
2285
2404
  </div>
2286
- </div>
2287
- <div className="rounded-xl border bg-muted/20 p-3">
2288
- <div className="text-muted-foreground">
2289
- {t('cards.projectHealth')}
2405
+ <div>
2406
+ <dt className="text-muted-foreground">
2407
+ {commonT('labels.code')}
2408
+ </dt>
2409
+ <dd className="font-medium">
2410
+ {project.code || commonT('labels.notAvailable')}
2411
+ </dd>
2290
2412
  </div>
2291
- <div className="mt-1 font-semibold">{projectHealthLabel}</div>
2292
- </div>
2293
- <div className="rounded-xl border bg-muted/20 p-3">
2294
- <div className="text-muted-foreground">
2295
- {t('executive.completedTasks')}
2413
+ <div>
2414
+ <dt className="text-muted-foreground">
2415
+ {commonT('labels.client')}
2416
+ </dt>
2417
+ <dd className="font-medium">
2418
+ <div className="flex items-center gap-2">
2419
+ <Avatar className="h-8 w-8 border border-border/60 bg-muted">
2420
+ <AvatarImage
2421
+ src={getPersonAvatarUrl(project.clientAvatarId)}
2422
+ alt={project.clientName || commonT('labels.client')}
2423
+ />
2424
+ <AvatarFallback className="bg-muted text-xs font-semibold text-foreground">
2425
+ {getInitials(
2426
+ project.clientName || commonT('labels.client')
2427
+ )}
2428
+ </AvatarFallback>
2429
+ </Avatar>
2430
+ <span>
2431
+ {project.clientName || commonT('labels.notAvailable')}
2432
+ </span>
2433
+ </div>
2434
+ </dd>
2296
2435
  </div>
2297
- <div className="mt-1 font-semibold">
2298
- {completedTasks}/{totalTasks}
2436
+ <div>
2437
+ <dt className="text-muted-foreground">
2438
+ {commonT('labels.manager')}
2439
+ </dt>
2440
+ <dd className="font-medium">
2441
+ {project.managerName || commonT('labels.notAssigned')}
2442
+ </dd>
2299
2443
  </div>
2300
- </div>
2301
- </div>
2302
- </div>
2303
- </div>
2304
- </div>
2305
- </motion.section>
2306
-
2307
- {!isLimitedView ? (
2308
- <>
2309
- <div className="rounded-3xl border bg-linear-to-b from-muted/50 to-background p-3 shadow-sm sm:p-4">
2310
- <div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-6">
2311
- {kpiWidgets.map((item, index) => (
2312
- <ProjectKpiWidget
2313
- key={item.key}
2314
- item={item}
2315
- index={index}
2316
- indicatorLabel={t('kpi.indicator')}
2317
- />
2318
- ))}
2319
- </div>
2320
- </div>
2321
-
2322
- <div className="grid gap-4 xl:grid-cols-12">
2323
- <SectionCard
2324
- title={t('sections.overview')}
2325
- className="rounded-xl border bg-card p-4 shadow-sm xl:col-span-12"
2326
- >
2327
- <dl className="grid gap-3 text-sm sm:grid-cols-2 xl:grid-cols-3">
2328
- <div>
2329
- <dt className="text-muted-foreground">
2330
- {commonT('labels.project')}
2331
- </dt>
2332
- <dd className="font-medium">{project.name}</dd>
2333
- </div>
2334
- <div>
2335
- <dt className="text-muted-foreground">
2336
- {commonT('labels.code')}
2337
- </dt>
2338
- <dd className="font-medium">
2339
- {project.code || commonT('labels.notAvailable')}
2340
- </dd>
2341
- </div>
2342
- <div>
2343
- <dt className="text-muted-foreground">
2344
- {commonT('labels.client')}
2345
- </dt>
2346
- <dd className="font-medium">
2347
- <div className="flex items-center gap-2">
2348
- <Avatar className="h-8 w-8 border border-border/60 bg-muted">
2349
- <AvatarImage
2350
- src={getPersonAvatarUrl(project.clientAvatarId)}
2351
- alt={project.clientName || commonT('labels.client')}
2352
- />
2353
- <AvatarFallback className="bg-muted text-xs font-semibold text-foreground">
2354
- {getInitials(
2355
- project.clientName || commonT('labels.client')
2356
- )}
2357
- </AvatarFallback>
2358
- </Avatar>
2359
- <span>
2360
- {project.clientName || commonT('labels.notAvailable')}
2361
- </span>
2362
- </div>
2363
- </dd>
2364
- </div>
2365
- <div>
2366
- <dt className="text-muted-foreground">
2367
- {commonT('labels.manager')}
2368
- </dt>
2369
- <dd className="font-medium">
2370
- {project.managerName || commonT('labels.notAssigned')}
2371
- </dd>
2372
- </div>
2373
- <div>
2374
- <dt className="text-muted-foreground">
2375
- {commonT('labels.status')}
2376
- </dt>
2377
- <dd className="font-medium">
2378
- <StatusBadge
2379
- label={getProjectStatusLabel(project.status)}
2380
- className={getStatusBadgeClass(project.status)}
2381
- />
2382
- </dd>
2383
- </div>
2384
- <div>
2385
- <dt className="text-muted-foreground">
2386
- {commonT('labels.deliveryModel')}
2387
- </dt>
2388
- <dd className="font-medium">
2389
- {project.deliveryModel
2390
- ? getDeliveryModelLabel(project.deliveryModel)
2391
- : commonT('labels.notAvailable')}
2392
- </dd>
2393
- </div>
2394
- <div>
2395
- <dt className="text-muted-foreground">
2396
- {commonT('labels.startDate')}
2397
- </dt>
2398
- <dd className="font-medium">
2399
- {formatDate(
2400
- project.startDate,
2401
- getSettingValue,
2402
- currentLocaleCode
2403
- )}
2404
- </dd>
2405
- </div>
2406
- <div>
2407
- <dt className="text-muted-foreground">
2408
- {commonT('labels.endDate')}
2409
- </dt>
2410
- <dd className="font-medium">
2411
- {formatDate(
2412
- project.endDate,
2413
- getSettingValue,
2414
- currentLocaleCode
2415
- )}
2416
- </dd>
2417
- </div>
2418
- <div>
2419
- <dt className="text-muted-foreground">
2420
- {commonT('labels.budget')}
2421
- </dt>
2422
- <dd className="font-medium">
2423
- {project.budgetAmount
2424
- ? formatCurrency(
2425
- project.budgetAmount,
2426
- getSettingValue,
2427
- currentLocaleCode
2428
- )
2429
- : commonT('labels.notAvailable')}
2430
- </dd>
2431
- </div>
2432
- <div>
2433
- <dt className="text-muted-foreground">
2434
- {commonT('labels.progress')}
2435
- </dt>
2436
- <dd className="font-medium">
2437
- {formatPercent(project.progressPercent)}
2438
- </dd>
2439
- </div>
2440
- <div>
2441
- <dt className="text-muted-foreground">
2442
- {commonT('labels.timeline')}
2443
- </dt>
2444
- <dd className="font-medium">
2445
- {formatDateRange(
2446
- project.startDate,
2447
- project.endDate,
2448
- getSettingValue,
2449
- currentLocaleCode
2450
- )}
2451
- </dd>
2452
- </div>
2453
- <div>
2454
- <dt className="text-muted-foreground">
2455
- {commonT('labels.contractStatus')}
2456
- </dt>
2457
- <dd className="font-medium">
2458
- {project.contractStatus ? (
2444
+ <div>
2445
+ <dt className="text-muted-foreground">
2446
+ {commonT('labels.status')}
2447
+ </dt>
2448
+ <dd className="font-medium">
2459
2449
  <StatusBadge
2460
- label={getContractStatusLabel(project.contractStatus)}
2461
- className={getStatusBadgeClass(project.contractStatus)}
2450
+ label={getProjectStatusLabel(project.status)}
2451
+ className={getStatusBadgeClass(project.status)}
2462
2452
  />
2463
- ) : (
2464
- commonT('labels.notAssigned')
2465
- )}
2466
- </dd>
2467
- </div>
2468
- </dl>
2469
- {project.summary ? (
2470
- <div className="mt-4 rounded-lg border border-border/70 bg-muted/30 p-3 text-sm text-muted-foreground">
2471
- {project.summary}
2472
- </div>
2473
- ) : null}
2474
-
2475
- {/* Contrato vinculado */}
2476
- <div className="mt-6 border-t pt-6">
2477
- <div className="mb-4 flex items-center gap-2 text-sm font-semibold">
2478
- <FileText className="size-4 text-muted-foreground" />
2479
- {t('sections.contract')}
2480
- </div>
2481
- {project.relatedContract ? (
2482
- <div className="space-y-4">
2483
- <div className="flex flex-col gap-3 rounded-xl border bg-muted/20 px-4 py-3 sm:flex-row sm:items-center sm:justify-between">
2484
- <div>
2485
- <div className="font-medium">
2486
- {project.relatedContract.name}
2487
- </div>
2488
- <div className="text-sm text-muted-foreground">
2489
- {[
2490
- project.relatedContract.code,
2491
- project.relatedContract.clientName,
2492
- ]
2493
- .filter(Boolean)
2494
- .join(' • ') || commonT('labels.notAvailable')}
2495
- </div>
2496
- </div>
2497
- <div className="flex items-center gap-3">
2453
+ </dd>
2454
+ </div>
2455
+ <div>
2456
+ <dt className="text-muted-foreground">
2457
+ {commonT('labels.deliveryModel')}
2458
+ </dt>
2459
+ <dd className="font-medium">
2460
+ {project.deliveryModel
2461
+ ? getDeliveryModelLabel(project.deliveryModel)
2462
+ : commonT('labels.notAvailable')}
2463
+ </dd>
2464
+ </div>
2465
+ <div>
2466
+ <dt className="text-muted-foreground">
2467
+ {commonT('labels.startDate')}
2468
+ </dt>
2469
+ <dd className="font-medium">
2470
+ {formatDate(
2471
+ project.startDate,
2472
+ getSettingValue,
2473
+ currentLocaleCode
2474
+ )}
2475
+ </dd>
2476
+ </div>
2477
+ <div>
2478
+ <dt className="text-muted-foreground">
2479
+ {commonT('labels.endDate')}
2480
+ </dt>
2481
+ <dd className="font-medium">
2482
+ {formatDate(
2483
+ project.endDate,
2484
+ getSettingValue,
2485
+ currentLocaleCode
2486
+ )}
2487
+ </dd>
2488
+ </div>
2489
+ <div>
2490
+ <dt className="text-muted-foreground">
2491
+ {commonT('labels.budget')}
2492
+ </dt>
2493
+ <dd className="font-medium">
2494
+ {project.budgetAmount
2495
+ ? formatCurrency(
2496
+ project.budgetAmount,
2497
+ getSettingValue,
2498
+ currentLocaleCode
2499
+ )
2500
+ : commonT('labels.notAvailable')}
2501
+ </dd>
2502
+ </div>
2503
+ <div>
2504
+ <dt className="text-muted-foreground">
2505
+ {commonT('labels.progress')}
2506
+ </dt>
2507
+ <dd className="font-medium">
2508
+ {formatPercent(project.progressPercent)}
2509
+ </dd>
2510
+ </div>
2511
+ <div>
2512
+ <dt className="text-muted-foreground">
2513
+ {commonT('labels.timeline')}
2514
+ </dt>
2515
+ <dd className="font-medium">
2516
+ {formatDateRange(
2517
+ project.startDate,
2518
+ project.endDate,
2519
+ getSettingValue,
2520
+ currentLocaleCode
2521
+ )}
2522
+ </dd>
2523
+ </div>
2524
+ <div>
2525
+ <dt className="text-muted-foreground">
2526
+ {commonT('labels.contractStatus')}
2527
+ </dt>
2528
+ <dd className="font-medium">
2529
+ {project.contractStatus ? (
2498
2530
  <StatusBadge
2499
- label={getContractStatusLabel(
2500
- project.relatedContract.status
2501
- )}
2531
+ label={getContractStatusLabel(project.contractStatus)}
2502
2532
  className={getStatusBadgeClass(
2503
- project.relatedContract.status
2533
+ project.contractStatus
2504
2534
  )}
2505
2535
  />
2506
- <Button
2507
- variant="outline"
2508
- size="sm"
2509
- asChild
2510
- className="shrink-0"
2511
- >
2512
- <Link
2513
- href={`/operations/contracts?edit=${project.relatedContract.id}`}
2536
+ ) : (
2537
+ commonT('labels.notAssigned')
2538
+ )}
2539
+ </dd>
2540
+ </div>
2541
+ </dl>
2542
+ {project.summary ? (
2543
+ <div className="mt-4 rounded-lg border border-border/70 bg-muted/30 p-3 text-sm text-muted-foreground">
2544
+ {project.summary}
2545
+ </div>
2546
+ ) : null}
2547
+
2548
+ {/* Contrato vinculado */}
2549
+ <div className="mt-6 border-t pt-6">
2550
+ <div className="mb-4 flex items-center gap-2 text-sm font-semibold">
2551
+ <FileText className="size-4 text-muted-foreground" />
2552
+ {t('sections.contract')}
2553
+ </div>
2554
+ {project.relatedContract ? (
2555
+ <div className="space-y-4">
2556
+ <div className="flex flex-col gap-3 rounded-xl border bg-muted/20 px-4 py-3 sm:flex-row sm:items-center sm:justify-between">
2557
+ <div>
2558
+ <div className="font-medium">
2559
+ {project.relatedContract.name}
2560
+ </div>
2561
+ <div className="text-sm text-muted-foreground">
2562
+ {[
2563
+ project.relatedContract.code,
2564
+ project.relatedContract.clientName,
2565
+ ]
2566
+ .filter(Boolean)
2567
+ .join(' • ') || commonT('labels.notAvailable')}
2568
+ </div>
2569
+ </div>
2570
+ <div className="flex items-center gap-3">
2571
+ <StatusBadge
2572
+ label={getContractStatusLabel(
2573
+ project.relatedContract.status
2574
+ )}
2575
+ className={getStatusBadgeClass(
2576
+ project.relatedContract.status
2577
+ )}
2578
+ />
2579
+ <Button
2580
+ variant="outline"
2581
+ size="sm"
2582
+ asChild
2583
+ className="shrink-0"
2514
2584
  >
2515
- <FileText className="size-4" />
2516
- {commonT('actions.openContract')}
2517
- </Link>
2518
- </Button>
2585
+ <Link
2586
+ href={`/operations/contracts?edit=${project.relatedContract.id}`}
2587
+ >
2588
+ <FileText className="size-4" />
2589
+ {commonT('actions.openContract')}
2590
+ </Link>
2591
+ </Button>
2592
+ </div>
2519
2593
  </div>
2594
+ <dl className="grid gap-3 text-sm sm:grid-cols-2 xl:grid-cols-3">
2595
+ <div>
2596
+ <dt className="text-muted-foreground">
2597
+ {commonT('labels.contractCategory')}
2598
+ </dt>
2599
+ <dd className="font-medium">
2600
+ {project.relatedContract.contractCategory
2601
+ ? getContractCategoryLabel(
2602
+ project.relatedContract.contractCategory
2603
+ )
2604
+ : commonT('labels.notAvailable')}
2605
+ </dd>
2606
+ </div>
2607
+ <div>
2608
+ <dt className="text-muted-foreground">
2609
+ {commonT('labels.contractType')}
2610
+ </dt>
2611
+ <dd className="font-medium">
2612
+ {project.relatedContract.contractType
2613
+ ? getContractTypeLabel(
2614
+ project.relatedContract.contractType
2615
+ )
2616
+ : commonT('labels.notAvailable')}
2617
+ </dd>
2618
+ </div>
2619
+ <div>
2620
+ <dt className="text-muted-foreground">
2621
+ {commonT('labels.billingModel')}
2622
+ </dt>
2623
+ <dd className="font-medium">
2624
+ {getBillingModelLabel(
2625
+ project.relatedContract.billingModel
2626
+ )}
2627
+ </dd>
2628
+ </div>
2629
+ <div>
2630
+ <dt className="text-muted-foreground">
2631
+ {commonT('labels.timeline')}
2632
+ </dt>
2633
+ <dd className="font-medium">
2634
+ {formatDateRange(
2635
+ project.relatedContract.startDate,
2636
+ project.relatedContract.endDate,
2637
+ getSettingValue,
2638
+ currentLocaleCode
2639
+ )}
2640
+ </dd>
2641
+ </div>
2642
+ <div>
2643
+ <dt className="text-muted-foreground">
2644
+ {commonT('labels.signatureStatus')}
2645
+ </dt>
2646
+ <dd className="font-medium">
2647
+ {project.relatedContract.signatureStatus
2648
+ ? getSignatureStatusLabel(
2649
+ project.relatedContract.signatureStatus
2650
+ )
2651
+ : commonT('labels.notAvailable')}
2652
+ </dd>
2653
+ </div>
2654
+ <div>
2655
+ <dt className="text-muted-foreground">
2656
+ {commonT('labels.budget')}
2657
+ </dt>
2658
+ <dd className="font-medium">
2659
+ {project.relatedContract.budgetAmount
2660
+ ? formatCurrency(
2661
+ project.relatedContract.budgetAmount,
2662
+ getSettingValue,
2663
+ currentLocaleCode
2664
+ )
2665
+ : commonT('labels.notAvailable')}
2666
+ </dd>
2667
+ </div>
2668
+ </dl>
2520
2669
  </div>
2521
- <dl className="grid gap-3 text-sm sm:grid-cols-2 xl:grid-cols-3">
2522
- <div>
2523
- <dt className="text-muted-foreground">
2524
- {commonT('labels.contractCategory')}
2525
- </dt>
2526
- <dd className="font-medium">
2527
- {project.relatedContract.contractCategory
2528
- ? getContractCategoryLabel(
2529
- project.relatedContract.contractCategory
2530
- )
2531
- : commonT('labels.notAvailable')}
2532
- </dd>
2533
- </div>
2534
- <div>
2535
- <dt className="text-muted-foreground">
2536
- {commonT('labels.contractType')}
2537
- </dt>
2538
- <dd className="font-medium">
2539
- {project.relatedContract.contractType
2540
- ? getContractTypeLabel(
2541
- project.relatedContract.contractType
2542
- )
2543
- : commonT('labels.notAvailable')}
2544
- </dd>
2545
- </div>
2546
- <div>
2547
- <dt className="text-muted-foreground">
2548
- {commonT('labels.billingModel')}
2549
- </dt>
2550
- <dd className="font-medium">
2551
- {getBillingModelLabel(
2552
- project.relatedContract.billingModel
2553
- )}
2554
- </dd>
2555
- </div>
2556
- <div>
2557
- <dt className="text-muted-foreground">
2558
- {commonT('labels.timeline')}
2559
- </dt>
2560
- <dd className="font-medium">
2561
- {formatDateRange(
2562
- project.relatedContract.startDate,
2563
- project.relatedContract.endDate,
2564
- getSettingValue,
2565
- currentLocaleCode
2566
- )}
2567
- </dd>
2568
- </div>
2569
- <div>
2570
- <dt className="text-muted-foreground">
2571
- {commonT('labels.signatureStatus')}
2572
- </dt>
2573
- <dd className="font-medium">
2574
- {project.relatedContract.signatureStatus
2575
- ? getSignatureStatusLabel(
2576
- project.relatedContract.signatureStatus
2577
- )
2578
- : commonT('labels.notAvailable')}
2579
- </dd>
2580
- </div>
2581
- <div>
2582
- <dt className="text-muted-foreground">
2583
- {commonT('labels.budget')}
2584
- </dt>
2585
- <dd className="font-medium">
2586
- {project.relatedContract.budgetAmount
2587
- ? formatCurrency(
2588
- project.relatedContract.budgetAmount,
2589
- getSettingValue,
2590
- currentLocaleCode
2591
- )
2592
- : commonT('labels.notAvailable')}
2593
- </dd>
2594
- </div>
2595
- </dl>
2596
- </div>
2597
- ) : (
2598
- <p className="text-sm text-muted-foreground">
2599
- {t('noContract')}
2600
- </p>
2601
- )}
2602
- </div>
2603
- </SectionCard>
2604
- </div>
2670
+ ) : (
2671
+ <p className="text-sm text-muted-foreground">
2672
+ {t('noContract')}
2673
+ </p>
2674
+ )}
2675
+ </div>
2676
+ </SectionCard>
2677
+ </div>
2678
+ </TabsContent>
2679
+ ) : null}
2605
2680
 
2606
- <SectionCard
2607
- title={t('sections.deliveryHealth')}
2608
- description={t('sections.deliveryHealthDescription')}
2609
- className="rounded-3xl border bg-card p-4 shadow-sm"
2610
- >
2611
- <div className="grid gap-4 xl:grid-cols-12">
2612
- <ProjectChartCard
2613
- title={t('charts.burnup')}
2614
- description={t('charts.burnupDescription')}
2615
- icon={LineChartIcon}
2616
- metric={formatPercent(displayedProgress)}
2617
- className="xl:col-span-8"
2618
- isLoading={chartDashboardLoading}
2619
- >
2620
- {burnupChartData.length > 1 ? (
2621
- <ChartContainer
2622
- className="h-80 w-full"
2623
- config={boardChartConfig}
2624
- >
2625
- <AreaChart data={burnupChartData}>
2626
- <defs>
2627
- <linearGradient
2628
- id="burnupLogged"
2629
- x1="0"
2630
- y1="0"
2631
- x2="0"
2632
- y2="1"
2633
- >
2634
- <stop
2635
- offset="5%"
2636
- stopColor="var(--color-loggedHours)"
2637
- stopOpacity={0.26}
2638
- />
2639
- <stop
2640
- offset="95%"
2641
- stopColor="var(--color-loggedHours)"
2642
- stopOpacity={0.02}
2643
- />
2644
- </linearGradient>
2645
- </defs>
2646
- <CartesianGrid vertical={false} strokeDasharray="3 3" />
2647
- <XAxis dataKey="week" tickLine={false} axisLine={false} />
2648
- <YAxis tickLine={false} axisLine={false} width={36} />
2649
- <ChartTooltip content={<ChartTooltipContent />} />
2650
- <Area
2651
- type="monotone"
2652
- dataKey="planned"
2653
- stroke="var(--color-planned)"
2654
- strokeDasharray="4 4"
2655
- strokeWidth={2}
2656
- fill="transparent"
2657
- />
2658
- <Area
2659
- type="monotone"
2660
- dataKey="loggedHours"
2661
- stroke="var(--color-loggedHours)"
2662
- strokeWidth={2.5}
2663
- fill="url(#burnupLogged)"
2664
- />
2665
- </AreaChart>
2666
- </ChartContainer>
2667
- ) : (
2668
- <ChartEmptyState
2669
- icon={LineChartIcon}
2670
- title={t('charts.emptyTitle')}
2671
- description={t('charts.emptyBurnup')}
2672
- />
2673
- )}
2674
- </ProjectChartCard>
2675
-
2676
- <ProjectChartCard
2677
- title={t('charts.weeklyVelocity')}
2678
- description={t('charts.weeklyVelocityDescription')}
2679
- icon={Rocket}
2680
- metric={formatHours(weeklyVelocity)}
2681
- className="xl:col-span-4"
2682
- isLoading={isProjectStatsLoading}
2683
- >
2684
- {velocityChartData.length > 0 ? (
2685
- <ChartContainer
2686
- className="h-80 w-full"
2687
- config={boardChartConfig}
2688
- >
2689
- <LineChart data={velocityChartData}>
2690
- <CartesianGrid vertical={false} strokeDasharray="3 3" />
2691
- <XAxis dataKey="week" tickLine={false} axisLine={false} />
2692
- <YAxis tickLine={false} axisLine={false} width={30} />
2693
- <ChartTooltip content={<ChartTooltipContent />} />
2694
- <Line
2695
- type="monotone"
2696
- dataKey="loggedHours"
2697
- stroke="var(--color-loggedHours)"
2698
- strokeWidth={2.5}
2699
- dot={{ r: 3 }}
2700
- activeDot={{ r: 5 }}
2701
- />
2702
- </LineChart>
2703
- </ChartContainer>
2704
- ) : (
2705
- <ChartEmptyState
2706
- icon={Rocket}
2707
- title={t('charts.emptyTitle')}
2708
- description={t('charts.emptyVelocity')}
2709
- />
2710
- )}
2711
- </ProjectChartCard>
2712
-
2713
- <ProjectChartCard
2714
- title={t('charts.allocationByCollaborator')}
2715
- description={t('charts.allocationDescription')}
2716
- icon={BarChart3}
2717
- metric={formatPercent(
2718
- project.operationalIndicators.averageAllocation
2719
- )}
2720
- className="xl:col-span-5"
2721
- isLoading={chartDashboardLoading}
2722
- >
2723
- {allocationChartData.length > 0 ? (
2724
- <ChartContainer
2725
- className="h-72 w-full"
2726
- config={boardChartConfig}
2727
- >
2728
- <BarChart data={allocationChartData}>
2729
- <CartesianGrid vertical={false} strokeDasharray="3 3" />
2730
- <XAxis dataKey="name" tickLine={false} axisLine={false} />
2731
- <YAxis tickLine={false} axisLine={false} width={32} />
2732
- <ChartTooltip
2733
- content={<ChartTooltipContent hideLabel />}
2734
- />
2735
- <Bar
2736
- dataKey="allocation"
2737
- radius={[8, 8, 3, 3]}
2738
- fill="var(--color-allocation)"
2739
- />
2740
- </BarChart>
2741
- </ChartContainer>
2742
- ) : (
2743
- <ChartEmptyState
2744
- icon={BarChart3}
2745
- title={t('charts.emptyTitle')}
2746
- description={t('charts.emptyAllocation')}
2747
- />
2748
- )}
2749
- </ProjectChartCard>
2750
-
2751
- <ProjectChartCard
2752
- title={t('charts.taskDistribution')}
2753
- description={t('charts.taskDistributionDescription')}
2754
- icon={ClipboardList}
2755
- metric={`${totalTasks} ${t('kanban.items')}`}
2756
- className="xl:col-span-4"
2757
- isLoading={isTasksLoading}
2758
- >
2759
- {taskDistributionData.length > 0 ? (
2760
- <div className="grid gap-4 md:grid-cols-[1fr_11rem]">
2681
+ {!isLimitedView ? (
2682
+ <TabsContent value="analysis">
2683
+ <SectionCard
2684
+ title={t('sections.deliveryHealth')}
2685
+ description={t('sections.deliveryHealthDescription')}
2686
+ className="rounded-3xl border bg-card p-4 shadow-sm"
2687
+ >
2688
+ <div className="grid gap-4 xl:grid-cols-12">
2689
+ <ProjectChartCard
2690
+ title={t('charts.burnup')}
2691
+ description={t('charts.burnupDescription')}
2692
+ icon={LineChartIcon}
2693
+ metric={formatPercent(displayedProgress)}
2694
+ className="xl:col-span-8"
2695
+ isLoading={chartDashboardLoading}
2696
+ >
2697
+ {burnupChartData.length > 1 ? (
2698
+ <ChartContainer
2699
+ className="h-80 w-full"
2700
+ config={boardChartConfig}
2701
+ >
2702
+ <AreaChart data={burnupChartData}>
2703
+ <defs>
2704
+ <linearGradient
2705
+ id="burnupLogged"
2706
+ x1="0"
2707
+ y1="0"
2708
+ x2="0"
2709
+ y2="1"
2710
+ >
2711
+ <stop
2712
+ offset="5%"
2713
+ stopColor="var(--color-loggedHours)"
2714
+ stopOpacity={0.26}
2715
+ />
2716
+ <stop
2717
+ offset="95%"
2718
+ stopColor="var(--color-loggedHours)"
2719
+ stopOpacity={0.02}
2720
+ />
2721
+ </linearGradient>
2722
+ </defs>
2723
+ <CartesianGrid vertical={false} strokeDasharray="3 3" />
2724
+ <XAxis
2725
+ dataKey="week"
2726
+ tickLine={false}
2727
+ axisLine={false}
2728
+ />
2729
+ <YAxis tickLine={false} axisLine={false} width={36} />
2730
+ <ChartTooltip content={<ChartTooltipContent />} />
2731
+ <Area
2732
+ type="monotone"
2733
+ dataKey="planned"
2734
+ stroke="var(--color-planned)"
2735
+ strokeDasharray="4 4"
2736
+ strokeWidth={2}
2737
+ fill="transparent"
2738
+ />
2739
+ <Area
2740
+ type="monotone"
2741
+ dataKey="loggedHours"
2742
+ stroke="var(--color-loggedHours)"
2743
+ strokeWidth={2.5}
2744
+ fill="url(#burnupLogged)"
2745
+ />
2746
+ </AreaChart>
2747
+ </ChartContainer>
2748
+ ) : (
2749
+ <ChartEmptyState
2750
+ icon={LineChartIcon}
2751
+ title={t('charts.emptyTitle')}
2752
+ description={t('charts.emptyBurnup')}
2753
+ />
2754
+ )}
2755
+ </ProjectChartCard>
2756
+
2757
+ <ProjectChartCard
2758
+ title={t('charts.weeklyVelocity')}
2759
+ description={t('charts.weeklyVelocityDescription')}
2760
+ icon={Rocket}
2761
+ metric={formatHours(weeklyVelocity)}
2762
+ className="xl:col-span-4"
2763
+ isLoading={isProjectStatsLoading}
2764
+ >
2765
+ {velocityChartData.length > 0 ? (
2766
+ <ChartContainer
2767
+ className="h-80 w-full"
2768
+ config={boardChartConfig}
2769
+ >
2770
+ <LineChart data={velocityChartData}>
2771
+ <CartesianGrid vertical={false} strokeDasharray="3 3" />
2772
+ <XAxis
2773
+ dataKey="week"
2774
+ tickLine={false}
2775
+ axisLine={false}
2776
+ />
2777
+ <YAxis tickLine={false} axisLine={false} width={30} />
2778
+ <ChartTooltip content={<ChartTooltipContent />} />
2779
+ <Line
2780
+ type="monotone"
2781
+ dataKey="loggedHours"
2782
+ stroke="var(--color-loggedHours)"
2783
+ strokeWidth={2.5}
2784
+ dot={{ r: 3 }}
2785
+ activeDot={{ r: 5 }}
2786
+ />
2787
+ </LineChart>
2788
+ </ChartContainer>
2789
+ ) : (
2790
+ <ChartEmptyState
2791
+ icon={Rocket}
2792
+ title={t('charts.emptyTitle')}
2793
+ description={t('charts.emptyVelocity')}
2794
+ />
2795
+ )}
2796
+ </ProjectChartCard>
2797
+
2798
+ <ProjectChartCard
2799
+ title={t('charts.allocationByCollaborator')}
2800
+ description={t('charts.allocationDescription')}
2801
+ icon={BarChart3}
2802
+ metric={formatPercent(
2803
+ project.operationalIndicators.averageAllocation
2804
+ )}
2805
+ className="xl:col-span-5"
2806
+ isLoading={chartDashboardLoading}
2807
+ >
2808
+ {allocationChartData.length > 0 ? (
2761
2809
  <ChartContainer
2762
2810
  className="h-72 w-full"
2763
2811
  config={boardChartConfig}
2764
2812
  >
2765
- <PieChart>
2813
+ <BarChart data={allocationChartData}>
2814
+ <CartesianGrid vertical={false} strokeDasharray="3 3" />
2815
+ <XAxis
2816
+ dataKey="name"
2817
+ tickLine={false}
2818
+ axisLine={false}
2819
+ />
2820
+ <YAxis tickLine={false} axisLine={false} width={32} />
2766
2821
  <ChartTooltip
2767
2822
  content={<ChartTooltipContent hideLabel />}
2768
2823
  />
2769
- <Pie
2770
- data={taskDistributionData}
2771
- dataKey="value"
2772
- nameKey="name"
2773
- innerRadius={58}
2774
- outerRadius={92}
2775
- paddingAngle={4}
2776
- >
2777
- {taskDistributionData.map((entry) => (
2778
- <Cell key={entry.key} fill={entry.fill} />
2779
- ))}
2780
- </Pie>
2781
- </PieChart>
2824
+ <Bar
2825
+ dataKey="allocation"
2826
+ radius={[8, 8, 3, 3]}
2827
+ fill="var(--color-allocation)"
2828
+ />
2829
+ </BarChart>
2782
2830
  </ChartContainer>
2783
- <div className="flex flex-col justify-center gap-2">
2784
- {taskDistributionData.map((item) => (
2785
- <div
2786
- key={item.key}
2787
- className="flex items-center justify-between gap-3 rounded-lg border bg-muted/10 px-3 py-2 text-xs"
2788
- >
2789
- <span className="flex min-w-0 items-center gap-2">
2790
- <span
2791
- className="size-2.5 shrink-0 rounded-full"
2792
- style={{ backgroundColor: item.fill }}
2793
- />
2794
- <span className="truncate">{item.name}</span>
2795
- </span>
2796
- <span className="font-semibold">{item.value}</span>
2797
- </div>
2798
- ))}
2831
+ ) : (
2832
+ <ChartEmptyState
2833
+ icon={BarChart3}
2834
+ title={t('charts.emptyTitle')}
2835
+ description={t('charts.emptyAllocation')}
2836
+ />
2837
+ )}
2838
+ </ProjectChartCard>
2839
+
2840
+ <ProjectChartCard
2841
+ title={t('charts.taskDistribution')}
2842
+ description={t('charts.taskDistributionDescription')}
2843
+ icon={ClipboardList}
2844
+ metric={`${totalTasks} ${t('kanban.items')}`}
2845
+ className="xl:col-span-4"
2846
+ isLoading={isTasksLoading}
2847
+ >
2848
+ {taskDistributionData.length > 0 ? (
2849
+ <div className="grid gap-4 md:grid-cols-[1fr_11rem]">
2850
+ <ChartContainer
2851
+ className="h-72 w-full"
2852
+ config={boardChartConfig}
2853
+ >
2854
+ <PieChart>
2855
+ <ChartTooltip
2856
+ content={<ChartTooltipContent hideLabel />}
2857
+ />
2858
+ <Pie
2859
+ data={taskDistributionData}
2860
+ dataKey="value"
2861
+ nameKey="name"
2862
+ innerRadius={58}
2863
+ outerRadius={92}
2864
+ paddingAngle={4}
2865
+ >
2866
+ {taskDistributionData.map((entry) => (
2867
+ <Cell key={entry.key} fill={entry.fill} />
2868
+ ))}
2869
+ </Pie>
2870
+ </PieChart>
2871
+ </ChartContainer>
2872
+ <div className="flex flex-col justify-center gap-2">
2873
+ {taskDistributionData.map((item) => (
2874
+ <div
2875
+ key={item.key}
2876
+ className="flex items-center justify-between gap-3 rounded-lg border bg-muted/10 px-3 py-2 text-xs"
2877
+ >
2878
+ <span className="flex min-w-0 items-center gap-2">
2879
+ <span
2880
+ className="size-2.5 shrink-0 rounded-full"
2881
+ style={{ backgroundColor: item.fill }}
2882
+ />
2883
+ <span className="truncate">{item.name}</span>
2884
+ </span>
2885
+ <span className="font-semibold">{item.value}</span>
2886
+ </div>
2887
+ ))}
2888
+ </div>
2799
2889
  </div>
2800
- </div>
2801
- ) : (
2802
- <ChartEmptyState
2803
- icon={ClipboardList}
2804
- title={t('charts.emptyTitle')}
2805
- description={t('charts.emptyTasks')}
2806
- />
2807
- )}
2808
- </ProjectChartCard>
2809
-
2810
- <ProjectChartCard
2811
- title={t('charts.operationalHealth')}
2812
- description={t('charts.operationalHealthDescription')}
2813
- icon={HeartPulse}
2814
- metric={projectHealthLabel}
2815
- className="xl:col-span-3"
2816
- isLoading={chartDashboardLoading}
2817
- >
2818
- <div className="grid h-72 place-items-center">
2819
- <ChartContainer
2820
- className="h-56 w-full"
2821
- config={boardChartConfig}
2822
- >
2823
- <RadialBarChart
2824
- data={healthChartData}
2825
- innerRadius="72%"
2826
- outerRadius="100%"
2827
- startAngle={180}
2828
- endAngle={0}
2890
+ ) : (
2891
+ <ChartEmptyState
2892
+ icon={ClipboardList}
2893
+ title={t('charts.emptyTitle')}
2894
+ description={t('charts.emptyTasks')}
2895
+ />
2896
+ )}
2897
+ </ProjectChartCard>
2898
+
2899
+ <ProjectChartCard
2900
+ title={t('charts.operationalHealth')}
2901
+ description={t('charts.operationalHealthDescription')}
2902
+ icon={HeartPulse}
2903
+ metric={projectHealthLabel}
2904
+ className="xl:col-span-3"
2905
+ isLoading={chartDashboardLoading}
2906
+ >
2907
+ <div className="grid h-72 place-items-center">
2908
+ <ChartContainer
2909
+ className="h-56 w-full"
2910
+ config={boardChartConfig}
2829
2911
  >
2830
- <PolarAngleAxis
2831
- type="number"
2832
- domain={[0, 100]}
2833
- tick={false}
2834
- />
2835
- <RadialBar
2836
- dataKey="value"
2837
- cornerRadius={12}
2838
- background={{ fill: 'hsl(var(--muted))' }}
2839
- />
2840
- <ChartTooltip
2841
- content={<ChartTooltipContent hideLabel />}
2842
- />
2843
- </RadialBarChart>
2844
- </ChartContainer>
2845
- <div className="-mt-20 text-center">
2846
- <div className="text-3xl font-semibold tabular-nums">
2847
- {projectHealth.value}%
2848
- </div>
2849
- <div className="mt-1 text-xs text-muted-foreground">
2850
- {t('charts.healthScore')}
2912
+ <RadialBarChart
2913
+ data={healthChartData}
2914
+ innerRadius="72%"
2915
+ outerRadius="100%"
2916
+ startAngle={180}
2917
+ endAngle={0}
2918
+ >
2919
+ <PolarAngleAxis
2920
+ type="number"
2921
+ domain={[0, 100]}
2922
+ tick={false}
2923
+ />
2924
+ <RadialBar
2925
+ dataKey="value"
2926
+ cornerRadius={12}
2927
+ background={{ fill: 'hsl(var(--muted))' }}
2928
+ />
2929
+ <ChartTooltip
2930
+ content={<ChartTooltipContent hideLabel />}
2931
+ />
2932
+ </RadialBarChart>
2933
+ </ChartContainer>
2934
+ <div className="-mt-20 text-center">
2935
+ <div className="text-3xl font-semibold tabular-nums">
2936
+ {projectHealth.value}%
2937
+ </div>
2938
+ <div className="mt-1 text-xs text-muted-foreground">
2939
+ {t('charts.healthScore')}
2940
+ </div>
2851
2941
  </div>
2852
2942
  </div>
2943
+ </ProjectChartCard>
2944
+ </div>
2945
+ </SectionCard>
2946
+ </TabsContent>
2947
+ ) : null}
2948
+
2949
+ <TabsContent value="tasks" className="space-y-4">
2950
+ <SectionCard
2951
+ title={t('sections.taskBoard')}
2952
+ description={t('sections.taskBoardDescription')}
2953
+ className="rounded-3xl border bg-card p-4 shadow-sm"
2954
+ actions={
2955
+ !isLimitedView ? (
2956
+ <Button
2957
+ size="sm"
2958
+ variant="outline"
2959
+ className="gap-2"
2960
+ onClick={() => openCreateTaskForm()}
2961
+ >
2962
+ <Plus className="size-4" />
2963
+ {t('taskForm.titleNew')}
2964
+ </Button>
2965
+ ) : undefined
2966
+ }
2967
+ >
2968
+ <div className="mb-4 flex flex-col gap-3 rounded-2xl border bg-muted/20 p-3 lg:flex-row lg:items-center lg:justify-between">
2969
+ <div className="relative min-w-0 flex-1">
2970
+ <Search className="pointer-events-none absolute left-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" />
2971
+ <Input
2972
+ value={boardSearch}
2973
+ onChange={(event) => setBoardSearch(event.target.value)}
2974
+ placeholder={t('kanban.searchPlaceholder')}
2975
+ className="h-10 bg-background pl-9"
2976
+ />
2977
+ </div>
2978
+ <div className="flex flex-col gap-2 sm:flex-row sm:items-center">
2979
+ <div className="flex items-center gap-2 rounded-xl border bg-background px-3 py-2 text-xs text-muted-foreground">
2980
+ <SlidersHorizontal className="size-4" />
2981
+ {t('kanban.filters')}
2853
2982
  </div>
2854
- </ProjectChartCard>
2983
+ <Select
2984
+ value={boardPriorityFilter}
2985
+ onValueChange={(value) =>
2986
+ setBoardPriorityFilter(value as typeof boardPriorityFilter)
2987
+ }
2988
+ >
2989
+ <SelectTrigger className="h-10 w-full bg-background sm:w-44">
2990
+ <SelectValue />
2991
+ </SelectTrigger>
2992
+ <SelectContent>
2993
+ <SelectItem value="all">
2994
+ {t('kanban.allPriorities')}
2995
+ </SelectItem>
2996
+ <SelectItem value="high">
2997
+ {getTaskPriorityLabel('high')}
2998
+ </SelectItem>
2999
+ <SelectItem value="medium">
3000
+ {getTaskPriorityLabel('medium')}
3001
+ </SelectItem>
3002
+ <SelectItem value="low">
3003
+ {getTaskPriorityLabel('low')}
3004
+ </SelectItem>
3005
+ </SelectContent>
3006
+ </Select>
3007
+ <Select
3008
+ value={boardGroupMode}
3009
+ onValueChange={setBoardGroupMode}
3010
+ >
3011
+ <SelectTrigger className="h-10 w-full bg-background sm:w-44">
3012
+ <SelectValue />
3013
+ </SelectTrigger>
3014
+ <SelectContent>
3015
+ <SelectItem value="status">
3016
+ {t('kanban.groupStatus')}
3017
+ </SelectItem>
3018
+ </SelectContent>
3019
+ </Select>
3020
+ </div>
2855
3021
  </div>
2856
- </SectionCard>
2857
- </>
2858
- ) : null}
2859
3022
 
2860
- <SectionCard
2861
- title={t('sections.taskBoard')}
2862
- description={t('sections.taskBoardDescription')}
2863
- className="rounded-3xl border bg-card p-4 shadow-sm"
2864
- actions={
2865
- !isLimitedView ? (
2866
- <Button
2867
- size="sm"
2868
- variant="default"
2869
- className="gap-2"
2870
- onClick={() => openCreateTaskForm()}
3023
+ <DndContext
3024
+ sensors={sensors}
3025
+ collisionDetection={kanbanCollision}
3026
+ onDragStart={onBoardDragStart}
3027
+ onDragCancel={() => setActiveDragTask(null)}
3028
+ onDragEnd={onBoardDragEnd}
2871
3029
  >
2872
- <Plus className="size-4" />
2873
- {t('taskForm.titleNew')}
2874
- </Button>
2875
- ) : undefined
2876
- }
2877
- >
2878
- <div className="mb-4 flex flex-col gap-3 rounded-2xl border bg-muted/20 p-3 lg:flex-row lg:items-center lg:justify-between">
2879
- <div className="relative min-w-0 flex-1">
2880
- <Search className="pointer-events-none absolute left-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" />
2881
- <Input
2882
- value={boardSearch}
2883
- onChange={(event) => setBoardSearch(event.target.value)}
2884
- placeholder={t('kanban.searchPlaceholder')}
2885
- className="h-10 bg-background pl-9"
2886
- />
2887
- </div>
2888
- <div className="flex flex-col gap-2 sm:flex-row sm:items-center">
2889
- <div className="flex items-center gap-2 rounded-xl border bg-background px-3 py-2 text-xs text-muted-foreground">
2890
- <SlidersHorizontal className="size-4" />
2891
- {t('kanban.filters')}
2892
- </div>
2893
- <Select
2894
- value={boardPriorityFilter}
2895
- onValueChange={(value) =>
2896
- setBoardPriorityFilter(value as typeof boardPriorityFilter)
2897
- }
2898
- >
2899
- <SelectTrigger className="h-10 w-full bg-background sm:w-44">
2900
- <SelectValue />
2901
- </SelectTrigger>
2902
- <SelectContent>
2903
- <SelectItem value="all">{t('kanban.allPriorities')}</SelectItem>
2904
- <SelectItem value="high">
2905
- {getTaskPriorityLabel('high')}
2906
- </SelectItem>
2907
- <SelectItem value="medium">
2908
- {getTaskPriorityLabel('medium')}
2909
- </SelectItem>
2910
- <SelectItem value="low">
2911
- {getTaskPriorityLabel('low')}
2912
- </SelectItem>
2913
- </SelectContent>
2914
- </Select>
2915
- <Select value={boardGroupMode} onValueChange={setBoardGroupMode}>
2916
- <SelectTrigger className="h-10 w-full bg-background sm:w-44">
2917
- <SelectValue />
2918
- </SelectTrigger>
2919
- <SelectContent>
2920
- <SelectItem value="status">
2921
- {t('kanban.groupStatus')}
2922
- </SelectItem>
2923
- </SelectContent>
2924
- </Select>
2925
- </div>
2926
- </div>
2927
-
2928
- <DndContext
2929
- sensors={sensors}
2930
- collisionDetection={kanbanCollision}
2931
- onDragStart={onBoardDragStart}
2932
- onDragCancel={() => setActiveDragTask(null)}
2933
- onDragEnd={onBoardDragEnd}
2934
- >
2935
- <div className="relative">
2936
- <div className="grid auto-cols-[minmax(19rem,1fr)] grid-flow-col gap-4 overflow-x-auto pb-2 xl:grid-flow-row xl:grid-cols-4 xl:overflow-visible xl:pb-0">
2937
- {KANBAN_COLUMNS.map((column) => (
2938
- <DroppableColumn key={column.id} columnId={column.id}>
2939
- {(isOver) => (
2940
- <div
2941
- className={[
2942
- 'flex min-h-[32rem] flex-col overflow-hidden rounded-3xl border bg-linear-to-b p-3 transition-all',
2943
- getColumnClassName(column.id),
2944
- isOver
2945
- ? 'border-primary shadow-lg ring-2 ring-primary/15'
2946
- : 'border-border',
2947
- ].join(' ')}
2948
- >
2949
- <div className="mb-3 flex items-center justify-between gap-3 rounded-2xl border bg-background/85 p-3 shadow-xs">
2950
- <div className="min-w-0">
2951
- <div className="flex items-center gap-2 text-sm font-semibold">
2952
- <span
2953
- className={[
2954
- 'size-2.5 rounded-full',
2955
- getColumnDotClassName(column.id),
2956
- ].join(' ')}
2957
- />
2958
- {column.label}
2959
- </div>
2960
- <div className="mt-1 text-xs text-muted-foreground">
2961
- {filteredTaskColumns[column.id].length}/
2962
- {taskColumns[column.id].length} {t('kanban.items')}
3030
+ <div className="relative">
3031
+ <div className="grid auto-cols-[minmax(19rem,1fr)] grid-flow-col gap-4 overflow-x-auto pb-2 xl:grid-flow-row xl:grid-cols-4 xl:overflow-visible xl:pb-0">
3032
+ {KANBAN_COLUMNS.map((column) => (
3033
+ <DroppableColumn key={column.id} columnId={column.id}>
3034
+ {(isOver) => (
3035
+ <div
3036
+ className={[
3037
+ 'flex min-h-128 flex-col overflow-hidden rounded-3xl border bg-linear-to-b p-3 transition-all',
3038
+ getColumnClassName(column.id),
3039
+ isOver
3040
+ ? 'border-primary shadow-lg ring-2 ring-primary/15'
3041
+ : 'border-border',
3042
+ ].join(' ')}
3043
+ >
3044
+ <div className="mb-3 flex items-center justify-between gap-3 rounded-2xl border bg-background/85 p-3 shadow-xs">
3045
+ <div className="min-w-0">
3046
+ <div className="flex items-center gap-2 text-sm font-semibold">
3047
+ <span
3048
+ className={[
3049
+ 'size-2.5 rounded-full',
3050
+ getColumnDotClassName(column.id),
3051
+ ].join(' ')}
3052
+ />
3053
+ {column.label}
3054
+ </div>
3055
+ <div className="mt-1 text-xs text-muted-foreground">
3056
+ {filteredTaskColumns[column.id].length}/
3057
+ {taskColumns[column.id].length}{' '}
3058
+ {t('kanban.items')}
3059
+ </div>
3060
+ </div>
3061
+ <div className="flex items-center gap-1">
3062
+ <span className="rounded-full border bg-background px-2 py-0.5 text-xs font-medium text-muted-foreground">
3063
+ {filteredTaskColumns[column.id].length}
3064
+ </span>
3065
+ {!isLimitedView ? (
3066
+ <button
3067
+ type="button"
3068
+ className="flex size-5 cursor-pointer items-center justify-center rounded-full text-muted-foreground transition hover:bg-muted hover:text-foreground"
3069
+ onClick={() => {
3070
+ setInlineCreateColumn(column.id);
3071
+ setInlineCreateName('');
3072
+ }}
3073
+ >
3074
+ <Plus className="size-3.5" />
3075
+ </button>
3076
+ ) : null}
3077
+ </div>
2963
3078
  </div>
2964
- </div>
2965
- <div className="flex items-center gap-1">
2966
- <span className="rounded-full border bg-background px-2 py-0.5 text-xs font-medium text-muted-foreground">
2967
- {filteredTaskColumns[column.id].length}
2968
- </span>
2969
- {!isLimitedView ? (
2970
- <button
2971
- type="button"
2972
- className="flex size-5 cursor-pointer items-center justify-center rounded-full text-muted-foreground transition hover:bg-muted hover:text-foreground"
2973
- onClick={() => {
2974
- setInlineCreateColumn(column.id);
2975
- setInlineCreateName('');
2976
- }}
2977
- >
2978
- <Plus className="size-3.5" />
2979
- </button>
2980
- ) : null}
2981
- </div>
2982
- </div>
2983
3079
 
2984
- <div className="flex flex-1 flex-col gap-2">
2985
- <AnimatePresence initial={false}>
2986
- {filteredTaskColumns[column.id].map((task) => {
2987
- const tags = getTaskTags(task);
2988
- const comments = getTaskCommentCount(task);
2989
- const attachments = getTaskAttachmentCount();
2990
- return (
2991
- <DraggableTaskCard
2992
- key={task.id}
2993
- task={task}
2994
- disabled={false}
2995
- >
2996
- {(isDragging) => (
2997
- <motion.div
2998
- initial={{ opacity: 0, scale: 0.96 }}
2999
- animate={{ opacity: 1, scale: 1 }}
3000
- exit={{ opacity: 0, scale: 0.95, y: -4 }}
3001
- transition={{ duration: 0.18 }}
3002
- role="button"
3003
- tabIndex={0}
3004
- className={[
3005
- 'group w-full cursor-pointer rounded-2xl border bg-card p-3 text-left shadow-xs transition',
3006
- isDragging
3007
- ? 'opacity-0'
3008
- : 'hover:border-primary/40 hover:shadow-lg',
3009
- ].join(' ')}
3010
- onClick={() => openEditTaskForm(task)}
3011
- onKeyDown={(event) => {
3012
- if (
3013
- event.key === 'Enter' ||
3014
- event.key === ' '
3015
- ) {
3016
- event.preventDefault();
3017
- openEditTaskForm(task);
3018
- }
3019
- }}
3080
+ <div className="flex flex-1 flex-col gap-2">
3081
+ <AnimatePresence initial={false}>
3082
+ {filteredTaskColumns[column.id].map((task) => {
3083
+ const tags = getTaskTags(task);
3084
+ const comments = getTaskCommentCount(task);
3085
+ const attachments =
3086
+ getTaskAttachmentCount(task);
3087
+ return (
3088
+ <DraggableTaskCard
3089
+ key={task.id}
3090
+ task={task}
3091
+ disabled={false}
3020
3092
  >
3021
- <div className="mb-3 flex items-start justify-between gap-2">
3022
- <div className="min-w-0 space-y-1">
3023
- <p className="line-clamp-2 text-sm font-semibold leading-snug">
3024
- {task.name}
3025
- </p>
3026
- {task.description ? (
3027
- <p className="line-clamp-2 text-xs leading-5 text-muted-foreground">
3028
- {task.description.replace(
3029
- /<[^>]*>/g,
3030
- ''
3031
- )}
3032
- </p>
3033
- ) : null}
3034
- </div>
3035
- <div className="flex items-start gap-2">
3036
- <span
3037
- className={[
3038
- 'shrink-0 rounded-full border px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide',
3039
- getPriorityClassName(task.priority),
3040
- ].join(' ')}
3041
- >
3042
- {getTaskPriorityLabel(task.priority)}
3043
- </span>
3044
- <Button
3045
- type="button"
3046
- variant="ghost"
3047
- size="icon"
3048
- className="size-7 shrink-0 rounded-full opacity-0 transition group-hover:opacity-100"
3049
- onPointerDown={(event) =>
3050
- event.stopPropagation()
3051
- }
3052
- onClick={(event) => {
3053
- event.stopPropagation();
3093
+ {(isDragging) => (
3094
+ <motion.div
3095
+ initial={{ opacity: 0, scale: 0.96 }}
3096
+ animate={{ opacity: 1, scale: 1 }}
3097
+ exit={{
3098
+ opacity: 0,
3099
+ scale: 0.95,
3100
+ y: -4,
3101
+ }}
3102
+ transition={{ duration: 0.18 }}
3103
+ role="button"
3104
+ tabIndex={0}
3105
+ className={[
3106
+ 'group w-full cursor-pointer rounded-2xl border bg-card p-3 text-left shadow-xs transition',
3107
+ isDragging
3108
+ ? 'opacity-0'
3109
+ : 'hover:border-primary/40 hover:shadow-lg',
3110
+ ].join(' ')}
3111
+ onClick={() =>
3112
+ !isDragging && openEditTaskForm(task)
3113
+ }
3114
+ onKeyDown={(event) => {
3115
+ if (
3116
+ event.key === 'Enter' ||
3117
+ event.key === ' '
3118
+ ) {
3119
+ event.preventDefault();
3054
3120
  openEditTaskForm(task);
3055
- }}
3056
- >
3057
- <Pencil className="size-3.5" />
3058
- </Button>
3059
- </div>
3060
- </div>
3121
+ }
3122
+ }}
3123
+ >
3124
+ <div className="mb-3 flex items-start justify-between gap-2">
3125
+ <div className="min-w-0 space-y-1">
3126
+ <p className="line-clamp-2 text-sm font-semibold leading-snug">
3127
+ {task.name}
3128
+ </p>
3129
+ {task.description ? (
3130
+ <p className="line-clamp-2 text-xs leading-5 text-muted-foreground">
3131
+ {task.description.replace(
3132
+ /<[^>]*>/g,
3133
+ ''
3134
+ )}
3135
+ </p>
3136
+ ) : null}
3137
+ </div>
3138
+ <div className="flex items-start gap-2">
3139
+ <span
3140
+ className={[
3141
+ 'shrink-0 rounded-full border px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide',
3142
+ getPriorityClassName(
3143
+ task.priority
3144
+ ),
3145
+ ].join(' ')}
3146
+ >
3147
+ {getTaskPriorityLabel(
3148
+ task.priority
3149
+ )}
3150
+ </span>
3151
+ <Button
3152
+ type="button"
3153
+ variant="ghost"
3154
+ size="icon"
3155
+ className="size-7 shrink-0 rounded-full opacity-0 transition group-hover:opacity-100"
3156
+ onPointerDown={(event) =>
3157
+ event.stopPropagation()
3158
+ }
3159
+ onClick={(event) => {
3160
+ event.stopPropagation();
3161
+ openEditTaskForm(task);
3162
+ }}
3163
+ >
3164
+ <Pencil className="size-3.5" />
3165
+ </Button>
3166
+ </div>
3167
+ </div>
3061
3168
 
3062
- {tags.length > 0 ? (
3063
- <div className="mb-3 flex flex-wrap gap-1">
3064
- {tags.slice(0, 4).map((tag) => (
3065
- <span
3066
- key={`${task.id}-${tag}`}
3067
- className="rounded-full border bg-muted/60 px-2 py-0.5 text-[10px] font-medium text-muted-foreground"
3068
- >
3069
- {tag}
3070
- </span>
3071
- ))}
3072
- {tags.length > 4 ? (
3073
- <span className="rounded-full border bg-muted/60 px-2 py-0.5 text-[10px] font-medium text-muted-foreground">
3074
- +{tags.length - 4}
3075
- </span>
3169
+ {tags.length > 0 ? (
3170
+ <div className="mb-3 flex flex-wrap gap-1">
3171
+ {tags.slice(0, 4).map((tag) => (
3172
+ <span
3173
+ key={`${task.id}-${tag}`}
3174
+ className="rounded-full border bg-muted/60 px-2 py-0.5 text-[10px] font-medium text-muted-foreground"
3175
+ >
3176
+ {tag}
3177
+ </span>
3178
+ ))}
3179
+ {tags.length > 4 ? (
3180
+ <span className="rounded-full border bg-muted/60 px-2 py-0.5 text-[10px] font-medium text-muted-foreground">
3181
+ +{tags.length - 4}
3182
+ </span>
3183
+ ) : null}
3184
+ </div>
3076
3185
  ) : null}
3077
- </div>
3078
- ) : null}
3079
3186
 
3080
- <div className="grid grid-cols-2 gap-2 text-xs">
3081
- <div
3082
- className={[
3083
- 'rounded-xl border bg-muted/20 px-2 py-1.5',
3084
- isPastDue(task.dueDate) &&
3085
- task.status !== 'done'
3086
- ? 'border-rose-500/30 bg-rose-500/10 text-rose-700 dark:text-rose-300'
3087
- : 'text-muted-foreground',
3088
- ].join(' ')}
3089
- >
3090
- <span className="flex items-center gap-1">
3091
- <AlarmClock className="size-3.5" />
3092
- {formatDate(
3093
- task.dueDate,
3094
- getSettingValue,
3095
- currentLocaleCode
3096
- )}
3097
- </span>
3098
- </div>
3099
- <div className="rounded-xl border bg-muted/20 px-2 py-1.5 text-muted-foreground">
3100
- <span className="flex items-center gap-1">
3101
- <Timer className="size-3.5" />
3102
- {task.estimateHours != null
3103
- ? `${task.estimateHours}h`
3104
- : t('kanban.noEstimate')}
3105
- </span>
3106
- </div>
3107
- </div>
3187
+ <div className="grid grid-cols-2 gap-2 text-xs">
3188
+ <div
3189
+ className={[
3190
+ 'rounded-xl border bg-muted/20 px-2 py-1.5',
3191
+ isPastDue(task.dueDate) &&
3192
+ task.status !== 'done'
3193
+ ? 'border-rose-500/30 bg-rose-500/10 text-rose-700 dark:text-rose-300'
3194
+ : 'text-muted-foreground',
3195
+ ].join(' ')}
3196
+ >
3197
+ <span className="flex items-center gap-1">
3198
+ <AlarmClock className="size-3.5" />
3199
+ {formatDate(
3200
+ task.dueDate,
3201
+ getSettingValue,
3202
+ currentLocaleCode
3203
+ )}
3204
+ </span>
3205
+ </div>
3206
+ <div className="rounded-xl border bg-muted/20 px-2 py-1.5 text-muted-foreground">
3207
+ <span className="flex items-center gap-1">
3208
+ <Timer className="size-3.5" />
3209
+ {task.estimateHours != null
3210
+ ? `${task.estimateHours}h`
3211
+ : t('kanban.noEstimate')}
3212
+ </span>
3213
+ </div>
3214
+ </div>
3108
3215
 
3109
- <div className="mt-3 space-y-1.5">
3110
- <div className="flex items-center justify-between text-[11px] text-muted-foreground">
3111
- <span>{t('kanban.progress')}</span>
3112
- <span>
3113
- {getTaskProgress(task.status)}%
3114
- </span>
3115
- </div>
3116
- <Progress
3117
- value={getTaskProgress(task.status)}
3118
- className="h-1.5"
3119
- />
3120
- </div>
3216
+ <div className="mt-3 space-y-1.5">
3217
+ <div className="flex items-center justify-between text-[11px] text-muted-foreground">
3218
+ <span>{t('kanban.progress')}</span>
3219
+ <span>
3220
+ {getTaskProgress(task.status)}%
3221
+ </span>
3222
+ </div>
3223
+ <Progress
3224
+ value={getTaskProgress(task.status)}
3225
+ className="h-1.5"
3226
+ />
3227
+ </div>
3121
3228
 
3122
- <div className="mt-3 flex items-center justify-between gap-3 border-t pt-3">
3123
- <div className="flex min-w-0 items-center gap-2">
3124
- <div className="flex size-7 shrink-0 items-center justify-center overflow-hidden rounded-full bg-muted text-[10px] font-semibold uppercase text-muted-foreground ring-1 ring-border">
3125
- {(() => {
3126
- const photoUrl = getUserPhotoUrl(
3127
- task.assigneeUserPhotoId
3128
- );
3129
- const avatarUrl =
3130
- task.assigneePersonAvatarId
3131
- ? getPersonAvatarUrl(
3132
- task.assigneePersonAvatarId
3133
- )
3134
- : null;
3135
- const imgSrc =
3136
- photoUrl ?? avatarUrl;
3137
- return imgSrc ? (
3138
- // eslint-disable-next-line @next/next/no-img-element
3139
- <img
3140
- src={imgSrc}
3141
- alt={
3142
- task.assigneeName ||
3143
- commonT('labels.notAssigned')
3144
- }
3145
- className="size-full object-cover"
3146
- />
3147
- ) : (
3148
- getInitials(task.assigneeName)
3149
- );
3150
- })()}
3229
+ <div className="mt-3 flex items-center justify-between gap-3 border-t pt-3">
3230
+ <div className="flex min-w-0 items-center gap-2">
3231
+ <div className="flex size-7 shrink-0 items-center justify-center overflow-hidden rounded-full bg-muted text-[10px] font-semibold uppercase text-muted-foreground ring-1 ring-border">
3232
+ {(() => {
3233
+ const photoUrl =
3234
+ getUserPhotoUrl(
3235
+ task.assigneeUserPhotoId
3236
+ );
3237
+ const avatarUrl =
3238
+ task.assigneePersonAvatarId
3239
+ ? getPersonAvatarUrl(
3240
+ task.assigneePersonAvatarId
3241
+ )
3242
+ : null;
3243
+ const imgSrc =
3244
+ photoUrl ?? avatarUrl;
3245
+ return imgSrc ? (
3246
+ // eslint-disable-next-line @next/next/no-img-element
3247
+ <img
3248
+ src={imgSrc}
3249
+ alt={
3250
+ task.assigneeName ||
3251
+ commonT(
3252
+ 'labels.notAssigned'
3253
+ )
3254
+ }
3255
+ className="size-full object-cover"
3256
+ />
3257
+ ) : (
3258
+ getInitials(task.assigneeName)
3259
+ );
3260
+ })()}
3261
+ </div>
3262
+ <span className="truncate text-[11px] text-muted-foreground">
3263
+ {task.assigneeName ||
3264
+ commonT('labels.notAssigned')}
3265
+ </span>
3266
+ </div>
3267
+ <div className="flex shrink-0 items-center gap-2 text-[11px] text-muted-foreground">
3268
+ <span className="inline-flex items-center gap-1">
3269
+ <MessageSquare className="size-3.5" />
3270
+ {comments}
3271
+ </span>
3272
+ <span className="inline-flex items-center gap-1">
3273
+ <Paperclip className="size-3.5" />
3274
+ {attachments}
3275
+ </span>
3276
+ </div>
3151
3277
  </div>
3152
- <span className="truncate text-[11px] text-muted-foreground">
3153
- {task.assigneeName ||
3154
- commonT('labels.notAssigned')}
3155
- </span>
3156
- </div>
3157
- <div className="flex shrink-0 items-center gap-2 text-[11px] text-muted-foreground">
3158
- <span className="inline-flex items-center gap-1">
3159
- <MessageSquare className="size-3.5" />
3160
- {comments}
3161
- </span>
3162
- <span className="inline-flex items-center gap-1">
3163
- <Paperclip className="size-3.5" />
3164
- {attachments}
3165
- </span>
3166
- </div>
3167
- </div>
3168
- </motion.div>
3169
- )}
3170
- </DraggableTaskCard>
3171
- );
3172
- })}
3173
- </AnimatePresence>
3174
- {filteredTaskColumns[column.id].length === 0 ? (
3175
- <div className="rounded-2xl border border-dashed bg-background/70 p-4 text-center text-xs text-muted-foreground">
3176
- {boardSearch || boardPriorityFilter !== 'all'
3177
- ? t('kanban.noFilteredTasks')
3178
- : t('kanban.emptyColumn')}
3179
- </div>
3180
- ) : null}
3181
- {!isLimitedView && inlineCreateColumn === column.id ? (
3182
- <div className="space-y-1.5 rounded-2xl border bg-card p-2 shadow-sm">
3183
- <Input
3184
- autoFocus
3185
- placeholder={t('taskForm.namePlaceholder')}
3186
- value={inlineCreateName}
3187
- onChange={(e) =>
3188
- setInlineCreateName(e.target.value)
3189
- }
3190
- onKeyDown={(e) => {
3191
- if (e.key === 'Enter') {
3192
- e.preventDefault();
3193
- void handleInlineCreateTask(
3194
- column.id,
3195
- inlineCreateName
3196
- );
3197
- } else if (e.key === 'Escape') {
3198
- setInlineCreateColumn(null);
3199
- setInlineCreateName('');
3200
- }
3201
- }}
3202
- onBlur={() => {
3203
- if (!inlineCreateName.trim()) {
3204
- setInlineCreateColumn(null);
3205
- setInlineCreateName('');
3206
- }
3207
- }}
3208
- disabled={inlineCreateLoading}
3209
- className="h-8 text-sm"
3210
- />
3211
- <div className="flex gap-1">
3212
- <Button
3213
- type="button"
3214
- size="sm"
3215
- className="h-7 px-2 text-xs"
3216
- disabled={
3217
- !inlineCreateName.trim() ||
3218
- inlineCreateLoading
3219
- }
3220
- onMouseDown={(e) => e.preventDefault()}
3221
- onClick={() =>
3222
- void handleInlineCreateTask(
3223
- column.id,
3224
- inlineCreateName
3225
- )
3226
- }
3227
- >
3228
- {t('taskForm.titleNew')}
3229
- </Button>
3230
- <Button
3278
+ </motion.div>
3279
+ )}
3280
+ </DraggableTaskCard>
3281
+ );
3282
+ })}
3283
+ </AnimatePresence>
3284
+ {filteredTaskColumns[column.id].length === 0 ? (
3285
+ <div className="rounded-2xl border border-dashed bg-background/70 p-4 text-center text-xs text-muted-foreground">
3286
+ {boardSearch || boardPriorityFilter !== 'all'
3287
+ ? t('kanban.noFilteredTasks')
3288
+ : t('kanban.emptyColumn')}
3289
+ </div>
3290
+ ) : null}
3291
+ {!isLimitedView &&
3292
+ inlineCreateColumn === column.id ? (
3293
+ <div className="space-y-1.5 rounded-2xl border bg-card p-2 shadow-sm">
3294
+ <Input
3295
+ autoFocus
3296
+ placeholder={t('taskForm.namePlaceholder')}
3297
+ value={inlineCreateName}
3298
+ onChange={(e) =>
3299
+ setInlineCreateName(e.target.value)
3300
+ }
3301
+ onKeyDown={(e) => {
3302
+ if (e.key === 'Enter') {
3303
+ e.preventDefault();
3304
+ void handleInlineCreateTask(
3305
+ column.id,
3306
+ inlineCreateName
3307
+ );
3308
+ } else if (e.key === 'Escape') {
3309
+ setInlineCreateColumn(null);
3310
+ setInlineCreateName('');
3311
+ }
3312
+ }}
3313
+ onBlur={() => {
3314
+ if (!inlineCreateName.trim()) {
3315
+ setInlineCreateColumn(null);
3316
+ setInlineCreateName('');
3317
+ }
3318
+ }}
3319
+ disabled={inlineCreateLoading}
3320
+ className="h-8 text-sm"
3321
+ />
3322
+ <div className="flex gap-1">
3323
+ <Button
3324
+ type="button"
3325
+ size="sm"
3326
+ className="h-7 px-2 text-xs"
3327
+ disabled={
3328
+ !inlineCreateName.trim() ||
3329
+ inlineCreateLoading
3330
+ }
3331
+ onMouseDown={(e) => e.preventDefault()}
3332
+ onClick={() =>
3333
+ void handleInlineCreateTask(
3334
+ column.id,
3335
+ inlineCreateName
3336
+ )
3337
+ }
3338
+ >
3339
+ {t('taskForm.titleNew')}
3340
+ </Button>
3341
+ <Button
3342
+ type="button"
3343
+ variant="ghost"
3344
+ size="sm"
3345
+ className="h-7 px-2 text-xs"
3346
+ onMouseDown={(e) => e.preventDefault()}
3347
+ onClick={() => {
3348
+ setInlineCreateColumn(null);
3349
+ setInlineCreateName('');
3350
+ }}
3351
+ >
3352
+ {commonT('actions.cancel')}
3353
+ </Button>
3354
+ </div>
3355
+ </div>
3356
+ ) : !isLimitedView ? (
3357
+ <button
3231
3358
  type="button"
3232
- variant="ghost"
3233
- size="sm"
3234
- className="h-7 px-2 text-xs"
3235
- onMouseDown={(e) => e.preventDefault()}
3359
+ className="mt-auto flex w-full cursor-pointer items-center justify-center gap-1 rounded-2xl border border-dashed bg-background/70 px-3 py-2 text-xs text-muted-foreground transition hover:border-primary/40 hover:bg-primary/5 hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50"
3236
3360
  onClick={() => {
3237
- setInlineCreateColumn(null);
3361
+ setInlineCreateColumn(column.id);
3238
3362
  setInlineCreateName('');
3239
3363
  }}
3240
3364
  >
3241
- {commonT('actions.cancel')}
3242
- </Button>
3243
- </div>
3365
+ <Plus className="size-3" />
3366
+ {t('taskForm.titleNew')}
3367
+ </button>
3368
+ ) : null}
3244
3369
  </div>
3245
- ) : !isLimitedView ? (
3246
- <button
3247
- type="button"
3248
- className="mt-auto flex w-full cursor-pointer items-center justify-center gap-1 rounded-2xl border border-dashed bg-background/70 px-3 py-2 text-xs text-muted-foreground transition hover:border-primary/40 hover:bg-primary/5 hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50"
3249
- onClick={() => {
3250
- setInlineCreateColumn(column.id);
3251
- setInlineCreateName('');
3252
- }}
3253
- >
3254
- <Plus className="size-3" />
3255
- {t('taskForm.titleNew')}
3256
- </button>
3257
- ) : null}
3258
- </div>
3259
- </div>
3260
- )}
3261
- </DroppableColumn>
3262
- ))}
3263
- </div>
3264
- {/* Scroll fade overlay — visible only on mobile/tablet */}
3265
- <div
3266
- aria-hidden
3267
- className="pointer-events-none absolute inset-y-0 right-0 w-16 bg-linear-to-l from-background/80 to-transparent xl:hidden"
3268
- />
3269
- </div>
3270
- {/* DragOverlay renders the floating card following the pointer */}
3271
- <DragOverlay dropAnimation={{ duration: 160, easing: 'ease' }}>
3272
- {activeDragTask
3273
- ? (() => {
3274
- const overlayTask = activeDragTask;
3275
- const overlayTags = getTaskTags(overlayTask);
3276
- const overlayComments = getTaskCommentCount(overlayTask);
3277
- const overlayAttachments = getTaskAttachmentCount();
3278
- return (
3279
- <div className="w-76 cursor-grabbing rounded-2xl border border-primary/60 bg-card p-3 shadow-2xl ring-2 ring-primary/20 opacity-95">
3280
- <div className="mb-3 flex items-start justify-between gap-2">
3281
- <div className="min-w-0 space-y-1">
3282
- <p className="line-clamp-2 text-sm font-semibold leading-snug">
3283
- {overlayTask.name}
3284
- </p>
3285
- {overlayTask.description ? (
3286
- <p className="line-clamp-2 text-xs leading-5 text-muted-foreground">
3287
- {overlayTask.description.replace(/<[^>]*>/g, '')}
3288
- </p>
3289
- ) : null}
3290
3370
  </div>
3291
- <span
3292
- className={[
3293
- 'shrink-0 rounded-full border px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide',
3294
- getPriorityClassName(overlayTask.priority),
3295
- ].join(' ')}
3296
- >
3297
- {getTaskPriorityLabel(overlayTask.priority)}
3298
- </span>
3299
- </div>
3300
-
3301
- {overlayTags.length > 0 ? (
3302
- <div className="mb-3 flex flex-wrap gap-1">
3303
- {overlayTags.slice(0, 4).map((tag) => (
3371
+ )}
3372
+ </DroppableColumn>
3373
+ ))}
3374
+ </div>
3375
+ {/* Scroll fade overlay — visible only on mobile/tablet */}
3376
+ <div
3377
+ aria-hidden
3378
+ className="pointer-events-none absolute inset-y-0 right-0 w-16 bg-linear-to-l from-background/80 to-transparent xl:hidden"
3379
+ />
3380
+ </div>
3381
+ {/* DragOverlay renders the floating card following the pointer */}
3382
+ <DragOverlay dropAnimation={{ duration: 160, easing: 'ease' }}>
3383
+ {activeDragTask
3384
+ ? (() => {
3385
+ const overlayTask = activeDragTask;
3386
+ const overlayTags = getTaskTags(overlayTask);
3387
+ const overlayComments = getTaskCommentCount(overlayTask);
3388
+ const overlayAttachments =
3389
+ getTaskAttachmentCount(overlayTask);
3390
+ return (
3391
+ <div className="w-76 cursor-grabbing rounded-2xl border border-primary/60 bg-card p-3 shadow-2xl ring-2 ring-primary/20 opacity-95">
3392
+ <div className="mb-3 flex items-start justify-between gap-2">
3393
+ <div className="min-w-0 space-y-1">
3394
+ <p className="line-clamp-2 text-sm font-semibold leading-snug">
3395
+ {overlayTask.name}
3396
+ </p>
3397
+ {overlayTask.description ? (
3398
+ <p className="line-clamp-2 text-xs leading-5 text-muted-foreground">
3399
+ {overlayTask.description.replace(
3400
+ /<[^>]*>/g,
3401
+ ''
3402
+ )}
3403
+ </p>
3404
+ ) : null}
3405
+ </div>
3304
3406
  <span
3305
- key={tag}
3306
- className="rounded-full border bg-muted/60 px-2 py-0.5 text-[10px] font-medium text-muted-foreground"
3407
+ className={[
3408
+ 'shrink-0 rounded-full border px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide',
3409
+ getPriorityClassName(overlayTask.priority),
3410
+ ].join(' ')}
3307
3411
  >
3308
- {tag}
3309
- </span>
3310
- ))}
3311
- {overlayTags.length > 4 ? (
3312
- <span className="rounded-full border bg-muted/60 px-2 py-0.5 text-[10px] font-medium text-muted-foreground">
3313
- +{overlayTags.length - 4}
3412
+ {getTaskPriorityLabel(overlayTask.priority)}
3314
3413
  </span>
3414
+ </div>
3415
+
3416
+ {overlayTags.length > 0 ? (
3417
+ <div className="mb-3 flex flex-wrap gap-1">
3418
+ {overlayTags.slice(0, 4).map((tag) => (
3419
+ <span
3420
+ key={tag}
3421
+ className="rounded-full border bg-muted/60 px-2 py-0.5 text-[10px] font-medium text-muted-foreground"
3422
+ >
3423
+ {tag}
3424
+ </span>
3425
+ ))}
3426
+ {overlayTags.length > 4 ? (
3427
+ <span className="rounded-full border bg-muted/60 px-2 py-0.5 text-[10px] font-medium text-muted-foreground">
3428
+ +{overlayTags.length - 4}
3429
+ </span>
3430
+ ) : null}
3431
+ </div>
3315
3432
  ) : null}
3316
- </div>
3317
- ) : null}
3318
3433
 
3319
- <div className="grid grid-cols-2 gap-2 text-xs">
3320
- <div
3321
- className={[
3322
- 'rounded-xl border bg-muted/20 px-2 py-1.5',
3323
- isPastDue(overlayTask.dueDate) &&
3324
- overlayTask.status !== 'done'
3325
- ? 'border-rose-500/30 bg-rose-500/10 text-rose-700 dark:text-rose-300'
3326
- : 'text-muted-foreground',
3327
- ].join(' ')}
3328
- >
3329
- <span className="flex items-center gap-1">
3330
- <AlarmClock className="size-3.5" />
3331
- {formatDate(
3332
- overlayTask.dueDate,
3333
- getSettingValue,
3334
- currentLocaleCode
3335
- )}
3336
- </span>
3337
- </div>
3338
- <div className="rounded-xl border bg-muted/20 px-2 py-1.5 text-muted-foreground">
3339
- <span className="flex items-center gap-1">
3340
- <Timer className="size-3.5" />
3341
- {overlayTask.estimateHours != null
3342
- ? `${overlayTask.estimateHours}h`
3343
- : t('kanban.noEstimate')}
3344
- </span>
3345
- </div>
3346
- </div>
3434
+ <div className="grid grid-cols-2 gap-2 text-xs">
3435
+ <div
3436
+ className={[
3437
+ 'rounded-xl border bg-muted/20 px-2 py-1.5',
3438
+ isPastDue(overlayTask.dueDate) &&
3439
+ overlayTask.status !== 'done'
3440
+ ? 'border-rose-500/30 bg-rose-500/10 text-rose-700 dark:text-rose-300'
3441
+ : 'text-muted-foreground',
3442
+ ].join(' ')}
3443
+ >
3444
+ <span className="flex items-center gap-1">
3445
+ <AlarmClock className="size-3.5" />
3446
+ {formatDate(
3447
+ overlayTask.dueDate,
3448
+ getSettingValue,
3449
+ currentLocaleCode
3450
+ )}
3451
+ </span>
3452
+ </div>
3453
+ <div className="rounded-xl border bg-muted/20 px-2 py-1.5 text-muted-foreground">
3454
+ <span className="flex items-center gap-1">
3455
+ <Timer className="size-3.5" />
3456
+ {overlayTask.estimateHours != null
3457
+ ? `${overlayTask.estimateHours}h`
3458
+ : t('kanban.noEstimate')}
3459
+ </span>
3460
+ </div>
3461
+ </div>
3347
3462
 
3348
- <div className="mt-3 space-y-1.5">
3349
- <div className="flex items-center justify-between text-[11px] text-muted-foreground">
3350
- <span>{t('kanban.progress')}</span>
3351
- <span>{getTaskProgress(overlayTask.status)}%</span>
3463
+ <div className="mt-3 space-y-1.5">
3464
+ <div className="flex items-center justify-between text-[11px] text-muted-foreground">
3465
+ <span>{t('kanban.progress')}</span>
3466
+ <span>
3467
+ {getTaskProgress(overlayTask.status)}%
3468
+ </span>
3469
+ </div>
3470
+ <Progress
3471
+ value={getTaskProgress(overlayTask.status)}
3472
+ className="h-1.5"
3473
+ />
3474
+ </div>
3475
+
3476
+ <div className="mt-3 flex items-center justify-between gap-3 border-t pt-3">
3477
+ <div className="flex min-w-0 items-center gap-2">
3478
+ <div className="flex size-7 shrink-0 items-center justify-center overflow-hidden rounded-full bg-muted text-[10px] font-semibold uppercase text-muted-foreground ring-1 ring-border">
3479
+ {(() => {
3480
+ const photoUrl = getUserPhotoUrl(
3481
+ overlayTask.assigneeUserPhotoId
3482
+ );
3483
+ const avatarUrl =
3484
+ overlayTask.assigneePersonAvatarId
3485
+ ? getPersonAvatarUrl(
3486
+ overlayTask.assigneePersonAvatarId
3487
+ )
3488
+ : null;
3489
+ const imgSrc = photoUrl ?? avatarUrl;
3490
+ return imgSrc ? (
3491
+ // eslint-disable-next-line @next/next/no-img-element
3492
+ <img
3493
+ src={imgSrc}
3494
+ alt={
3495
+ overlayTask.assigneeName ||
3496
+ commonT('labels.notAssigned')
3497
+ }
3498
+ className="size-full object-cover"
3499
+ />
3500
+ ) : (
3501
+ getInitials(overlayTask.assigneeName)
3502
+ );
3503
+ })()}
3504
+ </div>
3505
+ <span className="truncate text-[11px] text-muted-foreground">
3506
+ {overlayTask.assigneeName ||
3507
+ commonT('labels.notAssigned')}
3508
+ </span>
3509
+ </div>
3510
+ <div className="flex shrink-0 items-center gap-2 text-[11px] text-muted-foreground">
3511
+ <span className="inline-flex items-center gap-1">
3512
+ <MessageSquare className="size-3.5" />
3513
+ {overlayComments}
3514
+ </span>
3515
+ <span className="inline-flex items-center gap-1">
3516
+ <Paperclip className="size-3.5" />
3517
+ {overlayAttachments}
3518
+ </span>
3519
+ </div>
3520
+ </div>
3352
3521
  </div>
3353
- <Progress
3354
- value={getTaskProgress(overlayTask.status)}
3355
- className="h-1.5"
3356
- />
3522
+ );
3523
+ })()
3524
+ : null}
3525
+ </DragOverlay>
3526
+ </DndContext>
3527
+ </SectionCard>
3528
+ </TabsContent>
3529
+
3530
+ <TabsContent value="timeline">
3531
+ <SectionCard
3532
+ title={t('sections.timeline')}
3533
+ description={t('sections.timelineDescription')}
3534
+ className="rounded-3xl border bg-card p-4 shadow-sm"
3535
+ actions={
3536
+ <div className="flex flex-wrap items-center gap-2">
3537
+ <Select
3538
+ value={timelineTypeFilter}
3539
+ onValueChange={(value) => {
3540
+ setTimelineTypeFilter(value as typeof timelineTypeFilter);
3541
+ setTimelineVisibleCount(8);
3542
+ }}
3543
+ >
3544
+ <SelectTrigger className="h-9 w-44 bg-background">
3545
+ <SelectValue />
3546
+ </SelectTrigger>
3547
+ <SelectContent>
3548
+ <SelectItem value="all">
3549
+ {t('timeline.filters.all')}
3550
+ </SelectItem>
3551
+ <SelectItem value="task">
3552
+ {t('timeline.filters.task')}
3553
+ </SelectItem>
3554
+ <SelectItem value="status">
3555
+ {t('timeline.filters.status')}
3556
+ </SelectItem>
3557
+ <SelectItem value="timesheet">
3558
+ {t('timeline.filters.timesheet')}
3559
+ </SelectItem>
3560
+ <SelectItem value="approval">
3561
+ {t('timeline.filters.approval')}
3562
+ </SelectItem>
3563
+ <SelectItem value="comment">
3564
+ {t('timeline.filters.comment')}
3565
+ </SelectItem>
3566
+ </SelectContent>
3567
+ </Select>
3568
+ </div>
3569
+ }
3570
+ >
3571
+ <div className="rounded-3xl border bg-linear-to-b from-muted/30 to-background p-4">
3572
+ {groupedTimelineEvents.length > 0 ? (
3573
+ <div className="space-y-6">
3574
+ {groupedTimelineEvents.map((group) => (
3575
+ <div key={group.dayKey} className="space-y-3">
3576
+ <div className="sticky top-0 z-10 w-fit rounded-full border bg-background px-3 py-1 text-xs font-medium text-muted-foreground shadow-xs">
3577
+ {formatDate(
3578
+ group.dayKey,
3579
+ getSettingValue,
3580
+ currentLocaleCode
3581
+ )}
3357
3582
  </div>
3583
+ <div className="space-y-0">
3584
+ {group.events.map((event, index) => {
3585
+ const Icon = event.icon;
3586
+ return (
3587
+ <motion.div
3588
+ key={event.id}
3589
+ initial={{ opacity: 0, y: 8 }}
3590
+ animate={{ opacity: 1, y: 0 }}
3591
+ transition={{ duration: 0.18 }}
3592
+ className="grid grid-cols-[2rem_1fr] gap-3"
3593
+ >
3594
+ <div className="flex flex-col items-center">
3595
+ <div
3596
+ className={[
3597
+ 'flex size-8 items-center justify-center rounded-full shadow-sm',
3598
+ event.toneClassName,
3599
+ ].join(' ')}
3600
+ >
3601
+ <Icon className="size-4" />
3602
+ </div>
3603
+ {index < group.events.length - 1 ? (
3604
+ <div className="w-px flex-1 bg-border" />
3605
+ ) : null}
3606
+ </div>
3607
+ <div className="pb-5">
3608
+ <div className="rounded-2xl border bg-card p-4 shadow-xs transition hover:shadow-sm">
3609
+ <div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
3610
+ <div className="min-w-0">
3611
+ <div className="flex flex-wrap items-center gap-2">
3612
+ <span className="text-sm font-semibold">
3613
+ {event.title}
3614
+ </span>
3615
+ <span className="rounded-full border bg-muted/40 px-2 py-0.5 text-[10px] font-medium text-muted-foreground">
3616
+ {t(`timeline.types.${event.type}`)}
3617
+ </span>
3618
+ </div>
3619
+ <p className="mt-1 line-clamp-2 text-sm text-muted-foreground">
3620
+ {event.description}
3621
+ </p>
3622
+ </div>
3623
+ <div className="shrink-0 text-xs text-muted-foreground">
3624
+ {formatRelativeTime(
3625
+ event.timestamp,
3626
+ currentLocaleCode
3627
+ )}
3628
+ </div>
3629
+ </div>
3630
+ <div className="mt-3 flex items-center gap-2 border-t pt-3">
3631
+ <Avatar className="size-7 border bg-muted">
3632
+ <AvatarImage
3633
+ src={
3634
+ getUserPhotoUrl(
3635
+ event.actorUserPhotoId
3636
+ ) ||
3637
+ getPersonAvatarUrl(
3638
+ event.actorAvatarId
3639
+ )
3640
+ }
3641
+ alt={
3642
+ event.actorName ||
3643
+ commonT('labels.notAssigned')
3644
+ }
3645
+ />
3646
+ <AvatarFallback className="text-[10px]">
3647
+ {getInitials(event.actorName)}
3648
+ </AvatarFallback>
3649
+ </Avatar>
3650
+ <span className="truncate text-xs text-muted-foreground">
3651
+ {event.actorName ||
3652
+ commonT('labels.notAssigned')}
3653
+ </span>
3654
+ </div>
3655
+ </div>
3656
+ </div>
3657
+ </motion.div>
3658
+ );
3659
+ })}
3660
+ </div>
3661
+ </div>
3662
+ ))}
3663
+ {visibleTimelineEvents.length <
3664
+ filteredTimelineEvents.length ? (
3665
+ <div className="flex justify-center">
3666
+ <Button
3667
+ type="button"
3668
+ variant="outline"
3669
+ size="sm"
3670
+ onClick={() =>
3671
+ setTimelineVisibleCount((current) => current + 8)
3672
+ }
3673
+ >
3674
+ {t('timeline.loadMore')}
3675
+ </Button>
3676
+ </div>
3677
+ ) : null}
3678
+ </div>
3679
+ ) : (
3680
+ <div className="flex min-h-56 flex-col items-center justify-center rounded-2xl border border-dashed bg-background p-6 text-center">
3681
+ <div className="flex size-12 items-center justify-center rounded-2xl bg-muted text-muted-foreground">
3682
+ <GitCommitHorizontal className="size-6" />
3683
+ </div>
3684
+ <div className="mt-3 text-sm font-medium">
3685
+ {t('timeline.emptyTitle')}
3686
+ </div>
3687
+ <p className="mt-1 max-w-sm text-xs leading-5 text-muted-foreground">
3688
+ {t('timeline.empty')}
3689
+ </p>
3690
+ </div>
3691
+ )}
3692
+ </div>
3693
+ </SectionCard>
3694
+ </TabsContent>
3358
3695
 
3359
- <div className="mt-3 flex items-center justify-between gap-3 border-t pt-3">
3360
- <div className="flex min-w-0 items-center gap-2">
3361
- <div className="flex size-7 shrink-0 items-center justify-center overflow-hidden rounded-full bg-muted text-[10px] font-semibold uppercase text-muted-foreground ring-1 ring-border">
3362
- {(() => {
3363
- const photoUrl = getUserPhotoUrl(
3364
- overlayTask.assigneeUserPhotoId
3365
- );
3366
- const avatarUrl =
3367
- overlayTask.assigneePersonAvatarId
3368
- ? getPersonAvatarUrl(
3369
- overlayTask.assigneePersonAvatarId
3370
- )
3371
- : null;
3372
- const imgSrc = photoUrl ?? avatarUrl;
3373
- return imgSrc ? (
3374
- // eslint-disable-next-line @next/next/no-img-element
3375
- <img
3376
- src={imgSrc}
3377
- alt={
3378
- overlayTask.assigneeName ||
3379
- commonT('labels.notAssigned')
3380
- }
3381
- className="size-full object-cover"
3382
- />
3696
+ <TabsContent value="archive">
3697
+ <SectionCard
3698
+ title={t('sections.archivedTasks')}
3699
+ description={t('sections.archivedTasksDescription')}
3700
+ className="rounded-xl border bg-card p-4 shadow-sm"
3701
+ >
3702
+ {archivedTasks.length > 0 ? (
3703
+ <div className="overflow-x-auto rounded-lg border bg-muted/10">
3704
+ <Table>
3705
+ <TableHeader>
3706
+ <TableRow>
3707
+ <TableHead>{commonT('labels.task')}</TableHead>
3708
+ <TableHead>{commonT('labels.status')}</TableHead>
3709
+ <TableHead>{t('taskForm.deadlineLabel')}</TableHead>
3710
+ <TableHead className="text-right">
3711
+ {commonT('labels.actions')}
3712
+ </TableHead>
3713
+ </TableRow>
3714
+ </TableHeader>
3715
+ <TableBody>
3716
+ {archivedTasks.map((task) => (
3717
+ <TableRow
3718
+ key={task.id}
3719
+ className="cursor-pointer hover:bg-muted/30"
3720
+ onClick={() => setSelectedTask(task)}
3721
+ >
3722
+ <TableCell>
3723
+ <div className="min-w-0">
3724
+ <div className="truncate font-medium">
3725
+ {task.name}
3726
+ </div>
3727
+ {task.description ? (
3728
+ <div className="truncate text-xs text-muted-foreground">
3729
+ {task.description}
3730
+ </div>
3731
+ ) : null}
3732
+ </div>
3733
+ </TableCell>
3734
+ <TableCell>
3735
+ <StatusBadge
3736
+ label={
3737
+ KANBAN_COLUMNS.find(
3738
+ (column) => column.id === task.status
3739
+ )?.label ?? formatEnumLabel(task.status)
3740
+ }
3741
+ className={getStatusBadgeClass(task.status)}
3742
+ />
3743
+ </TableCell>
3744
+ <TableCell>
3745
+ {formatDate(
3746
+ task.dueDate,
3747
+ getSettingValue,
3748
+ currentLocaleCode
3749
+ )}
3750
+ </TableCell>
3751
+ <TableCell>
3752
+ <div className="flex justify-end gap-2">
3753
+ <Button
3754
+ variant="outline"
3755
+ size="sm"
3756
+ className="gap-2"
3757
+ disabled={restoringTaskId === task.id}
3758
+ onClick={(event) => {
3759
+ event.stopPropagation();
3760
+ void handleRestoreTask(task.id);
3761
+ }}
3762
+ >
3763
+ {restoringTaskId === task.id ? (
3764
+ <Loader2 className="size-4 animate-spin" />
3383
3765
  ) : (
3384
- getInitials(overlayTask.assigneeName)
3385
- );
3386
- })()}
3766
+ <ArchiveRestore className="size-4" />
3767
+ )}
3768
+ {commonT('actions.unarchive')}
3769
+ </Button>
3770
+ <Button
3771
+ variant="destructive"
3772
+ size="sm"
3773
+ className="gap-2"
3774
+ onClick={(event) => {
3775
+ event.stopPropagation();
3776
+ setDeletePromptTask(task);
3777
+ }}
3778
+ >
3779
+ <Trash2 className="size-4" />
3780
+ {commonT('actions.delete')}
3781
+ </Button>
3387
3782
  </div>
3388
- <span className="truncate text-[11px] text-muted-foreground">
3389
- {overlayTask.assigneeName ||
3390
- commonT('labels.notAssigned')}
3391
- </span>
3392
- </div>
3393
- <div className="flex shrink-0 items-center gap-2 text-[11px] text-muted-foreground">
3394
- <span className="inline-flex items-center gap-1">
3395
- <MessageSquare className="size-3.5" />
3396
- {overlayComments}
3397
- </span>
3398
- <span className="inline-flex items-center gap-1">
3399
- <Paperclip className="size-3.5" />
3400
- {overlayAttachments}
3401
- </span>
3402
- </div>
3783
+ </TableCell>
3784
+ </TableRow>
3785
+ ))}
3786
+ </TableBody>
3787
+ </Table>
3788
+ </div>
3789
+ ) : (
3790
+ <ChartEmptyState
3791
+ icon={Archive}
3792
+ title={commonT('states.emptyTitle')}
3793
+ description={t('emptyArchivedDescription')}
3794
+ />
3795
+ )}
3796
+ </SectionCard>
3797
+ </TabsContent>
3798
+
3799
+ <TabsContent value="team">
3800
+ <div className="grid gap-4 xl:grid-cols-12">
3801
+ <SectionCard
3802
+ title={t('sections.team')}
3803
+ description={t('sections.teamDescription')}
3804
+ className={[
3805
+ 'rounded-2xl border bg-card p-4 shadow-sm',
3806
+ isLimitedView ? 'xl:col-span-12' : 'xl:col-span-8',
3807
+ ].join(' ')}
3808
+ >
3809
+ {project.assignments.length > 0 ? (
3810
+ <div className="space-y-4">
3811
+ <div className="grid gap-3 sm:grid-cols-3">
3812
+ <div className="rounded-2xl border bg-emerald-500/10 p-3">
3813
+ <div className="text-xs text-muted-foreground">
3814
+ {t('teamPanel.available')}
3815
+ </div>
3816
+ <div className="mt-1 text-2xl font-semibold text-emerald-700 dark:text-emerald-300">
3817
+ {availableAssignments}
3818
+ </div>
3819
+ </div>
3820
+ <div className="rounded-2xl border bg-amber-500/10 p-3">
3821
+ <div className="text-xs text-muted-foreground">
3822
+ {t('teamPanel.highAllocation')}
3823
+ </div>
3824
+ <div className="mt-1 text-2xl font-semibold text-amber-700 dark:text-amber-300">
3825
+ {highAllocationAssignments}
3826
+ </div>
3827
+ </div>
3828
+ <div className="rounded-2xl border bg-rose-500/10 p-3">
3829
+ <div className="text-xs text-muted-foreground">
3830
+ {t('teamPanel.overload')}
3831
+ </div>
3832
+ <div className="mt-1 text-2xl font-semibold text-rose-700 dark:text-rose-300">
3833
+ {overloadedAssignments}
3403
3834
  </div>
3404
3835
  </div>
3405
- );
3406
- })()
3407
- : null}
3408
- </DragOverlay>
3409
- </DndContext>
3410
- </SectionCard>
3411
-
3412
- <SectionCard
3413
- title={t('sections.timeline')}
3414
- description={t('sections.timelineDescription')}
3415
- className="rounded-3xl border bg-card p-4 shadow-sm"
3416
- actions={
3417
- <div className="flex flex-wrap items-center gap-2">
3418
- <Select
3419
- value={timelineTypeFilter}
3420
- onValueChange={(value) => {
3421
- setTimelineTypeFilter(value as typeof timelineTypeFilter);
3422
- setTimelineVisibleCount(8);
3423
- }}
3424
- >
3425
- <SelectTrigger className="h-9 w-44 bg-background">
3426
- <SelectValue />
3427
- </SelectTrigger>
3428
- <SelectContent>
3429
- <SelectItem value="all">{t('timeline.filters.all')}</SelectItem>
3430
- <SelectItem value="task">
3431
- {t('timeline.filters.task')}
3432
- </SelectItem>
3433
- <SelectItem value="status">
3434
- {t('timeline.filters.status')}
3435
- </SelectItem>
3436
- <SelectItem value="timesheet">
3437
- {t('timeline.filters.timesheet')}
3438
- </SelectItem>
3439
- <SelectItem value="approval">
3440
- {t('timeline.filters.approval')}
3441
- </SelectItem>
3442
- <SelectItem value="comment">
3443
- {t('timeline.filters.comment')}
3444
- </SelectItem>
3445
- </SelectContent>
3446
- </Select>
3447
- </div>
3448
- }
3449
- >
3450
- <div className="rounded-3xl border bg-linear-to-b from-muted/30 to-background p-4">
3451
- {groupedTimelineEvents.length > 0 ? (
3452
- <div className="space-y-6">
3453
- {groupedTimelineEvents.map((group) => (
3454
- <div key={group.dayKey} className="space-y-3">
3455
- <div className="sticky top-0 z-10 w-fit rounded-full border bg-background px-3 py-1 text-xs font-medium text-muted-foreground shadow-xs">
3456
- {formatDate(
3457
- group.dayKey,
3458
- getSettingValue,
3459
- currentLocaleCode
3460
- )}
3461
3836
  </div>
3462
- <div className="space-y-0">
3463
- {group.events.map((event, index) => {
3464
- const Icon = event.icon;
3837
+
3838
+ <div className="grid gap-3 md:grid-cols-2">
3839
+ {project.assignments.map((assignment) => {
3840
+ const allocationValue =
3841
+ typeof assignment.allocationPercent === 'number'
3842
+ ? Math.round(assignment.allocationPercent)
3843
+ : 0;
3844
+ const allocation = clampPercent(allocationValue);
3845
+ const weeklyHours = assignment.weeklyHours ?? 0;
3846
+ const usedHours =
3847
+ weeklyHours > 0
3848
+ ? (weeklyHours * Math.max(allocationValue, 0)) / 100
3849
+ : 0;
3850
+ const availablePercent = Math.max(
3851
+ 0,
3852
+ 100 - allocationValue
3853
+ );
3854
+ const availabilityHours =
3855
+ weeklyHours > 0
3856
+ ? Math.max(0, weeklyHours - usedHours)
3857
+ : 0;
3858
+ const tone = getAllocationTone(allocationValue);
3859
+ const ToneIcon = tone.icon;
3860
+
3465
3861
  return (
3466
3862
  <motion.div
3467
- key={event.id}
3468
- initial={{ opacity: 0, y: 8 }}
3469
- animate={{ opacity: 1, y: 0 }}
3470
- transition={{ duration: 0.18 }}
3471
- className="grid grid-cols-[2rem_1fr] gap-3"
3863
+ key={assignment.id}
3864
+ whileHover={{ y: -2 }}
3865
+ className={[
3866
+ 'overflow-hidden rounded-2xl border bg-background shadow-xs transition hover:shadow-md',
3867
+ tone.border,
3868
+ ].join(' ')}
3472
3869
  >
3473
- <div className="flex flex-col items-center">
3474
- <div
3475
- className={[
3476
- 'flex size-8 items-center justify-center rounded-full shadow-sm',
3477
- event.toneClassName,
3478
- ].join(' ')}
3479
- >
3480
- <Icon className="size-4" />
3870
+ <div className="border-b bg-linear-to-br from-muted/50 to-background p-4">
3871
+ <div className="flex items-start justify-between gap-3">
3872
+ <div className="flex min-w-0 items-center gap-3">
3873
+ <Avatar className="size-12 border bg-muted">
3874
+ <AvatarImage
3875
+ src={
3876
+ getUserPhotoUrl(assignment.userPhotoId) ||
3877
+ getPersonAvatarUrl(
3878
+ assignment.personAvatarId
3879
+ )
3880
+ }
3881
+ alt={assignment.collaboratorName}
3882
+ />
3883
+ <AvatarFallback className="text-xs font-semibold">
3884
+ {getInitials(assignment.collaboratorName)}
3885
+ </AvatarFallback>
3886
+ </Avatar>
3887
+ <div className="min-w-0">
3888
+ <div className="truncate text-sm font-semibold">
3889
+ {assignment.collaboratorName}
3890
+ </div>
3891
+ <div className="truncate text-xs text-muted-foreground">
3892
+ {assignment.roleLabel ||
3893
+ commonT('labels.notAssigned')}
3894
+ </div>
3895
+ </div>
3896
+ </div>
3897
+ <div className="flex shrink-0 flex-col items-end gap-2">
3898
+ <StatusBadge
3899
+ label={formatEnumLabel(assignment.status)}
3900
+ className={getStatusBadgeClass(
3901
+ assignment.status
3902
+ )}
3903
+ />
3904
+ <span
3905
+ className={[
3906
+ 'inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-[10px] font-semibold',
3907
+ tone.border,
3908
+ tone.bg,
3909
+ tone.text,
3910
+ ].join(' ')}
3911
+ >
3912
+ <ToneIcon className="size-3" />
3913
+ {t(`teamPanel.status.${tone.labelKey}`)}
3914
+ </span>
3915
+ </div>
3916
+ </div>
3917
+ </div>
3918
+
3919
+ <div className="space-y-4 p-4">
3920
+ <div className="space-y-2">
3921
+ <div className="flex items-center justify-between text-xs">
3922
+ <span className="text-muted-foreground">
3923
+ {commonT('labels.allocationPercent')}
3924
+ </span>
3925
+ <span
3926
+ className={['font-semibold', tone.text].join(
3927
+ ' '
3928
+ )}
3929
+ >
3930
+ {formatPercent(assignment.allocationPercent)}
3931
+ </span>
3932
+ </div>
3933
+ <Progress
3934
+ value={allocation}
3935
+ className={['h-2.5', tone.progress].join(' ')}
3936
+ />
3937
+ {allocationValue > 100 ? (
3938
+ <div className="flex items-center gap-1 text-xs text-rose-700 dark:text-rose-300">
3939
+ <AlertTriangle className="size-3.5" />
3940
+ {t('teamPanel.overloadWarning', {
3941
+ value: allocationValue - 100,
3942
+ })}
3943
+ </div>
3944
+ ) : null}
3481
3945
  </div>
3482
- {index < group.events.length - 1 ? (
3483
- <div className="w-px flex-1 bg-border" />
3484
- ) : null}
3485
- </div>
3486
- <div className="pb-5">
3487
- <div className="rounded-2xl border bg-card p-4 shadow-xs transition hover:shadow-sm">
3488
- <div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
3489
- <div className="min-w-0">
3490
- <div className="flex flex-wrap items-center gap-2">
3491
- <span className="text-sm font-semibold">
3492
- {event.title}
3493
- </span>
3494
- <span className="rounded-full border bg-muted/40 px-2 py-0.5 text-[10px] font-medium text-muted-foreground">
3495
- {t(`timeline.types.${event.type}`)}
3496
- </span>
3497
- </div>
3498
- <p className="mt-1 line-clamp-2 text-sm text-muted-foreground">
3499
- {event.description}
3500
- </p>
3946
+
3947
+ <div className="grid grid-cols-2 gap-3 text-xs xl:grid-cols-4">
3948
+ <div className="rounded-xl border bg-muted/20 p-2">
3949
+ <div className="text-muted-foreground">
3950
+ {commonT('labels.weeklyCapacity')}
3951
+ </div>
3952
+ <div className="mt-1 font-semibold">
3953
+ {weeklyHours
3954
+ ? formatHours(weeklyHours)
3955
+ : commonT('labels.notAvailable')}
3956
+ </div>
3957
+ </div>
3958
+ <div className="rounded-xl border bg-muted/20 p-2">
3959
+ <div className="text-muted-foreground">
3960
+ {t('teamPanel.usedHours')}
3961
+ </div>
3962
+ <div className="mt-1 font-semibold">
3963
+ {weeklyHours
3964
+ ? formatHours(usedHours)
3965
+ : commonT('labels.notAvailable')}
3966
+ </div>
3967
+ </div>
3968
+ <div className="rounded-xl border bg-muted/20 p-2">
3969
+ <div className="text-muted-foreground">
3970
+ {t('teamPanel.availability')}
3971
+ </div>
3972
+ <div className="mt-1 font-semibold">
3973
+ {weeklyHours
3974
+ ? formatHours(availabilityHours)
3975
+ : `${clampPercent(availablePercent)}%`}
3501
3976
  </div>
3502
- <div className="shrink-0 text-xs text-muted-foreground">
3503
- {formatRelativeTime(
3504
- event.timestamp,
3977
+ </div>
3978
+ <div className="rounded-xl border bg-muted/20 p-2">
3979
+ <div className="text-muted-foreground">
3980
+ {commonT('labels.timeline')}
3981
+ </div>
3982
+ <div className="mt-1 truncate font-semibold">
3983
+ {formatDateRange(
3984
+ assignment.startDate,
3985
+ assignment.endDate,
3986
+ getSettingValue,
3505
3987
  currentLocaleCode
3506
3988
  )}
3507
3989
  </div>
3508
3990
  </div>
3509
- <div className="mt-3 flex items-center gap-2 border-t pt-3">
3510
- <Avatar className="size-7 border bg-muted">
3511
- <AvatarImage
3512
- src={
3513
- getUserPhotoUrl(event.actorUserPhotoId) ||
3514
- getPersonAvatarUrl(event.actorAvatarId)
3515
- }
3516
- alt={
3517
- event.actorName ||
3518
- commonT('labels.notAssigned')
3519
- }
3520
- />
3521
- <AvatarFallback className="text-[10px]">
3522
- {getInitials(event.actorName)}
3523
- </AvatarFallback>
3524
- </Avatar>
3525
- <span className="truncate text-xs text-muted-foreground">
3526
- {event.actorName ||
3527
- commonT('labels.notAssigned')}
3528
- </span>
3529
- </div>
3530
3991
  </div>
3531
3992
  </div>
3532
3993
  </motion.div>
@@ -3534,446 +3995,141 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
3534
3995
  })}
3535
3996
  </div>
3536
3997
  </div>
3537
- ))}
3538
- {visibleTimelineEvents.length < filteredTimelineEvents.length ? (
3539
- <div className="flex justify-center">
3540
- <Button
3541
- type="button"
3542
- variant="outline"
3543
- size="sm"
3544
- onClick={() =>
3545
- setTimelineVisibleCount((current) => current + 8)
3546
- }
3547
- >
3548
- {t('timeline.loadMore')}
3549
- </Button>
3550
- </div>
3551
- ) : null}
3552
- </div>
3553
- ) : (
3554
- <div className="flex min-h-56 flex-col items-center justify-center rounded-2xl border border-dashed bg-background p-6 text-center">
3555
- <div className="flex size-12 items-center justify-center rounded-2xl bg-muted text-muted-foreground">
3556
- <GitCommitHorizontal className="size-6" />
3557
- </div>
3558
- <div className="mt-3 text-sm font-medium">
3559
- {t('timeline.emptyTitle')}
3560
- </div>
3561
- <p className="mt-1 max-w-sm text-xs leading-5 text-muted-foreground">
3562
- {t('timeline.empty')}
3563
- </p>
3564
- </div>
3565
- )}
3566
- </div>
3567
- </SectionCard>
3568
-
3569
- <SectionCard
3570
- title={t('sections.archivedTasks')}
3571
- description={t('sections.archivedTasksDescription')}
3572
- className="rounded-xl border bg-card p-4 shadow-sm"
3573
- >
3574
- {archivedTasks.length > 0 ? (
3575
- <div className="overflow-x-auto rounded-lg border bg-muted/10">
3576
- <Table>
3577
- <TableHeader>
3578
- <TableRow>
3579
- <TableHead>{commonT('labels.task')}</TableHead>
3580
- <TableHead>{commonT('labels.status')}</TableHead>
3581
- <TableHead>{t('taskForm.deadlineLabel')}</TableHead>
3582
- <TableHead className="text-right">
3583
- {commonT('labels.actions')}
3584
- </TableHead>
3585
- </TableRow>
3586
- </TableHeader>
3587
- <TableBody>
3588
- {archivedTasks.map((task) => (
3589
- <TableRow
3590
- key={task.id}
3591
- className="cursor-pointer hover:bg-muted/30"
3592
- onClick={() => setSelectedTask(task)}
3593
- >
3594
- <TableCell>
3595
- <div className="min-w-0">
3596
- <div className="truncate font-medium">{task.name}</div>
3597
- {task.description ? (
3598
- <div className="truncate text-xs text-muted-foreground">
3599
- {task.description}
3600
- </div>
3601
- ) : null}
3602
- </div>
3603
- </TableCell>
3604
- <TableCell>
3605
- <StatusBadge
3606
- label={
3607
- KANBAN_COLUMNS.find(
3608
- (column) => column.id === task.status
3609
- )?.label ?? formatEnumLabel(task.status)
3610
- }
3611
- className={getStatusBadgeClass(task.status)}
3612
- />
3613
- </TableCell>
3614
- <TableCell>
3615
- {formatDate(
3616
- task.dueDate,
3617
- getSettingValue,
3618
- currentLocaleCode
3619
- )}
3620
- </TableCell>
3621
- <TableCell>
3622
- <div className="flex justify-end gap-2">
3623
- <Button
3624
- variant="outline"
3625
- size="sm"
3626
- className="gap-2"
3627
- disabled={restoringTaskId === task.id}
3628
- onClick={(event) => {
3629
- event.stopPropagation();
3630
- void handleRestoreTask(task.id);
3631
- }}
3632
- >
3633
- {restoringTaskId === task.id ? (
3634
- <Loader2 className="size-4 animate-spin" />
3635
- ) : (
3636
- <ArchiveRestore className="size-4" />
3637
- )}
3638
- {commonT('actions.unarchive')}
3639
- </Button>
3640
- <Button
3641
- variant="destructive"
3642
- size="sm"
3643
- className="gap-2"
3644
- onClick={(event) => {
3645
- event.stopPropagation();
3646
- setDeletePromptTask(task);
3647
- }}
3648
- >
3649
- <Trash2 className="size-4" />
3650
- {commonT('actions.delete')}
3651
- </Button>
3652
- </div>
3653
- </TableCell>
3654
- </TableRow>
3655
- ))}
3656
- </TableBody>
3657
- </Table>
3658
- </div>
3659
- ) : (
3660
- <ChartEmptyState
3661
- icon={Archive}
3662
- title={commonT('states.emptyTitle')}
3663
- description={t('emptyArchivedDescription')}
3664
- />
3665
- )}
3666
- </SectionCard>
3667
-
3668
- <div className="grid gap-4 xl:grid-cols-12">
3669
- <SectionCard
3670
- title={t('sections.team')}
3671
- description={t('sections.teamDescription')}
3672
- className={[
3673
- 'rounded-2xl border bg-card p-4 shadow-sm',
3674
- isLimitedView ? 'xl:col-span-12' : 'xl:col-span-8',
3675
- ].join(' ')}
3676
- >
3677
- {project.assignments.length > 0 ? (
3678
- <div className="space-y-4">
3679
- <div className="grid gap-3 sm:grid-cols-3">
3680
- <div className="rounded-2xl border bg-emerald-500/10 p-3">
3681
- <div className="text-xs text-muted-foreground">
3682
- {t('teamPanel.available')}
3683
- </div>
3684
- <div className="mt-1 text-2xl font-semibold text-emerald-700 dark:text-emerald-300">
3685
- {availableAssignments}
3686
- </div>
3687
- </div>
3688
- <div className="rounded-2xl border bg-amber-500/10 p-3">
3689
- <div className="text-xs text-muted-foreground">
3690
- {t('teamPanel.highAllocation')}
3691
- </div>
3692
- <div className="mt-1 text-2xl font-semibold text-amber-700 dark:text-amber-300">
3693
- {highAllocationAssignments}
3694
- </div>
3695
- </div>
3696
- <div className="rounded-2xl border bg-rose-500/10 p-3">
3697
- <div className="text-xs text-muted-foreground">
3698
- {t('teamPanel.overload')}
3699
- </div>
3700
- <div className="mt-1 text-2xl font-semibold text-rose-700 dark:text-rose-300">
3701
- {overloadedAssignments}
3702
- </div>
3703
- </div>
3704
- </div>
3998
+ ) : (
3999
+ <ChartEmptyState
4000
+ icon={Users}
4001
+ title={commonT('states.emptyTitle')}
4002
+ description={t('noAssignments')}
4003
+ />
4004
+ )}
4005
+ </SectionCard>
3705
4006
 
3706
- <div className="grid gap-3 md:grid-cols-2">
3707
- {project.assignments.map((assignment) => {
3708
- const allocationValue =
3709
- typeof assignment.allocationPercent === 'number'
3710
- ? Math.round(assignment.allocationPercent)
3711
- : 0;
3712
- const allocation = clampPercent(allocationValue);
3713
- const weeklyHours = assignment.weeklyHours ?? 0;
3714
- const usedHours =
3715
- weeklyHours > 0
3716
- ? (weeklyHours * Math.max(allocationValue, 0)) / 100
3717
- : 0;
3718
- const availablePercent = Math.max(0, 100 - allocationValue);
3719
- const availabilityHours =
3720
- weeklyHours > 0 ? Math.max(0, weeklyHours - usedHours) : 0;
3721
- const tone = getAllocationTone(allocationValue);
3722
- const ToneIcon = tone.icon;
3723
-
3724
- return (
3725
- <motion.div
3726
- key={assignment.id}
3727
- whileHover={{ y: -2 }}
3728
- className={[
3729
- 'overflow-hidden rounded-2xl border bg-background shadow-xs transition hover:shadow-md',
3730
- tone.border,
3731
- ].join(' ')}
4007
+ {!isLimitedView ? (
4008
+ <SectionCard
4009
+ title={t('sections.indicators')}
4010
+ description={t('sections.indicatorsDescription')}
4011
+ className="rounded-xl border bg-card p-4 shadow-sm xl:col-span-4"
4012
+ >
4013
+ <div className="grid gap-3 sm:grid-cols-2 xl:grid-cols-1">
4014
+ {[
4015
+ {
4016
+ icon: Users,
4017
+ label: t('indicators.activeAssignments'),
4018
+ value: project.operationalIndicators.activeAssignments,
4019
+ tone: 'text-sky-700 dark:text-sky-300',
4020
+ bg: 'bg-sky-500/10',
4021
+ },
4022
+ {
4023
+ icon: CheckCircle2,
4024
+ label: t('indicators.completedAssignments'),
4025
+ value: project.operationalIndicators.completedAssignments,
4026
+ tone: 'text-emerald-700 dark:text-emerald-300',
4027
+ bg: 'bg-emerald-500/10',
4028
+ },
4029
+ {
4030
+ icon: Gauge,
4031
+ label: t('indicators.averageAllocation'),
4032
+ value: formatPercent(
4033
+ project.operationalIndicators.averageAllocation
4034
+ ),
4035
+ tone:
4036
+ averageAllocation > 100
4037
+ ? 'text-rose-700 dark:text-rose-300'
4038
+ : averageAllocation > 85
4039
+ ? 'text-amber-700 dark:text-amber-300'
4040
+ : 'text-emerald-700 dark:text-emerald-300',
4041
+ bg:
4042
+ averageAllocation > 100
4043
+ ? 'bg-rose-500/10'
4044
+ : averageAllocation > 85
4045
+ ? 'bg-amber-500/10'
4046
+ : 'bg-emerald-500/10',
4047
+ },
4048
+ {
4049
+ icon: Timer,
4050
+ label: t('indicators.totalWeeklyHours'),
4051
+ value: formatHours(
4052
+ project.operationalIndicators.totalWeeklyHours
4053
+ ),
4054
+ tone: 'text-violet-700 dark:text-violet-300',
4055
+ bg: 'bg-violet-500/10',
4056
+ },
4057
+ {
4058
+ icon: ClipboardList,
4059
+ label: t('cards.timesheets'),
4060
+ value: project.timesheetSummary.totalTimesheets,
4061
+ tone: 'text-foreground',
4062
+ bg: 'bg-muted/40',
4063
+ },
4064
+ {
4065
+ icon: AlarmClock,
4066
+ label: commonT('labels.pending'),
4067
+ value: project.timesheetSummary.pendingTimesheets,
4068
+ tone:
4069
+ project.timesheetSummary.pendingTimesheets > 0
4070
+ ? 'text-amber-700 dark:text-amber-300'
4071
+ : 'text-foreground',
4072
+ bg:
4073
+ project.timesheetSummary.pendingTimesheets > 0
4074
+ ? 'bg-amber-500/10'
4075
+ : 'bg-muted/40',
4076
+ },
4077
+ {
4078
+ icon: BarChart2,
4079
+ label: t('cards.loggedHours'),
4080
+ value: formatHours(project.timesheetSummary.totalHours),
4081
+ tone: 'text-sky-700 dark:text-sky-300',
4082
+ bg: 'bg-sky-500/10',
4083
+ },
4084
+ ].map(({ icon: Icon, label, value, tone, bg }) => (
4085
+ <div
4086
+ key={label}
4087
+ className="flex items-center gap-3 rounded-xl border bg-card p-3 transition-shadow hover:shadow-sm"
3732
4088
  >
3733
- <div className="border-b bg-linear-to-br from-muted/50 to-background p-4">
3734
- <div className="flex items-start justify-between gap-3">
3735
- <div className="flex min-w-0 items-center gap-3">
3736
- <Avatar className="size-12 border bg-muted">
3737
- <AvatarImage
3738
- src={
3739
- getUserPhotoUrl(assignment.userPhotoId) ||
3740
- getPersonAvatarUrl(assignment.personAvatarId)
3741
- }
3742
- alt={assignment.collaboratorName}
3743
- />
3744
- <AvatarFallback className="text-xs font-semibold">
3745
- {getInitials(assignment.collaboratorName)}
3746
- </AvatarFallback>
3747
- </Avatar>
3748
- <div className="min-w-0">
3749
- <div className="truncate text-sm font-semibold">
3750
- {assignment.collaboratorName}
3751
- </div>
3752
- <div className="truncate text-xs text-muted-foreground">
3753
- {assignment.roleLabel ||
3754
- commonT('labels.notAssigned')}
3755
- </div>
3756
- </div>
3757
- </div>
3758
- <div className="flex shrink-0 flex-col items-end gap-2">
3759
- <StatusBadge
3760
- label={formatEnumLabel(assignment.status)}
3761
- className={getStatusBadgeClass(assignment.status)}
3762
- />
3763
- <span
3764
- className={[
3765
- 'inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-[10px] font-semibold',
3766
- tone.border,
3767
- tone.bg,
3768
- tone.text,
3769
- ].join(' ')}
3770
- >
3771
- <ToneIcon className="size-3" />
3772
- {t(`teamPanel.status.${tone.labelKey}`)}
3773
- </span>
3774
- </div>
3775
- </div>
4089
+ <div
4090
+ className={[
4091
+ 'flex size-9 shrink-0 items-center justify-center rounded-xl',
4092
+ bg,
4093
+ ].join(' ')}
4094
+ >
4095
+ <Icon className={['size-4', tone].join(' ')} />
3776
4096
  </div>
3777
-
3778
- <div className="space-y-4 p-4">
3779
- <div className="space-y-2">
3780
- <div className="flex items-center justify-between text-xs">
3781
- <span className="text-muted-foreground">
3782
- {commonT('labels.allocationPercent')}
3783
- </span>
3784
- <span
3785
- className={['font-semibold', tone.text].join(' ')}
3786
- >
3787
- {formatPercent(assignment.allocationPercent)}
3788
- </span>
3789
- </div>
3790
- <Progress
3791
- value={allocation}
3792
- className={['h-2.5', tone.progress].join(' ')}
3793
- />
3794
- {allocationValue > 100 ? (
3795
- <div className="flex items-center gap-1 text-xs text-rose-700 dark:text-rose-300">
3796
- <AlertTriangle className="size-3.5" />
3797
- {t('teamPanel.overloadWarning', {
3798
- value: allocationValue - 100,
3799
- })}
3800
- </div>
3801
- ) : null}
4097
+ <div className="min-w-0 flex-1">
4098
+ <div className="truncate text-xs text-muted-foreground">
4099
+ {label}
3802
4100
  </div>
3803
-
3804
- <div className="grid grid-cols-2 gap-3 text-xs xl:grid-cols-4">
3805
- <div className="rounded-xl border bg-muted/20 p-2">
3806
- <div className="text-muted-foreground">
3807
- {commonT('labels.weeklyCapacity')}
3808
- </div>
3809
- <div className="mt-1 font-semibold">
3810
- {weeklyHours
3811
- ? formatHours(weeklyHours)
3812
- : commonT('labels.notAvailable')}
3813
- </div>
3814
- </div>
3815
- <div className="rounded-xl border bg-muted/20 p-2">
3816
- <div className="text-muted-foreground">
3817
- {t('teamPanel.usedHours')}
3818
- </div>
3819
- <div className="mt-1 font-semibold">
3820
- {weeklyHours
3821
- ? formatHours(usedHours)
3822
- : commonT('labels.notAvailable')}
3823
- </div>
3824
- </div>
3825
- <div className="rounded-xl border bg-muted/20 p-2">
3826
- <div className="text-muted-foreground">
3827
- {t('teamPanel.availability')}
3828
- </div>
3829
- <div className="mt-1 font-semibold">
3830
- {weeklyHours
3831
- ? formatHours(availabilityHours)
3832
- : `${clampPercent(availablePercent)}%`}
3833
- </div>
3834
- </div>
3835
- <div className="rounded-xl border bg-muted/20 p-2">
3836
- <div className="text-muted-foreground">
3837
- {commonT('labels.timeline')}
3838
- </div>
3839
- <div className="mt-1 truncate font-semibold">
3840
- {formatDateRange(
3841
- assignment.startDate,
3842
- assignment.endDate,
3843
- getSettingValue,
3844
- currentLocaleCode
3845
- )}
3846
- </div>
3847
- </div>
4101
+ <div
4102
+ className={[
4103
+ 'mt-0.5 text-sm font-semibold tabular-nums',
4104
+ tone,
4105
+ ].join(' ')}
4106
+ >
4107
+ {value}
3848
4108
  </div>
3849
4109
  </div>
3850
- </motion.div>
3851
- );
3852
- })}
3853
- </div>
3854
- </div>
3855
- ) : (
3856
- <ChartEmptyState
3857
- icon={Users}
3858
- title={commonT('states.emptyTitle')}
3859
- description={t('noAssignments')}
3860
- />
3861
- )}
3862
- </SectionCard>
4110
+ </div>
4111
+ ))}
4112
+ </div>
4113
+ </SectionCard>
4114
+ ) : null}
4115
+ </div>
4116
+ </TabsContent>
3863
4117
 
3864
- {!isLimitedView ? (
4118
+ <TabsContent value="costs">
3865
4119
  <SectionCard
3866
- title={t('sections.indicators')}
3867
- description={t('sections.indicatorsDescription')}
3868
- className="rounded-xl border bg-card p-4 shadow-sm xl:col-span-4"
4120
+ title={t('sections.costs')}
4121
+ description={t('sections.costsDescription')}
4122
+ className="rounded-2xl border bg-card p-4 shadow-sm"
3869
4123
  >
3870
- <div className="grid gap-3 sm:grid-cols-2 xl:grid-cols-1">
3871
- {[
3872
- {
3873
- icon: Users,
3874
- label: t('indicators.activeAssignments'),
3875
- value: project.operationalIndicators.activeAssignments,
3876
- tone: 'text-sky-700 dark:text-sky-300',
3877
- bg: 'bg-sky-500/10',
3878
- },
3879
- {
3880
- icon: CheckCircle2,
3881
- label: t('indicators.completedAssignments'),
3882
- value: project.operationalIndicators.completedAssignments,
3883
- tone: 'text-emerald-700 dark:text-emerald-300',
3884
- bg: 'bg-emerald-500/10',
3885
- },
3886
- {
3887
- icon: Gauge,
3888
- label: t('indicators.averageAllocation'),
3889
- value: formatPercent(
3890
- project.operationalIndicators.averageAllocation
3891
- ),
3892
- tone:
3893
- averageAllocation > 100
3894
- ? 'text-rose-700 dark:text-rose-300'
3895
- : averageAllocation > 85
3896
- ? 'text-amber-700 dark:text-amber-300'
3897
- : 'text-emerald-700 dark:text-emerald-300',
3898
- bg:
3899
- averageAllocation > 100
3900
- ? 'bg-rose-500/10'
3901
- : averageAllocation > 85
3902
- ? 'bg-amber-500/10'
3903
- : 'bg-emerald-500/10',
3904
- },
3905
- {
3906
- icon: Timer,
3907
- label: t('indicators.totalWeeklyHours'),
3908
- value: formatHours(
3909
- project.operationalIndicators.totalWeeklyHours
3910
- ),
3911
- tone: 'text-violet-700 dark:text-violet-300',
3912
- bg: 'bg-violet-500/10',
3913
- },
3914
- {
3915
- icon: ClipboardList,
3916
- label: t('cards.timesheets'),
3917
- value: project.timesheetSummary.totalTimesheets,
3918
- tone: 'text-foreground',
3919
- bg: 'bg-muted/40',
3920
- },
3921
- {
3922
- icon: AlarmClock,
3923
- label: commonT('labels.pending'),
3924
- value: project.timesheetSummary.pendingTimesheets,
3925
- tone:
3926
- project.timesheetSummary.pendingTimesheets > 0
3927
- ? 'text-amber-700 dark:text-amber-300'
3928
- : 'text-foreground',
3929
- bg:
3930
- project.timesheetSummary.pendingTimesheets > 0
3931
- ? 'bg-amber-500/10'
3932
- : 'bg-muted/40',
3933
- },
3934
- {
3935
- icon: BarChart2,
3936
- label: t('cards.loggedHours'),
3937
- value: formatHours(project.timesheetSummary.totalHours),
3938
- tone: 'text-sky-700 dark:text-sky-300',
3939
- bg: 'bg-sky-500/10',
3940
- },
3941
- ].map(({ icon: Icon, label, value, tone, bg }) => (
3942
- <div
3943
- key={label}
3944
- className="flex items-center gap-3 rounded-xl border bg-card p-3 transition-shadow hover:shadow-sm"
3945
- >
3946
- <div
3947
- className={[
3948
- 'flex size-9 shrink-0 items-center justify-center rounded-xl',
3949
- bg,
3950
- ].join(' ')}
3951
- >
3952
- <Icon className={['size-4', tone].join(' ')} />
3953
- </div>
3954
- <div className="min-w-0 flex-1">
3955
- <div className="truncate text-xs text-muted-foreground">
3956
- {label}
3957
- </div>
3958
- <div
3959
- className={[
3960
- 'mt-0.5 text-sm font-semibold tabular-nums',
3961
- tone,
3962
- ].join(' ')}
3963
- >
3964
- {value}
3965
- </div>
3966
- </div>
3967
- </div>
3968
- ))}
3969
- </div>
4124
+ <ProjectCostsSection projectId={projectId} />
3970
4125
  </SectionCard>
3971
- ) : null}
3972
- </div>
4126
+ </TabsContent>
4127
+ </Tabs>
3973
4128
 
3974
4129
  <TaskDetailSheet
3975
4130
  task={selectedTask}
3976
4131
  open={selectedTask !== null}
4132
+ defaultTab="comments"
3977
4133
  onOpenChange={(open) => {
3978
4134
  if (!open) {
3979
4135
  setSelectedTask(null);
@@ -4071,7 +4227,7 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
4071
4227
  }}
4072
4228
  >
4073
4229
  <SheetContent className="flex w-full flex-col overflow-hidden sm:max-w-xl">
4074
- <SheetHeader>
4230
+ <SheetHeader className="shrink-0">
4075
4231
  <SheetTitle>
4076
4232
  {editingTaskId
4077
4233
  ? t('taskForm.titleEdit')
@@ -4079,234 +4235,263 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
4079
4235
  </SheetTitle>
4080
4236
  </SheetHeader>
4081
4237
 
4082
- <div className="flex-1 space-y-4 overflow-y-auto px-4 py-2">
4083
- <div className="space-y-1.5">
4084
- <Label htmlFor="task-name">{t('taskForm.nameLabel')} *</Label>
4085
- <Input
4086
- id="task-name"
4087
- placeholder={t('taskForm.namePlaceholder')}
4088
- value={taskFormData.name}
4089
- onChange={(e) =>
4090
- setTaskFormData((prev) => ({
4091
- ...prev,
4092
- name: e.target.value,
4093
- }))
4094
- }
4095
- />
4096
- </div>
4238
+ <Tabs defaultValue="info" className="flex min-h-0 flex-1 flex-col">
4239
+ <TabsList className="mx-4 grid w-[calc(100%-2rem)] shrink-0 grid-cols-2">
4240
+ <TabsTrigger value="info">{t('taskForm.tabInfo')}</TabsTrigger>
4241
+ {editingTaskId ? (
4242
+ <TabsTrigger value="comments">
4243
+ {t('taskForm.tabComments')}
4244
+ </TabsTrigger>
4245
+ ) : null}
4246
+ </TabsList>
4247
+ <TabsContent
4248
+ value="info"
4249
+ className="flex min-h-0 flex-1 flex-col data-[state=inactive]:hidden"
4250
+ >
4251
+ <div className="flex-1 space-y-4 overflow-y-auto px-4 py-2">
4252
+ <div className="space-y-1.5">
4253
+ <Label htmlFor="task-name">
4254
+ {t('taskForm.nameLabel')} *
4255
+ </Label>
4256
+ <Input
4257
+ id="task-name"
4258
+ placeholder={t('taskForm.namePlaceholder')}
4259
+ value={taskFormData.name}
4260
+ onChange={(e) =>
4261
+ setTaskFormData((prev) => ({
4262
+ ...prev,
4263
+ name: e.target.value,
4264
+ }))
4265
+ }
4266
+ />
4267
+ </div>
4097
4268
 
4098
- <div className="space-y-1.5">
4099
- <Label htmlFor="task-description">
4100
- {t('taskForm.descriptionLabel')}
4101
- </Label>
4102
- <RichTextEditor
4103
- value={taskFormData.description}
4104
- onChange={(val) =>
4105
- setTaskFormData((prev) => ({
4106
- ...prev,
4107
- description: val,
4108
- }))
4109
- }
4110
- />
4111
- </div>
4269
+ <div className="space-y-1.5">
4270
+ <Label htmlFor="task-description">
4271
+ {t('taskForm.descriptionLabel')}
4272
+ </Label>
4273
+ <RichTextEditor
4274
+ value={taskFormData.description}
4275
+ onChange={(val) =>
4276
+ setTaskFormData((prev) => ({
4277
+ ...prev,
4278
+ description: val,
4279
+ }))
4280
+ }
4281
+ mentions={mentionItems}
4282
+ />
4283
+ </div>
4112
4284
 
4113
- <div className="grid grid-cols-2 gap-3">
4114
- <div className="space-y-1.5">
4115
- <Label>{t('taskForm.priorityLabel')}</Label>
4116
- <Select
4117
- value={taskFormData.priority}
4118
- onValueChange={(v) =>
4119
- setTaskFormData((prev) => ({
4120
- ...prev,
4121
- priority: v as TaskFormState['priority'],
4122
- }))
4123
- }
4124
- >
4125
- <SelectTrigger className="w-full">
4126
- <SelectValue />
4127
- </SelectTrigger>
4128
- <SelectContent>
4129
- <SelectItem value="low">
4130
- {getTaskPriorityLabel('low')}
4131
- </SelectItem>
4132
- <SelectItem value="medium">
4133
- {getTaskPriorityLabel('medium')}
4134
- </SelectItem>
4135
- <SelectItem value="high">
4136
- {getTaskPriorityLabel('high')}
4137
- </SelectItem>
4138
- </SelectContent>
4139
- </Select>
4140
- </div>
4285
+ <div className="grid grid-cols-2 gap-3">
4286
+ <div className="space-y-1.5">
4287
+ <Label>{t('taskForm.priorityLabel')}</Label>
4288
+ <Select
4289
+ value={taskFormData.priority}
4290
+ onValueChange={(v) =>
4291
+ setTaskFormData((prev) => ({
4292
+ ...prev,
4293
+ priority: v as TaskFormState['priority'],
4294
+ }))
4295
+ }
4296
+ >
4297
+ <SelectTrigger className="w-full">
4298
+ <SelectValue />
4299
+ </SelectTrigger>
4300
+ <SelectContent>
4301
+ <SelectItem value="low">
4302
+ {getTaskPriorityLabel('low')}
4303
+ </SelectItem>
4304
+ <SelectItem value="medium">
4305
+ {getTaskPriorityLabel('medium')}
4306
+ </SelectItem>
4307
+ <SelectItem value="high">
4308
+ {getTaskPriorityLabel('high')}
4309
+ </SelectItem>
4310
+ </SelectContent>
4311
+ </Select>
4312
+ </div>
4141
4313
 
4142
- <div className="space-y-1.5">
4143
- <Label>{t('taskForm.columnLabel')}</Label>
4144
- <Select
4145
- value={taskFormData.status}
4146
- onValueChange={(v) =>
4147
- setTaskFormData((prev) => ({
4148
- ...prev,
4149
- status: v as BoardColumnId,
4150
- }))
4151
- }
4152
- >
4153
- <SelectTrigger className="w-full">
4154
- <SelectValue />
4155
- </SelectTrigger>
4156
- <SelectContent>
4157
- {KANBAN_COLUMNS.map((col) => (
4158
- <SelectItem key={col.id} value={col.id}>
4159
- {col.label}
4314
+ <div className="space-y-1.5">
4315
+ <Label>{t('taskForm.columnLabel')}</Label>
4316
+ <Select
4317
+ value={taskFormData.status}
4318
+ onValueChange={(v) =>
4319
+ setTaskFormData((prev) => ({
4320
+ ...prev,
4321
+ status: v as BoardColumnId,
4322
+ }))
4323
+ }
4324
+ >
4325
+ <SelectTrigger className="w-full">
4326
+ <SelectValue />
4327
+ </SelectTrigger>
4328
+ <SelectContent>
4329
+ {KANBAN_COLUMNS.map((col) => (
4330
+ <SelectItem key={col.id} value={col.id}>
4331
+ {col.label}
4332
+ </SelectItem>
4333
+ ))}
4334
+ </SelectContent>
4335
+ </Select>
4336
+ </div>
4337
+ </div>
4338
+
4339
+ <div className="space-y-1.5">
4340
+ <Label>Responsável</Label>
4341
+ <Select
4342
+ value={taskFormData.assigneeCollaboratorId}
4343
+ onValueChange={(value) =>
4344
+ setTaskFormData((prev) => ({
4345
+ ...prev,
4346
+ assigneeCollaboratorId: value,
4347
+ }))
4348
+ }
4349
+ >
4350
+ <SelectTrigger className="w-full">
4351
+ <SelectValue
4352
+ placeholder={commonT('labels.notAssigned')}
4353
+ />
4354
+ </SelectTrigger>
4355
+ <SelectContent>
4356
+ <SelectItem value="none">
4357
+ {commonT('labels.notAssigned')}
4160
4358
  </SelectItem>
4161
- ))}
4162
- </SelectContent>
4163
- </Select>
4164
- </div>
4165
- </div>
4359
+ {taskAssigneeOptions.map((option) => (
4360
+ <SelectItem key={option.id} value={option.id}>
4361
+ {option.label}
4362
+ </SelectItem>
4363
+ ))}
4364
+ </SelectContent>
4365
+ </Select>
4366
+ </div>
4166
4367
 
4167
- <div className="space-y-1.5">
4168
- <Label>Responsável</Label>
4169
- <Select
4170
- value={taskFormData.assigneeCollaboratorId}
4171
- onValueChange={(value) =>
4172
- setTaskFormData((prev) => ({
4173
- ...prev,
4174
- assigneeCollaboratorId: value,
4175
- }))
4176
- }
4177
- >
4178
- <SelectTrigger className="w-full">
4179
- <SelectValue placeholder={commonT('labels.notAssigned')} />
4180
- </SelectTrigger>
4181
- <SelectContent>
4182
- <SelectItem value="none">
4183
- {commonT('labels.notAssigned')}
4184
- </SelectItem>
4185
- {taskAssigneeOptions.map((option) => (
4186
- <SelectItem key={option.id} value={option.id}>
4187
- {option.label}
4188
- </SelectItem>
4189
- ))}
4190
- </SelectContent>
4191
- </Select>
4192
- </div>
4368
+ <div className="grid grid-cols-2 gap-3">
4369
+ <div className="space-y-1.5">
4370
+ <Label htmlFor="task-due-date">
4371
+ {t('taskForm.deadlineLabel')}
4372
+ </Label>
4373
+ <Input
4374
+ id="task-due-date"
4375
+ type="date"
4376
+ value={taskFormData.dueDate}
4377
+ onChange={(e) =>
4378
+ setTaskFormData((prev) => ({
4379
+ ...prev,
4380
+ dueDate: e.target.value,
4381
+ }))
4382
+ }
4383
+ />
4384
+ </div>
4193
4385
 
4194
- <div className="grid grid-cols-2 gap-3">
4195
- <div className="space-y-1.5">
4196
- <Label htmlFor="task-due-date">
4197
- {t('taskForm.deadlineLabel')}
4198
- </Label>
4199
- <Input
4200
- id="task-due-date"
4201
- type="date"
4202
- value={taskFormData.dueDate}
4203
- onChange={(e) =>
4204
- setTaskFormData((prev) => ({
4205
- ...prev,
4206
- dueDate: e.target.value,
4207
- }))
4208
- }
4209
- />
4210
- </div>
4386
+ <div className="space-y-1.5">
4387
+ <Label htmlFor="task-estimate">
4388
+ {t('taskForm.estimateLabel')}
4389
+ </Label>
4390
+ <Input
4391
+ id="task-estimate"
4392
+ type="number"
4393
+ min="0"
4394
+ step="0.5"
4395
+ placeholder="0"
4396
+ value={taskFormData.estimateHours}
4397
+ onChange={(e) =>
4398
+ setTaskFormData((prev) => ({
4399
+ ...prev,
4400
+ estimateHours: e.target.value,
4401
+ }))
4402
+ }
4403
+ />
4404
+ </div>
4405
+ </div>
4211
4406
 
4212
- <div className="space-y-1.5">
4213
- <Label htmlFor="task-estimate">
4214
- {t('taskForm.estimateLabel')}
4215
- </Label>
4216
- <Input
4217
- id="task-estimate"
4218
- type="number"
4219
- min="0"
4220
- step="0.5"
4221
- placeholder="0"
4222
- value={taskFormData.estimateHours}
4223
- onChange={(e) =>
4224
- setTaskFormData((prev) => ({
4225
- ...prev,
4226
- estimateHours: e.target.value,
4227
- }))
4228
- }
4229
- />
4230
- </div>
4231
- </div>
4407
+ <div className="space-y-1.5">
4408
+ <Label htmlFor="task-tags">{t('taskForm.tagsLabel')}</Label>
4409
+ <Input
4410
+ id="task-tags"
4411
+ placeholder={t('taskForm.tagsPlaceholder')}
4412
+ value={taskFormData.tags}
4413
+ onChange={(e) =>
4414
+ setTaskFormData((prev) => ({
4415
+ ...prev,
4416
+ tags: e.target.value,
4417
+ }))
4418
+ }
4419
+ />
4420
+ </div>
4232
4421
 
4233
- <div className="space-y-1.5">
4234
- <Label htmlFor="task-tags">{t('taskForm.tagsLabel')}</Label>
4235
- <Input
4236
- id="task-tags"
4237
- placeholder={t('taskForm.tagsPlaceholder')}
4238
- value={taskFormData.tags}
4239
- onChange={(e) =>
4240
- setTaskFormData((prev) => ({
4241
- ...prev,
4242
- tags: e.target.value,
4243
- }))
4244
- }
4245
- />
4246
- </div>
4422
+ {editingTaskId ? (
4423
+ <div className="space-y-1.5">
4424
+ <Label className="flex items-center gap-1.5">
4425
+ <Paperclip className="size-3.5" />
4426
+ {t('taskForm.attachmentsLabel')}
4427
+ </Label>
4428
+ <TaskFileAttachments taskId={editingTaskId} />
4429
+ </div>
4430
+ ) : null}
4431
+ </div>
4247
4432
 
4248
- {editingTaskId ? (
4249
- <div className="space-y-1.5">
4250
- <Label className="flex items-center gap-1.5">
4251
- <Paperclip className="size-3.5" />
4252
- {t('taskForm.attachmentsLabel')}
4253
- </Label>
4254
- <TaskFileAttachments taskId={editingTaskId} />
4433
+ <div className="mt-4 flex flex-wrap items-center justify-between gap-2 border-t px-4 pb-4 pt-4">
4434
+ <div className="flex gap-2">
4435
+ {editingTaskId ? (
4436
+ <Button
4437
+ type="button"
4438
+ variant="outline"
4439
+ disabled={
4440
+ taskFormLoading || archivingTaskId === editingTaskId
4441
+ }
4442
+ onClick={() => {
4443
+ if (!editingTaskId) return;
4444
+ const id = editingTaskId;
4445
+ setTaskFormOpen(false);
4446
+ setEditingTaskId(null);
4447
+ setTaskFormData(EMPTY_TASK_FORM);
4448
+ void handleArchiveTask(id);
4449
+ }}
4450
+ >
4451
+ {archivingTaskId === editingTaskId ? (
4452
+ <Loader2 className="mr-2 size-4 animate-spin" />
4453
+ ) : (
4454
+ <Archive className="mr-2 size-4" />
4455
+ )}
4456
+ {commonT('actions.archive')}
4457
+ </Button>
4458
+ ) : null}
4459
+ </div>
4460
+ <div className="flex gap-2">
4461
+ <Button
4462
+ variant="outline"
4463
+ onClick={() => {
4464
+ setTaskFormOpen(false);
4465
+ setEditingTaskId(null);
4466
+ setTaskFormData(EMPTY_TASK_FORM);
4467
+ }}
4468
+ disabled={taskFormLoading}
4469
+ >
4470
+ {commonT('actions.cancel')}
4471
+ </Button>
4472
+ <Button
4473
+ onClick={() => void handleTaskFormSubmit()}
4474
+ disabled={taskFormLoading || !taskFormData.name.trim()}
4475
+ >
4476
+ {taskFormLoading
4477
+ ? t('taskForm.saving')
4478
+ : editingTaskId
4479
+ ? commonT('actions.save')
4480
+ : commonT('actions.create')}
4481
+ </Button>
4482
+ </div>
4255
4483
  </div>
4256
- ) : null}
4257
- </div>
4484
+ </TabsContent>
4258
4485
 
4259
- <div className="mt-4 flex flex-wrap items-center justify-between gap-2 border-t px-4 pb-4 pt-4">
4260
- <div className="flex gap-2">
4261
- {editingTaskId ? (
4262
- <Button
4263
- type="button"
4264
- variant="outline"
4265
- disabled={
4266
- taskFormLoading || archivingTaskId === editingTaskId
4267
- }
4268
- onClick={() => {
4269
- if (!editingTaskId) return;
4270
- const id = editingTaskId;
4271
- setTaskFormOpen(false);
4272
- setEditingTaskId(null);
4273
- setTaskFormData(EMPTY_TASK_FORM);
4274
- void handleArchiveTask(id);
4275
- }}
4276
- >
4277
- {archivingTaskId === editingTaskId ? (
4278
- <Loader2 className="mr-2 size-4 animate-spin" />
4279
- ) : (
4280
- <Archive className="mr-2 size-4" />
4281
- )}
4282
- {commonT('actions.archive')}
4283
- </Button>
4284
- ) : null}
4285
- </div>
4286
- <div className="flex gap-2">
4287
- <Button
4288
- variant="outline"
4289
- onClick={() => {
4290
- setTaskFormOpen(false);
4291
- setEditingTaskId(null);
4292
- setTaskFormData(EMPTY_TASK_FORM);
4293
- }}
4294
- disabled={taskFormLoading}
4295
- >
4296
- {commonT('actions.cancel')}
4297
- </Button>
4298
- <Button
4299
- onClick={() => void handleTaskFormSubmit()}
4300
- disabled={taskFormLoading || !taskFormData.name.trim()}
4486
+ {editingTaskId ? (
4487
+ <TabsContent
4488
+ value="comments"
4489
+ className="min-h-0 flex-1 overflow-y-auto px-4 py-2 data-[state=inactive]:hidden"
4301
4490
  >
4302
- {taskFormLoading
4303
- ? t('taskForm.saving')
4304
- : editingTaskId
4305
- ? commonT('actions.save')
4306
- : commonT('actions.create')}
4307
- </Button>
4308
- </div>
4309
- </div>
4491
+ <TaskCommentsSection taskId={editingTaskId} />
4492
+ </TabsContent>
4493
+ ) : null}
4494
+ </Tabs>
4310
4495
  </SheetContent>
4311
4496
  </Sheet>
4312
4497
  ) : null}