import { 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, enableUser, fetchUsers, resetUserPassword, setUserRoles, type UserStatus, type UserSummary, } from "../api/users"; import { PageHeader } from "../components/common/PageHeader"; import { Button } from "../components/ui/button"; import { Input } from "../components/ui/input"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../components/ui/table"; import { toast } from "sonner"; import { TableSkeleton } from "../components/common/TableSkeleton"; import { StatusBadge } from "../components/common/StatusBadge"; import { Pagination } from "../components/common/Pagination"; import { CreateUserDialog, type CreateUserFormData } from "./user-management/CreateUserDialog"; import { UserImportDialog } from "./user-management/UserImportDialog"; import { UserRoleDialog } from "./user-management/UserRoleDialog"; const PAGE_SIZE = 20; const ROLE_OPTIONS: Array<{ code: string; label: string }> = [ { code: "employee", label: "employee" }, { code: "manager", label: "manager" }, { code: "ceo", label: "ceo" }, { code: "finance", label: "finance" }, { code: "finance_mgr", label: "finance_mgr" }, { code: "administration", label: "administration" }, { code: "hr", label: "hr" }, { code: "admin", label: "admin" }, ]; function formatRoleCodes(roleCodes: string[]) { return roleCodes.length ? roleCodes.join(", ") : "-"; } export function UserManagementPage() { const queryClient = useQueryClient(); const [status, setStatus] = useState(""); const [q, setQ] = useState(""); const [page, setPage] = useState(1); const [error, setError] = useState(""); const [credentialResult, setCredentialResult] = useState<{ mode: "create" | "reset"; userId: string; employeeNo?: string; password: string; } | null>(null); const [createOpen, setCreateOpen] = useState(false); const [importOpen, setImportOpen] = useState(false); const [roleDialogUser, setRoleDialogUser] = useState(null); const listQuery = useQuery({ queryKey: ["users", "list", status, q, page], 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 createMutation = useMutation({ mutationFn: (payload: CreateUserFormData) => createUser(payload), onSuccess: async (data) => { await queryClient.invalidateQueries({ queryKey: ["users"] }); setCreateOpen(false); setError(""); setCredentialResult({ mode: "create", userId: data.id, employeeNo: data.employeeNo, password: data.oneTimePassword, }); toast.success("用户已创建"); }, onError: (err) => { const msg = isApiError(err) ? `${err.code}: ${mapApiErrorMessage(err.code, err.details, err.message)}` : err instanceof Error ? err.message : "创建用户失败"; setError(msg); toast.error(msg); }, }); const toggleMutation = useMutation({ mutationFn: async (item: UserSummary) => { if (item.status === "active") { return disableUser(item.id); } return enableUser(item.id); }, onSuccess: async () => { await queryClient.invalidateQueries({ queryKey: ["users"] }); setError(""); toast.success("状态已更新"); }, onError: (err) => { const msg = isApiError(err) ? `${err.code}: ${mapApiErrorMessage(err.code, err.details, err.message)}` : err instanceof Error ? err.message : "更新用户状态失败"; setError(msg); toast.error(msg); }, }); const resetMutation = useMutation({ mutationFn: async (item: UserSummary) => resetUserPassword(item.id), onSuccess: async (data) => { setCredentialResult({ mode: "reset", userId: data.id, password: data.oneTimePassword }); await queryClient.invalidateQueries({ queryKey: ["users"] }); setError(""); toast.success("密码已重置"); }, onError: (err) => { const msg = isApiError(err) ? `${err.code}: ${mapApiErrorMessage(err.code, err.details, err.message)}` : err instanceof Error ? err.message : "重置密码失败"; setError(msg); toast.error(msg); }, }); const rolesMutation = useMutation({ mutationFn: async ({ userId, roles }: { userId: string; roles: string[] }) => { return setUserRoles(userId, roles); }, onSuccess: async () => { await queryClient.invalidateQueries({ queryKey: ["users"] }); setRoleDialogUser(null); setError(""); toast.success("角色已更新"); }, onError: (err) => { const msg = isApiError(err) ? `${err.code}: ${mapApiErrorMessage(err.code, err.details, err.message)}` : err instanceof Error ? err.message : "更新角色失败"; setError(msg); toast.error(msg); }, }); return (
{ await Promise.all([ queryClient.invalidateQueries({ queryKey: ["users"] }), queryClient.invalidateQueries({ queryKey: ["employees"] }), ]); }} /> createMutation.mutate(data)} isPending={createMutation.isPending} departments={{ isLoading: departmentsQuery.isLoading, items: departmentsQuery.data?.items ?? [], }} managers={{ isLoading: managersQuery.isLoading, items: (managersQuery.data?.items ?? []).map((item) => ({ id: item.id, name: item.name, employeeNo: item.employeeNo, })), }} roleOptions={ROLE_OPTIONS} />
} /> {error ? (

{error}

) : null} {credentialResult ? (

{credentialResult.mode === "create" ? ( <> 已创建用户(user={credentialResult.userId} {credentialResult.employeeNo ? `,工号=${credentialResult.employeeNo}` : ""} ),一次性口令:{credentialResult.password} ) : ( <> 已重置密码(user={credentialResult.userId}),一次性口令:{credentialResult.password} )}

) : null}
{ setQ(e.target.value); setPage(1); }} placeholder="搜索工号/姓名/手机号/邮箱" />
{listQuery.isLoading ? : null} {listQuery.isError ? (

{isApiError(listQuery.error) ? `${listQuery.error.code}: ${mapApiErrorMessage( listQuery.error.code, listQuery.error.details, listQuery.error.message, )}` : "加载用户列表失败"}

) : null}
工号/姓名联系方式状态角色操作 {(listQuery.data?.items ?? []).length ? ( listQuery.data?.items.map((item) => (
{item.employeeNo} / {item.name}
{item.id}
{item.email}
{item.phone}
{formatRoleCodes(item.roleCodes)}
)) ) : ( 暂无数据 )}
{ if (!open) { setRoleDialogUser(null); } }} user={roleDialogUser} roleOptions={ROLE_OPTIONS} onSubmit={(roles) => { if (roleDialogUser) { rolesMutation.mutate({ userId: roleDialogUser.id, roles }); } }} isPending={rolesMutation.isPending} /> ); }