@locusai/web 0.1.7 → 0.2.2

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 (346) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/next.config.js +15 -2
  3. package/package.json +26 -3
  4. package/src/app/(auth)/invite/page.tsx +109 -0
  5. package/src/app/(auth)/layout.tsx +19 -0
  6. package/src/app/(auth)/login/page.tsx +65 -0
  7. package/src/app/(auth)/onboarding/workspace/page.tsx +46 -0
  8. package/src/app/(auth)/register/page.tsx +165 -0
  9. package/src/app/(dashboard)/activity/page.tsx +7 -0
  10. package/src/app/(dashboard)/backlog/page.tsx +195 -0
  11. package/src/app/(dashboard)/board/page.tsx +141 -0
  12. package/src/app/(dashboard)/layout.tsx +32 -0
  13. package/src/app/(dashboard)/page.tsx +14 -0
  14. package/src/app/(dashboard)/settings/page.tsx +161 -0
  15. package/src/app/(dashboard)/settings/team/page.tsx +75 -0
  16. package/src/app/globals.css +259 -0
  17. package/src/app/layout.tsx +10 -20
  18. package/src/app/providers.tsx +26 -3
  19. package/src/components/AuthLayoutUI.tsx +53 -0
  20. package/src/components/BoardFilter.tsx +75 -74
  21. package/src/components/CreateModal/CreateModal.tsx +142 -0
  22. package/src/components/CreateModal/index.ts +1 -0
  23. package/src/components/Editor.tsx +279 -0
  24. package/src/components/Header.tsx +99 -12
  25. package/src/components/PageLayout.tsx +69 -0
  26. package/src/components/PropertyItem.tsx +55 -9
  27. package/src/components/Sidebar.tsx +280 -36
  28. package/src/components/SprintCreateModal.tsx +84 -0
  29. package/src/components/TaskCard.tsx +196 -78
  30. package/src/components/TaskCreateModal.tsx +181 -178
  31. package/src/components/TaskPanel.tsx +140 -692
  32. package/src/components/WorkspaceCreateModal.tsx +97 -0
  33. package/src/components/WorkspaceProtected.tsx +91 -0
  34. package/src/components/auth/InviteSteps.tsx +220 -0
  35. package/src/components/auth/LoginSteps.tsx +86 -0
  36. package/src/components/auth/RegisterSteps.tsx +371 -0
  37. package/src/components/auth/index.ts +3 -0
  38. package/src/components/backlog/BacklogList.tsx +92 -0
  39. package/src/components/backlog/BacklogSection.tsx +137 -0
  40. package/src/components/backlog/CompletedSprintItem.tsx +95 -0
  41. package/src/components/backlog/CompletedSprintsSection.tsx +77 -0
  42. package/src/components/backlog/SprintSection.tsx +155 -0
  43. package/src/components/backlog/constants.ts +26 -0
  44. package/src/components/board/BoardColumn.tsx +97 -0
  45. package/src/components/board/BoardContent.tsx +84 -0
  46. package/src/components/board/BoardEmptyState.tsx +82 -0
  47. package/src/components/board/BoardHeader.tsx +47 -0
  48. package/src/components/board/SprintMindmap.tsx +65 -0
  49. package/src/components/board/constants.ts +40 -0
  50. package/src/components/board/index.ts +5 -0
  51. package/src/components/common/ErrorState.tsx +124 -0
  52. package/src/components/common/LoadingState.tsx +83 -0
  53. package/src/components/common/index.ts +40 -0
  54. package/src/components/dashboard/ActivityFeed.tsx +77 -0
  55. package/src/components/dashboard/ActivityItem.tsx +207 -0
  56. package/src/components/dashboard/QuickActions.tsx +50 -0
  57. package/src/components/dashboard/StatCard.tsx +79 -0
  58. package/src/components/dnd/index.tsx +51 -0
  59. package/src/components/docs/DocsEditorArea.tsx +87 -0
  60. package/src/components/docs/DocsHeaderActions.tsx +121 -0
  61. package/src/components/docs/DocsSidebar.tsx +351 -0
  62. package/src/components/index.ts +7 -0
  63. package/src/components/onboarding/StepProgress.tsx +21 -0
  64. package/src/components/onboarding/index.ts +1 -0
  65. package/src/components/settings/ApiKeyConfirmationModal.tsx +81 -0
  66. package/src/components/settings/ApiKeyCreatedModal.tsx +96 -0
  67. package/src/components/settings/ApiKeysList.tsx +143 -0
  68. package/src/components/settings/ApiKeysSettings.tsx +144 -0
  69. package/src/components/settings/InviteMemberModal.tsx +106 -0
  70. package/src/components/settings/ProjectSetupGuide.tsx +147 -0
  71. package/src/components/settings/SettingItem.tsx +32 -0
  72. package/src/components/settings/SettingSection.tsx +50 -0
  73. package/src/components/settings/TeamInvitationsList.tsx +90 -0
  74. package/src/components/settings/TeamMembersList.tsx +95 -0
  75. package/src/components/settings/index.ts +8 -0
  76. package/src/components/task-panel/TaskActivity.tsx +127 -0
  77. package/src/components/task-panel/TaskChecklist.tsx +142 -0
  78. package/src/components/task-panel/TaskDescription.tsx +201 -0
  79. package/src/components/task-panel/TaskDocs.tsx +137 -0
  80. package/src/components/task-panel/TaskHeader.tsx +125 -0
  81. package/src/components/task-panel/TaskProperties.tsx +111 -0
  82. package/src/components/task-panel/index.ts +12 -0
  83. package/src/components/typography/EmptyStateText.tsx +59 -0
  84. package/src/components/typography/MetadataText.tsx +65 -0
  85. package/src/components/typography/SecondaryText.tsx +60 -0
  86. package/src/components/typography/SectionLabel.tsx +60 -0
  87. package/src/components/typography/index.ts +14 -0
  88. package/src/components/typography-scales.tsx +218 -0
  89. package/src/components/ui/Avatar.tsx +123 -0
  90. package/src/components/ui/Badge.tsx +69 -2
  91. package/src/components/ui/Button.tsx +71 -30
  92. package/src/components/ui/Checkbox.tsx +34 -0
  93. package/src/components/ui/Dropdown.tsx +67 -1
  94. package/src/components/ui/EmptyState.tsx +129 -0
  95. package/src/components/ui/Input.tsx +53 -6
  96. package/src/components/ui/Modal.tsx +45 -12
  97. package/src/components/ui/OtpInput.tsx +148 -0
  98. package/src/components/ui/Skeleton.tsx +36 -0
  99. package/src/components/ui/Spinner.tsx +112 -0
  100. package/src/components/ui/Textarea.tsx +28 -3
  101. package/src/components/ui/Toast.tsx +99 -0
  102. package/src/components/ui/Toggle.tsx +63 -0
  103. package/src/components/ui/constants.ts +108 -0
  104. package/src/components/ui/index.ts +7 -0
  105. package/src/context/AuthContext.tsx +140 -0
  106. package/src/context/index.ts +1 -0
  107. package/src/hooks/backlog/index.ts +13 -0
  108. package/src/hooks/backlog/useBacklogActions.ts +144 -0
  109. package/src/hooks/backlog/useBacklogComposite.ts +73 -0
  110. package/src/hooks/backlog/useBacklogData.ts +74 -0
  111. package/src/hooks/backlog/useBacklogDragDrop.ts +118 -0
  112. package/src/hooks/backlog/useBacklogUI.ts +74 -0
  113. package/src/hooks/index.ts +22 -0
  114. package/src/hooks/task-panel/index.ts +13 -0
  115. package/src/hooks/task-panel/useTaskActions.ts +200 -0
  116. package/src/hooks/task-panel/useTaskComputedValues.ts +30 -0
  117. package/src/hooks/task-panel/useTaskData.ts +78 -0
  118. package/src/hooks/task-panel/useTaskPanelComposite.ts +161 -0
  119. package/src/hooks/task-panel/useTaskUIState.ts +80 -0
  120. package/src/hooks/useAuthLayoutLogic.ts +43 -0
  121. package/src/hooks/useAuthenticatedUser.ts +36 -0
  122. package/src/hooks/useAuthenticatedUserWithOrg.ts +41 -0
  123. package/src/hooks/useBacklog.ts +303 -0
  124. package/src/hooks/useBoard.ts +230 -0
  125. package/src/hooks/useDashboardLayout.ts +49 -0
  126. package/src/hooks/useDocs.ts +279 -0
  127. package/src/hooks/useDocsQuery.ts +99 -0
  128. package/src/hooks/useDocsSidebarState.ts +104 -0
  129. package/src/hooks/useFormState.ts +40 -0
  130. package/src/hooks/useGlobalKeydowns.ts +52 -0
  131. package/src/hooks/useInviteForm.ts +122 -0
  132. package/src/hooks/useLoginForm.ts +84 -0
  133. package/src/hooks/useMutationWithToast.ts +56 -0
  134. package/src/hooks/useOrganizationQuery.ts +55 -0
  135. package/src/hooks/useRegisterForm.ts +216 -0
  136. package/src/hooks/useSprintsQuery.ts +38 -0
  137. package/src/hooks/useTaskDescription.ts +102 -0
  138. package/src/hooks/useTaskPanel.ts +341 -0
  139. package/src/hooks/useTasksQuery.ts +39 -0
  140. package/src/hooks/useTeamManagement.ts +92 -0
  141. package/src/hooks/useWorkspaceCreateForm.ts +70 -0
  142. package/src/hooks/useWorkspaceId.ts +29 -0
  143. package/src/lib/api-client.ts +40 -23
  144. package/src/lib/config.ts +25 -0
  145. package/src/lib/constants.ts +83 -0
  146. package/src/lib/options.ts +96 -0
  147. package/src/lib/query-keys.ts +91 -0
  148. package/src/lib/typography.ts +103 -0
  149. package/src/lib/utils.ts +4 -0
  150. package/src/lib/validation.ts +192 -0
  151. package/src/services/index.ts +7 -3
  152. package/src/services/notifications.ts +80 -0
  153. package/src/utils/env.utils.ts +15 -0
  154. package/src/views/ActivityView.tsx +123 -0
  155. package/src/views/Dashboard.tsx +126 -0
  156. package/src/views/Docs.tsx +98 -612
  157. package/tsconfig.tsbuildinfo +1 -1
  158. package/.next/BUILD_ID +0 -1
  159. package/.next/app-build-manifest.json +0 -55
  160. package/.next/app-path-routes-manifest.json +0 -8
  161. package/.next/build-manifest.json +0 -33
  162. package/.next/cache/.previewinfo +0 -1
  163. package/.next/cache/.rscinfo +0 -1
  164. package/.next/cache/.tsbuildinfo +0 -1
  165. package/.next/cache/config.json +0 -7
  166. package/.next/cache/webpack/client-production/0.pack +0 -0
  167. package/.next/cache/webpack/client-production/index.pack +0 -0
  168. package/.next/cache/webpack/edge-server-production/0.pack +0 -0
  169. package/.next/cache/webpack/edge-server-production/index.pack +0 -0
  170. package/.next/cache/webpack/server-production/0.pack +0 -0
  171. package/.next/cache/webpack/server-production/index.pack +0 -0
  172. package/.next/diagnostics/build-diagnostics.json +0 -6
  173. package/.next/diagnostics/framework.json +0 -1
  174. package/.next/export-detail.json +0 -5
  175. package/.next/export-marker.json +0 -6
  176. package/.next/images-manifest.json +0 -57
  177. package/.next/next-minimal-server.js.nft.json +0 -1
  178. package/.next/next-server.js.nft.json +0 -1
  179. package/.next/package.json +0 -1
  180. package/.next/prerender-manifest.json +0 -137
  181. package/.next/react-loadable-manifest.json +0 -32
  182. package/.next/required-server-files.json +0 -324
  183. package/.next/routes-manifest.json +0 -77
  184. package/.next/server/app/_not-found/page.js +0 -2
  185. package/.next/server/app/_not-found/page.js.nft.json +0 -1
  186. package/.next/server/app/_not-found/page_client-reference-manifest.js +0 -1
  187. package/.next/server/app/_not-found.html +0 -1
  188. package/.next/server/app/_not-found.meta +0 -8
  189. package/.next/server/app/_not-found.rsc +0 -21
  190. package/.next/server/app/backlog/page.js +0 -2
  191. package/.next/server/app/backlog/page.js.nft.json +0 -1
  192. package/.next/server/app/backlog/page_client-reference-manifest.js +0 -1
  193. package/.next/server/app/backlog.html +0 -1
  194. package/.next/server/app/backlog.meta +0 -7
  195. package/.next/server/app/backlog.rsc +0 -25
  196. package/.next/server/app/docs/page.js +0 -97
  197. package/.next/server/app/docs/page.js.nft.json +0 -1
  198. package/.next/server/app/docs/page_client-reference-manifest.js +0 -1
  199. package/.next/server/app/docs.html +0 -1
  200. package/.next/server/app/docs.meta +0 -7
  201. package/.next/server/app/docs.rsc +0 -26
  202. package/.next/server/app/favicon.ico/route.js +0 -1
  203. package/.next/server/app/favicon.ico/route.js.nft.json +0 -1
  204. package/.next/server/app/favicon.ico.body +0 -0
  205. package/.next/server/app/favicon.ico.meta +0 -1
  206. package/.next/server/app/index.html +0 -1
  207. package/.next/server/app/index.meta +0 -7
  208. package/.next/server/app/index.rsc +0 -25
  209. package/.next/server/app/page.js +0 -2
  210. package/.next/server/app/page.js.nft.json +0 -1
  211. package/.next/server/app/page_client-reference-manifest.js +0 -1
  212. package/.next/server/app/settings/page.js +0 -2
  213. package/.next/server/app/settings/page.js.nft.json +0 -1
  214. package/.next/server/app/settings/page_client-reference-manifest.js +0 -1
  215. package/.next/server/app/settings.html +0 -1
  216. package/.next/server/app/settings.meta +0 -7
  217. package/.next/server/app/settings.rsc +0 -25
  218. package/.next/server/app-paths-manifest.json +0 -8
  219. package/.next/server/chunks/496.js +0 -6
  220. package/.next/server/chunks/585.js +0 -1
  221. package/.next/server/chunks/665.js +0 -1
  222. package/.next/server/chunks/699.js +0 -22
  223. package/.next/server/chunks/852.js +0 -7
  224. package/.next/server/functions-config-manifest.json +0 -4
  225. package/.next/server/interception-route-rewrite-manifest.js +0 -1
  226. package/.next/server/middleware-build-manifest.js +0 -1
  227. package/.next/server/middleware-manifest.json +0 -6
  228. package/.next/server/middleware-react-loadable-manifest.js +0 -1
  229. package/.next/server/next-font-manifest.js +0 -1
  230. package/.next/server/next-font-manifest.json +0 -1
  231. package/.next/server/pages/404.html +0 -1
  232. package/.next/server/pages/500.html +0 -1
  233. package/.next/server/pages/_app.js +0 -1
  234. package/.next/server/pages/_app.js.nft.json +0 -1
  235. package/.next/server/pages/_document.js +0 -1
  236. package/.next/server/pages/_document.js.nft.json +0 -1
  237. package/.next/server/pages/_error.js +0 -19
  238. package/.next/server/pages/_error.js.nft.json +0 -1
  239. package/.next/server/pages-manifest.json +0 -6
  240. package/.next/server/server-reference-manifest.js +0 -1
  241. package/.next/server/server-reference-manifest.json +0 -1
  242. package/.next/server/webpack-runtime.js +0 -1
  243. package/.next/static/D0NXe04ZCLNDckV_quc8g/_buildManifest.js +0 -1
  244. package/.next/static/D0NXe04ZCLNDckV_quc8g/_ssgManifest.js +0 -1
  245. package/.next/static/chunks/138.b98511c56423f8bb.js +0 -1
  246. package/.next/static/chunks/146-34259952c594a3b0.js +0 -1
  247. package/.next/static/chunks/337-d3bb75304d130513.js +0 -1
  248. package/.next/static/chunks/477.1a6ecfe53375bd9c.js +0 -1
  249. package/.next/static/chunks/487-1808785ba665f784.js +0 -1
  250. package/.next/static/chunks/544.a9569941cc886e9d.js +0 -1
  251. package/.next/static/chunks/87c73c54-1f4741035a95c140.js +0 -1
  252. package/.next/static/chunks/902-d6926825a9fe8784.js +0 -1
  253. package/.next/static/chunks/955-c8f8f6235ae8f8c6.js +0 -1
  254. package/.next/static/chunks/996.e0a334e6ae90900e.js +0 -1
  255. package/.next/static/chunks/app/_not-found/page-44b1804abb44a34d.js +0 -1
  256. package/.next/static/chunks/app/backlog/page-dce1450769bfae8f.js +0 -1
  257. package/.next/static/chunks/app/docs/page-1efee819f25492cb.js +0 -1
  258. package/.next/static/chunks/app/layout-05f504c042b9f7ee.js +0 -1
  259. package/.next/static/chunks/app/page-3fd91aaaa4776ced.js +0 -1
  260. package/.next/static/chunks/app/settings/page-84e16c9638d657e4.js +0 -1
  261. package/.next/static/chunks/framework-152a1bc8c81c7458.js +0 -1
  262. package/.next/static/chunks/main-843ab130fc1be309.js +0 -1
  263. package/.next/static/chunks/main-app-123e879c5a937a00.js +0 -1
  264. package/.next/static/chunks/pages/_app-a050a8e6e4fb04cf.js +0 -1
  265. package/.next/static/chunks/pages/_error-3e422ffd891594de.js +0 -1
  266. package/.next/static/chunks/polyfills-42372ed130431b0a.js +0 -1
  267. package/.next/static/chunks/webpack-99a10a055b5bb9c4.js +0 -1
  268. package/.next/static/css/13e8617b72f9d3aa.css +0 -1
  269. package/.next/static/css/8aea088cdc4338f0.css +0 -1
  270. package/.next/static/css/b301ab0424111664.css +0 -1
  271. package/.next/static/media/24c15609eaa28576-s.woff2 +0 -0
  272. package/.next/static/media/2c07349e02a7b712-s.woff2 +0 -0
  273. package/.next/static/media/456105d6ea6d39e0-s.woff2 +0 -0
  274. package/.next/static/media/47cbc4e2adbc5db9-s.p.woff2 +0 -0
  275. package/.next/static/media/4f77bef990aad698-s.woff2 +0 -0
  276. package/.next/static/media/627d916fd739a539-s.woff2 +0 -0
  277. package/.next/static/media/63b255f18bea0ca9-s.woff2 +0 -0
  278. package/.next/static/media/70bd82ac89b4fa42-s.woff2 +0 -0
  279. package/.next/static/media/84602850c8fd81c3-s.woff2 +0 -0
  280. package/.next/trace +0 -46
  281. package/.next/types/app/backlog/page.ts +0 -84
  282. package/.next/types/app/docs/page.ts +0 -84
  283. package/.next/types/app/layout.ts +0 -84
  284. package/.next/types/app/page.ts +0 -84
  285. package/.next/types/app/settings/page.ts +0 -84
  286. package/.next/types/cache-life.d.ts +0 -141
  287. package/.next/types/package.json +0 -1
  288. package/next-env.d.ts +0 -5
  289. package/out/404.html +0 -1
  290. package/out/_next/static/D0NXe04ZCLNDckV_quc8g/_buildManifest.js +0 -1
  291. package/out/_next/static/D0NXe04ZCLNDckV_quc8g/_ssgManifest.js +0 -1
  292. package/out/_next/static/chunks/138.b98511c56423f8bb.js +0 -1
  293. package/out/_next/static/chunks/146-34259952c594a3b0.js +0 -1
  294. package/out/_next/static/chunks/337-d3bb75304d130513.js +0 -1
  295. package/out/_next/static/chunks/477.1a6ecfe53375bd9c.js +0 -1
  296. package/out/_next/static/chunks/487-1808785ba665f784.js +0 -1
  297. package/out/_next/static/chunks/544.a9569941cc886e9d.js +0 -1
  298. package/out/_next/static/chunks/87c73c54-1f4741035a95c140.js +0 -1
  299. package/out/_next/static/chunks/902-d6926825a9fe8784.js +0 -1
  300. package/out/_next/static/chunks/955-c8f8f6235ae8f8c6.js +0 -1
  301. package/out/_next/static/chunks/996.e0a334e6ae90900e.js +0 -1
  302. package/out/_next/static/chunks/app/_not-found/page-44b1804abb44a34d.js +0 -1
  303. package/out/_next/static/chunks/app/backlog/page-dce1450769bfae8f.js +0 -1
  304. package/out/_next/static/chunks/app/docs/page-1efee819f25492cb.js +0 -1
  305. package/out/_next/static/chunks/app/layout-05f504c042b9f7ee.js +0 -1
  306. package/out/_next/static/chunks/app/page-3fd91aaaa4776ced.js +0 -1
  307. package/out/_next/static/chunks/app/settings/page-84e16c9638d657e4.js +0 -1
  308. package/out/_next/static/chunks/framework-152a1bc8c81c7458.js +0 -1
  309. package/out/_next/static/chunks/main-843ab130fc1be309.js +0 -1
  310. package/out/_next/static/chunks/main-app-123e879c5a937a00.js +0 -1
  311. package/out/_next/static/chunks/pages/_app-a050a8e6e4fb04cf.js +0 -1
  312. package/out/_next/static/chunks/pages/_error-3e422ffd891594de.js +0 -1
  313. package/out/_next/static/chunks/polyfills-42372ed130431b0a.js +0 -1
  314. package/out/_next/static/chunks/webpack-99a10a055b5bb9c4.js +0 -1
  315. package/out/_next/static/css/13e8617b72f9d3aa.css +0 -1
  316. package/out/_next/static/css/8aea088cdc4338f0.css +0 -1
  317. package/out/_next/static/css/b301ab0424111664.css +0 -1
  318. package/out/_next/static/media/24c15609eaa28576-s.woff2 +0 -0
  319. package/out/_next/static/media/2c07349e02a7b712-s.woff2 +0 -0
  320. package/out/_next/static/media/456105d6ea6d39e0-s.woff2 +0 -0
  321. package/out/_next/static/media/47cbc4e2adbc5db9-s.p.woff2 +0 -0
  322. package/out/_next/static/media/4f77bef990aad698-s.woff2 +0 -0
  323. package/out/_next/static/media/627d916fd739a539-s.woff2 +0 -0
  324. package/out/_next/static/media/63b255f18bea0ca9-s.woff2 +0 -0
  325. package/out/_next/static/media/70bd82ac89b4fa42-s.woff2 +0 -0
  326. package/out/_next/static/media/84602850c8fd81c3-s.woff2 +0 -0
  327. package/out/backlog.html +0 -1
  328. package/out/backlog.txt +0 -25
  329. package/out/docs.html +0 -1
  330. package/out/docs.txt +0 -26
  331. package/out/favicon.ico +0 -0
  332. package/out/index.html +0 -1
  333. package/out/index.txt +0 -25
  334. package/out/logo.png +0 -0
  335. package/out/settings.html +0 -1
  336. package/out/settings.txt +0 -25
  337. package/src/app/backlog/page.tsx +0 -19
  338. package/src/app/page.tsx +0 -16
  339. package/src/app/settings/page.tsx +0 -194
  340. package/src/hooks/useTasks.ts +0 -119
  341. package/src/services/doc.service.ts +0 -27
  342. package/src/services/sprint.service.ts +0 -24
  343. package/src/services/task.service.ts +0 -75
  344. package/src/views/Backlog.tsx +0 -691
  345. package/src/views/Board.tsx +0 -306
  346. /package/src/app/{docs → (dashboard)/docs}/page.tsx +0 -0
