"use client" import { useEffect, useMemo, useState } from "react" import { Controller, Resolver, useForm } from "react-hook-form" import { zodResolver } from "@hookform/resolvers/zod" import { SearchIcon, ShieldAlertIcon, ShieldCheckIcon, ShieldIcon } from "lucide-react" import { z } from "zod/v4" import type { AdminRole, AdminUser } from "@/lib/api/admin" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" import { Checkbox } from "@/components/ui/checkbox" import { Drawer, DrawerContent, DrawerFooter, DrawerHeader, DrawerTitle, } from "@/components/ui/drawer" import { Field, FieldContent, FieldError, FieldLabel, } from "@/components/ui/field" import { Input } from "@/components/ui/input" import { Status } from "@/lib/generated/enums" import { cn } from "@/lib/utils" type AssignRolesDrawerProps = { open: boolean saving: boolean loading: boolean item: AdminUser | null roles: AdminRole[] selectedRoleIds: number[] onOpenChange: (open: boolean) => void onSubmit: (roleIds: number[]) => Promise } const assignRolesSchema = z.object({ roleIds: z.array(z.number().int().positive()), }) type AssignRolesForm = z.infer const assignRolesResolver = zodResolver(assignRolesSchema as never) as Resolver< z.input, undefined, z.output > function buildForm(selectedRoleIds: number[]): AssignRolesForm { return { roleIds: selectedRoleIds, } } export function AssignRolesDrawer({ open, saving, loading, item, roles, selectedRoleIds, onOpenChange, onSubmit, }: AssignRolesDrawerProps) { return ( {open ? ( ) : null} ) } type AssignRolesDrawerBodyProps = { saving: boolean loading: boolean item: AdminUser | null roles: AdminRole[] selectedRoleIds: number[] onOpenChange: (open: boolean) => void onSubmit: (roleIds: number[]) => Promise } function AssignRolesDrawerBody({ saving, loading, item, roles, selectedRoleIds, onOpenChange, onSubmit, }: AssignRolesDrawerBodyProps) { const [keyword, setKeyword] = useState("") const form = useForm< z.input, undefined, z.output >({ resolver: assignRolesResolver, defaultValues: buildForm(selectedRoleIds), }) const { control, handleSubmit, reset, formState: { errors }, } = form useEffect(() => { reset(buildForm(selectedRoleIds)) }, [reset, selectedRoleIds]) const roleMap = useMemo( () => new Map(roles.map((role) => [role.id, role])), [roles] ) async function onFormSubmit(values: AssignRolesForm) { await onSubmit(values.roleIds) } return ( 分配角色
{ const value = field.value || [] const selectedRoleSet = new Set(value) const initiallySelectedSet = new Set(selectedRoleIds) const selectedRoles = roles.filter((role) => selectedRoleSet.has(role.id)) const removedRoles = selectedRoleIds .map((roleId) => roleMap.get(roleId)) .filter((role): role is AdminRole => !!role && !selectedRoleSet.has(role.id)) const addedRoles = value .map((roleId) => roleMap.get(roleId)) .filter((role): role is AdminRole => !!role && !initiallySelectedSet.has(role.id)) const filteredRoles = roles.filter((role) => { const output = keyword.trim().toLowerCase() if (!output) { return true } return `${role.name} ${role.code}`.toLowerCase().includes(output) }) return (
当前已分配
{selectedRoles.length > 0 ? (
{selectedRoles.map((role) => ( {role.status === Status.Ok ? ( ) : ( )} {role.name} {role.status !== Status.Ok ? "(已禁用)" : ""} ))}
) : (
当前未分配角色
)}
角色列表
setKeyword(event.target.value)} placeholder="搜索角色名称或编码" className="pl-9" disabled={loading} />
{loading ? (
正在加载角色列表...
) : filteredRoles.length > 0 ? ( filteredRoles.map((role) => { const checked = selectedRoleSet.has(role.id) const disabled = role.status !== Status.Ok && !checked return ( ) }) ) : (
没有匹配的角色
)}
本次变更
新增角色
{addedRoles.length > 0 ? (
{addedRoles.map((role) => ( {role.name} ))}
) : (
无新增
)}
移除角色
{removedRoles.length > 0 ? (
{removedRoles.map((role) => ( {role.name} ))}
) : (
无移除
)}
) }} />
) }