feat(client): 部门管理与员工创建增强(F32)
- 新增部门管理页:列表/创建/归档 - 用户创建支持选择部门与直属上级 - 补齐 departments/employees API 封装与导航入口
这个提交包含在:
@@ -2,6 +2,8 @@ import { useMemo, useState } from "react";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { mapApiErrorMessage } from "../api/errorMessages";
|
||||
import { isApiError } from "../api/types";
|
||||
import { fetchDepartments } from "../api/departments";
|
||||
import { fetchEmployees } from "../api/employees";
|
||||
import {
|
||||
createUser,
|
||||
disableUser,
|
||||
@@ -51,6 +53,8 @@ export function UserManagementPage() {
|
||||
const [phone, setPhone] = useState("");
|
||||
const [email, setEmail] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [departmentId, setDepartmentId] = useState("");
|
||||
const [directManagerId, setDirectManagerId] = useState("");
|
||||
const [createRoles, setCreateRoles] = useState<Set<string>>(() => new Set(["employee"]));
|
||||
|
||||
const [roleDialogUser, setRoleDialogUser] = useState<UserSummary | null>(null);
|
||||
@@ -61,6 +65,16 @@ export function UserManagementPage() {
|
||||
queryFn: () => fetchUsers({ page, pageSize: PAGE_SIZE, status: status || undefined, q: q || undefined }),
|
||||
});
|
||||
|
||||
const departmentsQuery = useQuery({
|
||||
queryKey: ["departments", "active"],
|
||||
queryFn: () => fetchDepartments({ includeArchived: false }),
|
||||
});
|
||||
|
||||
const managersQuery = useQuery({
|
||||
queryKey: ["employees", "active"],
|
||||
queryFn: () => fetchEmployees({ page: 1, pageSize: 200, status: "active" }),
|
||||
});
|
||||
|
||||
const totalPages = Math.max(1, Math.ceil((listQuery.data?.total ?? 0) / PAGE_SIZE));
|
||||
|
||||
const createPayload = useMemo(() => {
|
||||
@@ -70,9 +84,11 @@ export function UserManagementPage() {
|
||||
phone: phone.trim(),
|
||||
email: email.trim(),
|
||||
password: password,
|
||||
departmentId: departmentId || undefined,
|
||||
directManagerId: directManagerId ? directManagerId : null,
|
||||
roles: Array.from(createRoles),
|
||||
};
|
||||
}, [createRoles, email, employeeNo, name, password, phone]);
|
||||
}, [createRoles, departmentId, directManagerId, email, employeeNo, name, password, phone]);
|
||||
|
||||
const createMutation = useMutation({
|
||||
mutationFn: () => createUser(createPayload),
|
||||
@@ -84,6 +100,8 @@ export function UserManagementPage() {
|
||||
setPhone("");
|
||||
setEmail("");
|
||||
setPassword("");
|
||||
setDepartmentId("");
|
||||
setDirectManagerId("");
|
||||
setCreateRoles(new Set(["employee"]));
|
||||
setError("");
|
||||
},
|
||||
@@ -225,6 +243,46 @@ export function UserManagementPage() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="departmentId">部门(可选)</Label>
|
||||
<select
|
||||
id="departmentId"
|
||||
data-testid="users-create-department"
|
||||
className="h-11 w-full rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm"
|
||||
value={departmentId}
|
||||
onChange={(event) => setDepartmentId(event.target.value)}
|
||||
>
|
||||
<option value="">
|
||||
{departmentsQuery.isLoading ? "加载中..." : "默认(与当前管理员一致)"}
|
||||
</option>
|
||||
{(departmentsQuery.data?.items ?? []).map((item) => (
|
||||
<option key={item.id} value={item.id}>
|
||||
{item.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="directManagerId">直属上级(可选)</Label>
|
||||
<select
|
||||
id="directManagerId"
|
||||
data-testid="users-create-direct-manager"
|
||||
className="h-11 w-full rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm"
|
||||
value={directManagerId}
|
||||
onChange={(event) => setDirectManagerId(event.target.value)}
|
||||
>
|
||||
<option value="">
|
||||
{managersQuery.isLoading ? "加载中..." : "无"}
|
||||
</option>
|
||||
{(managersQuery.data?.items ?? []).map((item) => (
|
||||
<option key={item.id} value={item.id}>
|
||||
{item.name} ({item.employeeNo})
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-2">
|
||||
<Label>角色</Label>
|
||||
<div className="flex flex-wrap gap-3 text-sm">
|
||||
@@ -500,4 +558,3 @@ export function UserManagementPage() {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
在新工单中引用
屏蔽一个用户