feat: reorganize project task planning UI

This commit is contained in:
2026-07-21 01:00:27 +08:00
parent c613d4c997
commit d6395d3c64
13 changed files with 251 additions and 171 deletions

View File

@@ -1,16 +1,17 @@
import { Button, Card, Descriptions, Progress, Space, Tag, Typography } from '@arco-design/web-react'
import { IconArrowLeft, IconCalendar, IconCheckCircle, IconPlus, IconUser } from '@arco-design/web-react/icon'
import { useState } from 'react'
import { Button, Card, Descriptions, Input, Modal, Progress, Space, Tag, Typography } from '@arco-design/web-react'
import { IconArrowLeft, IconCalendar, IconCheckCircle, IconCheckCircleFill, IconClockCircle, IconPlus } from '@arco-design/web-react/icon'
import type { ProjectWorkspace, TaskItem } from './project-types'
const { Title, Text } = Typography
const { Title, Text, Paragraph } = Typography
export function ProjectTasks({
activeWorkspace,
activeTaskID,
onOpenTask,
onCloseTask,
onSelectItem,
onCreateTask,
onCreateProjectTag,
}: {
activeWorkspace: ProjectWorkspace
activeTaskID: string | null
@@ -18,59 +19,107 @@ export function ProjectTasks({
onCloseTask: () => void
onSelectItem: (title: string) => void
onCreateTask: () => void
onCreateProjectTag: (name: string) => void
}) {
const { tasks, project } = activeWorkspace
const { tasks, project, tags } = activeWorkspace
const activeTask = tasks.find((task) => task.id === activeTaskID)
const lanes = makeLanes(tasks)
const pendingTasks = tasks.filter((task) => !task.completed)
const completedTasks = tasks.filter((task) => task.completed)
const projectTags = tags.filter((tag) => tag !== 'all' && tag !== '全部')
const [tagModalOpen, setTagModalOpen] = useState(false)
const [tagName, setTagName] = useState('')
if (activeTask) {
return <TaskDetail activeWorkspace={activeWorkspace} task={activeTask} onBack={onCloseTask} />
}
function submitTag() {
const nextTag = tagName.trim()
if (!nextTag) return
onCreateProjectTag(nextTag)
setTagName('')
setTagModalOpen(false)
}
return (
<div className="project-channel-page project-tasks-page overview-page">
<div className="overview-head">
<div>
<Title heading={4}></Title>
<Text type="secondary">{project.name} TODO </Text>
<Text type="secondary">{project.name} </Text>
</div>
<Button type="primary" icon={<IconPlus />} onClick={onCreateTask}></Button>
</div>
<Card className="queue-section" bordered>
<Card className="queue-section task-tag-section" bordered>
<div className="section-header">
<Title heading={6}></Title>
<Tag color="arcoblue">{tasks.length} </Tag>
<Title heading={6}></Title>
<Button className="tag-create-button" size="mini" type="text" icon={<IconPlus />} onClick={() => setTagModalOpen(true)}></Button>
</div>
{tasks.map((task) => (
<button className="data-row task-row" key={task.id} onClick={() => onOpenTask(task.id)}>
<span className="row-title"><IconCheckCircle /> {task.title}</span>
<Tag color="arcoblue">{task.tag}</Tag>
<span><IconUser /> {task.owner}</span>
<span><IconCalendar /> {task.createdAt}</span>
<Progress percent={Number.parseInt(task.progress, 10)} size="small" />
</button>
))}
<Space wrap>
<Tag color="arcoblue"></Tag>
{projectTags.map((tag) => (
<Tag key={tag} color="gray">{tag}</Tag>
))}
</Space>
</Card>
<div className="task-board">
{lanes.map((lane) => (
<Card className="task-lane" bordered key={lane.title}>
<div className="section-header">
<Title heading={6}>{lane.title}</Title>
<Tag color={lane.color}>{lane.items.length}</Tag>
</div>
<Space direction="vertical" size={8}>
{lane.items.map((title) => (
<button className="task-card-button" key={title} onClick={() => openTaskByTitle(tasks, title, onOpenTask, onSelectItem)}>
<Text>{title}</Text>
<Text type="secondary"> · </Text>
</button>
))}
</Space>
</Card>
<Card className="queue-section" bordered>
<div className="section-header">
<Title heading={6}></Title>
<Tag color="arcoblue">{pendingTasks.length}</Tag>
</div>
<div className="task-plan-card-grid">
{pendingTasks.map((task) => (
<button className="task-plan-card" key={task.id} onClick={() => onOpenTask(task.id)}>
<span className="task-plan-title"><IconCheckCircleFill />{task.title}</span>
<Paragraph ellipsis={{ rows: 2 }} type="secondary">{task.summary || '暂无正文'}</Paragraph>
<div className="task-plan-meta">
<Tag color="arcoblue">{task.tag || '未设置标签'}</Tag>
<span><IconCalendar /> {task.createdAt}</span>
</div>
</button>
))}
{pendingTasks.length === 0 && <Text type="secondary"></Text>}
</div>
</Card>
<Card className="queue-section" bordered>
<div className="section-header">
<Title heading={6}></Title>
<Tag color="green">{completedTasks.length}</Tag>
</div>
{completedTasks.map((task) => (
<button className="completed-plan-row" key={task.id} onClick={() => onOpenTask(task.id)}>
<span className="completed-plan-main">
<Text className="completed-plan-title"><IconCheckCircle />{task.title}</Text>
<Text type="secondary" ellipsis={{ showTooltip: true }}>{task.summary || '暂无正文'}</Text>
</span>
<span>{task.createdAt}</span>
<span>{task.completedAt || '未知'}</span>
<span><IconClockCircle /> {task.duration || '未知'}</span>
</button>
))}
</div>
{completedTasks.length === 0 && <Text type="secondary"></Text>}
</Card>
<Modal
className="action-modal"
title="新建标签"
visible={tagModalOpen}
onCancel={() => {
setTagModalOpen(false)
setTagName('')
}}
onOk={submitTag}
>
<Space direction="vertical" size={12} className="action-form">
<label>
<Text></Text>
<Input autoFocus placeholder="例如:设计、客户、重要" value={tagName} onChange={setTagName} onPressEnter={submitTag} />
</label>
</Space>
</Modal>
</div>
)
}
@@ -86,7 +135,7 @@ function TaskDetail({ activeWorkspace, task, onBack }: { activeWorkspace: Projec
<Title heading={4}>{task.title}</Title>
<Text type="secondary">{activeWorkspace.project.name} </Text>
</div>
<Tag color={task.completed ? 'green' : 'arcoblue'}>{task.tag}</Tag>
<Tag color={task.completed ? 'green' : 'arcoblue'}>{task.tag || '未设置标签'}</Tag>
</div>
<Card className="queue-section task-detail-card" bordered>
@@ -98,6 +147,8 @@ function TaskDetail({ activeWorkspace, task, onBack }: { activeWorkspace: Projec
{ label: '负责人', value: task.owner },
{ label: '创建时间', value: task.createdAt },
{ label: '完成状态', value: task.completed ? '已完成' : '未完成' },
{ label: '完成时间', value: task.completedAt || '未完成' },
{ label: '耗时', value: task.duration || '未完成' },
]}
/>
<div className="task-detail-progress">
@@ -112,22 +163,3 @@ function TaskDetail({ activeWorkspace, task, onBack }: { activeWorkspace: Projec
</div>
)
}
function openTaskByTitle(tasks: TaskItem[], title: string, onOpenTask: (taskID: string) => void, onSelectItem: (title: string) => void) {
const task = tasks.find((item) => item.title === title)
if (task) {
onOpenTask(task.id)
return
}
onSelectItem(title)
}
function makeLanes(tasks: TaskItem[]) {
const done = tasks.filter((task) => task.completed)
const active = tasks.filter((task) => !task.completed)
return [
{ title: '待开始', color: 'gray', items: active.slice(0, 1).map((task) => task.title) },
{ title: '进行中', color: 'arcoblue', items: active.map((task) => task.title) },
{ title: '已完成', color: 'green', items: done.length ? done.map((task) => task.title) : ['暂无已完成任务'] },
]
}