'use client'; import { collectDefaultPermissionIds, collectPermissionIdsByKeys, } from '@/components/permission-tree'; import { roleService } from '@/services/api'; import type { PermissionTreeItem, Role } from '@/types'; import { zodResolver } from '@hookform/resolvers/zod'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import { useCallback } from 'react'; import { useForm, useWatch } from 'react-hook-form'; import { toast } from 'sonner'; import { z } from 'zod'; import { roleKeys } from '../queries/roleKeys'; const roleFormSchema = z.object({ id: z.number().optional(), name: z.string().trim().min(1, 'Role name is required'), display_name: z.string().trim().min(1, 'Display name is required'), description: z.string().optional(), permission_ids: z.array(z.number()).min(1, 'Select at least one permission'), }); export type RoleFormValues = z.infer; const defaultValues: RoleFormValues = { name: '', display_name: '', description: '', permission_ids: [], }; export function useRoleForm({ organizationId, permissionTree, onSaved, }: { organizationId: number | null; permissionTree: PermissionTreeItem[]; onSaved: () => void; }) { const queryClient = useQueryClient(); const { control, register, handleSubmit, reset, setValue, formState: { errors, isSubmitting, isSubmitted }, } = useForm({ defaultValues, mode: 'onSubmit', reValidateMode: 'onChange', resolver: zodResolver(roleFormSchema), }); const permissionIds = useWatch({ control, name: 'permission_ids' }) || []; const roleId = useWatch({ control, name: 'id' }); const roleName = useWatch({ control, name: 'name' }) || ''; const displayName = useWatch({ control, name: 'display_name' }) || ''; const canSubmit = roleName.trim().length > 0 && displayName.trim().length > 0 && permissionIds.length > 0; const saveMutation = useMutation({ mutationFn: (values: RoleFormValues) => roleService.saveRole({ id: values.id, name: values.name.trim(), display_name: values.display_name.trim(), description: values.description?.trim() ?? '', organization_id: organizationId, permission_ids: values.permission_ids, is_default: false, }), onSuccess: (_data, values) => { toast.success(values.id ? 'Role updated' : 'Role created'); reset(defaultValues); onSaved(); queryClient.invalidateQueries({ queryKey: roleKeys.all }); }, onError: (_error, values) => { toast.error( values.id ? 'Failed to update role' : 'Failed to create role', ); }, }); const onSubmit = handleSubmit((values) => saveMutation.mutate(values)); const openCreate = useCallback(() => { reset({ ...defaultValues, permission_ids: collectDefaultPermissionIds(permissionTree), }); }, [permissionTree, reset]); const openEdit = useCallback( (role: Role) => { reset({ id: role.id, name: role.name || '', display_name: role.display_name || '', description: role.description || '', permission_ids: collectPermissionIdsByKeys( permissionTree, role.permissions || [], ), }); }, [permissionTree, reset], ); const setPermissionIds = useCallback( (ids: number[]) => setValue('permission_ids', ids, { shouldDirty: true, shouldValidate: true, }), [setValue], ); return { register, onSubmit, openCreate, openEdit, setPermissionIds, permissionIds, roleId, errors, isSubmitted, canSubmit, isSaving: isSubmitting || saveMutation.isPending, }; }