@@ -1,46 +1,50 @@
1
+ /**
2
+ * Task Panel Component
3
+ *
4
+ * Side panel for displaying and editing task details.
5
+ * Shows title, description, properties, activity, artifacts, checklists, and docs.
6
+ * Integrates with `useTaskPanel` hook for comprehensive state management.
7
+ *
8
+ * Features:
9
+ * - Task details and description editing
10
+ * - Task properties (status, priority, assignee, deadline)
11
+ * - Activity feed and comments
12
+ * - Checklists
13
+ * - Related documents
14
+ * - Task approval (if in verification state)
15
+ * - Task deletion
16
+ *
17
+ * @example
18
+ * <TaskPanel
19
+ * taskId="task-123"
20
+ * onClose={handleClose}
21
+ * onDeleted={handleDeleted}
22
+ * onUpdated={handleUpdated}
23
+ * />
24
+ */
25
+
1
26
  "use client";
2
27
 
3
- import {
4
- type AcceptanceItem,
5
- AssigneeRole,
6
- EventType,
7
- type Task,
8
- type Event as TaskEvent,
9
- TaskPriority,
10
- TaskStatus,
11
- } from "@locusai/shared";
12
- import { format, formatDistanceToNow } from "date-fns";
13
28
  import { motion } from "framer-motion";
