@hed-hog/operations 0.0.322 → 0.0.325

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