Files
codex-app-server/cloud/console-react/src/sections/MembersPanel.tsx
T
host 08484302ac console: 项目导航移到左侧栏,工作台不再承担项目模块
左侧栏 ProjectNav:项目完整列表(名 + 短 id + 我的角色,
选中高亮,点击切换)+ 创建项目(名/GitHub 仓库/repo_url)
+ 选中项目成员(邀请/改角色/新增移除);工作台删除
sec-projects 一节与任务列表里的项目下拉框,侧栏为唯一
项目选择器,自动选第一个项目逻辑不变。
薄模块补齐(均为已核实后端路由):成员移除 DELETE、
自动化详情(GET 单条 + 产出)与编辑 PATCH、分诊条目点进
任务/自动化详情、Workers 搜索与在线筛选、管理区设置配额
POST。主线新功能(Todos、Browser、taskMeta 等)原样保留,
未做任何回退。console 标记单测本地 53 项通过。
2026-10-30 07:43:54 +00:00

50 lines
2.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState } from "react";
import { useConsole } from "../context";
import { Card, Bar, DataListRaw } from "../components/ui";
import { ROLES } from "../api/types";
// Right-panel context for the Projects view. Previously embedded in the
// projects card; moved here so the centre stays the project list/create
// and the right stays the people for the selected project.
export default function MembersPanel() {
const ctx = useConsole();
const [inviteUser, setInviteUser] = useState("");
const [inviteRole, setInviteRole] = useState("contributor");
const proj = ctx.projects.find((p) => p.id === ctx.projectId);
return (
<Card title="项目成员">
{proj ? <div className="muted">{proj.name} | {proj.id} | 仓库 {proj.repo_url || "未绑定"} | 我的角色 {proj.my_role || "未知"}</div> : <div className="muted">先选项目</div>}
<Bar>
<input type="text" placeholder="成员 user_id 或用户名" value={inviteUser} onChange={(e) => setInviteUser(e.target.value)} />
<select aria-label="邀请角色" value={inviteRole} onChange={(e) => setInviteRole(e.target.value)}>
<option value="viewer">viewer(只读)</option>
<option value="contributor">contributor(可建任务)</option>
<option value="maintainer">maintainer(可批合并)</option>
<option value="owner">owner(可管项目)</option>
</select>
<button onClick={async () => { await ctx.inviteMember(inviteUser, inviteRole); setInviteUser(""); }}>邀请成员</button>
<button onClick={ctx.refreshMembers}>刷新成员</button>
</Bar>
{ctx.memberError ? <div className="muted">{ctx.memberError}</div>
: ctx.members.length === 0 ? <div className="muted">先选项目</div>
: (
<DataListRaw>
{ctx.members.map((m) => {
const label = (m.username || "") + (m.username ? "(" + String(m.user_id).slice(0, 8) + ")" : String(m.user_id).slice(0, 8));
return (
<li key={m.user_id}>
<span className="row-main">{label}</span>
<select aria-label="成员角色" value={m.role || "viewer"} onChange={(e) => void ctx.changeMemberRole(m.user_id, e.target.value)}>
{ROLES.map((r) => <option key={r} value={r}>{r}</option>)}
</select>
<button onClick={() => void ctx.removeMember(m.user_id)}>移除</button>
</li>
);
})}
</DataListRaw>
)}
<span className="muted">{ctx.memberMsg}</span>
</Card>
);
}