refactor: SchedulesPageRoute.tsx, UserManagementPage.tsx, ProjectManagementPage.tsx
This commit is contained in:
@@ -1,68 +1,69 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect, type FormEvent } from "react";
|
||||
|
||||
import { ApiRequestError, type Employee } from "../../services/api";
|
||||
import { useApi, useAuth } from "../../context/AuthContext";
|
||||
import { type Employee } from "../../services/api";
|
||||
import { useAuth } from "../../context/AuthContext";
|
||||
import Icon from "../../components/common/Icon";
|
||||
import { useAdminStore } from "../../features/admin/state/adminStore";
|
||||
|
||||
import "../../styles/admin.css";
|
||||
|
||||
const EMPTY_FORM = {
|
||||
username: "",
|
||||
display_name: "",
|
||||
email: "",
|
||||
role_code: "developer" as "admin" | "developer",
|
||||
password: "",
|
||||
status: "active" as "active" | "disabled" | "locked",
|
||||
type Notice = {
|
||||
tone: "success" | "error" | "info";
|
||||
message: string;
|
||||
};
|
||||
|
||||
export function UserManagementPage({
|
||||
onNotify,
|
||||
onConnectionChange,
|
||||
}: {
|
||||
onNotify: (notice: { tone: "success" | "error" | "info"; message: string }) => void;
|
||||
onNotify: (notice: Notice) => void;
|
||||
onConnectionChange: (online: boolean) => void;
|
||||
}) {
|
||||
const api = useApi();
|
||||
const { user } = useAuth();
|
||||
const [employees, setEmployees] = useState<Employee[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [editing, setEditing] = useState<Employee | null>(null);
|
||||
const [dialogOpen, setDialogOpen] = useState(false);
|
||||
const [form, setForm] = useState(EMPTY_FORM);
|
||||
const [userSearchTerm, setUserSearchTerm] = useState("");
|
||||
const { user, currentWorkspace } = useAuth();
|
||||
|
||||
const employees = useAdminStore((s) => s.employees);
|
||||
const loading = useAdminStore((s) => s.usersLoading);
|
||||
const saving = useAdminStore((s) => s.usersSaving);
|
||||
const editing = useAdminStore((s) => s.editingUser);
|
||||
const dialogOpen = useAdminStore((s) => s.userDialogOpen);
|
||||
const form = useAdminStore((s) => s.userForm);
|
||||
const userSearchTerm = useAdminStore((s) => s.userSearchTerm);
|
||||
|
||||
const setEditingUser = useAdminStore((s) => s.setEditingUser);
|
||||
const setUserDialogOpen = useAdminStore((s) => s.setUserDialogOpen);
|
||||
const setUserForm = useAdminStore((s) => s.setUserForm);
|
||||
const setUserSearchTerm = useAdminStore((s) => s.setUserSearchTerm);
|
||||
|
||||
const loadEmployees = useAdminStore((s) => s.loadEmployees);
|
||||
const createEmployee = useAdminStore((s) => s.createEmployee);
|
||||
const updateEmployee = useAdminStore((s) => s.updateEmployee);
|
||||
const deleteEmployee = useAdminStore((s) => s.deleteEmployee);
|
||||
const reset = useAdminStore((s) => s.reset);
|
||||
|
||||
const canManage = user?.role_code === "admin";
|
||||
|
||||
const load = async (): Promise<void> => {
|
||||
setLoading(true);
|
||||
try {
|
||||
setEmployees(await api.listEmployees());
|
||||
onConnectionChange(true);
|
||||
} catch (error) {
|
||||
onConnectionChange(false);
|
||||
onNotify({
|
||||
tone: "error",
|
||||
message: error instanceof Error ? error.message : "用户列表加载失败",
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const wsId = currentWorkspace?.workspace_id;
|
||||
useEffect(() => {
|
||||
void load();
|
||||
}, []);
|
||||
reset();
|
||||
void loadEmployees();
|
||||
}, [loadEmployees, reset, wsId]);
|
||||
|
||||
const openCreate = (): void => {
|
||||
setEditing(null);
|
||||
setForm(EMPTY_FORM);
|
||||
setDialogOpen(true);
|
||||
setEditingUser(null);
|
||||
setUserForm({
|
||||
username: "",
|
||||
display_name: "",
|
||||
email: "",
|
||||
role_code: "developer",
|
||||
password: "",
|
||||
status: "active",
|
||||
});
|
||||
setUserDialogOpen(true);
|
||||
};
|
||||
|
||||
const openEdit = (employee: Employee): void => {
|
||||
setEditing(employee);
|
||||
setForm({
|
||||
setEditingUser(employee);
|
||||
setUserForm({
|
||||
username: employee.username,
|
||||
display_name: employee.display_name,
|
||||
email: employee.email ?? "",
|
||||
@@ -70,18 +71,16 @@ export function UserManagementPage({
|
||||
password: "",
|
||||
status: employee.status,
|
||||
});
|
||||
setDialogOpen(true);
|
||||
setUserDialogOpen(true);
|
||||
};
|
||||
|
||||
const submit = async (event: React.FormEvent): Promise<void> => {
|
||||
const submit = async (event: FormEvent): Promise<void> => {
|
||||
event.preventDefault();
|
||||
if (!form.display_name.trim() || (!editing && !form.username.trim())) return;
|
||||
// 新建用户时密码必填
|
||||
if (!editing && !form.password.trim()) {
|
||||
onNotify({ tone: "error", message: "请输入密码" });
|
||||
return;
|
||||
}
|
||||
// 编辑自身或管理员时,前端二次拦截禁用 status / role_code 的修改
|
||||
if (editing) {
|
||||
const editingSelf = editing.user_id === user?.user_id;
|
||||
const targetIsAdmin = editing.role_code === "admin";
|
||||
@@ -102,58 +101,40 @@ export function UserManagementPage({
|
||||
return;
|
||||
}
|
||||
}
|
||||
// 密码长度校验 8~72 字符
|
||||
if (form.password && (form.password.length < 8 || form.password.length > 72)) {
|
||||
onNotify({ tone: "error", message: "密码长度必须在 8~72 字符之间" });
|
||||
return;
|
||||
}
|
||||
setSaving(true);
|
||||
try {
|
||||
if (editing) {
|
||||
const updated = await api.updateEmployee(editing.user_id, {
|
||||
if (editing) {
|
||||
await updateEmployee(
|
||||
editing,
|
||||
{
|
||||
display_name: form.display_name.trim(),
|
||||
email: form.email.trim() || null,
|
||||
role_code: form.role_code,
|
||||
status: form.status,
|
||||
});
|
||||
setEmployees((current) => current.map(
|
||||
(item) => item.user_id === updated.user_id ? updated : item,
|
||||
));
|
||||
onNotify({ tone: "success", message: "用户信息已更新" });
|
||||
} else {
|
||||
const created = await api.createEmployee({
|
||||
},
|
||||
onNotify,
|
||||
onConnectionChange,
|
||||
);
|
||||
} else {
|
||||
await createEmployee(
|
||||
{
|
||||
username: form.username.trim(),
|
||||
display_name: form.display_name.trim(),
|
||||
email: form.email.trim() || null,
|
||||
role_code: form.role_code,
|
||||
password: form.password,
|
||||
});
|
||||
setEmployees((current) => [...current, created]);
|
||||
onNotify({ tone: "success", message: "用户已添加" });
|
||||
}
|
||||
setDialogOpen(false);
|
||||
} catch (error) {
|
||||
onNotify({
|
||||
tone: "error",
|
||||
message: error instanceof ApiRequestError ? error.message : "保存用户失败",
|
||||
});
|
||||
} finally {
|
||||
setSaving(false);
|
||||
},
|
||||
onNotify,
|
||||
onConnectionChange,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const remove = async (employee: Employee): Promise<void> => {
|
||||
if (!window.confirm(`确定从当前 Workspace 删除用户"${employee.display_name}"吗?`)) return;
|
||||
try {
|
||||
await api.deleteEmployee(employee.user_id);
|
||||
setEmployees((current) => current.filter((item) => item.user_id !== employee.user_id));
|
||||
onNotify({ tone: "success", message: "用户已删除" });
|
||||
} catch (error) {
|
||||
onNotify({
|
||||
tone: "error",
|
||||
message: error instanceof Error ? error.message : "删除用户失败",
|
||||
});
|
||||
}
|
||||
await deleteEmployee(employee, onNotify, onConnectionChange);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -222,7 +203,7 @@ export function UserManagementPage({
|
||||
<span className="modal__eyebrow">EMPLOYEE</span>
|
||||
<h2>{editing ? "编辑用户" : "添加用户"}</h2>
|
||||
</div>
|
||||
<button className="icon-button" type="button" onClick={() => setDialogOpen(false)}><Icon name="close" /></button>
|
||||
<button className="icon-button" type="button" onClick={() => setUserDialogOpen(false)}><Icon name="close" /></button>
|
||||
</div>
|
||||
<form onSubmit={(event) => void submit(event)}>
|
||||
<label className="form-field">
|
||||
@@ -230,7 +211,7 @@ export function UserManagementPage({
|
||||
<input
|
||||
autoFocus={!editing}
|
||||
value={form.display_name}
|
||||
onChange={(event) => setForm({ ...form, display_name: event.target.value })}
|
||||
onChange={(event) => setUserForm({ ...form, display_name: event.target.value })}
|
||||
placeholder="请输入姓名"
|
||||
/>
|
||||
</label>
|
||||
@@ -239,7 +220,7 @@ export function UserManagementPage({
|
||||
<input
|
||||
disabled={Boolean(editing)}
|
||||
value={form.username}
|
||||
onChange={(event) => setForm({ ...form, username: event.target.value })}
|
||||
onChange={(event) => setUserForm({ ...form, username: event.target.value })}
|
||||
placeholder="请输入登录账号"
|
||||
autoComplete="username"
|
||||
/>
|
||||
@@ -251,7 +232,7 @@ export function UserManagementPage({
|
||||
type="password"
|
||||
autoComplete="new-password"
|
||||
value={form.password}
|
||||
onChange={(event) => setForm({ ...form, password: event.target.value })}
|
||||
onChange={(event) => setUserForm({ ...form, password: event.target.value })}
|
||||
placeholder="请输入密码(8~72 字符)"
|
||||
/>
|
||||
</label>
|
||||
@@ -262,13 +243,13 @@ export function UserManagementPage({
|
||||
type="email"
|
||||
autoComplete="email"
|
||||
value={form.email}
|
||||
onChange={(event) => setForm({ ...form, email: event.target.value })}
|
||||
onChange={(event) => setUserForm({ ...form, email: event.target.value })}
|
||||
placeholder="请输入邮箱(可选)"
|
||||
/>
|
||||
</label>
|
||||
<label className="form-field">
|
||||
<span>角色</span>
|
||||
<select value={form.role_code} disabled={Boolean(editing) && ((editing as Employee).user_id === user?.user_id || (editing as Employee).role_code === "admin")} onChange={(event) => setForm({ ...form, role_code: event.target.value as "admin" | "developer" })}>
|
||||
<select value={form.role_code} disabled={Boolean(editing) && ((editing as Employee).user_id === user?.user_id || (editing as Employee).role_code === "admin")} onChange={(event) => setUserForm({ ...form, role_code: event.target.value as "admin" | "developer" })}>
|
||||
<option value="developer">开发人员</option>
|
||||
<option value="admin">管理员</option>
|
||||
</select>
|
||||
@@ -276,7 +257,7 @@ export function UserManagementPage({
|
||||
{editing && (
|
||||
<label className="form-field">
|
||||
<span>状态</span>
|
||||
<select value={form.status} disabled={Boolean(editing) && ((editing as Employee).user_id === user?.user_id || (editing as Employee).role_code === "admin")} onChange={(event) => setForm({ ...form, status: event.target.value as typeof form.status })}>
|
||||
<select value={form.status} disabled={Boolean(editing) && ((editing as Employee).user_id === user?.user_id || (editing as Employee).role_code === "admin")} onChange={(event) => setUserForm({ ...form, status: event.target.value as typeof form.status })}>
|
||||
<option value="active">正常</option>
|
||||
<option value="disabled">停用</option>
|
||||
<option value="locked">锁定</option>
|
||||
@@ -284,7 +265,7 @@ export function UserManagementPage({
|
||||
</label>
|
||||
)}
|
||||
<div className="modal__footer">
|
||||
<button className="secondary-button" type="button" onClick={() => setDialogOpen(false)}>取消</button>
|
||||
<button className="secondary-button" type="button" onClick={() => setUserDialogOpen(false)}>取消</button>
|
||||
<button className="primary-button" type="submit" disabled={saving}>{saving ? "保存中…" : "保存"}</button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -293,4 +274,4 @@ export function UserManagementPage({
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user