Files
hair-keeper/src/app/(main)/users/components/BatchAuthorizationDialog.tsx

199 lines
6.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client'
import React, { useState } from 'react'
import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { z } from 'zod'
import { trpc } from '@/lib/trpc'
import { Button } from '@/components/ui/button'
import { FormDialog, FormActionBar, FormGridContent, FormCancelAction, FormSubmitAction } from '@/components/common/form-dialog'
import {
AdvancedSelect,
SelectPopover,
SelectTrigger,
SelectContent,
SelectInput,
SelectItemList,
SelectedName,
SelectedBadges
} from '@/components/common/advanced-select'
import { Users } from 'lucide-react'
import { toast } from 'sonner'
// 定义表单数据结构
const batchAuthorizationSchema = z.object({
roleId: z.number({ message: '请选择角色' }),
deptCodes: z.array(z.string()).optional(),
})
type BatchAuthorizationFormData = z.input<typeof batchAuthorizationSchema>
export const BatchAuthorizationDialog = function BatchAuthorizationDialog() {
const [isOpen, setIsOpen] = useState(false)
const [currentAction, setCurrentAction] = useState<'grant' | 'revoke' | null>(null)
const utils = trpc.useUtils()
// 获取部门列表和角色列表
const { data: depts = [] } = trpc.common.getDepts.useQuery(undefined, {
enabled: isOpen
})
const { data: roles = [] } = trpc.users.getRoles.useQuery(undefined, {
enabled: isOpen
})
// 初始化表单
const form = useForm<BatchAuthorizationFormData>({
resolver: zodResolver(batchAuthorizationSchema),
defaultValues: {
roleId: undefined,
deptCodes: [],
},
})
// 批量更新角色mutation
const batchUpdateRoleMutation = trpc.users.batchUpdateRole.useMutation({
onSuccess: (result: { count: number }, variables) => {
const action = variables.action === 'grant' ? '授予' : '撤销'
toast.success(`成功为 ${result.count} 个用户${action}角色`)
utils.users.list.invalidate()
setCurrentAction(null)
setIsOpen(false)
},
onError: (error: { message?: string }) => {
toast.error(error.message || '批量操作失败')
setCurrentAction(null)
}
})
// 处理授权
const handleGrant = async (values: BatchAuthorizationFormData) => {
setCurrentAction('grant')
batchUpdateRoleMutation.mutate({
roleId: values.roleId,
deptCodes: values.deptCodes && values.deptCodes.length > 0 ? values.deptCodes : undefined,
action: 'grant'
})
}
// 处理撤销
const handleRevoke = async (values: BatchAuthorizationFormData) => {
setCurrentAction('revoke')
batchUpdateRoleMutation.mutate({
roleId: values.roleId,
deptCodes: values.deptCodes && values.deptCodes.length > 0 ? values.deptCodes : undefined,
action: 'revoke'
})
}
// 处理对话框关闭
const handleClose = () => {
setIsOpen(false)
setCurrentAction(null)
}
// 处理对话框打开
const handleOpen = () => {
setIsOpen(true)
}
const isLoading = batchUpdateRoleMutation.isPending
// 定义表单字段配置
const fields = [
{
name: 'roleId',
label: '选择角色',
required: true,
render: ({ field }: any) => (
<div className="space-y-2">
<AdvancedSelect
options={roles.map(role => ({ ...role, id: role.id.toString() }))}
value={field.value?.toString() || ''}
onChange={(value) => field.onChange(value ? Number(value) : undefined)}
disabled={isLoading}
>
<SelectPopover>
<SelectTrigger placeholder="请选择角色" clearable>
<SelectedName />
</SelectTrigger>
<SelectContent>
<SelectInput placeholder="搜索角色..." />
<SelectItemList />
</SelectContent>
</SelectPopover>
</AdvancedSelect>
<p className="text-xs text-muted-foreground">
</p>
</div>
),
},
{
name: 'deptCodes',
label: '选择院系(可选)',
render: ({ field }: any) => (
<div className="space-y-2">
<AdvancedSelect
options={depts.map(dept => ({ id: dept.code, name: dept.name }))}
value={field.value || []}
onChange={(value) => field.onChange(value)}
disabled={isLoading}
multiple={{ enable: true }}
>
<SelectPopover>
<SelectTrigger placeholder="不选择则针对所有用户" clearable>
<SelectedBadges maxDisplay={3} />
</SelectTrigger>
<SelectContent>
<SelectInput placeholder="搜索院系..." />
<SelectItemList />
</SelectContent>
</SelectPopover>
</AdvancedSelect>
<p className="text-xs text-muted-foreground">
</p>
</div>
),
},
]
return (
<>
<Button variant="outline" className="flex items-center gap-2" onClick={handleOpen}>
<Users className="h-4 w-4" />
</Button>
<FormDialog
isOpen={isOpen}
title="批量授权"
description="为指定范围的用户批量授予或撤销角色"
form={form}
fields={fields}
onClose={handleClose}
className="max-w-md"
>
<FormGridContent />
<FormActionBar>
<FormCancelAction />
<FormSubmitAction
onSubmit={handleRevoke}
variant="destructive"
isSubmitting={batchUpdateRoleMutation.isPending}
showSpinningLoader={currentAction === 'revoke'}
>
</FormSubmitAction>
<FormSubmitAction
onSubmit={handleGrant}
isSubmitting={batchUpdateRoleMutation.isPending}
showSpinningLoader={currentAction === 'grant'}
>
</FormSubmitAction>
</FormActionBar>
</FormDialog>
</>
)
}