140 lines
3.7 KiB
TypeScript
140 lines
3.7 KiB
TypeScript
'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<typeof roleFormSchema>;
|
|
|
|
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<RoleFormValues>({
|
|
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,
|
|
};
|
|
}
|