Files
road-monitoring-ui/src/app/(modules)/roles/hooks/useRoleForm.ts

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,
};
}