29
+ import { useTaskPanel } from "@/hooks/useTaskPanel";
14
30
  import {
15
- CheckCircle,
16
- ChevronRight,
17
- Edit,
18
- FileText,
19
- Lock,
20
- MessageSquare,
21
- PlusSquare,
22
- Tag,
23
- Terminal,
24
- Trash2,
25
- Unlock,
26
- X,
27
- } from "lucide-react";
28
- import { useCallback, useEffect, useState } from "react";
29
- import { PropertyItem } from "@/components";
30
- import {
31
- Button,
32
- Checkbox,
33
- Input,
34
- PriorityBadge,
35
- StatusBadge,
36
- Textarea,
37
- } from "@/components/ui";
38
- import { taskService } from "@/services";
31
+ TaskActivity,
32
+ TaskChecklist,
33
+ TaskDescription,
34
+ TaskDocs,
35
+ TaskHeader,
36
+ TaskProperties,
37
+ } from "./task-panel";
38
+ import { Button, Textarea } from "./ui";
39
39
 
40
40
  interface TaskPanelProps {
41
- taskId: number;
41
+ /** ID of the task to display */
42
+ taskId: string;
43
+ /** Called when closing the panel */
42
44
  onClose: () => void;
45
+ /** Called after task is deleted */
43
46
  onDeleted: () => void;
47
+ /** Called after task is updated */
44
48
  onUpdated: () => void;
45
49
  }
