"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 } from "lucide-react" import { z } from "zod/v4" import { fetchRoleListAll, type AdminRole, type CreateAdminUserPayload, } from "@/lib/api/admin" import { useAppLocale, useI18n } from "@/i18n/provider" import { Status } from "@/lib/generated/enums" import { getRoleDisplayName } from "@/lib/role-i18n" import { cn } from "@/lib/utils" import { ImageInput } from "@/components/image-input" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" import { Checkbox } from "@/components/ui/checkbox" import { Drawer, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerTitle, } from "@/components/ui/drawer" import { Field, FieldContent, FieldError, FieldLabel, } from "@/components/ui/field" import { Input } from "@/components/ui/input" type CreateUserDrawerProps = { open: boolean saving: boolean onOpenChange: (open: boolean) => void onSubmit: (payload: CreateAdminUserPayload) => Promise } type CreateForm = { username: string nickname: string avatar: string mobile: string email: string remark: string roleIds: number[] } const emptyForm: CreateForm = { username: "", nickname: "", avatar: "", mobile: "", email: "", remark: "", roleIds: [], } function toNullableString(value: string) { const output = value.trim() return output ? output : null } function buildPayload(form: CreateForm): CreateAdminUserPayload { return { username: form.username.trim(), nickname: form.nickname.trim(), avatar: form.avatar.trim(), mobile: toNullableString(form.mobile), email: toNullableString(form.email), remark: form.remark.trim(), roleIds: form.roleIds, } } export function CreateUserDrawer({ open, saving, onOpenChange, onSubmit, }: CreateUserDrawerProps) { return ( {open ? ( ) : null} ) } type CreateUserDrawerBodyProps = { saving: boolean onOpenChange: (open: boolean) => void onSubmit: (payload: CreateAdminUserPayload) => Promise } function CreateUserDrawerBody({ saving, onOpenChange, onSubmit, }: CreateUserDrawerBodyProps) { const t = useI18n() const { locale } = useAppLocale() const [rolesLoading, setRolesLoading] = useState(true) const [roles, setRoles] = useState([]) const [roleKeyword, setRoleKeyword] = useState("") const createFormSchema = useMemo( () => z.object({ username: z.string().trim().min(1, t("user.usernameRequired")), nickname: z.string().trim(), avatar: z.string().trim(), mobile: z .string() .trim() .refine( (value) => value.length === 0 || /^[0-9+\-\s]{6,20}$/.test(value), t("user.mobileInvalid") ), email: z .string() .trim() .refine( (value) => value.length === 0 || /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value), t("user.emailInvalid") ), remark: z.string().trim(), roleIds: z.array(z.number().int().positive()), }), [t] ) const createFormResolver = useMemo( () => zodResolver(createFormSchema as never) as Resolver, [createFormSchema] ) const form = useForm({ resolver: createFormResolver, defaultValues: emptyForm, }) const { control, handleSubmit, register, reset, formState: { errors }, } = form useEffect(() => { async function loadRoles() { setRolesLoading(true) try { const list = await fetchRoleListAll() setRoles(list) } catch { setRoles([]) } finally { setRolesLoading(false) } } void loadRoles() }, []) const filteredRoles = useMemo(() => { const q = roleKeyword.trim().toLowerCase() if (!q) { return roles } return roles.filter((role) => `${role.name} ${getRoleDisplayName(role.code, role.name, locale)} ${role.code}` .toLowerCase() .includes(q) ) }, [locale, roleKeyword, roles]) async function onFormSubmit(values: CreateForm) { await onSubmit(buildPayload(values)) reset(emptyForm) setRoleKeyword("") } return ( {t("user.createTitle")} {t("user.createDescription")}
{t("user.username")} {t("user.nickname")} {t("user.avatar")} ( )} /> {t("user.mobile")} {t("user.email")} {t("user.remark")} {t("user.rolesOptional")}
setRoleKeyword(event.target.value)} placeholder={t("user.searchRoles")} className="pl-9" disabled={rolesLoading} />
{ const value = field.value || [] const selectedSet = new Set(value) return (
{rolesLoading ? (
{t("user.loadingRoles")}
) : filteredRoles.length > 0 ? ( filteredRoles.map((role) => { const checked = selectedSet.has(role.id) const disabled = role.status !== Status.Ok && !checked return ( ) }) ) : (
{t("user.emptyRoles")}
)}
) }} />
) }