@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
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { type Task } from "@locusai/shared";
|
|
4
|
+
import { motion } from "framer-motion";
|
|
5
|
+
import { CheckCircle, X } from "lucide-react";
|
|
6
|
+
import { SecondaryText, SectionLabel } from "@/components/typography";
|
|
7
|
+
import { Button, Checkbox, EmptyState, Input } from "@/components/ui";
|
|
8
|
+
import { cn } from "@/lib/utils";
|
|
9
|
+
|
|
10
|
+
interface TaskChecklistProps {
|
|
11
|
+
task: Task;
|
|
12
|
+
isLoading?: boolean;
|
|
13
|
+
checklistProgress: number;
|
|
14
|
+
newChecklistItem: string;
|
|
15
|
+
setNewChecklistItem: (val: string) => void;
|
|
16
|
+
handleAddChecklistItem: () => void;
|
|
17
|
+
handleToggleChecklistItem: (id: string) => void;
|
|
18
|
+
handleRemoveChecklistItem: (id: string) => void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function TaskChecklist({
|
|
22
|
+
task,
|
|
23
|
+
isLoading = false,
|
|
24
|
+
checklistProgress,
|
|
25
|
+
newChecklistItem,
|
|
26
|
+
setNewChecklistItem,
|
|
27
|
+
handleAddChecklistItem,
|
|
28
|
+
handleToggleChecklistItem,
|
|
29
|
+
handleRemoveChecklistItem,
|
|
30
|
+
}: TaskChecklistProps) {
|
|
31
|
+
return (
|
|
32
|
+
<div className="px-8 pb-8">
|
|
33
|
+
<div className="flex items-center justify-between mb-6">
|
|
34
|
+
<div className="flex items-center gap-3">
|
|
35
|
+
<div className="h-8 w-8 rounded-xl bg-sky-500/10 flex items-center justify-center text-sky-500">
|
|
36
|
+
<CheckCircle size={16} />
|
|
37
|
+
</div>
|
|
38
|
+
<SectionLabel as="h4">Definition of Done</SectionLabel>
|
|
39
|
+
</div>
|
|
40
|
+
<div className="flex items-center gap-6">
|
|
41
|
+
<div className="flex flex-col items-end">
|
|
42
|
+
<SecondaryText size="xs" className="mb-1.5">
|
|
43
|
+
Calibration
|
|
44
|
+
</SecondaryText>
|
|
45
|
+
<span className="text-sm font-mono font-black text-sky-500">
|
|
46
|
+
{checklistProgress}%
|
|
47
|
+
</span>
|
|
48
|
+
</div>
|
|
49
|
+
<div className="w-48 h-2 bg-secondary/40 rounded-full overflow-hidden border border-border/30 shadow-inner">
|
|
50
|
+
<motion.div
|
|
51
|
+
initial={{ width: 0 }}
|
|
52
|
+
animate={{ width: `${checklistProgress}%` }}
|
|
53
|
+
transition={{ duration: 1, ease: "easeOut" }}
|
|
54
|
+
className="h-full bg-sky-500 shadow-[0_0_15px_rgba(14,165,233,0.4)]"
|
|
55
|
+
/>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
|
|
60
|
+
<div className="grid gap-3 mb-6">
|
|
61
|
+
{task.acceptanceChecklist.length === 0 && (
|
|
62
|
+
<EmptyState
|
|
63
|
+
variant="compact"
|
|
64
|
+
title="Zero Quality Gates"
|
|
65
|
+
description="Deployment requires standard validation criteria."
|
|
66
|
+
className="bg-secondary/5 border-dashed border-2 border-border/40 py-8"
|
|
67
|
+
action={
|
|
68
|
+
<Button
|
|
69
|
+
variant="ghost"
|
|
70
|
+
size="sm"
|
|
71
|
+
onClick={() =>
|
|
72
|
+
setNewChecklistItem("Initialize unit validation")
|
|
73
|
+
}
|
|
74
|
+
className="text-[10px] font-black uppercase tracking-widest hover:text-sky-500"
|
|
75
|
+
>
|
|
76
|
+
Suggest Criteria
|
|
77
|
+
</Button>
|
|
78
|
+
}
|
|
79
|
+
/>
|
|
80
|
+
)}
|
|
81
|
+
{task.acceptanceChecklist.map((item) => (
|
|
82
|
+
<motion.div
|
|
83
|
+
layout
|
|
84
|
+
key={item.id}
|
|
85
|
+
className={cn(
|
|
86
|
+
"group flex items-center gap-4 p-4 bg-card/30 border border-border/40 rounded-2xl hover:border-sky-500/50 hover:bg-card/50 transition-all duration-300 shadow-sm",
|
|
87
|
+
isLoading && "opacity-60 pointer-events-none"
|
|
88
|
+
)}
|
|
89
|
+
>
|
|
90
|
+
<Checkbox
|
|
91
|
+
checked={item.done}
|
|
92
|
+
onChange={() => !isLoading && handleToggleChecklistItem(item.id)}
|
|
93
|
+
disabled={isLoading}
|
|
94
|
+
className="scale-110"
|
|
95
|
+
/>
|
|
96
|
+
<span
|
|
97
|
+
className={cn(
|
|
98
|
+
"flex-1 text-sm font-bold transition-all duration-300",
|
|
99
|
+
item.done
|
|
100
|
+
? "line-through text-muted-foreground/30 scale-[0.98] translate-x-1"
|
|
101
|
+
: "text-foreground/90"
|
|
102
|
+
)}
|
|
103
|
+
>
|
|
104
|
+
{item.text}
|
|
105
|
+
</span>
|
|
106
|
+
<Button
|
|
107
|
+
size="icon"
|
|
108
|
+
variant="ghost"
|
|
109
|
+
disabled={isLoading}
|
|
110
|
+
className="opacity-0 group-hover:opacity-100 h-8 w-8 text-muted-foreground/40 hover:text-destructive hover:bg-destructive/10 transition-all rounded-lg"
|
|
111
|
+
onClick={() => !isLoading && handleRemoveChecklistItem(item.id)}
|
|
112
|
+
>
|
|
113
|
+
<X size={14} />
|
|
114
|
+
</Button>
|
|
115
|
+
</motion.div>
|
|
116
|
+
))}
|
|
117
|
+
</div>
|
|
118
|
+
|
|
119
|
+
<div className="flex gap-4 p-2.5 bg-secondary/5 rounded-2xl border border-border/40 focus-within:border-primary/40 transition-all shadow-inner">
|
|
120
|
+
<Input
|
|
121
|
+
value={newChecklistItem}
|
|
122
|
+
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
|
123
|
+
!isLoading && setNewChecklistItem(e.target.value)
|
|
124
|
+
}
|
|
125
|
+
disabled={isLoading}
|
|
126
|
+
placeholder="Add validation gate..."
|
|
127
|
+
className="h-10 bg-transparent border-none focus:ring-0 text-sm font-bold placeholder:font-black placeholder:uppercase placeholder:text-[10px] placeholder:tracking-widest"
|
|
128
|
+
onKeyDown={(e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
129
|
+
if (e.key === "Enter" && !isLoading) handleAddChecklistItem();
|
|
130
|
+
}}
|
|
131
|
+
/>
|
|
132
|
+
<Button
|
|
133
|
+
onClick={handleAddChecklistItem}
|
|
134
|
+
disabled={!newChecklistItem.trim() || isLoading}
|
|
135
|
+
className="px-6 h-10 bg-foreground text-background font-black text-[10px] uppercase tracking-widest hover:scale-105 active:scale-95 transition-all rounded-xl"
|
|
136
|
+
>
|
|
137
|
+
Append
|
|
138
|
+
</Button>
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
141
|
+
);
|
|
142
|
+
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task Description Component
|
|
3
|
+
*
|
|
4
|
+
* Displays and allows editing of task title and description.
|
|
5
|
+
* Supports markdown editing and preview modes.
|
|
6
|
+
* Integrates with useTaskPanel for optimistic updates.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* <TaskDescription task={task} onUpdate={handleUpdate} isLoading={isLoading} />
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
"use client";
|
|
13
|
+
|
|
14
|
+
import { type Task } from "@locusai/shared";
|
|
15
|
+
import { Edit, FileText, Loader2 } from "lucide-react";
|
|
16
|
+
import { SectionLabel } from "@/components/typography";
|
|
17
|
+
import { Input, Textarea } from "@/components/ui";
|
|
18
|
+
import { useTaskDescription } from "@/hooks/useTaskDescription";
|
|
19
|
+
import { cn } from "@/lib/utils";
|
|
20
|
+
|
|
21
|
+
interface TaskDescriptionProps {
|
|
22
|
+
/** Task to display */
|
|
23
|
+
task: Task;
|
|
24
|
+
/** Whether a task mutation is loading */
|
|
25
|
+
isLoading?: boolean;
|
|
26
|
+
/** Callback when task is updated */
|
|
27
|
+
onUpdate: (updates: Partial<Task>) => void;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Task Description Component
|
|
32
|
+
*
|
|
33
|
+
* Features:
|
|
34
|
+
* - Editable title with inline editing
|
|
35
|
+
* - Description with markdown support
|
|
36
|
+
* - Preview/Edit mode toggle
|
|
37
|
+
* - Auto-save on blur
|
|
38
|
+
* - Loading states during mutations
|
|
39
|
+
*
|
|
40
|
+
* @component
|
|
41
|
+
*/
|
|
42
|
+
export function TaskDescription({
|
|
43
|
+
task,
|
|
44
|
+
isLoading = false,
|
|
45
|
+
onUpdate,
|
|
46
|
+
}: TaskDescriptionProps) {
|
|
47
|
+
const {
|
|
48
|
+
isEditingTitle,
|
|
49
|
+
editTitle,
|
|
50
|
+
setIsEditingTitle,
|
|
51
|
+
setEditTitle,
|
|
52
|
+
handleTitleSave,
|
|
53
|
+
editDesc,
|
|
54
|
+
setEditDesc,
|
|
55
|
+
handleDescSave,
|
|
56
|
+
descMode,
|
|
57
|
+
setDescMode,
|
|
58
|
+
} = useTaskDescription({ task, onUpdate });
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<div className="p-8 overflow-y-auto scrollbar-thin">
|
|
62
|
+
{/* Title Section */}
|
|
63
|
+
<div className="mb-8">
|
|
64
|
+
{isEditingTitle ? (
|
|
65
|
+
<div className="relative">
|
|
66
|
+
<Input
|
|
67
|
+
value={editTitle}
|
|
68
|
+
onChange={(e) => setEditTitle(e.target.value)}
|
|
69
|
+
onBlur={handleTitleSave}
|
|
70
|
+
disabled={isLoading}
|
|
71
|
+
onKeyDown={(e) => {
|
|
72
|
+
if (e.key === "Enter" && !isLoading) handleTitleSave();
|
|
73
|
+
if (e.key === "Escape") {
|
|
74
|
+
setEditTitle(task.title);
|
|
75
|
+
setIsEditingTitle(false);
|
|
76
|
+
}
|
|
77
|
+
}}
|
|
78
|
+
className="text-3xl h-16 font-black tracking-tight bg-secondary/20 border-primary/20 rounded-2xl px-6"
|
|
79
|
+
autoFocus
|
|
80
|
+
aria-label="Edit task title"
|
|
81
|
+
/>
|
|
82
|
+
{isLoading && (
|
|
83
|
+
<Loader2
|
|
84
|
+
size={20}
|
|
85
|
+
className="absolute right-4 top-1/2 -translate-y-1/2 animate-spin text-primary"
|
|
86
|
+
aria-hidden="true"
|
|
87
|
+
/>
|
|
88
|
+
)}
|
|
89
|
+
</div>
|
|
90
|
+
) : (
|
|
91
|
+
<h2
|
|
92
|
+
className={cn(
|
|
93
|
+
"text-3xl font-black tracking-tight transition-all cursor-pointer leading-tight group flex items-start",
|
|
94
|
+
isLoading ? "opacity-50" : "hover:text-primary"
|
|
95
|
+
)}
|
|
96
|
+
onClick={() => !isLoading && setIsEditingTitle(true)}
|
|
97
|
+
onKeyDown={(e) => {
|
|
98
|
+
if ((e.key === "Enter" || e.key === " ") && !isLoading) {
|
|
99
|
+
setIsEditingTitle(true);
|
|
100
|
+
}
|
|
101
|
+
}}
|
|
102
|
+
aria-label="Task title - click to edit"
|
|
103
|
+
>
|
|
104
|
+
{task.title}
|
|
105
|
+
{!isLoading && (
|
|
106
|
+
<Edit
|
|
107
|
+
size={18}
|
|
108
|
+
className="ml-4 mt-1.5 opacity-0 group-hover:opacity-40 transition-opacity"
|
|
109
|
+
aria-hidden="true"
|
|
110
|
+
/>
|
|
111
|
+
)}
|
|
112
|
+
</h2>
|
|
113
|
+
)}
|
|
114
|
+
</div>
|
|
115
|
+
|
|
116
|
+
{/* Description Section */}
|
|
117
|
+
<div className="mb-10">
|
|
118
|
+
<div className="flex items-center justify-between mb-5">
|
|
119
|
+
<div className="flex items-center gap-3">
|
|
120
|
+
<div className="h-8 w-8 rounded-xl bg-primary/10 flex items-center justify-center text-primary group-hover:bg-primary group-hover:text-primary-foreground transition-all">
|
|
121
|
+
<FileText size={16} aria-hidden="true" />
|
|
122
|
+
</div>
|
|
123
|
+
<SectionLabel as="h4">Technical Documentation</SectionLabel>
|
|
124
|
+
</div>
|
|
125
|
+
<div className="flex bg-secondary/40 p-1 rounded-xl border border-border/20 shadow-inner">
|
|
126
|
+
<button
|
|
127
|
+
className={cn(
|
|
128
|
+
"px-4 py-1.5 rounded-lg text-[10px] font-black uppercase tracking-widest transition-all",
|
|
129
|
+
descMode === "preview"
|
|
130
|
+
? "bg-background shadow-md text-primary scale-105"
|
|
131
|
+
: "text-muted-foreground hover:text-foreground",
|
|
132
|
+
isLoading && "opacity-50 cursor-not-allowed"
|
|
133
|
+
)}
|
|
134
|
+
onClick={() => !isLoading && setDescMode("preview")}
|
|
135
|
+
disabled={isLoading}
|
|
136
|
+
aria-pressed={descMode === "preview"}
|
|
137
|
+
>
|
|
138
|
+
Visual
|
|
139
|
+
</button>
|
|
140
|
+
<button
|
|
141
|
+
className={cn(
|
|
142
|
+
"px-4 py-1.5 rounded-lg text-[10px] font-black uppercase tracking-widest transition-all",
|
|
143
|
+
descMode === "edit"
|
|
144
|
+
? "bg-background shadow-md text-primary scale-105"
|
|
145
|
+
: "text-muted-foreground hover:text-foreground",
|
|
146
|
+
isLoading && "opacity-50 cursor-not-allowed"
|
|
147
|
+
)}
|
|
148
|
+
onClick={() => !isLoading && setDescMode("edit")}
|
|
149
|
+
disabled={isLoading}
|
|
150
|
+
aria-pressed={descMode === "edit"}
|
|
151
|
+
>
|
|
152
|
+
Markdown
|
|
153
|
+
</button>
|
|
154
|
+
</div>
|
|
155
|
+
</div>
|
|
156
|
+
|
|
157
|
+
{descMode === "edit" ? (
|
|
158
|
+
<div
|
|
159
|
+
className={cn(
|
|
160
|
+
"group border border-border/40 rounded-2xl overflow-hidden focus-within:ring-2 focus-within:ring-primary/20 transition-all bg-secondary/5 shadow-inner",
|
|
161
|
+
isLoading && "opacity-60"
|
|
162
|
+
)}
|
|
163
|
+
>
|
|
164
|
+
<Textarea
|
|
165
|
+
value={editDesc}
|
|
166
|
+
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) =>
|
|
167
|
+
!isLoading && setEditDesc(e.target.value)
|
|
168
|
+
}
|
|
169
|
+
disabled={isLoading}
|
|
170
|
+
placeholder="Define implementation architecture, requirements, and scope..."
|
|
171
|
+
rows={12}
|
|
172
|
+
className="border-none focus:ring-0 text-base leading-relaxed p-8 bg-transparent scrollbar-thin"
|
|
173
|
+
onBlur={handleDescSave}
|
|
174
|
+
aria-label="Task description (markdown)"
|
|
175
|
+
/>
|
|
176
|
+
</div>
|
|
177
|
+
) : (
|
|
178
|
+
<div
|
|
179
|
+
className={cn(
|
|
180
|
+
"prose prose-invert max-w-none bg-secondary/10 p-10 rounded-3xl border border-border/40 shadow-[inset_0_2px_10px_rgba(0,0,0,0.1)] relative group",
|
|
181
|
+
isLoading && "opacity-60 pointer-events-none"
|
|
182
|
+
)}
|
|
183
|
+
>
|
|
184
|
+
{task.description ? (
|
|
185
|
+
<p className="text-foreground/80 leading-relaxed whitespace-pre-wrap font-medium">
|
|
186
|
+
{task.description}
|
|
187
|
+
</p>
|
|
188
|
+
) : (
|
|
189
|
+
<div className="flex flex-col items-center justify-center py-10 opacity-30 select-none">
|
|
190
|
+
<FileText size={32} className="mb-4" aria-hidden="true" />
|
|
191
|
+
<span className="text-xs font-black uppercase tracking-[0.2em]">
|
|
192
|
+
Waiting for Specs
|
|
193
|
+
</span>
|
|
194
|
+
</div>
|
|
195
|
+
)}
|
|
196
|
+
</div>
|
|
197
|
+
)}
|
|
198
|
+
</div>
|
|
199
|
+
</div>
|
|
200
|
+
);
|
|
201
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { type Task } from "@locusai/shared";
|
|
4
|
+
import { FileText, Link, Plus, Trash2, X } from "lucide-react";
|
|
5
|
+
import { useState } from "react";
|
|
6
|
+
import { useDocsQuery } from "@/hooks/useDocsQuery";
|
|
7
|
+
import { cn } from "@/lib/utils";
|
|
8
|
+
import {
|
|
9
|
+
EmptyStateText,
|
|
10
|
+
MetadataText,
|
|
11
|
+
SecondaryText,
|
|
12
|
+
SectionLabel,
|
|
13
|
+
} from "../typography";
|
|
14
|
+
import { Button } from "../ui";
|
|
15
|
+
|
|
16
|
+
interface TaskDocsProps {
|
|
17
|
+
/** Task to manage documents for */
|
|
18
|
+
task: Task;
|
|
19
|
+
/** Called when linking a document */
|
|
20
|
+
onLinkDoc: (docId: string) => void;
|
|
21
|
+
/** Called when unlinking a document */
|
|
22
|
+
onUnlinkDoc: (docId: string) => void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Task Docs Component
|
|
27
|
+
*
|
|
28
|
+
* Displays and manages linked documentation for a task.
|
|
29
|
+
* Allows linking/unlinking documents from the knowledge base.
|
|
30
|
+
* Uses standardized typography components for consistent text styling.
|
|
31
|
+
*
|
|
32
|
+
* @component
|
|
33
|
+
*/
|
|
34
|
+
export function TaskDocs({ task, onLinkDoc, onUnlinkDoc }: TaskDocsProps) {
|
|
35
|
+
const [isLinking, setIsLinking] = useState(false);
|
|
36
|
+
const { data: allDocs = [] } = useDocsQuery();
|
|
37
|
+
|
|
38
|
+
const linkedDocs =
|
|
39
|
+
(task as Task & { docs: { id: string; title: string }[] }).docs || [];
|
|
40
|
+
const linkedDocIds = linkedDocs.map((d: { id: string }) => d.id);
|
|
41
|
+
const availableDocs = allDocs.filter((d) => !linkedDocIds.includes(d.id));
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<div className="space-y-6 animate-in fade-in slide-in-from-bottom-4 duration-500">
|
|
45
|
+
<div className="flex items-center justify-between px-1">
|
|
46
|
+
<SectionLabel as="h3">Knowledge base</SectionLabel>
|
|
47
|
+
<Button
|
|
48
|
+
variant="ghost"
|
|
49
|
+
size="sm"
|
|
50
|
+
onClick={() => setIsLinking(!isLinking)}
|
|
51
|
+
className={cn(
|
|
52
|
+
"h-7 px-2 text-[9px] font-black uppercase tracking-widest gap-1.5 transition-all rounded-lg",
|
|
53
|
+
isLinking
|
|
54
|
+
? "bg-primary/20 text-primary"
|
|
55
|
+
: "hover:bg-primary/10 hover:text-primary opacity-60 hover:opacity-100"
|
|
56
|
+
)}
|
|
57
|
+
>
|
|
58
|
+
{isLinking ? <X size={12} /> : <Plus size={12} />}
|
|
59
|
+
{isLinking ? "Cancel" : "Link Doc"}
|
|
60
|
+
</Button>
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
{isLinking && (
|
|
64
|
+
<div className="p-3 bg-primary/5 border border-primary/20 rounded-2xl animate-in zoom-in-95 duration-200">
|
|
65
|
+
<SectionLabel className="mb-2 px-1 text-primary">
|
|
66
|
+
Available Nodes
|
|
67
|
+
</SectionLabel>
|
|
68
|
+
{availableDocs.length > 0 ? (
|
|
69
|
+
<div className="max-h-[200px] overflow-y-auto pr-1 space-y-1 scrollbar-thin">
|
|
70
|
+
{availableDocs.map((doc) => (
|
|
71
|
+
<button
|
|
72
|
+
key={doc.id}
|
|
73
|
+
onClick={() => {
|
|
74
|
+
onLinkDoc(doc.id);
|
|
75
|
+
setIsLinking(false);
|
|
76
|
+
}}
|
|
77
|
+
className="flex items-center gap-3 w-full p-2.5 text-xs font-bold rounded-xl bg-card border border-border/40 hover:border-primary/40 hover:bg-primary/5 transition-all text-left group"
|
|
78
|
+
>
|
|
79
|
+
<FileText
|
|
80
|
+
size={14}
|
|
81
|
+
className="text-muted-foreground group-hover:text-primary transition-colors"
|
|
82
|
+
/>
|
|
83
|
+
<span className="flex-1 truncate uppercase tracking-tight opacity-80 group-hover:opacity-100 transition-opacity">
|
|
84
|
+
{doc.title}
|
|
85
|
+
</span>
|
|
86
|
+
<Link
|
|
87
|
+
size={12}
|
|
88
|
+
className="opacity-0 group-hover:opacity-40"
|
|
89
|
+
/>
|
|
90
|
+
</button>
|
|
91
|
+
))}
|
|
92
|
+
</div>
|
|
93
|
+
) : (
|
|
94
|
+
<div className="py-6 text-center">
|
|
95
|
+
<SecondaryText size="xs" className="italic">
|
|
96
|
+
No Unlinked Nodes
|
|
97
|
+
</SecondaryText>
|
|
98
|
+
</div>
|
|
99
|
+
)}
|
|
100
|
+
</div>
|
|
101
|
+
)}
|
|
102
|
+
|
|
103
|
+
<div className="space-y-2 px-1">
|
|
104
|
+
{linkedDocs.length > 0
|
|
105
|
+
? linkedDocs.map((doc: { id: string; title: string }) => (
|
|
106
|
+
<div
|
|
107
|
+
key={doc.id}
|
|
108
|
+
className="flex items-center gap-3 p-3 rounded-2xl bg-secondary/20 border border-border/20 hover:border-border/60 transition-all group"
|
|
109
|
+
>
|
|
110
|
+
<div className="w-8 h-8 flex items-center justify-center rounded-xl bg-primary/10 text-primary border border-primary/20">
|
|
111
|
+
<FileText size={16} />
|
|
112
|
+
</div>
|
|
113
|
+
<div className="flex-1 min-w-0">
|
|
114
|
+
<div className="text-[11px] font-black uppercase tracking-wider text-foreground/90 truncate">
|
|
115
|
+
{doc.title}
|
|
116
|
+
</div>
|
|
117
|
+
<MetadataText size="xs">
|
|
118
|
+
Document ID: {doc.id.split("-")[0]}
|
|
119
|
+
</MetadataText>
|
|
120
|
+
</div>
|
|
121
|
+
<button
|
|
122
|
+
onClick={() => onUnlinkDoc(doc.id)}
|
|
123
|
+
className="p-1.5 text-muted-foreground/40 hover:text-destructive transition-colors opacity-0 group-hover:opacity-100"
|
|
124
|
+
>
|
|
125
|
+
<Trash2 size={14} />
|
|
126
|
+
</button>
|
|
127
|
+
</div>
|
|
128
|
+
))
|
|
129
|
+
: !isLinking && (
|
|
130
|
+
<EmptyStateText icon={<Link size={24} />}>
|
|
131
|
+
No Linked Documents
|
|
132
|
+
</EmptyStateText>
|
|
133
|
+
)}
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
);
|
|
137
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task Header Component
|
|
3
|
+
*
|
|
4
|
+
* Displays task metadata and action buttons in the panel header.
|
|
5
|
+
* Shows task ID, status, priority, and contextual actions.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* <TaskHeader
|
|
9
|
+
* task={task}
|
|
10
|
+
* onClose={handleClose}
|
|
11
|
+
* onDelete={handleDelete}
|
|
12
|
+
* onApprove={handleApprove}
|
|
13
|
+
* onReject={handleReject}
|
|
14
|
+
* />
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
"use client";
|
|
18
|
+
|
|
19
|
+
import { type Task, TaskPriority, TaskStatus } from "@locusai/shared";
|
|
20
|
+
import { CheckCircle, ChevronRight, Trash2 } from "lucide-react";
|
|
21
|
+
import { MetadataText } from "@/components/typography";
|
|
22
|
+
import { Button, PriorityBadge, StatusBadge } from "@/components/ui";
|
|
23
|
+
|
|
24
|
+
interface TaskHeaderProps {
|
|
25
|
+
/** Task to display in header */
|
|
26
|
+
task: Task;
|
|
27
|
+
/** Whether a task mutation is loading */
|
|
28
|
+
isLoading?: boolean;
|
|
29
|
+
/** Whether task deletion is in progress */
|
|
30
|
+
isDeleting?: boolean;
|
|
31
|
+
/** Callback when closing panel */
|
|
32
|
+
onClose: () => void;
|
|
33
|
+
/** Callback to delete task */
|
|
34
|
+
onDelete: () => void;
|
|
35
|
+
/** Callback to approve task (if in verification) */
|
|
36
|
+
onApprove: () => void;
|
|
37
|
+
/** Callback to reject task (if in verification) */
|
|
38
|
+
onReject: () => void;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Task Header Component
|
|
43
|
+
*
|
|
44
|
+
* Features:
|
|
45
|
+
* - Task ID reference
|
|
46
|
+
* - Status and priority badges
|
|
47
|
+
* - Approve/reject buttons (if in verification status)
|
|
48
|
+
* - Delete button
|
|
49
|
+
* - Close button
|
|
50
|
+
*
|
|
51
|
+
* @component
|
|
52
|
+
*/
|
|
53
|
+
export function TaskHeader({
|
|
54
|
+
task,
|
|
55
|
+
isLoading = false,
|
|
56
|
+
isDeleting = false,
|
|
57
|
+
onClose,
|
|
58
|
+
onDelete,
|
|
59
|
+
onApprove,
|
|
60
|
+
onReject,
|
|
61
|
+
}: TaskHeaderProps) {
|
|
62
|
+
const canApproveReject = task.status === TaskStatus.VERIFICATION;
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<header className="flex items-center gap-6 px-10 border-b border-border bg-card/50 backdrop-blur-md h-[84px] shrink-0">
|
|
66
|
+
<button
|
|
67
|
+
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"
|
|
68
|
+
onClick={onClose}
|
|
69
|
+
aria-label="Close task panel"
|
|
70
|
+
>
|
|
71
|
+
<ChevronRight size={20} aria-hidden="true" />
|
|
72
|
+
</button>
|
|
73
|
+
|
|
74
|
+
<div className="flex-1 min-w-0">
|
|
75
|
+
<MetadataText size="sm" className="mb-1.5 block">
|
|
76
|
+
Reference: #{task.id}
|
|
77
|
+
</MetadataText>
|
|
78
|
+
<div className="flex gap-3">
|
|
79
|
+
<StatusBadge status={task.status} />
|
|
80
|
+
<PriorityBadge priority={task.priority || TaskPriority.MEDIUM} />
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
|
|
84
|
+
<div className="flex items-center gap-2">
|
|
85
|
+
{canApproveReject && (
|
|
86
|
+
<>
|
|
87
|
+
<Button
|
|
88
|
+
variant="danger"
|
|
89
|
+
size="sm"
|
|
90
|
+
onClick={onReject}
|
|
91
|
+
disabled={isLoading}
|
|
92
|
+
className="h-10 px-5 rounded-xl font-black uppercase tracking-widest text-[10px]"
|
|
93
|
+
aria-label="Reject task"
|
|
94
|
+
>
|
|
95
|
+
Reject
|
|
96
|
+
</Button>
|
|
97
|
+
<Button
|
|
98
|
+
size="sm"
|
|
99
|
+
variant="success"
|
|
100
|
+
onClick={onApprove}
|
|
101
|
+
disabled={isLoading}
|
|
102
|
+
className="h-10 px-5 rounded-xl font-black uppercase tracking-widest text-[10px]"
|
|
103
|
+
aria-label="Approve task"
|
|
104
|
+
>
|
|
105
|
+
<CheckCircle size={16} className="mr-2" aria-hidden="true" />
|
|
106
|
+
Approve
|
|
107
|
+
</Button>
|
|
108
|
+
<div className="w-px h-6 bg-border mx-2" aria-hidden="true" />
|
|
109
|
+
</>
|
|
110
|
+
)}
|
|
111
|
+
<Button
|
|
112
|
+
size="icon"
|
|
113
|
+
variant="danger"
|
|
114
|
+
onClick={onDelete}
|
|
115
|
+
disabled={isDeleting}
|
|
116
|
+
title="Delete"
|
|
117
|
+
className="h-10 w-10 hover:scale-105 active:scale-95 transition-transform rounded-xl"
|
|
118
|
+
aria-label="Delete task"
|
|
119
|
+
>
|
|
120
|
+
<Trash2 size={18} aria-hidden="true" />
|
|
121
|
+
</Button>
|
|
122
|
+
</div>
|
|
123
|
+
</header>
|
|
124
|
+
);
|
|
125
|
+
}
|