R38: 完成员工管理筛选与部门树试跑

这个提交包含在:
2026-03-28 13:15:32 +08:00
父节点 debef078f6
当前提交 f33431fdb8
共修改 9 个文件,包含 474 行新增和 60 行删除
+91 -27
查看文件
@@ -7,6 +7,7 @@ import {
startEmployeeOffboarding,
startEmployeeOnboarding,
} from "../api/employees";
import { fetchDepartments } from "../api/departments";
import { isApiError } from "../api/types";
import { mapApiErrorMessage } from "../api/errorMessages";
import { PageHeader } from "../components/common/PageHeader";
@@ -19,10 +20,24 @@ import { toast } from "sonner";
export function PeoplePage() {
const queryClient = useQueryClient();
const [q, setQ] = useState("");
const [status, setStatus] = useState<"" | "active" | "archived">("");
const [departmentId, setDepartmentId] = useState("");
const departmentsQuery = useQuery({
queryKey: ["departments", "people-page", "active"],
queryFn: () => fetchDepartments({ includeArchived: false }),
});
const employeesQuery = useQuery({
queryKey: ["employees", "people-page", q],
queryFn: () => fetchEmployees({ page: 1, pageSize: 100, q: q || undefined }),
queryKey: ["employees", "people-page", q, status, departmentId],
queryFn: () =>
fetchEmployees({
page: 1,
pageSize: 100,
q: q || undefined,
status: status || undefined,
departmentId: departmentId || undefined,
}),
});
const onboardingMutation = useMutation({
@@ -74,6 +89,7 @@ export function PeoplePage() {
});
const items = employeesQuery.data?.items ?? [];
const departments = departmentsQuery.data?.items ?? [];
return (
<div className="space-y-6">
@@ -90,10 +106,48 @@ export function PeoplePage() {
/>
<div className="card-std p-4">
<Input placeholder="搜索姓名、工号、手机号、邮箱" value={q} onChange={(event) => setQ(event.target.value)} />
<div className="flex flex-wrap items-center gap-3">
<Input
placeholder="搜索姓名、工号、手机号、邮箱"
value={q}
onChange={(event) => setQ(event.target.value)}
/>
<select
className="select-std w-auto"
value={departmentId}
onChange={(event) => setDepartmentId(event.target.value)}
>
<option value="">全部部门</option>
{departments.map((item) => (
<option key={item.id} value={item.id}>
{item.name}
</option>
))}
</select>
<select
className="select-std w-auto"
value={status}
onChange={(event) => setStatus(event.target.value as "" | "active" | "archived")}
>
<option value="">全部状态</option>
<option value="active">生效中</option>
<option value="archived">已归档</option>
</select>
</div>
</div>
<div className="card-std p-6">
{employeesQuery.isError ? (
<p className="mb-4 rounded-lg border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-600">
{isApiError(employeesQuery.error)
? `${employeesQuery.error.code}: ${mapApiErrorMessage(
employeesQuery.error.code,
employeesQuery.error.details,
employeesQuery.error.message,
)}`
: "加载人员列表失败"}
</p>
) : null}
<Table>
<TableHeader>
<TableRow>
@@ -104,32 +158,42 @@ export function PeoplePage() {
</TableRow>
</TableHeader>
<TableBody>
{items.map((item) => (
<TableRow key={item.id}>
<TableCell>
<div className="font-medium text-zinc-900">{item.name}</div>
<div className="text-xs text-zinc-500">{item.employeeNo} · {item.email}</div>
</TableCell>
<TableCell>{item.roleCodes.join(" / ") || "-"}</TableCell>
<TableCell><StatusBadge status={item.status} /></TableCell>
<TableCell>
<div className="flex flex-wrap gap-2">
<Button asChild size="sm" variant="secondary" type="button">
<Link to={`/people/${item.id}`}>查看档案</Link>
</Button>
<Button size="sm" type="button" disabled={onboardingMutation.isPending} onClick={() => onboardingMutation.mutate(item.id)}>
发起入职
</Button>
<Button size="sm" variant="secondary" type="button" disabled={regularizeMutation.isPending} onClick={() => regularizeMutation.mutate(item.id)}>
转正
</Button>
<Button size="sm" variant="secondary" type="button" disabled={offboardingMutation.isPending} onClick={() => offboardingMutation.mutate(item.id)}>
发起离职
</Button>
</div>
{items.length ? (
items.map((item) => (
<TableRow key={item.id}>
<TableCell>
<div className="font-medium text-zinc-900">{item.name}</div>
<div className="text-xs text-zinc-500">
{item.employeeNo} · {item.email}
</div>
</TableCell>
<TableCell>{item.roleCodes.join(" / ") || "-"}</TableCell>
<TableCell><StatusBadge status={item.status} /></TableCell>
<TableCell>
<div className="flex flex-wrap gap-2">
<Button asChild size="sm" variant="secondary" type="button">
<Link to={`/people/${item.id}`}>查看档案</Link>
</Button>
<Button size="sm" type="button" disabled={onboardingMutation.isPending} onClick={() => onboardingMutation.mutate(item.id)}>
发起入职
</Button>
<Button size="sm" variant="secondary" type="button" disabled={regularizeMutation.isPending} onClick={() => regularizeMutation.mutate(item.id)}>
转正
</Button>
<Button size="sm" variant="secondary" type="button" disabled={offboardingMutation.isPending} onClick={() => offboardingMutation.mutate(item.id)}>
发起离职
</Button>
</div>
</TableCell>
</TableRow>
))
) : (
<TableRow>
<TableCell className="py-3 text-zinc-500" colSpan={4}>
暂无匹配员工
</TableCell>
</TableRow>
))}
)}
</TableBody>
</Table>
</div>