feat: add project action modals and APIs
This commit is contained in:
@@ -0,0 +1,162 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Input, Modal, Space, Switch, Typography } from '@arco-design/web-react'
|
||||
|
||||
const { Text } = Typography
|
||||
const { TextArea } = Input
|
||||
|
||||
export type ProjectActionModal = 'project' | 'task' | 'source' | 'cron' | null
|
||||
|
||||
export type ProjectDraft = {
|
||||
name: string
|
||||
description: string
|
||||
}
|
||||
|
||||
export type TaskDraft = {
|
||||
title: string
|
||||
description: string
|
||||
dueAt: string
|
||||
}
|
||||
|
||||
export type SourceDraft = {
|
||||
title: string
|
||||
file: File | null
|
||||
}
|
||||
|
||||
export type CronDraft = {
|
||||
title: string
|
||||
schedule: string
|
||||
enabled: boolean
|
||||
nextRunAt: string
|
||||
}
|
||||
|
||||
export function ProjectActionModals({
|
||||
activeModal,
|
||||
loading,
|
||||
onClose,
|
||||
onCreateProject,
|
||||
onCreateTask,
|
||||
onUploadSource,
|
||||
onCreateCronPlan,
|
||||
}: {
|
||||
activeModal: ProjectActionModal
|
||||
loading: boolean
|
||||
onClose: () => void
|
||||
onCreateProject: (draft: ProjectDraft) => void
|
||||
onCreateTask: (draft: TaskDraft) => void
|
||||
onUploadSource: (draft: SourceDraft) => void
|
||||
onCreateCronPlan: (draft: CronDraft) => void
|
||||
}) {
|
||||
const [project, setProject] = useState<ProjectDraft>({ name: '', description: '' })
|
||||
const [task, setTask] = useState<TaskDraft>({ title: '', description: '', dueAt: '' })
|
||||
const [source, setSource] = useState<SourceDraft>({ title: '', file: null })
|
||||
const [cron, setCron] = useState<CronDraft>({ title: '', schedule: '0 9 * * *', enabled: true, nextRunAt: '' })
|
||||
|
||||
useEffect(() => {
|
||||
if (activeModal === null) {
|
||||
setProject({ name: '', description: '' })
|
||||
setTask({ title: '', description: '', dueAt: '' })
|
||||
setSource({ title: '', file: null })
|
||||
setCron({ title: '', schedule: '0 9 * * *', enabled: true, nextRunAt: '' })
|
||||
}
|
||||
}, [activeModal])
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
className="action-modal"
|
||||
title="新建项目"
|
||||
visible={activeModal === 'project'}
|
||||
confirmLoading={loading}
|
||||
onCancel={onClose}
|
||||
onOk={() => onCreateProject(project)}
|
||||
>
|
||||
<Space direction="vertical" size={12} className="action-form">
|
||||
<label>
|
||||
<Text>项目名称</Text>
|
||||
<Input placeholder="例如:项目 A3" value={project.name} onChange={(name) => setProject((draft) => ({ ...draft, name }))} />
|
||||
</label>
|
||||
<label>
|
||||
<Text>项目说明</Text>
|
||||
<TextArea rows={4} placeholder="项目目标、背景或协作说明" value={project.description} onChange={(description) => setProject((draft) => ({ ...draft, description }))} />
|
||||
</label>
|
||||
</Space>
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
className="action-modal"
|
||||
title="新建任务"
|
||||
visible={activeModal === 'task'}
|
||||
confirmLoading={loading}
|
||||
onCancel={onClose}
|
||||
onOk={() => onCreateTask(task)}
|
||||
>
|
||||
<Space direction="vertical" size={12} className="action-form">
|
||||
<label>
|
||||
<Text>任务标题</Text>
|
||||
<Input placeholder="要完成什么?" value={task.title} onChange={(title) => setTask((draft) => ({ ...draft, title }))} />
|
||||
</label>
|
||||
<label>
|
||||
<Text>任务说明</Text>
|
||||
<TextArea rows={4} placeholder="补充背景、验收口径或下一步" value={task.description} onChange={(description) => setTask((draft) => ({ ...draft, description }))} />
|
||||
</label>
|
||||
<label>
|
||||
<Text>截止时间</Text>
|
||||
<Input placeholder="2026-07-21T09:30:00Z,可留空" value={task.dueAt} onChange={(dueAt) => setTask((draft) => ({ ...draft, dueAt }))} />
|
||||
</label>
|
||||
</Space>
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
className="action-modal"
|
||||
title="上传文件"
|
||||
visible={activeModal === 'source'}
|
||||
confirmLoading={loading}
|
||||
onCancel={onClose}
|
||||
onOk={() => onUploadSource(source)}
|
||||
>
|
||||
<Space direction="vertical" size={12} className="action-form">
|
||||
<label>
|
||||
<Text>资料标题</Text>
|
||||
<Input placeholder="默认使用文件名" value={source.title} onChange={(title) => setSource((draft) => ({ ...draft, title }))} />
|
||||
</label>
|
||||
<label>
|
||||
<Text>选择文件</Text>
|
||||
<input
|
||||
className="native-file-input"
|
||||
type="file"
|
||||
onChange={(event) => setSource((draft) => ({ ...draft, file: event.target.files?.[0] ?? null }))}
|
||||
/>
|
||||
</label>
|
||||
</Space>
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
className="action-modal"
|
||||
title="新建计划任务"
|
||||
visible={activeModal === 'cron'}
|
||||
confirmLoading={loading}
|
||||
onCancel={onClose}
|
||||
onOk={() => onCreateCronPlan(cron)}
|
||||
>
|
||||
<Space direction="vertical" size={12} className="action-form">
|
||||
<label>
|
||||
<Text>计划名称</Text>
|
||||
<Input placeholder="例如:每日 Inbox 整理" value={cron.title} onChange={(title) => setCron((draft) => ({ ...draft, title }))} />
|
||||
</label>
|
||||
<label>
|
||||
<Text>Cron 表达式</Text>
|
||||
<Input value={cron.schedule} onChange={(schedule) => setCron((draft) => ({ ...draft, schedule }))} />
|
||||
</label>
|
||||
<label>
|
||||
<Text>下次运行时间</Text>
|
||||
<Input placeholder="2026-07-22T08:00:00Z,可留空" value={cron.nextRunAt} onChange={(nextRunAt) => setCron((draft) => ({ ...draft, nextRunAt }))} />
|
||||
</label>
|
||||
<label className="action-switch">
|
||||
<Text>启用计划</Text>
|
||||
<Switch checked={cron.enabled} onChange={(enabled) => setCron((draft) => ({ ...draft, enabled }))} />
|
||||
</label>
|
||||
</Space>
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -12,6 +12,9 @@ export function ProjectChannelPage({
|
||||
onSelectItem,
|
||||
onOpenTask,
|
||||
onCloseTask,
|
||||
onCreateTask,
|
||||
onUploadSource,
|
||||
onCreateCronPlan,
|
||||
}: {
|
||||
activeChannel: ChannelKey
|
||||
activeWorkspace: ProjectWorkspace
|
||||
@@ -19,16 +22,19 @@ export function ProjectChannelPage({
|
||||
onSelectItem: (title: string) => void
|
||||
onOpenTask: (taskID: string) => void
|
||||
onCloseTask: () => void
|
||||
onCreateTask: () => void
|
||||
onUploadSource: () => void
|
||||
onCreateCronPlan: () => void
|
||||
}) {
|
||||
switch (activeChannel) {
|
||||
case 'tasks':
|
||||
return <ProjectTasks activeWorkspace={activeWorkspace} activeTaskID={activeTaskID} onOpenTask={onOpenTask} onCloseTask={onCloseTask} onSelectItem={onSelectItem} />
|
||||
return <ProjectTasks activeWorkspace={activeWorkspace} activeTaskID={activeTaskID} onOpenTask={onOpenTask} onCloseTask={onCloseTask} onSelectItem={onSelectItem} onCreateTask={onCreateTask} />
|
||||
case 'ai':
|
||||
return <ProjectAi activeWorkspace={activeWorkspace} onSelectItem={onSelectItem} />
|
||||
case 'notes':
|
||||
return <ProjectNotes activeWorkspace={activeWorkspace} onSelectItem={onSelectItem} />
|
||||
return <ProjectNotes activeWorkspace={activeWorkspace} onSelectItem={onSelectItem} onUploadSource={onUploadSource} />
|
||||
case 'cron':
|
||||
return <ProjectCron activeWorkspace={activeWorkspace} onSelectItem={onSelectItem} />
|
||||
return <ProjectCron activeWorkspace={activeWorkspace} onSelectItem={onSelectItem} onCreateCronPlan={onCreateCronPlan} />
|
||||
case 'overview':
|
||||
default:
|
||||
return <ProjectOverview activeWorkspace={activeWorkspace} onSelectItem={onSelectItem} />
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { ProjectWorkspace } from './project-types'
|
||||
|
||||
const { Title, Text } = Typography
|
||||
|
||||
export function ProjectCron({ activeWorkspace, onSelectItem }: { activeWorkspace: ProjectWorkspace; onSelectItem: (title: string) => void }) {
|
||||
export function ProjectCron({ activeWorkspace, onSelectItem, onCreateCronPlan }: { activeWorkspace: ProjectWorkspace; onSelectItem: (title: string) => void; onCreateCronPlan: () => void }) {
|
||||
const { cronJobs, project } = activeWorkspace
|
||||
const enabledCount = cronJobs.filter((job) => job.enabled).length
|
||||
const pausedCount = cronJobs.length - enabledCount
|
||||
@@ -16,7 +16,7 @@ export function ProjectCron({ activeWorkspace, onSelectItem }: { activeWorkspace
|
||||
<Title heading={4}>Cron 计划任务</Title>
|
||||
<Text type="secondary">{project.name} 的定时任务、自动检查和周期性 AI 工作。</Text>
|
||||
</div>
|
||||
<Button type="primary" icon={<IconPlus />}>新建计划</Button>
|
||||
<Button type="primary" icon={<IconPlus />} onClick={onCreateCronPlan}>新建计划</Button>
|
||||
</div>
|
||||
|
||||
<Card className="queue-section" bordered>
|
||||
|
||||
@@ -6,7 +6,7 @@ const { Title, Text } = Typography
|
||||
|
||||
const folders = ['需求文档', '技术方案', '会议纪要', '竞品资料']
|
||||
|
||||
export function ProjectNotes({ activeWorkspace, onSelectItem }: { activeWorkspace: ProjectWorkspace; onSelectItem: (title: string) => void }) {
|
||||
export function ProjectNotes({ activeWorkspace, onSelectItem, onUploadSource }: { activeWorkspace: ProjectWorkspace; onSelectItem: (title: string) => void; onUploadSource: () => void }) {
|
||||
const { notes, project } = activeWorkspace
|
||||
|
||||
return (
|
||||
@@ -18,7 +18,7 @@ export function ProjectNotes({ activeWorkspace, onSelectItem }: { activeWorkspac
|
||||
</div>
|
||||
<Space>
|
||||
<Button icon={<IconSearch />}>搜索资料</Button>
|
||||
<Button type="primary" icon={<IconPlus />}>上传/新建</Button>
|
||||
<Button type="primary" icon={<IconPlus />} onClick={onUploadSource}>上传/新建</Button>
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ export function ProjectRail({
|
||||
onSelectWorkspace,
|
||||
onSelectWorkspaceInbox,
|
||||
onSelectProject,
|
||||
onCreateProject,
|
||||
}: {
|
||||
activeProject: Project
|
||||
projects: Project[]
|
||||
@@ -19,6 +20,7 @@ export function ProjectRail({
|
||||
onSelectWorkspace: () => void
|
||||
onSelectWorkspaceInbox: () => void
|
||||
onSelectProject: (project: Project) => void
|
||||
onCreateProject: () => void
|
||||
}) {
|
||||
return (
|
||||
<Sider className="project-rail" width={96}>
|
||||
@@ -50,7 +52,7 @@ export function ProjectRail({
|
||||
</Badge>
|
||||
))}
|
||||
</Space>
|
||||
<Button className="create-project" aria-label="新建项目" icon={<IconPlus />} title="新建项目" />
|
||||
<Button className="create-project" aria-label="新建项目" icon={<IconPlus />} title="新建项目" onClick={onCreateProject} />
|
||||
</Sider>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -10,12 +10,14 @@ export function ProjectTasks({
|
||||
onOpenTask,
|
||||
onCloseTask,
|
||||
onSelectItem,
|
||||
onCreateTask,
|
||||
}: {
|
||||
activeWorkspace: ProjectWorkspace
|
||||
activeTaskID: string | null
|
||||
onOpenTask: (taskID: string) => void
|
||||
onCloseTask: () => void
|
||||
onSelectItem: (title: string) => void
|
||||
onCreateTask: () => void
|
||||
}) {
|
||||
const { tasks, project } = activeWorkspace
|
||||
const activeTask = tasks.find((task) => task.id === activeTaskID)
|
||||
@@ -32,7 +34,7 @@ export function ProjectTasks({
|
||||
<Title heading={4}>工作计划</Title>
|
||||
<Text type="secondary">{project.name} 的 TODO 计划、负责人和创建时间。</Text>
|
||||
</div>
|
||||
<Button type="primary" icon={<IconPlus />}>新建任务</Button>
|
||||
<Button type="primary" icon={<IconPlus />} onClick={onCreateTask}>新建任务</Button>
|
||||
</div>
|
||||
|
||||
<Card className="queue-section" bordered>
|
||||
|
||||
@@ -25,6 +25,10 @@ export function ProjectPage({
|
||||
onOpenTask,
|
||||
onCloseTask,
|
||||
onToggleTheme,
|
||||
onCreateProject,
|
||||
onCreateTask,
|
||||
onUploadSource,
|
||||
onCreateCronPlan,
|
||||
}: {
|
||||
activeView: WorkbenchView
|
||||
activeWorkspace: ProjectWorkspace
|
||||
@@ -40,6 +44,10 @@ export function ProjectPage({
|
||||
onOpenTask: (project: Project, taskID: string) => void
|
||||
onCloseTask: () => void
|
||||
onToggleTheme: () => void
|
||||
onCreateProject: () => void
|
||||
onCreateTask: () => void
|
||||
onUploadSource: () => void
|
||||
onCreateCronPlan: () => void
|
||||
}) {
|
||||
const isProject = activeView === 'project'
|
||||
const projects = workspaces.map((workspace) => workspace.project)
|
||||
@@ -56,6 +64,7 @@ export function ProjectPage({
|
||||
onSelectWorkspace={onSelectWorkspace}
|
||||
onSelectWorkspaceInbox={onSelectWorkspaceInbox}
|
||||
onSelectProject={onSelectProject}
|
||||
onCreateProject={onCreateProject}
|
||||
/>
|
||||
|
||||
{isProject && (
|
||||
@@ -81,6 +90,9 @@ export function ProjectPage({
|
||||
onSelectItem={onSelectItem}
|
||||
onOpenTask={(taskID) => onOpenTask(activeWorkspace.project, taskID)}
|
||||
onCloseTask={onCloseTask}
|
||||
onCreateTask={onCreateTask}
|
||||
onUploadSource={onUploadSource}
|
||||
onCreateCronPlan={onCreateCronPlan}
|
||||
/>
|
||||
)}
|
||||
</Content>
|
||||
|
||||
Reference in New Issue
Block a user