import { useEffect, type FormEvent } from "react"; import { type Workspace } from "../../services/api"; import { useAuth } from "../../context/AuthContext"; import Icon from "../common/Icon"; import { useAdminStore } from "../../features/admin/state/adminStore"; import "../../styles/admin.css"; import { UserMultiSelect } from "./UserMultiSelect"; type Notice = { tone: "success" | "error" | "info"; message: string; }; export function ProjectManagementPage({ onNotify, onConnectionChange, }: { onNotify: (notice: Notice) => void; onConnectionChange: (online: boolean) => void; }) { const { user, currentWorkspace } = useAuth(); const projects = useAdminStore((s) => s.projects); const projectLoading = useAdminStore((s) => s.projectsLoading); const saving = useAdminStore((s) => s.projectsSaving); const editingProject = useAdminStore((s) => s.editingProject); const projectDialogOpen = useAdminStore((s) => s.projectDialogOpen); const projectForm = useAdminStore((s) => s.projectForm); const importMemberDialogOpen = useAdminStore((s) => s.importMemberDialogOpen); const selectedProject = useAdminStore((s) => s.selectedProject); const availableUsers = useAdminStore((s) => s.availableUsers); const selectedUserIds = useAdminStore((s) => s.selectedUserIds); const selectedRoleCode = useAdminStore((s) => s.selectedRoleCode); const projectSearchTerm = useAdminStore((s) => s.projectSearchTerm); const setProjectDialogOpen = useAdminStore((s) => s.setProjectDialogOpen); const setProjectForm = useAdminStore((s) => s.setProjectForm); const setEditingProject = useAdminStore((s) => s.setEditingProject); const setImportMemberDialogOpen = useAdminStore((s) => s.setImportMemberDialogOpen); const setSelectedProject = useAdminStore((s) => s.setSelectedProject); const setSelectedUserIds = useAdminStore((s) => s.setSelectedUserIds); const setSelectedRoleCode = useAdminStore((s) => s.setSelectedRoleCode); const setProjectSearchTerm = useAdminStore((s) => s.setProjectSearchTerm); const loadProjects = useAdminStore((s) => s.loadProjects); const createProject = useAdminStore((s) => s.createProject); const updateProject = useAdminStore((s) => s.updateProject); const deleteProject = useAdminStore((s) => s.deleteProject); const openImportMembers = useAdminStore((s) => s.openImportMembers); const importMembers = useAdminStore((s) => s.importMembers); const reset = useAdminStore((s) => s.reset); const canManage = user?.role_code === "admin"; const wsId = currentWorkspace?.workspace_id; useEffect(() => { reset(); void loadProjects(onNotify, onConnectionChange); }, [loadProjects, reset, wsId, onNotify, onConnectionChange]); const openCreateProject = (): void => { setEditingProject(null); setProjectForm({ workspace_code: "", workspace_name: "", quota_bytes: 0, description: "", }); setProjectDialogOpen(true); }; const openEditProject = (project: Workspace): void => { setEditingProject(project); setProjectForm({ workspace_code: project.workspace_code, workspace_name: project.workspace_name, quota_bytes: project.quota_bytes, description: project.description ?? "", }); setProjectDialogOpen(true); }; const submitProject = async (event: FormEvent): Promise => { event.preventDefault(); if (!projectForm.workspace_name.trim()) { onNotify({ tone: "error", message: "请输入项目名称" }); return; } if (!projectForm.workspace_code.trim() && !editingProject) { onNotify({ tone: "error", message: "请输入项目编码" }); return; } if (editingProject) { await updateProject( editingProject, { workspace_name: projectForm.workspace_name.trim(), quota_bytes: projectForm.quota_bytes, description: projectForm.description.trim() || "", }, onNotify, onConnectionChange, ); } else { const generatedCode = projectForm.workspace_code.trim() || projectForm.workspace_name.trim().toLowerCase().replace(/[^a-z0-9-]/g, "-").slice(0, 32); await createProject( { workspace_code: generatedCode, workspace_name: projectForm.workspace_name.trim(), quota_bytes: projectForm.quota_bytes, description: projectForm.description.trim() || "", }, onNotify, onConnectionChange, ); } }; const handleDelete = async (project: Workspace): Promise => { if (!window.confirm(`确定要删除项目"${project.workspace_name}"吗?此操作将级联软删所有成员。`)) return; await deleteProject(project, onNotify, onConnectionChange); }; const openImportMemberDialog = (project: Workspace): void => { setSelectedProject(project); setSelectedUserIds([]); setSelectedRoleCode("developer"); void openImportMembers(project, onNotify); }; const handleImportMember = async (): Promise => { if (!selectedProject || selectedUserIds.length === 0) { onNotify({ tone: "error", message: "请选择要添加的用户" }); return; } if (!selectedProject) return; await importMembers( selectedProject, selectedUserIds, selectedRoleCode, onNotify, ); }; return (
setProjectSearchTerm(event.target.value)} />
{!canManage &&
当前为开发人员,只能查看项目列表。
}
项目名称 项目编码 状态 配额 操作
{projectLoading ? (

正在加载项目…

) : projects.length === 0 ? (

暂无项目

) : ( projects .filter((project) => { const term = projectSearchTerm.toLowerCase().trim(); if (!term) return true; return ( project.workspace_name.toLowerCase().includes(term) || project.workspace_code.toLowerCase().includes(term) || (project.description && project.description.toLowerCase().includes(term)) ); }) .map((project) => (
{project.workspace_name.slice(0, 1)} {project.workspace_name} {project.description ?? "无描述"} {project.workspace_code} {project.status === "active" ? "正常" : project.status === "archived" ? "已归档" : "已删除"} {project.quota_bytes > 0 ? `${(project.quota_bytes / 1024 / 1024 / 1024).toFixed(1)} GB` : "无限制"}
)) )}
{projectDialogOpen && (
PROJECT

{editingProject ? "编辑项目" : "新建项目"}

void submitProject(event)}> {!editingProject && ( )}