import { createFileRoute } from '@tanstack/react-router' import { PERM, requirePermission } from '@/auth/permissions' import { useQuery } from '@tanstack/react-query' import { useEffect, useState } from 'react' import type { ColumnDef } from '@tanstack/react-table' import { toast } from 'sonner' import type { RoleUser } from '@/api/types' import { fetchRoles, searchRoleUsers, syncUserRoles } from '@/api/modules/roles' import { useAuthStore } from '@/auth/store' import { handleApiError } from '@/lib/errors' import { DataTable } from '@/components/data-table/data-table' import { PageHeader } from '@/components/page-header' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { Checkbox } from '@/components/ui/checkbox' import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog' import { Input } from '@/components/ui/input' export const Route = createFileRoute('/_authed/admin-users')({ beforeLoad: () => requirePermission(PERM.ROLE_ASSIGN), component: AdminUsersPage, }) function AdminUsersPage() { const myUid = useAuthStore((s) => s.user?.uid) // search applies on Enter to avoid a request per keystroke const [queryInput, setQueryInput] = useState('') const [query, setQuery] = useState('') const [editing, setEditing] = useState(null) const results = useQuery({ queryKey: ['role-users', query], queryFn: () => searchRoleUsers(query), enabled: query.length > 0, }) const columns: ColumnDef[] = [ { header: '用户', cell: ({ row }) => (
{row.original.display_name ?? row.original.username ?? '—'}
{row.original.email} {row.original.uid}
), }, { header: '角色', cell: ({ row }) => row.original.roles.length > 0 ? (
{row.original.roles.map((role) => ( {role} ))}
) : ( '—' ), }, { header: '操作', cell: ({ row }) => row.original.uid === myUid ? ( 不能修改自己 ) : ( ), }, ] return (
setQueryInput(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') setQuery(queryInput.trim()) }} placeholder="邮箱 / 用户名 / UID,回车搜索" className="h-8 w-64" /> } /> 0 && results.isPending} emptyText={query ? '没有匹配的用户' : '输入关键字并回车搜索'} /> setEditing(null)} onSaved={() => void results.refetch()} />
) } function AssignRolesDialog({ user, onClose, onSaved, }: { user: RoleUser | null onClose: () => void onSaved: () => void }) { const roles = useQuery({ queryKey: ['roles'], queryFn: fetchRoles, enabled: user !== null }) const [selected, setSelected] = useState>(new Set()) const [busy, setBusy] = useState(false) useEffect(() => { setSelected(new Set(user?.roles ?? [])) }, [user]) const toggle = (name: string, checked: boolean) => { setSelected((prev) => { const next = new Set(prev) if (checked) next.add(name) else next.delete(name) return next }) } const save = async () => { if (!user) return setBusy(true) try { await syncUserRoles(user.uid, [...selected]) toast.success(`已更新 ${user.display_name ?? user.email} 的角色`) onSaved() onClose() } catch (error) { handleApiError(error) } finally { setBusy(false) } } return ( !open && onClose()}> 分配角色:{user?.display_name ?? user?.email}
{(roles.data?.data ?? []).map((role) => ( ))}
) }