Skip to content

Commit 7696cbf

Browse files
7418claude
andcommitted
refactor: 右侧面板 Tasks/Files 合并为单视图 + 简化 TaskList
- RightPanel: 移除 Files/Tasks 双 Tab 切换,改为单视图布局 - 顶部标题改为 TASKS,下方直接渲染 TaskList - 中间用 mx-4 非贯通分隔线 + border-border/40 降低视觉强度 - 分隔线下方增加 FILES 小标题,再接 FileTree - TaskList: 大幅简化 - 移除筛选 Tab(全部/进行中/已完成) - 移除新增任务输入框 - 移除 TaskCard 组件引用,改为 checkbox + 标题单行展示 - 点击切换 completed/pending 状态 - 勾选框使用 foreground 色(黑/白)替代蓝色 - 空状态显示"暂无任务" - bump version to 0.20.1 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent b174527 commit 7696cbf

File tree

4 files changed

+77
-156
lines changed

4 files changed

+77
-156
lines changed

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "codepilot",
3-
"version": "0.20.0",
3+
"version": "0.20.1",
44
"private": true,
55
"author": {
66
"name": "op7418",

src/components/layout/RightPanel.tsx

Lines changed: 21 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,13 @@ import { usePanel } from "@/hooks/usePanel";
1313
import { useTranslation } from "@/hooks/useTranslation";
1414
import { FileTree } from "@/components/project/FileTree";
1515
import { TaskList } from "@/components/project/TaskList";
16-
import { cn } from "@/lib/utils";
1716

1817
interface RightPanelProps {
1918
width?: number;
2019
}
2120

2221
export function RightPanel({ width }: RightPanelProps) {
23-
const { panelOpen, setPanelOpen, panelContent, setPanelContent, workingDirectory, sessionId, previewFile, setPreviewFile } = usePanel();
22+
const { panelOpen, setPanelOpen, workingDirectory, sessionId, previewFile, setPreviewFile } = usePanel();
2423
const { t } = useTranslation();
2524

2625
const handleFileAdd = useCallback((path: string) => {
@@ -73,26 +72,9 @@ export function RightPanel({ width }: RightPanelProps) {
7372
<aside className="hidden h-full shrink-0 flex-col overflow-hidden bg-background lg:flex" style={{ width: width ?? 288 }}>
7473
{/* Header */}
7574
<div className="flex h-12 mt-5 shrink-0 items-center justify-between px-4">
76-
<div className="flex items-center gap-1">
77-
<button
78-
className={cn(
79-
"text-[11px] font-semibold uppercase tracking-wider px-2 py-1 rounded",
80-
panelContent === "files" ? "bg-accent text-accent-foreground" : "text-muted-foreground hover:text-foreground"
81-
)}
82-
onClick={() => setPanelContent("files")}
83-
>
84-
{t('panel.files')}
85-
</button>
86-
<button
87-
className={cn(
88-
"text-[11px] font-semibold uppercase tracking-wider px-2 py-1 rounded",
89-
panelContent === "tasks" ? "bg-accent text-accent-foreground" : "text-muted-foreground hover:text-foreground"
90-
)}
91-
onClick={() => setPanelContent("tasks")}
92-
>
93-
{t('panel.tasks')}
94-
</button>
95-
</div>
75+
<span className="text-[11px] font-semibold uppercase tracking-wider text-muted-foreground">
76+
{t('panel.tasks')}
77+
</span>
9678
<Tooltip>
9779
<TooltipTrigger asChild>
9880
<Button
@@ -108,19 +90,29 @@ export function RightPanel({ width }: RightPanelProps) {
10890
</Tooltip>
10991
</div>
11092

111-
{/* Body */}
93+
{/* Body — TaskList + divider + FileTree */}
11294
<div className="flex flex-1 flex-col min-h-0 overflow-hidden">
113-
{panelContent === "files" ? (
95+
{/* Tasks */}
96+
<div className="shrink-0 px-3 pb-3">
97+
<TaskList sessionId={sessionId} />
98+
</div>
99+
100+
{/* Divider */}
101+
<div className="mx-4 mt-1 mb-2 border-t border-border/40" />
102+
103+
{/* File tree */}
104+
<div className="flex-1 min-h-0 overflow-hidden">
105+
<div className="px-4 pt-1 pb-1">
106+
<span className="text-[11px] font-semibold uppercase tracking-wider text-muted-foreground">
107+
{t('panel.files')}
108+
</span>
109+
</div>
114110
<FileTree
115111
workingDirectory={workingDirectory}
116112
onFileSelect={handleFileSelect}
117113
onFileAdd={handleFileAdd}
118114
/>
119-
) : (
120-
<div className="px-3 pt-1 h-full">
121-
<TaskList sessionId={sessionId} />
122-
</div>
123-
)}
115+
</div>
124116
</div>
125117
</aside>
126118
);
Lines changed: 53 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,18 @@
11
"use client";
22

33
import { useState, useEffect, useCallback } from "react";
4-
import { HugeiconsIcon } from "@hugeicons/react";
5-
import { PlusSignIcon } from "@hugeicons/core-free-icons";
6-
import { Input } from "@/components/ui/input";
7-
import { Button } from "@/components/ui/button";
8-
import { ScrollArea } from "@/components/ui/scroll-area";
94
import { cn } from "@/lib/utils";
10-
import { TaskCard } from "./TaskCard";
115
import { useTranslation } from "@/hooks/useTranslation";
126
import type { TaskItem, TaskStatus } from "@/types";
137

148
interface TaskListProps {
159
sessionId: string;
1610
}
1711

18-
type FilterTab = "all" | "in_progress" | "completed";
19-
2012
export function TaskList({ sessionId }: TaskListProps) {
2113
const { t } = useTranslation();
2214
const [tasks, setTasks] = useState<TaskItem[]>([]);
2315
const [loading, setLoading] = useState(false);
24-
const [newTitle, setNewTitle] = useState("");
25-
const [filter, setFilter] = useState<FilterTab>("all");
2616

2717
const fetchTasks = useCallback(async () => {
2818
if (!sessionId) return;
@@ -51,135 +41,74 @@ export function TaskList({ sessionId }: TaskListProps) {
5141
return () => window.removeEventListener('tasks-updated', handler);
5242
}, [fetchTasks]);
5343

54-
const handleCreate = async () => {
55-
const title = newTitle.trim();
56-
if (!title || !sessionId) return;
57-
58-
try {
59-
const res = await fetch("/api/tasks", {
60-
method: "POST",
61-
headers: { "Content-Type": "application/json" },
62-
body: JSON.stringify({ session_id: sessionId, title }),
63-
});
64-
if (res.ok) {
65-
const data = await res.json();
66-
setTasks((prev) => [...prev, data.task]);
67-
setNewTitle("");
68-
}
69-
} catch {
70-
// silently fail
71-
}
72-
};
73-
74-
const handleUpdate = async (
75-
id: string,
76-
updates: { title?: string; status?: TaskStatus }
77-
) => {
44+
const handleToggle = async (task: TaskItem) => {
45+
const nextStatus: TaskStatus = task.status === "completed" ? "pending" : "completed";
7846
try {
79-
const res = await fetch(`/api/tasks/${id}`, {
47+
const res = await fetch(`/api/tasks/${task.id}`, {
8048
method: "PATCH",
8149
headers: { "Content-Type": "application/json" },
82-
body: JSON.stringify(updates),
50+
body: JSON.stringify({ status: nextStatus }),
8351
});
8452
if (res.ok) {
8553
const data = await res.json();
86-
setTasks((prev) => prev.map((t) => (t.id === id ? data.task : t)));
87-
}
88-
} catch {
89-
// silently fail
90-
}
91-
};
92-
93-
const handleDelete = async (id: string) => {
94-
try {
95-
const res = await fetch(`/api/tasks/${id}`, { method: "DELETE" });
96-
if (res.ok) {
97-
setTasks((prev) => prev.filter((t) => t.id !== id));
54+
setTasks((prev) => prev.map((t) => (t.id === task.id ? data.task : t)));
9855
}
9956
} catch {
10057
// silently fail
10158
}
10259
};
10360

104-
const filtered = tasks.filter((task) => {
105-
if (filter === "all") return true;
106-
if (filter === "in_progress")
107-
return task.status === "pending" || task.status === "in_progress";
108-
if (filter === "completed") return task.status === "completed";
109-
return true;
110-
});
111-
112-
const filterTabs: { key: FilterTab; label: string }[] = [
113-
{ key: "all", label: t('tasks.all') },
114-
{ key: "in_progress", label: t('tasks.active') },
115-
{ key: "completed", label: t('tasks.done') },
116-
];
61+
if (loading && tasks.length === 0) {
62+
return (
63+
<p className="py-2 text-center text-xs text-muted-foreground">
64+
{t('tasks.loading')}
65+
</p>
66+
);
67+
}
68+
69+
if (tasks.length === 0) {
70+
return (
71+
<p className="py-2 text-center text-xs text-muted-foreground">
72+
{t('tasks.noTasks')}
73+
</p>
74+
);
75+
}
11776

11877
return (
119-
<div className="flex h-full flex-col">
120-
{/* Filter tabs */}
121-
<div className="flex items-center gap-1 pb-2">
122-
{filterTabs.map((tab) => (
123-
<Button
124-
key={tab.key}
125-
variant="ghost"
126-
size="sm"
127-
className={cn(
128-
"h-6 px-2 text-[10px]",
129-
filter === tab.key && "bg-accent"
130-
)}
131-
onClick={() => setFilter(tab.key)}
78+
<div className="flex flex-col gap-0.5">
79+
{tasks.map((task) => {
80+
const isDone = task.status === "completed";
81+
return (
82+
<button
83+
key={task.id}
84+
className="flex items-center gap-2 rounded-md px-1 py-1 text-left hover:bg-accent/50 transition-colors"
85+
onClick={() => handleToggle(task)}
13286
>
133-
{tab.label}
134-
</Button>
135-
))}
136-
</div>
137-
138-
{/* Add task input */}
139-
<div className="flex items-center gap-1 pb-2">
140-
<Input
141-
placeholder={t('tasks.addPlaceholder')}
142-
value={newTitle}
143-
onChange={(e) => setNewTitle(e.target.value)}
144-
onKeyDown={(e) => {
145-
if (e.key === "Enter") handleCreate();
146-
}}
147-
className="h-7 text-xs"
148-
/>
149-
<Button
150-
variant="ghost"
151-
size="icon-sm"
152-
onClick={handleCreate}
153-
disabled={!newTitle.trim()}
154-
>
155-
<HugeiconsIcon icon={PlusSignIcon} className="h-3.5 w-3.5" />
156-
<span className="sr-only">{t('tasks.addTask')}</span>
157-
</Button>
158-
</div>
159-
160-
{/* Task list */}
161-
<ScrollArea className="flex-1">
162-
{loading && tasks.length === 0 ? (
163-
<p className="py-4 text-center text-xs text-muted-foreground">
164-
{t('tasks.loading')}
165-
</p>
166-
) : filtered.length === 0 ? (
167-
<p className="py-4 text-center text-xs text-muted-foreground">
168-
{tasks.length === 0 ? t('tasks.noTasks') : t('tasks.noMatching')}
169-
</p>
170-
) : (
171-
<div className="flex flex-col gap-1.5 pb-4">
172-
{filtered.map((task) => (
173-
<TaskCard
174-
key={task.id}
175-
task={task}
176-
onUpdate={handleUpdate}
177-
onDelete={handleDelete}
178-
/>
179-
))}
180-
</div>
181-
)}
182-
</ScrollArea>
87+
<span
88+
className={cn(
89+
"flex h-3.5 w-3.5 shrink-0 items-center justify-center rounded border transition-colors",
90+
isDone
91+
? "border-foreground bg-foreground text-background"
92+
: "border-muted-foreground/40"
93+
)}
94+
>
95+
{isDone && (
96+
<svg className="h-2.5 w-2.5" viewBox="0 0 12 12" fill="none" stroke="currentColor" strokeWidth="2">
97+
<path d="M2.5 6l2.5 2.5 4.5-5" />
98+
</svg>
99+
)}
100+
</span>
101+
<span
102+
className={cn(
103+
"flex-1 truncate text-xs",
104+
isDone && "text-muted-foreground line-through"
105+
)}
106+
>
107+
{task.title}
108+
</span>
109+
</button>
110+
);
111+
})}
183112
</div>
184113
);
185114
}

0 commit comments

Comments
 (0)