46
50
 
@@ -50,203 +54,30 @@ export function TaskPanel({
50
54
  onDeleted,
51
55
  onUpdated,
52
56
  }: TaskPanelProps) {
53
- const [task, setTask] = useState<Task | null>(null);
54
- const [isEditingTitle, setIsEditingTitle] = useState(false);
55
- const [editTitle, setEditTitle] = useState("");
56
- const [editDesc, setEditDesc] = useState("");
57
- const [newComment, setNewComment] = useState("");
58
- const [newChecklistItem, setNewChecklistItem] = useState("");
59
- const [descMode, setDescMode] = useState<"edit" | "preview">("preview");
60
- const [showRejectModal, setShowRejectModal] = useState(false);
61
- const [rejectReason, setRejectReason] = useState("");
62
-
63
- const formatDate = (date: string | number | Date) => {
64
- return format(new Date(date), "MMM d, yyyy");
65
- };
66
-
67
- const fetchTask = useCallback(async () => {
68
- try {
69
- const taskData = await taskService.getById(taskId);
70
- const initializedTask: Task = {
71
- ...taskData,
72
- acceptanceChecklist: taskData.acceptanceChecklist || [],
73
- artifacts: taskData.artifacts || [],
74
- activityLog: taskData.activityLog || [],
75
- comments: taskData.comments || [],
76
- };
77
- setTask(initializedTask);
78
- setEditTitle(taskData.title);
79
- setEditDesc(taskData.description || "");
80
- } catch (err) {
81
- console.error("Failed to fetch task:", err);
82
- }
83
- }, [taskId]);
84
-
85
- useEffect(() => {
86
- fetchTask();
87
- }, [fetchTask]);
88
-
89
- const handleUpdateTask = async (updates: Partial<Task>) => {
90
- try {
91
- await taskService.update(taskId, updates);
92
- fetchTask();
93
- onUpdated();
94
- } catch (err) {
95
- console.error("Failed to update task:", err);
96
- }
97
- };
98
-
99
- const handleDelete = async () => {
100
- if (
101
- !confirm(
102
- "Are you sure you want to delete this task? This action cannot be undone."
103
- )
104
- ) {
105
- return;
106
- }
107
- try {
108
- await taskService.delete(taskId);
109
- onDeleted();
110
- onClose();
111
- } catch (err) {
112
- console.error("Failed to delete task:", err);
113
- }
114
- };
115
-
116
- const handleTitleSave = () => {
117
- if (editTitle.trim() && editTitle !== task?.title) {
118
- handleUpdateTask({ title: editTitle.trim() });
119
- }
120
- setIsEditingTitle(false);
121
- };
122
-
123
- const handleDescSave = () => {
124
- if (editDesc !== task?.description) {
125
- handleUpdateTask({ description: editDesc });
126
- }
127
- };
128
-
129
- const handleAddChecklistItem = () => {
130
- if (!newChecklistItem.trim() || !task) return;
131
- const newItem: AcceptanceItem = {
132
- id: crypto.randomUUID(),
133
- text: newChecklistItem.trim(),
134
- done: false,
135
- };
136
- handleUpdateTask({
137
- acceptanceChecklist: [...task.acceptanceChecklist, newItem],
138
- });
139
- setNewChecklistItem("");
140
- };
141
-
142
- const handleToggleChecklistItem = (itemId: string) => {
143
- if (!task) return;
144
- const updated = task.acceptanceChecklist.map((item) =>
145
- item.id === itemId ? { ...item, done: !item.done } : item
146
- );
147
- handleUpdateTask({ acceptanceChecklist: updated });
148
- };
149
-
150
- const handleRemoveChecklistItem = (itemId: string) => {
151
- if (!task) return;
152
- const updated = task.acceptanceChecklist.filter(
153
- (item) => item.id !== itemId
154
- );
155
- handleUpdateTask({ acceptanceChecklist: updated });
156
- };
157
-
158
- const handleAddComment = async () => {
159
- if (!newComment.trim()) return;
160
- try {
161
- await taskService.addComment(taskId, {
162
- author: "Human",
163
- text: newComment,
164
- });
165
- setNewComment("");
166
- await fetchTask();
167
- } catch (err) {
168
- console.error("Failed to add comment:", err);
169
- }
170
- };
171
-
172
- const handleRunCi = async (preset: string) => {
173
- try {
174
- const data = await taskService.runCi(taskId, preset);
175
- alert(data.summary);
176
- fetchTask();
177
- } catch (err) {
178
- console.error("Failed to run CI:", err);
179
- }
180
- };
181
-
182
- const handleLock = async () => {
183
- try {
184
- await taskService.lock(taskId, "human", 3600);
185
- fetchTask();
186
- onUpdated();
187
- } catch (err) {
188
- console.error("Failed to lock task:", err);
189
- }
190
- };
191
-
192
- const handleUnlock = async () => {
193
- try {
194
- await taskService.unlock(taskId, "human");
195
- fetchTask();
196
- onUpdated();
197
- } catch (err) {
198
- console.error("Failed to unlock task:", err);
199
- }
200
- };
201
-
202
- const handleReject = async () => {
203
- if (!rejectReason.trim()) return;
204
- try {
205
- // Move back to IN_PROGRESS
206
- await taskService.update(taskId, { status: TaskStatus.IN_PROGRESS });
207
- // Add rejection comment
208
- await taskService.addComment(taskId, {
209
- author: "Manager",
210
- text: `❌ **Rejected**: ${rejectReason}`,
211
- });
212
- setShowRejectModal(false);
213
- setRejectReason("");
214
- fetchTask();
215
- onUpdated();
216
- } catch (err) {
217
- console.error("Failed to reject task:", err);
218
- }
219
- };
220
-
221
- const handleApprove = async () => {
222
- try {
223
- await taskService.update(taskId, { status: TaskStatus.DONE });
224
- fetchTask();
225
- onUpdated();
226
- } catch (err) {
227
- console.error("Failed to approve task:", err);
228
- }
229
- };
230
-
231
- if (!task) {
232
- return (
233
- <div className="fixed top-0 right-0 bottom-0 w-[1152px] max-w-[95vw] bg-background border-l border-border z-950 flex items-center justify-center">
234
- <div className="text-muted-foreground animate-pulse font-medium">
235
- Loading task specs...
236
- </div>
237
- </div>
238
- );
239
- }
240
-
241
- const isLocked =
242
- task.lockedBy && (!task.lockExpiresAt || task.lockExpiresAt > Date.now());
243
- const checklistProgress = task.acceptanceChecklist.length
244
- ? Math.round(
245
- (task.acceptanceChecklist.filter((i) => i.done).length /
246
- task.acceptanceChecklist.length) *
247
- 100
248
- )
249
- : 0;
57
+ const {
58
+ task,
59
+ isLoading,
60
+ isDeleting,
61
+ newComment,
62
+ setNewComment,
63
+ newChecklistItem,
64
+ setNewChecklistItem,
65
+ showRejectModal,
66
+ setShowRejectModal,
67
+ rejectReason,
68
+ setRejectReason,
69
+ checklistProgress,
70
+ handleUpdateTask,
71
+ handleDelete,
72
+ handleAddChecklistItem,
73
+ handleToggleChecklistItem,
74
+ handleRemoveChecklistItem,
75
+ handleAddComment,
76
+ handleReject,
77
+ handleApprove,
78
+ handleLinkDoc,
79
+ handleUnlinkDoc,
80
+ } = useTaskPanel({ taskId, onUpdated, onDeleted, onClose });
250
81
 
251
82
  return (
252
83
  <>
@@ -254,6 +85,7 @@ export function TaskPanel({
254
85
  initial={{ opacity: 0 }}
255
86
  animate={{ opacity: 1 }}
256
87
  exit={{ opacity: 0 }}
88
+ transition={{ duration: 0.3 }}
257
89
  className="fixed inset-0 bg-black/60 backdrop-blur-sm z-940"
258
90
  onClick={onClose}
259
91
  />
@@ -261,505 +93,121 @@ export function TaskPanel({
261
93
  initial={{ x: "100%" }}
262
94
  animate={{ x: 0 }}
263
95
  exit={{ x: "100%" }}
264
- transition={{ type: "spring", damping: 25, stiffness: 300 }}
265
- className="fixed top-0 right-0 bottom-0 w-[1000px] max-w-[95vw] bg-background border-l border-border z-950 flex flex-col shadow-[-20px_0_80px_rgba(0,0,0,0.6)]"
96
+ transition={{ ease: [0.23, 1, 0.32, 1], duration: 0.5 }}
97
+ className="fixed top-0 right-0 bottom-0 w-[1000px] max-w-[95vw] bg-background border-l border-border z-950 flex flex-col shadow-2xl"
266
98
  >
267
- <header className="flex items-center gap-6 px-10 border-b border-border bg-card/50 backdrop-blur-md h-[84px] shrink-0">
268
- <button
269
- className="p-2.5 rounded-xl text-muted-foreground hover:bg-secondary hover:text-foreground hover:scale-105 transition-all duration-200 border border-transparent hover:border-border"
270
- onClick={onClose}
271
- >
272
- <ChevronRight size={20} />
273
- </button>
274
-
275
- <div className="flex-1 min-w-0">
276
- <span className="text-[10px] font-bold uppercase tracking-[0.2em] text-muted-foreground/60 mb-1 block">
277
- Reference: #{task.id}
278
- </span>
279
- <div className="flex gap-3">
280
- <StatusBadge status={task.status} />
281
- <PriorityBadge priority={task.priority || TaskPriority.MEDIUM} />
282
- </div>
283
- </div>
284
-
285
- <div className="flex items-center gap-2">
286
- <Button
287
- size="icon"
288
- variant="ghost"
289
- onClick={() => handleRunCi("quick")}
290
- title="Run Quality Checks"
291
- className="h-10 w-10 hover:bg-primary/10 hover:text-primary transition-all rounded-xl"
292
- >
293
- <Terminal size={18} />
294
- </Button>
295
- {isLocked ? (
296
- <Button
297
- size="icon"
298
- variant="ghost"
299
- onClick={handleUnlock}
300
- title="Unlock"
301
- className="h-10 w-10 text-amber-500 bg-amber-500/10 hover:bg-amber-500/20 rounded-xl"
302
- >
303
- <Unlock size={18} />
304
- </Button>
305
- ) : (
306
- <Button
307
- size="icon"
308
- variant="ghost"
309
- onClick={handleLock}
310
- title="Lock"
311
- className="h-10 w-10 hover:bg-primary/10 hover:text-primary rounded-xl"
312
- >
313
- <Lock size={18} />
314
- </Button>
315
- )}
316
- <div className="w-px h-6 bg-border mx-1" />
317
- {task.status === TaskStatus.VERIFICATION && (
318
- <>
319
- <Button
320
- variant="danger"
321
- size="sm"
322
- onClick={() => setShowRejectModal(true)}
323
- className="h-10 px-4 rounded-xl"
324
- >
325
- Reject
326
- </Button>
327
- <Button
328
- size="sm"
329
- variant="success"
330
- onClick={handleApprove}
331
- className="h-10 px-4 rounded-xl"
332
- >
333
- <CheckCircle size={16} className="mr-2" />
334
- Approve
335
- </Button>
336
- <div className="w-px h-6 bg-border mx-1" />
337
- </>
338
- )}
339
- <Button
340
- size="icon"
341
- variant="danger"
342
- onClick={handleDelete}
343
- title="Delete"
344
- className="h-10 w-10 hover:scale-105 active:scale-95 transition-transform rounded-xl"
345
- >
346
- <Trash2 size={18} />
347
- </Button>
348
- </div>
349
- </header>
350
-
351
- <div className="flex-1 grid grid-cols-[1fr_360px] overflow-hidden min-h-0">
352
- <div className="p-6 overflow-y-auto scrollbar-thin">
353
- {/* Title Section */}
354
- <div className="mb-6">
355
- {isEditingTitle ? (
356
- <Input
357
- value={editTitle}
358
- onChange={(e) => setEditTitle(e.target.value)}
359
- onBlur={handleTitleSave}
360
- onKeyDown={(e) => {
361
- if (e.key === "Enter") handleTitleSave();
362
- if (e.key === "Escape") {
363
- setEditTitle(task.title);
364
- setIsEditingTitle(false);
365
- }
366
- }}
367
- className="text-2xl h-14 font-bold tracking-tight"
368
- autoFocus
369
- />
370
- ) : (
371
- <h2
372
- className="text-2xl font-bold tracking-tight hover:text-primary transition-all cursor-pointer leading-tight group"
373
- onClick={() => setIsEditingTitle(true)}
374
- >
375
- {task.title}
376
- <Edit
377
- size={18}
378
- className="inline-block ml-3 opacity-0 group-hover:opacity-40 transition-opacity"
379
- />
380
- </h2>
381
- )}
382
- </div>
383
-
384
- {/* Description Section */}
385
- <div className="mb-6">
386
- <div className="flex items-center justify-between mb-3">
387
- <div className="flex items-center gap-3">
388
- <div className="h-8 w-8 rounded-lg bg-primary/10 flex items-center justify-center text-primary">
389
- <FileText size={16} />
390
- </div>
391
- <h4 className="text-sm font-bold uppercase tracking-widest text-foreground/80">
392
- Documentation
393
- </h4>
394
- </div>
395
- <div className="flex bg-secondary/30 p-1 rounded-xl">
396
- <button
397
- className={`px-4 py-1.5 rounded-lg text-xs font-bold transition-all ${descMode === "preview" ? "bg-background shadow-md text-foreground scale-105" : "text-muted-foreground hover:text-foreground"}`}
398
- onClick={() => setDescMode("preview")}
399
- >
400
- Visual
401
- </button>
402
- <button
403
- className={`px-4 py-1.5 rounded-lg text-xs font-bold transition-all ${descMode === "edit" ? "bg-background shadow-md text-foreground scale-105" : "text-muted-foreground hover:text-foreground"}`}
404
- onClick={() => setDescMode("edit")}
405
- >
406
- Markdown
407
- </button>
408
- </div>
409
- </div>
410
-
411
- {descMode === "edit" ? (
412
- <div className="group border border-border rounded-2xl overflow-hidden focus-within:ring-2 focus-within:ring-primary/20 transition-all bg-secondary/5">
413
- <Textarea
414
- value={editDesc}
415
- onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) =>
416
- setEditDesc(e.target.value)
417
- }
418
- placeholder="Describe the implementation details, edge cases, and technical requirements..."
419
- rows={10}
420
- className="border-none focus:ring-0 text-base leading-relaxed p-6 bg-transparent"
421
- onBlur={handleDescSave}
422
- />
423
- </div>
424
- ) : (
425
- <div className="prose prose-invert max-w-none bg-secondary/10 p-8 rounded-2xl border border-border/50 shadow-inner">
426
- {task.description ? (
427
- <p className="text-foreground/90 leading-relaxed whitespace-pre-wrap">
428
- {task.description}
429
- </p>
430
- ) : (
431
- <span className="text-muted-foreground/30 italic text-sm select-none">
432
- Waiting for technical documentation...
433
- </span>
434
- )}
435
- </div>
436
- )}
437
- </div>
438
-
439
- {/* Acceptance Checklist */}
440
- <div className="mb-4">
441
- <div className="flex items-center justify-between mb-4">
442
- <div className="flex items-center gap-3">
443
- <div className="h-8 w-8 rounded-lg bg-sky-500/10 flex items-center justify-center text-sky-500">
444
- <CheckCircle size={16} />
445
- </div>
446
- <h4 className="text-sm font-bold uppercase tracking-widest text-foreground/80">
447
- Definition of Done
448
- </h4>
449
- </div>
450
- <div className="flex items-center gap-4">
451
- <div className="flex flex-col items-end">
452
- <span className="text-[10px] font-black uppercase tracking-[0.2em] text-muted-foreground mb-1">
453
- Status
454
- </span>
455
- <span className="text-sm font-mono font-black text-sky-500">
456
- {checklistProgress}%
457
- </span>
458
- </div>
459
- <div className="w-48 h-2 bg-secondary/30 rounded-full overflow-hidden border border-border/50">
460
- <div
461
- className="h-full bg-sky-500 transition-all duration-1000 ease-out shadow-[0_0_20px_rgba(14,165,233,0.5)]"
462
- style={{ width: `${checklistProgress}%` }}
463
- />
464
- </div>
465
- </div>
466
- </div>
467
-
468
- <div className="grid gap-2 mb-4">
469
- {task.acceptanceChecklist.length === 0 && (
470
- <div className="text-center py-6 border-2 border-dashed border-border/40 rounded-xl group hover:border-accent/40 transition-colors">
471
- <p className="text-[10px] font-bold uppercase tracking-[0.3em] text-muted-foreground/40 mb-2 italic">
472
- Standard quality checks required
473
- </p>
474
- <Button
475
- variant="ghost"
476
- size="sm"
477
- onClick={() => setNewChecklistItem("Add unit tests")}
478
- className="text-xs hover:text-sky-500"
479
- >
480
- Suggest Criteria
481
- </Button>
482
- </div>
483
- )}
484
- {task.acceptanceChecklist.map((item) => (
485
- <div
486
- key={item.id}
487
- className="group flex items-center gap-3 p-3 bg-secondary/10 border border-border/40 rounded-xl hover:border-sky-500/50 hover:bg-secondary/20 transition-all duration-300 shadow-sm"
488
- >
489
- <Checkbox
490
- checked={item.done}
491
- onChange={() => handleToggleChecklistItem(item.id)}
492
- />
493
- <span
494
- className={`flex-1 text-sm font-semibold transition-all duration-300 ${item.done ? "line-through text-muted-foreground/40 scale-[0.98] translate-x-1" : "text-foreground"}`}
495
- >
496
- {item.text}
497
- </span>
498
- <Button
499
- size="icon"
500
- variant="ghost"
501
- className="opacity-0 group-hover:opacity-100 h-8 w-8 text-muted-foreground hover:text-destructive hover:bg-destructive/10 transition-all rounded-lg"
502
- onClick={() => handleRemoveChecklistItem(item.id)}
503
- >
504
- <X size={14} />
505
- </Button>
506
- </div>
507
- ))}
508
- </div>
509
-
510
- <div className="flex gap-3 p-2 bg-secondary/5 rounded-xl border border-border/40">
511
- <Input
512
- value={newChecklistItem}
513
- onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
514
- setNewChecklistItem(e.target.value)
515
- }
516
- placeholder="Add quality criteria..."
517
- className="h-10 bg-transparent border-none focus:ring-0 text-sm font-medium"
518
- onKeyDown={(e: React.KeyboardEvent<HTMLInputElement>) => {
519
- if (e.key === "Enter") handleAddChecklistItem();
520
- }}
521
- />
522
- <Button
523
- onClick={handleAddChecklistItem}
524
- className="px-5 h-10 bg-foreground text-background font-bold text-xs tracking-wide hover:scale-105 active:scale-95 transition-all rounded-lg"
525
- >
526
- Append
527
- </Button>
99
+ {!task ? (
100
+ <div className="flex-1 flex items-center justify-center">
101
+ <div className="flex flex-col items-center gap-4">
102
+ <div className="w-12 h-12 border-4 border-primary/20 border-t-primary rounded-full animate-spin" />
103
+ <div className="text-muted-foreground font-black uppercase tracking-[0.2em] text-[10px] animate-pulse">
104
+ Synchronizing Task Stream...
528
105
  </div>
529
106
  </div>
530
107
  </div>
108
+ ) : (
109
+ <>
110
+ <TaskHeader
111
+ task={task}
112
+ isLoading={isLoading}
113
+ isDeleting={isDeleting}
114
+ onClose={onClose}
115
+ onDelete={handleDelete}
116
+ onApprove={handleApprove}
117
+ onReject={() => setShowRejectModal(true)}
118
+ />
531
119
 
532
- <div className="p-4 overflow-y-auto border-l border-border bg-secondary/10 backdrop-blur-3xl shadow-[inset_1px_0_0_rgba(255,255,255,0.02)] scrollbar-thin">
533
- {/* Properties Section */}
534
- <div className="mb-6">
535
- <h4 className="text-[10px] font-black uppercase tracking-[0.2em] text-muted-foreground/60 mb-4 pb-2 border-b border-border/40">
536
- Specifications
537
- </h4>
538
- <div className="space-y-2">
539
- <PropertyItem
540
- label="Status"
541
- value={task.status}
542
- onEdit={(newValue: string) =>
543
- handleUpdateTask({ status: newValue as TaskStatus })
544
- }
545
- options={Object.values(TaskStatus)}
546
- type="dropdown"
547
- />
548
- <PropertyItem
549
- label="Assignee Group"
550
- value={task.assigneeRole || "Unassigned"}
551
- onEdit={(newValue: string) =>
552
- handleUpdateTask({ assigneeRole: newValue as AssigneeRole })
553
- }
554
- options={Object.values(AssigneeRole)}
555
- type="dropdown"
556
- />
557
- <PropertyItem
558
- label="Priority"
559
- value={task.priority || TaskPriority.MEDIUM}
560
- onEdit={(newValue: string) =>
561
- handleUpdateTask({ priority: newValue as TaskPriority })
562
- }
563
- options={Object.values(TaskPriority)}
564
- type="dropdown"
120
+ <div className="flex-1 grid grid-cols-[1fr_360px] overflow-hidden min-h-0">
121
+ <div className="flex flex-col overflow-y-auto scrollbar-thin">
122
+ <TaskDescription
123
+ task={task}
124
+ isLoading={isLoading}
125
+ onUpdate={handleUpdateTask}
565
126
  />
566
- <PropertyItem
567
- label="Deadline"
568
- value={
569
- task.dueDate ? formatDate(task.dueDate) : "Not Defined"
570
- }
571
- onEdit={(newValue: string) =>
572
- handleUpdateTask({ dueDate: newValue || null })
573
- }
574
- type="date"
575
- />
576
- <PropertyItem
577
- label="Owner"
578
- value={task.assignedTo || "Open Seat"}
579
- onEdit={(newValue: string) =>
580
- handleUpdateTask({ assignedTo: newValue || null })
581
- }
582
- type="text"
583
- />
584
- </div>
585
- </div>
586
-
587
- {/* Artifacts Section */}
588
- <div className="mb-6">
589
- <h4 className="text-[10px] font-black uppercase tracking-[0.2em] text-muted-foreground/60 mb-4 pb-2 border-b border-border/40">
590
- Generated Assets
591
- </h4>
592
- <div className="grid gap-2">
593
- {task.artifacts.length > 0 ? (
594
- task.artifacts.map((artifact) => (
595
- <a
596
- key={artifact.id}
597
- href={artifact.url}
598
- target="_blank"
599
- rel="noopener noreferrer"
600
- className="group flex items-center gap-3 p-3 bg-background/50 border border-border/40 rounded-xl hover:border-primary/50 hover:bg-background transition-all duration-300 shadow-sm"
601
- >
602
- <div className="h-8 w-8 bg-primary/10 rounded-lg flex items-center justify-center text-primary group-hover:scale-110 group-hover:bg-primary group-hover:text-primary-foreground transition-all duration-500">
603
- <FileText size={14} />
604
- </div>
605
- <div className="flex-1 min-w-0">
606
- <span className="block text-xs font-black truncate text-foreground/90">
607
- {artifact.title}
608
- </span>
609
- <span className="text-[9px] text-muted-foreground font-black uppercase tracking-widest mt-1 block">
610
- {artifact.type} • {artifact.size}
611
- </span>
612
- </div>
613
- </a>
614
- ))
615
- ) : (
616
- <div className="py-6 text-center bg-background/20 rounded-xl border border-dashed border-border/40">
617
- <p className="text-[9px] font-black uppercase tracking-[0.2em] text-muted-foreground/30 italic">
618
- No output generated
619
- </p>
620
- </div>
621
- )}
622
- </div>
623
- </div>
624
-
625
- {/* Activity Feed */}
626
- <div>
627
- <h4 className="text-[10px] font-black uppercase tracking-[0.2em] text-muted-foreground/60 mb-4 pb-2 border-b border-border/40">
628
- Activity Stream
629
- </h4>
630
127
 
631
- <div className="flex gap-2 mb-4">
632
- <Input
633
- value={newComment}
634
- onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
635
- setNewComment(e.target.value)
636
- }
637
- placeholder="Post an update..."
638
- className="h-11 text-xs bg-background/40 border-border/40 rounded-xl focus:bg-background"
639
- onKeyDown={(e: React.KeyboardEvent<HTMLInputElement>) => {
640
- if (e.key === "Enter") handleAddComment();
641
- }}
128
+ <TaskChecklist
129
+ task={task}
130
+ isLoading={isLoading}
131
+ checklistProgress={checklistProgress}
132
+ newChecklistItem={newChecklistItem}
133
+ setNewChecklistItem={setNewChecklistItem}
134
+ handleAddChecklistItem={handleAddChecklistItem}
135
+ handleToggleChecklistItem={handleToggleChecklistItem}
136
+ handleRemoveChecklistItem={handleRemoveChecklistItem}
642
137
  />
643
- <Button
644
- onClick={handleAddComment}
645
- variant="secondary"
646
- className="h-9 px-4 rounded-lg group"
647
- >
648
- <MessageSquare
649
- size={14}
650
- className="group-hover:rotate-12 transition-transform"
651
- />
652
- </Button>
653
138
  </div>
654
139
 
655
- <div className="space-y-8 max-h-[500px] overflow-y-auto pr-3 scrollbar-none hover:scrollbar-thin transition-all">
656
- {task.activityLog.map((event: TaskEvent) => (
657
- <div key={event.id} className="relative flex gap-5 group">
658
- <div className="absolute left-[17px] top-8 bottom-[-24px] w-px bg-border/40 group-last:hidden" />
659
- <div className="h-9 w-9 rounded-xl bg-card border border-border/60 flex items-center justify-center shrink-0 z-10 shadow-sm group-hover:border-primary/40 transition-colors">
660
- {event.type === EventType.COMMENT_ADDED && (
661
- <MessageSquare size={14} className="text-blue-500" />
662
- )}
663
- {event.type === EventType.STATUS_CHANGED && (
664
- <Tag size={14} className="text-amber-500" />
665
- )}
666
- {event.type === EventType.TASK_CREATED && (
667
- <PlusSquare size={14} className="text-emerald-400" />
668
- )}
669
- {event.type === EventType.TASK_UPDATED && (
670
- <Edit size={14} className="text-primary" />
671
- )}
672
- {event.type === EventType.ARTIFACT_ADDED && (
673
- <FileText size={14} className="text-purple-400" />
674
- )}
675
- {(event.type === EventType.LOCKED ||
676
- event.type === EventType.UNLOCKED) && (
677
- <Lock size={14} className="text-rose-400" />
678
- )}
679
- {event.type === EventType.CI_RAN && (
680
- <CheckCircle size={14} className="text-accent" />
681
- )}
682
- </div>
683
- <div className="pt-1.5 min-w-0">
684
- <p className="text-xs font-bold text-foreground/90 leading-snug mb-1.5">
685
- {formatActivityEvent(event)}
686
- </p>
687
- <span className="text-[10px] font-black uppercase tracking-[0.15em] text-muted-foreground/50">
688
- {formatDistanceToNow(new Date(event.createdAt), {
689
- addSuffix: true,
690
- })}
691
- </span>
692
- </div>
693
- </div>
694
- ))}
140
+ <div className="flex flex-col border-l border-border bg-secondary/10 backdrop-blur-3xl shadow-[inset_1px_0_0_rgba(255,255,255,0.02)] overflow-hidden">
141
+ <div className="flex-1 overflow-y-auto p-6 space-y-8 scrollbar-thin">
142
+ <TaskProperties
143
+ task={task}
144
+ isLoading={isLoading}
145
+ onUpdate={handleUpdateTask}
146
+ />
147
+ <TaskDocs
148
+ task={task}
149
+ onLinkDoc={handleLinkDoc}
150
+ onUnlinkDoc={handleUnlinkDoc}
151
+ />
152
+ <TaskActivity
153
+ task={task}
154
+ isLoading={isLoading}
155
+ newComment={newComment}
156
+ setNewComment={setNewComment}
157
+ handleAddComment={handleAddComment}
158
+ />
159
+ </div>
695
160
  </div>
696
161
  </div>
697
- </div>
698
- </div>
162
+ </>
163
+ )}
699
164
  </motion.div>
700
165
 
701
166
  {/* Rejection Modal */}
702
167
  {showRejectModal && (
703
- <div className="fixed inset-0 bg-black/70 backdrop-blur-sm z-960 flex items-center justify-center">
704
- <div className="bg-background border border-border rounded-2xl p-6 w-[480px] shadow-2xl">
705
- <h3 className="text-lg font-bold mb-4">Reject Task</h3>
706
- <p className="text-sm text-muted-foreground mb-4">
707
- This task will be moved back to IN_PROGRESS. The same agent will
708
- receive your feedback and can fix the issues.
168
+ <div className="fixed inset-0 bg-black/80 backdrop-blur-md z-960 flex items-center justify-center p-6 animate-in fade-in duration-300">
169
+ <motion.div
170
+ initial={{ scale: 0.95, opacity: 0 }}
171
+ animate={{ scale: 1, opacity: 1 }}
172
+ className="bg-card border border-border/40 rounded-3xl p-8 w-full max-w-[500px] shadow-2xl shadow-black"
173
+ >
174
+ <h3 className="text-xl font-black uppercase tracking-widest text-destructive mb-4">
175
+ Reject Mission
176
+ </h3>
177
+ <p className="text-sm text-muted-foreground/80 mb-6 font-medium leading-relaxed">
178
+ This task will be demoted to IN_PROGRESS. Assigned operators will
179
+ receive your feedback for recalibration.
709
180
  </p>
710
181
  <Textarea
711
182
  value={rejectReason}
712
183
  onChange={(e) => setRejectReason(e.target.value)}
713
- placeholder="Explain what needs to be fixed..."
714
- className="min-h-[120px] mb-4"
184
+ placeholder="Detailed failure report and corrective actions..."
185
+ className="min-h-[140px] mb-6 bg-secondary/20 border-border/40 rounded-2xl p-4 font-bold text-sm focus:ring-destructive/20"
715
186
  autoFocus
716
187
  />
717
- <div className="flex gap-3 justify-end">
188
+ <div className="flex gap-4 justify-end">
718
189
  <Button
719
190
  variant="ghost"
720
191
  onClick={() => {
721
192
  setShowRejectModal(false);
722
193
  setRejectReason("");
723
194
  }}
195
+ className="px-6 rounded-xl font-black uppercase tracking-widest text-[10px]"
724
196
  >
725
197
  Cancel
726
198
  </Button>
727
199
  <Button
728
200
  variant="danger"
729
201
  onClick={handleReject}
730
- disabled={!rejectReason.trim()}
202
+ disabled={!rejectReason.trim() || isLoading}
203
+ className="px-8 rounded-xl font-black uppercase tracking-widest text-[10px] shadow-lg shadow-destructive/20"
731
204
  >
732
- Reject Task
205
+ {isLoading ? "Processing..." : "Confirm Rejection"}
733
206
  </Button>
734
207
  </div>
735
- </div>
208
+ </motion.div>
736
209
  </div>
737
210
  )}
738
211
  </>
739
212
  );
740
213
  }
741
-
742
- function formatActivityEvent(event: TaskEvent): string {
743
- const { type, payload } = event;
744
- const p = payload as Record<string, string | number | undefined>;
745
- switch (type) {
746
- case EventType.STATUS_CHANGED:
747
- return `Status moved ${p.oldStatus} ➟ ${p.newStatus}`;
748
- case EventType.COMMENT_ADDED:
749
- return `${p.author}: "${p.text}"`;
750
- case EventType.TASK_CREATED:
751
- return "Task initialized";
752
- case EventType.TASK_UPDATED:
753
- return "Parameters calibrated";
754
- case EventType.ARTIFACT_ADDED:
755
- return `Output: ${p.title}`;
756
- case EventType.LOCKED:
757
- return `Protected by ${p.agentId}`;
758
- case EventType.UNLOCKED:
759
- return "Protection released";
760
- case EventType.CI_RAN:
761
- return `Valuation complete: ${p.summary}`;
762
- default:
763
- return (type as string).replace(/_/g, " ").toLowerCase();
764
- }
765
- }