左侧栏 ProjectNav:项目完整列表(名 + 短 id + 我的角色, 选中高亮,点击切换)+ 创建项目(名/GitHub 仓库/repo_url) + 选中项目成员(邀请/改角色/新增移除);工作台删除 sec-projects 一节与任务列表里的项目下拉框,侧栏为唯一 项目选择器,自动选第一个项目逻辑不变。 薄模块补齐(均为已核实后端路由):成员移除 DELETE、 自动化详情(GET 单条 + 产出)与编辑 PATCH、分诊条目点进 任务/自动化详情、Workers 搜索与在线筛选、管理区设置配额 POST。主线新功能(Todos、Browser、taskMeta 等)原样保留, 未做任何回退。console 标记单测本地 53 项通过。
52 lines
2.5 KiB
TypeScript
52 lines
2.5 KiB
TypeScript
import { useState } from "react";
|
|
import { useConsole } from "../context";
|
|
import { Card, Bar } from "../components/ui";
|
|
import { shortId } from "../api/client";
|
|
import MembersPanel from "./MembersPanel";
|
|
|
|
// Left sidebar: the one project selector for the whole console.
|
|
// Lists every project (name + short id + my role), click selects it;
|
|
// below the list is the create-project form (same fields as the old
|
|
// workbench ProjectsSection: name, GitHub repo select, repo_url) and
|
|
// the selected project's members. The workbench no longer carries any
|
|
// project-module UI; it only consumes ctx.projectId.
|
|
export default function ProjectNav() {
|
|
const ctx = useConsole();
|
|
const [name, setName] = useState("");
|
|
const [repoSelect, setRepoSelect] = useState("");
|
|
const [repo, setRepo] = useState("");
|
|
return (
|
|
<div className="project-nav">
|
|
<Card title="项目">
|
|
<Bar><button onClick={ctx.refreshProjects}>刷新项目</button></Bar>
|
|
{ctx.projects.length === 0 ? <div className="muted">暂无项目</div> : (
|
|
<ul className="project-nav-list">
|
|
{ctx.projects.map((p) => (
|
|
<li key={p.id} className={"project-nav-item" + (p.id === ctx.projectId ? " selected" : "")} onClick={() => ctx.selectProject(p.id)} title={p.id}>
|
|
<span className="project-nav-name">{p.name}</span>
|
|
<span className="project-nav-meta">{shortId(p.id)}{p.my_role ? " · " + p.my_role : ""}</span>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
)}
|
|
<h3>创建项目</h3>
|
|
<Bar>
|
|
<input type="text" placeholder="新项目名" value={name} onChange={(e) => setName(e.target.value)} />
|
|
</Bar>
|
|
<Bar>
|
|
<select aria-label="从 GitHub 仓库选" value={repoSelect} onChange={(e) => { setRepoSelect(e.target.value); if (e.target.value) setRepo(e.target.value); }}>
|
|
<option value="">(从 GitHub App 安装仓库里选,或手动输入)</option>
|
|
{ctx.githubRepos.map((r) => <option key={r.clone_url} value={r.clone_url}>{r.repo_full_name}</option>)}
|
|
</select>
|
|
</Bar>
|
|
<Bar>
|
|
<input type="text" placeholder="repo_url(可选)" value={repo} onChange={(e) => setRepo(e.target.value)} />
|
|
<button className="primary" onClick={async () => { await ctx.createProject(name, repo); setName(""); setRepo(""); setRepoSelect(""); }}>创建项目</button>
|
|
</Bar>
|
|
<span className="muted">{ctx.projectMsg}</span>
|
|
</Card>
|
|
<MembersPanel />
|
|
</div>
|
|
);
|
|
}
|