@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.
- package/CHANGELOG.md +26 -0
- package/next.config.js +15 -2
- package/package.json +26 -3
- package/src/app/(auth)/invite/page.tsx +109 -0
- package/src/app/(auth)/layout.tsx +19 -0
- package/src/app/(auth)/login/page.tsx +65 -0
- package/src/app/(auth)/onboarding/workspace/page.tsx +46 -0
- package/src/app/(auth)/register/page.tsx +165 -0
- package/src/app/(dashboard)/activity/page.tsx +7 -0
- package/src/app/(dashboard)/backlog/page.tsx +195 -0
- package/src/app/(dashboard)/board/page.tsx +141 -0
- package/src/app/(dashboard)/layout.tsx +32 -0
- package/src/app/(dashboard)/page.tsx +14 -0
- package/src/app/(dashboard)/settings/page.tsx +161 -0
- package/src/app/(dashboard)/settings/team/page.tsx +75 -0
- package/src/app/globals.css +259 -0
- package/src/app/layout.tsx +10 -20
- package/src/app/providers.tsx +26 -3
- package/src/components/AuthLayoutUI.tsx +53 -0
- package/src/components/BoardFilter.tsx +75 -74
- package/src/components/CreateModal/CreateModal.tsx +142 -0
- package/src/components/CreateModal/index.ts +1 -0
- package/src/components/Editor.tsx +279 -0
- package/src/components/Header.tsx +99 -12
- package/src/components/PageLayout.tsx +69 -0
- package/src/components/PropertyItem.tsx +55 -9
- package/src/components/Sidebar.tsx +280 -36
- package/src/components/SprintCreateModal.tsx +84 -0
- package/src/components/TaskCard.tsx +196 -78
- package/src/components/TaskCreateModal.tsx +181 -178
- package/src/components/TaskPanel.tsx +140 -692
- package/src/components/WorkspaceCreateModal.tsx +97 -0
- package/src/components/WorkspaceProtected.tsx +91 -0
- package/src/components/auth/InviteSteps.tsx +220 -0
- package/src/components/auth/LoginSteps.tsx +86 -0
- package/src/components/auth/RegisterSteps.tsx +371 -0
- package/src/components/auth/index.ts +3 -0
- package/src/components/backlog/BacklogList.tsx +92 -0
- package/src/components/backlog/BacklogSection.tsx +137 -0
- package/src/components/backlog/CompletedSprintItem.tsx +95 -0
- package/src/components/backlog/CompletedSprintsSection.tsx +77 -0
- package/src/components/backlog/SprintSection.tsx +155 -0
- package/src/components/backlog/constants.ts +26 -0
- package/src/components/board/BoardColumn.tsx +97 -0
- package/src/components/board/BoardContent.tsx +84 -0
- package/src/components/board/BoardEmptyState.tsx +82 -0
- package/src/components/board/BoardHeader.tsx +47 -0
- package/src/components/board/SprintMindmap.tsx +65 -0
- package/src/components/board/constants.ts +40 -0
- package/src/components/board/index.ts +5 -0
- package/src/components/common/ErrorState.tsx +124 -0
- package/src/components/common/LoadingState.tsx +83 -0
- package/src/components/common/index.ts +40 -0
- package/src/components/dashboard/ActivityFeed.tsx +77 -0
- package/src/components/dashboard/ActivityItem.tsx +207 -0
- package/src/components/dashboard/QuickActions.tsx +50 -0
- package/src/components/dashboard/StatCard.tsx +79 -0
- package/src/components/dnd/index.tsx +51 -0
- package/src/components/docs/DocsEditorArea.tsx +87 -0
- package/src/components/docs/DocsHeaderActions.tsx +121 -0
- package/src/components/docs/DocsSidebar.tsx +351 -0
- package/src/components/index.ts +7 -0
- package/src/components/onboarding/StepProgress.tsx +21 -0
- package/src/components/onboarding/index.ts +1 -0
- package/src/components/settings/ApiKeyConfirmationModal.tsx +81 -0
- package/src/components/settings/ApiKeyCreatedModal.tsx +96 -0
- package/src/components/settings/ApiKeysList.tsx +143 -0
- package/src/components/settings/ApiKeysSettings.tsx +144 -0
- package/src/components/settings/InviteMemberModal.tsx +106 -0
- package/src/components/settings/ProjectSetupGuide.tsx +147 -0
- package/src/components/settings/SettingItem.tsx +32 -0
- package/src/components/settings/SettingSection.tsx +50 -0
- package/src/components/settings/TeamInvitationsList.tsx +90 -0
- package/src/components/settings/TeamMembersList.tsx +95 -0
- package/src/components/settings/index.ts +8 -0
- package/src/components/task-panel/TaskActivity.tsx +127 -0
- package/src/components/task-panel/TaskChecklist.tsx +142 -0
- package/src/components/task-panel/TaskDescription.tsx +201 -0
- package/src/components/task-panel/TaskDocs.tsx +137 -0
- package/src/components/task-panel/TaskHeader.tsx +125 -0
- package/src/components/task-panel/TaskProperties.tsx +111 -0
- package/src/components/task-panel/index.ts +12 -0
- package/src/components/typography/EmptyStateText.tsx +59 -0
- package/src/components/typography/MetadataText.tsx +65 -0
- package/src/components/typography/SecondaryText.tsx +60 -0
- package/src/components/typography/SectionLabel.tsx +60 -0
- package/src/components/typography/index.ts +14 -0
- package/src/components/typography-scales.tsx +218 -0
- package/src/components/ui/Avatar.tsx +123 -0
- package/src/components/ui/Badge.tsx +69 -2
- package/src/components/ui/Button.tsx +71 -30
- package/src/components/ui/Checkbox.tsx +34 -0
- package/src/components/ui/Dropdown.tsx +67 -1
- package/src/components/ui/EmptyState.tsx +129 -0
- package/src/components/ui/Input.tsx +53 -6
- package/src/components/ui/Modal.tsx +45 -12
- package/src/components/ui/OtpInput.tsx +148 -0
- package/src/components/ui/Skeleton.tsx +36 -0
- package/src/components/ui/Spinner.tsx +112 -0
- package/src/components/ui/Textarea.tsx +28 -3
- package/src/components/ui/Toast.tsx +99 -0
- package/src/components/ui/Toggle.tsx +63 -0
- package/src/components/ui/constants.ts +108 -0
- package/src/components/ui/index.ts +7 -0
- package/src/context/AuthContext.tsx +140 -0
- package/src/context/index.ts +1 -0
- package/src/hooks/backlog/index.ts +13 -0
- package/src/hooks/backlog/useBacklogActions.ts +144 -0
- package/src/hooks/backlog/useBacklogComposite.ts +73 -0
- package/src/hooks/backlog/useBacklogData.ts +74 -0
- package/src/hooks/backlog/useBacklogDragDrop.ts +118 -0
- package/src/hooks/backlog/useBacklogUI.ts +74 -0
- package/src/hooks/index.ts +22 -0
- package/src/hooks/task-panel/index.ts +13 -0
- package/src/hooks/task-panel/useTaskActions.ts +200 -0
- package/src/hooks/task-panel/useTaskComputedValues.ts +30 -0
- package/src/hooks/task-panel/useTaskData.ts +78 -0
- package/src/hooks/task-panel/useTaskPanelComposite.ts +161 -0
- package/src/hooks/task-panel/useTaskUIState.ts +80 -0
- package/src/hooks/useAuthLayoutLogic.ts +43 -0
- package/src/hooks/useAuthenticatedUser.ts +36 -0
- package/src/hooks/useAuthenticatedUserWithOrg.ts +41 -0
- package/src/hooks/useBacklog.ts +303 -0
- package/src/hooks/useBoard.ts +230 -0
- package/src/hooks/useDashboardLayout.ts +49 -0
- package/src/hooks/useDocs.ts +279 -0
- package/src/hooks/useDocsQuery.ts +99 -0
- package/src/hooks/useDocsSidebarState.ts +104 -0
- package/src/hooks/useFormState.ts +40 -0
- package/src/hooks/useGlobalKeydowns.ts +52 -0
- package/src/hooks/useInviteForm.ts +122 -0
- package/src/hooks/useLoginForm.ts +84 -0
- package/src/hooks/useMutationWithToast.ts +56 -0
- package/src/hooks/useOrganizationQuery.ts +55 -0
- package/src/hooks/useRegisterForm.ts +216 -0
- package/src/hooks/useSprintsQuery.ts +38 -0
- package/src/hooks/useTaskDescription.ts +102 -0
- package/src/hooks/useTaskPanel.ts +341 -0
- package/src/hooks/useTasksQuery.ts +39 -0
- package/src/hooks/useTeamManagement.ts +92 -0
- package/src/hooks/useWorkspaceCreateForm.ts +70 -0
- package/src/hooks/useWorkspaceId.ts +29 -0
- package/src/lib/api-client.ts +40 -23
- package/src/lib/config.ts +25 -0
- package/src/lib/constants.ts +83 -0
- package/src/lib/options.ts +96 -0
- package/src/lib/query-keys.ts +91 -0
- package/src/lib/typography.ts +103 -0
- package/src/lib/utils.ts +4 -0
- package/src/lib/validation.ts +192 -0
- package/src/services/index.ts +7 -3
- package/src/services/notifications.ts +80 -0
- package/src/utils/env.utils.ts +15 -0
- package/src/views/ActivityView.tsx +123 -0
- package/src/views/Dashboard.tsx +126 -0
- package/src/views/Docs.tsx +98 -612
- package/tsconfig.tsbuildinfo +1 -1
- package/.next/BUILD_ID +0 -1
- package/.next/app-build-manifest.json +0 -55
- package/.next/app-path-routes-manifest.json +0 -8
- package/.next/build-manifest.json +0 -33
- package/.next/cache/.previewinfo +0 -1
- package/.next/cache/.rscinfo +0 -1
- package/.next/cache/.tsbuildinfo +0 -1
- package/.next/cache/config.json +0 -7
- package/.next/cache/webpack/client-production/0.pack +0 -0
- package/.next/cache/webpack/client-production/index.pack +0 -0
- package/.next/cache/webpack/edge-server-production/0.pack +0 -0
- package/.next/cache/webpack/edge-server-production/index.pack +0 -0
- package/.next/cache/webpack/server-production/0.pack +0 -0
- package/.next/cache/webpack/server-production/index.pack +0 -0
- package/.next/diagnostics/build-diagnostics.json +0 -6
- package/.next/diagnostics/framework.json +0 -1
- package/.next/export-detail.json +0 -5
- package/.next/export-marker.json +0 -6
- package/.next/images-manifest.json +0 -57
- package/.next/next-minimal-server.js.nft.json +0 -1
- package/.next/next-server.js.nft.json +0 -1
- package/.next/package.json +0 -1
- package/.next/prerender-manifest.json +0 -137
- package/.next/react-loadable-manifest.json +0 -32
- package/.next/required-server-files.json +0 -324
- package/.next/routes-manifest.json +0 -77
- package/.next/server/app/_not-found/page.js +0 -2
- package/.next/server/app/_not-found/page.js.nft.json +0 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +0 -1
- package/.next/server/app/_not-found.html +0 -1
- package/.next/server/app/_not-found.meta +0 -8
- package/.next/server/app/_not-found.rsc +0 -21
- package/.next/server/app/backlog/page.js +0 -2
- package/.next/server/app/backlog/page.js.nft.json +0 -1
- package/.next/server/app/backlog/page_client-reference-manifest.js +0 -1
- package/.next/server/app/backlog.html +0 -1
- package/.next/server/app/backlog.meta +0 -7
- package/.next/server/app/backlog.rsc +0 -25
- package/.next/server/app/docs/page.js +0 -97
- package/.next/server/app/docs/page.js.nft.json +0 -1
- package/.next/server/app/docs/page_client-reference-manifest.js +0 -1
- package/.next/server/app/docs.html +0 -1
- package/.next/server/app/docs.meta +0 -7
- package/.next/server/app/docs.rsc +0 -26
- package/.next/server/app/favicon.ico/route.js +0 -1
- package/.next/server/app/favicon.ico/route.js.nft.json +0 -1
- package/.next/server/app/favicon.ico.body +0 -0
- package/.next/server/app/favicon.ico.meta +0 -1
- package/.next/server/app/index.html +0 -1
- package/.next/server/app/index.meta +0 -7
- package/.next/server/app/index.rsc +0 -25
- package/.next/server/app/page.js +0 -2
- package/.next/server/app/page.js.nft.json +0 -1
- package/.next/server/app/page_client-reference-manifest.js +0 -1
- package/.next/server/app/settings/page.js +0 -2
- package/.next/server/app/settings/page.js.nft.json +0 -1
- package/.next/server/app/settings/page_client-reference-manifest.js +0 -1
- package/.next/server/app/settings.html +0 -1
- package/.next/server/app/settings.meta +0 -7
- package/.next/server/app/settings.rsc +0 -25
- package/.next/server/app-paths-manifest.json +0 -8
- package/.next/server/chunks/496.js +0 -6
- package/.next/server/chunks/585.js +0 -1
- package/.next/server/chunks/665.js +0 -1
- package/.next/server/chunks/699.js +0 -22
- package/.next/server/chunks/852.js +0 -7
- package/.next/server/functions-config-manifest.json +0 -4
- package/.next/server/interception-route-rewrite-manifest.js +0 -1
- package/.next/server/middleware-build-manifest.js +0 -1
- package/.next/server/middleware-manifest.json +0 -6
- package/.next/server/middleware-react-loadable-manifest.js +0 -1
- package/.next/server/next-font-manifest.js +0 -1
- package/.next/server/next-font-manifest.json +0 -1
- package/.next/server/pages/404.html +0 -1
- package/.next/server/pages/500.html +0 -1
- package/.next/server/pages/_app.js +0 -1
- package/.next/server/pages/_app.js.nft.json +0 -1
- package/.next/server/pages/_document.js +0 -1
- package/.next/server/pages/_document.js.nft.json +0 -1
- package/.next/server/pages/_error.js +0 -19
- package/.next/server/pages/_error.js.nft.json +0 -1
- package/.next/server/pages-manifest.json +0 -6
- package/.next/server/server-reference-manifest.js +0 -1
- package/.next/server/server-reference-manifest.json +0 -1
- package/.next/server/webpack-runtime.js +0 -1
- package/.next/static/D0NXe04ZCLNDckV_quc8g/_buildManifest.js +0 -1
- package/.next/static/D0NXe04ZCLNDckV_quc8g/_ssgManifest.js +0 -1
- package/.next/static/chunks/138.b98511c56423f8bb.js +0 -1
- package/.next/static/chunks/146-34259952c594a3b0.js +0 -1
- package/.next/static/chunks/337-d3bb75304d130513.js +0 -1
- package/.next/static/chunks/477.1a6ecfe53375bd9c.js +0 -1
- package/.next/static/chunks/487-1808785ba665f784.js +0 -1
- package/.next/static/chunks/544.a9569941cc886e9d.js +0 -1
- package/.next/static/chunks/87c73c54-1f4741035a95c140.js +0 -1
- package/.next/static/chunks/902-d6926825a9fe8784.js +0 -1
- package/.next/static/chunks/955-c8f8f6235ae8f8c6.js +0 -1
- package/.next/static/chunks/996.e0a334e6ae90900e.js +0 -1
- package/.next/static/chunks/app/_not-found/page-44b1804abb44a34d.js +0 -1
- package/.next/static/chunks/app/backlog/page-dce1450769bfae8f.js +0 -1
- package/.next/static/chunks/app/docs/page-1efee819f25492cb.js +0 -1
- package/.next/static/chunks/app/layout-05f504c042b9f7ee.js +0 -1
- package/.next/static/chunks/app/page-3fd91aaaa4776ced.js +0 -1
- package/.next/static/chunks/app/settings/page-84e16c9638d657e4.js +0 -1
- package/.next/static/chunks/framework-152a1bc8c81c7458.js +0 -1
- package/.next/static/chunks/main-843ab130fc1be309.js +0 -1
- package/.next/static/chunks/main-app-123e879c5a937a00.js +0 -1
- package/.next/static/chunks/pages/_app-a050a8e6e4fb04cf.js +0 -1
- package/.next/static/chunks/pages/_error-3e422ffd891594de.js +0 -1
- package/.next/static/chunks/polyfills-42372ed130431b0a.js +0 -1
- package/.next/static/chunks/webpack-99a10a055b5bb9c4.js +0 -1
- package/.next/static/css/13e8617b72f9d3aa.css +0 -1
- package/.next/static/css/8aea088cdc4338f0.css +0 -1
- package/.next/static/css/b301ab0424111664.css +0 -1
- package/.next/static/media/24c15609eaa28576-s.woff2 +0 -0
- package/.next/static/media/2c07349e02a7b712-s.woff2 +0 -0
- package/.next/static/media/456105d6ea6d39e0-s.woff2 +0 -0
- package/.next/static/media/47cbc4e2adbc5db9-s.p.woff2 +0 -0
- package/.next/static/media/4f77bef990aad698-s.woff2 +0 -0
- package/.next/static/media/627d916fd739a539-s.woff2 +0 -0
- package/.next/static/media/63b255f18bea0ca9-s.woff2 +0 -0
- package/.next/static/media/70bd82ac89b4fa42-s.woff2 +0 -0
- package/.next/static/media/84602850c8fd81c3-s.woff2 +0 -0
- package/.next/trace +0 -46
- package/.next/types/app/backlog/page.ts +0 -84
- package/.next/types/app/docs/page.ts +0 -84
- package/.next/types/app/layout.ts +0 -84
- package/.next/types/app/page.ts +0 -84
- package/.next/types/app/settings/page.ts +0 -84
- package/.next/types/cache-life.d.ts +0 -141
- package/.next/types/package.json +0 -1
- package/next-env.d.ts +0 -5
- package/out/404.html +0 -1
- package/out/_next/static/D0NXe04ZCLNDckV_quc8g/_buildManifest.js +0 -1
- package/out/_next/static/D0NXe04ZCLNDckV_quc8g/_ssgManifest.js +0 -1
- package/out/_next/static/chunks/138.b98511c56423f8bb.js +0 -1
- package/out/_next/static/chunks/146-34259952c594a3b0.js +0 -1
- package/out/_next/static/chunks/337-d3bb75304d130513.js +0 -1
- package/out/_next/static/chunks/477.1a6ecfe53375bd9c.js +0 -1
- package/out/_next/static/chunks/487-1808785ba665f784.js +0 -1
- package/out/_next/static/chunks/544.a9569941cc886e9d.js +0 -1
- package/out/_next/static/chunks/87c73c54-1f4741035a95c140.js +0 -1
- package/out/_next/static/chunks/902-d6926825a9fe8784.js +0 -1
- package/out/_next/static/chunks/955-c8f8f6235ae8f8c6.js +0 -1
- package/out/_next/static/chunks/996.e0a334e6ae90900e.js +0 -1
- package/out/_next/static/chunks/app/_not-found/page-44b1804abb44a34d.js +0 -1
- package/out/_next/static/chunks/app/backlog/page-dce1450769bfae8f.js +0 -1
- package/out/_next/static/chunks/app/docs/page-1efee819f25492cb.js +0 -1
- package/out/_next/static/chunks/app/layout-05f504c042b9f7ee.js +0 -1
- package/out/_next/static/chunks/app/page-3fd91aaaa4776ced.js +0 -1
- package/out/_next/static/chunks/app/settings/page-84e16c9638d657e4.js +0 -1
- package/out/_next/static/chunks/framework-152a1bc8c81c7458.js +0 -1
- package/out/_next/static/chunks/main-843ab130fc1be309.js +0 -1
- package/out/_next/static/chunks/main-app-123e879c5a937a00.js +0 -1
- package/out/_next/static/chunks/pages/_app-a050a8e6e4fb04cf.js +0 -1
- package/out/_next/static/chunks/pages/_error-3e422ffd891594de.js +0 -1
- package/out/_next/static/chunks/polyfills-42372ed130431b0a.js +0 -1
- package/out/_next/static/chunks/webpack-99a10a055b5bb9c4.js +0 -1
- package/out/_next/static/css/13e8617b72f9d3aa.css +0 -1
- package/out/_next/static/css/8aea088cdc4338f0.css +0 -1
- package/out/_next/static/css/b301ab0424111664.css +0 -1
- package/out/_next/static/media/24c15609eaa28576-s.woff2 +0 -0
- package/out/_next/static/media/2c07349e02a7b712-s.woff2 +0 -0
- package/out/_next/static/media/456105d6ea6d39e0-s.woff2 +0 -0
- package/out/_next/static/media/47cbc4e2adbc5db9-s.p.woff2 +0 -0
- package/out/_next/static/media/4f77bef990aad698-s.woff2 +0 -0
- package/out/_next/static/media/627d916fd739a539-s.woff2 +0 -0
- package/out/_next/static/media/63b255f18bea0ca9-s.woff2 +0 -0
- package/out/_next/static/media/70bd82ac89b4fa42-s.woff2 +0 -0
- package/out/_next/static/media/84602850c8fd81c3-s.woff2 +0 -0
- package/out/backlog.html +0 -1
- package/out/backlog.txt +0 -25
- package/out/docs.html +0 -1
- package/out/docs.txt +0 -26
- package/out/favicon.ico +0 -0
- package/out/index.html +0 -1
- package/out/index.txt +0 -25
- package/out/logo.png +0 -0
- package/out/settings.html +0 -1
- package/out/settings.txt +0 -25
- package/src/app/backlog/page.tsx +0 -19
- package/src/app/page.tsx +0 -16
- package/src/app/settings/page.tsx +0 -194
- package/src/hooks/useTasks.ts +0 -119
- package/src/services/doc.service.ts +0 -27
- package/src/services/sprint.service.ts +0 -24
- package/src/services/task.service.ts +0 -75
- package/src/views/Backlog.tsx +0 -691
- package/src/views/Board.tsx +0 -306
- /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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
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
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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={{
|
|
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-
|
|
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
|
-
|
|
268
|
-
<
|
|
269
|
-
className="
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
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
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
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
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
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="
|
|
656
|
-
|
|
657
|
-
<
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
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
|
-
|
|
698
|
-
|
|
162
|
+
</>
|
|
163
|
+
)}
|
|
699
164
|
</motion.div>
|
|
700
165
|
|
|
701
166
|
{/* Rejection Modal */}
|
|
702
167
|
{showRejectModal && (
|
|
703
|
-
<div className="fixed inset-0 bg-black/
|
|
704
|
-
<div
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
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="
|
|
714
|
-
className="min-h-[
|
|
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-
|
|
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
|
-
|
|
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
|
-
}
|