diff --git a/src/management-system-v2/app/(dashboard)/iam/roles/[roleId]/role-members.tsx b/src/management-system-v2/app/(dashboard)/iam/roles/[roleId]/role-members.tsx index 214fe9891..03713544f 100644 --- a/src/management-system-v2/app/(dashboard)/iam/roles/[roleId]/role-members.tsx +++ b/src/management-system-v2/app/(dashboard)/iam/roles/[roleId]/role-members.tsx @@ -144,7 +144,7 @@ const RoleMembers: FC<{ role: Role; isLoadingRole?: boolean }> = ({ role, isLoad )} searchBarRightNode={ } /> diff --git a/src/management-system-v2/app/(dashboard)/iam/roles/[roleId]/role-page.tsx b/src/management-system-v2/app/(dashboard)/iam/roles/[roleId]/role-page.tsx index 2634c43b9..a2cddaf2d 100644 --- a/src/management-system-v2/app/(dashboard)/iam/roles/[roleId]/role-page.tsx +++ b/src/management-system-v2/app/(dashboard)/iam/roles/[roleId]/role-page.tsx @@ -24,17 +24,17 @@ function RolePage({ params: { roleId } }: { params: { roleId: string } }) { const items: Items = role ? [ - { - key: 'members', - label: 'Manage Members', - children: , - }, - { key: 'permissions', label: 'Permissions', children: }, { key: 'generalData', label: 'General Data', children: , }, + { key: 'permissions', label: 'Permissions', children: }, + { + key: 'members', + label: 'Manage Members', + children: , + }, ] : []; @@ -58,9 +58,11 @@ function RolePage({ params: { roleId } }: { params: { roleId: string } }) { } > - - - +
+ + + +
); } diff --git a/src/management-system-v2/app/(dashboard)/iam/roles/[roleId]/roleGeneralData.tsx b/src/management-system-v2/app/(dashboard)/iam/roles/[roleId]/roleGeneralData.tsx index f305babb5..fbd2cc131 100644 --- a/src/management-system-v2/app/(dashboard)/iam/roles/[roleId]/roleGeneralData.tsx +++ b/src/management-system-v2/app/(dashboard)/iam/roles/[roleId]/roleGeneralData.tsx @@ -3,7 +3,7 @@ import { toCaslResource } from '@/lib/ability/caslAbility'; import { useGetAsset, usePutAsset } from '@/lib/fetch-data'; import { Alert, App, Button, DatePicker, Form, Input, Spin } from 'antd'; -import { FC } from 'react'; +import { FC, useEffect, useState } from 'react'; import dayjs from 'dayjs'; import germanLocale from 'antd/es/date-picker/locale/de_DE'; import { useAbilityStore } from '@/lib/abilityStore'; @@ -21,6 +21,20 @@ const RoleGeneralData: FC<{ roleId: string }> = ({ roleId }) => { onError: () => message.open({ type: 'error', content: 'Something went wrong' }), }); + const [submittable, setSubmittable] = useState(false); + const values = Form.useWatch('name', form); + + useEffect(() => { + form.validateFields({ validateOnly: true }).then( + () => { + setSubmittable(true); + }, + () => { + setSubmittable(false); + }, + ); + }, [form, values]); + if (isLoading || error || !data) return ; const role = toCaslResource('Role', data); @@ -51,7 +65,13 @@ const RoleGeneralData: FC<{ roleId: string }> = ({ roleId }) => {
)} - + + @@ -73,8 +93,8 @@ const RoleGeneralData: FC<{ roleId: string }> = ({ roleId }) => { - diff --git a/src/management-system-v2/app/(dashboard)/iam/roles/header-actions.tsx b/src/management-system-v2/app/(dashboard)/iam/roles/header-actions.tsx index f616646f0..81f3d547e 100644 --- a/src/management-system-v2/app/(dashboard)/iam/roles/header-actions.tsx +++ b/src/management-system-v2/app/(dashboard)/iam/roles/header-actions.tsx @@ -1,68 +1,34 @@ 'use client'; -import { AuthCan } from '@/lib/clientAuthComponents'; -import { ApiRequestBody, usePostAsset } from '@/lib/fetch-data'; +import { usePostAsset } from '@/lib/fetch-data'; import { PlusOutlined } from '@ant-design/icons'; -import { Button, Form, App, Input, Modal } from 'antd'; +import { Button, Form, App, Input, Modal, DatePicker } from 'antd'; import { FC, ReactNode, useEffect, useState } from 'react'; +import dayjs from 'dayjs'; +import germanLocale from 'antd/es/date-picker/locale/de_DE'; +import { useRouter } from 'next/navigation'; +import { AuthCan } from '@/lib/clientAuthComponents'; + +type PostRoleKeys = 'name' | 'description' | 'expiration'; -type PostUserField = keyof ApiRequestBody<'/users', 'post'>; - -const modalStructureWithoutPassword: { - dataKey: PostUserField; - label: string; - type: string; -}[] = [ - { - dataKey: 'firstName', - label: 'First Name', - type: 'text', - }, - { - dataKey: 'lastName', - label: 'Last Name', - type: 'text', - }, - { - dataKey: 'username', - label: 'Username Name', - type: 'text', - }, - { - dataKey: 'email', - label: 'Email', - type: 'email', - }, -]; - -const fieldNameToLabel: Record = modalStructureWithoutPassword.reduce( - (acc, curr) => { - acc[curr.dataKey] = curr.label; - return acc; - }, - {} as Record, -); - -const CreateUserModal: FC<{ +const CreateRoleModal: FC<{ modalOpen: boolean; close: () => void; }> = ({ modalOpen, close }) => { const [form] = Form.useForm(); + const router = useRouter(); const { message: messageApi } = App.useApp(); - type ErrorsObject = { [field in PostUserField]?: ReactNode[] }; + type ErrorsObject = { [field in PostRoleKeys]?: ReactNode[] }; const [formatError, setFormatError] = useState({}); - - const { mutateAsync: postUser, isLoading } = usePostAsset('/users', { + const { mutateAsync: postRole, isLoading } = usePostAsset('/roles', { onError(e) { if (!(typeof e === 'object' && e !== null && 'errors' in e)) { return; } - const errors: { [key in PostUserField]?: ReactNode[] } = {}; - - function appendError(key: PostUserField, error: string) { - error = error.replace(key, fieldNameToLabel[key]); + const errors: { [key in PostRoleKeys]?: ReactNode[] } = {}; + function appendError(key: PostRoleKeys, error: string) { if (key in errors) { errors[key]!.push(

{error}

); } else { @@ -71,102 +37,93 @@ const CreateUserModal: FC<{ } for (const error of e.errors as string[]) { - if (error.includes('username')) appendError('username', error); - else if (error.includes('email')) appendError('email', error); - else if (error.includes('firstName')) appendError('firstName', error); - else if (error.includes('lastName')) appendError('lastName', error); - else if (error.includes('password')) appendError('password', error); + if (error.includes('name')) appendError('name', error); + else if (error.includes('description')) appendError('description', error); + else if (error.includes('expiration')) appendError('expiration', error); } setFormatError(errors); }, }); + const [submittable, setSubmittable] = useState(false); + const values = Form.useWatch('name', form); + + useEffect(() => { + form.validateFields({ validateOnly: true }).then( + () => { + setSubmittable(true); + }, + () => { + setSubmittable(false); + }, + ); + }, [form, values]); + useEffect(() => { form.resetFields(); setFormatError({}); }, [form, modalOpen]); - const submitData = async ( - values: Record | 'confirm_password', string>, - ) => { - debugger; + const submitData = async (values: Record<'name' | 'description' | 'expirationDayJs', 'post'>) => { + let expiration; + if (typeof values.expirationDayJs === 'object') + expiration = (values.expirationDayJs as dayjs.Dayjs).toISOString(); + try { - await postUser({ + const newRole = await postRole({ body: { - email: values.email, - firstName: values.firstName, - lastName: values.lastName, - username: values.username, - password: values.password, + name: values.name, + description: values.description, + expiration, }, }); - messageApi.success({ content: 'Account created' }); - close(); + messageApi.success({ content: 'Role created' }); + router.push(`/iam/roles/${newRole.id}`); } catch (e) { - messageApi.error({ content: 'An error ocurred' }); + messageApi.error({ content: 'Something went wrong' }); } }; return ( - +
- {modalStructureWithoutPassword.map((formField) => ( - - - - ))} + + + - + ({ - validator(_, value) { - if (!value || getFieldValue('password') === value) { - return Promise.resolve(); - } - return Promise.reject(new Error('The new password that you entered do not match!')); - }, - }), - ]} + label="Expiration" + name="expirationDayJs" + help={formatError.expiration} + validateStatus={formatError.expiration && 'error'} > - + -
@@ -175,18 +132,18 @@ const CreateUserModal: FC<{ }; const HeaderActions: FC = () => { - const [createUserModalOpen, setCreateUserModalOpen] = useState(false); + const [createRoleModalOpen, setCreateRoleModalOpen] = useState(false); return ( <> - setCreateUserModalOpen(false)} + setCreateRoleModalOpen(false)} /> - - diff --git a/src/management-system-v2/app/(dashboard)/iam/roles/role-page.tsx b/src/management-system-v2/app/(dashboard)/iam/roles/role-page.tsx index 519444dcc..aafad4618 100644 --- a/src/management-system-v2/app/(dashboard)/iam/roles/role-page.tsx +++ b/src/management-system-v2/app/(dashboard)/iam/roles/role-page.tsx @@ -1,10 +1,8 @@ 'use client'; -import React, { FC, useState } from 'react'; -import styles from '@/components/processes.module.scss'; -import cn from 'classnames'; +import { FC, useState } from 'react'; import { DeleteOutlined } from '@ant-design/icons'; -import { Tooltip, Space, Row, Col, Button, Input, Result, Table, Popconfirm, App } from 'antd'; +import { Tooltip, Space, Button, Result, Table, Popconfirm, App } from 'antd'; import { useGetAsset, useDeleteAsset, ApiData } from '@/lib/fetch-data'; import { CloseOutlined } from '@ant-design/icons'; import Content from '@/components/content'; @@ -14,29 +12,33 @@ import Link from 'next/link'; import { toCaslResource } from '@/lib/ability/caslAbility'; import Bar from '@/components/bar'; import { AuthCan } from '@/lib/clientAuthComponents'; +import { useAbilityStore } from '@/lib/abilityStore'; + type Role = ApiData<'/roles', 'get'>[number]; const RolesPage: FC = () => { const { message: messageApi } = App.useApp(); - + const ability = useAbilityStore((store) => store.ability); const { error, data: roles, isLoading, refetch: refetchRoles } = useGetAsset('/roles', {}); const { mutateAsync: deleteRole, isLoading: deletingRole } = useDeleteAsset('/roles/{id}', { onSuccess: () => refetchRoles(), onError: () => messageApi.open({ type: 'error', content: 'Something went wrong' }), }); - const { - searchQuery, - setSearchQuery, - filteredData: filteredRoles, - } = useFuzySearch(roles || [], ['name'], { + const { setSearchQuery, filteredData: filteredRoles } = useFuzySearch(roles || [], ['name'], { useSearchParams: false, }); const [selectedRowKeys, setSelectedRowKeys] = useState([]); + const [selectedRow, setSelectedRows] = useState([]); + + const cannotDeleteSelected = selectedRow.some( + (role) => !ability.can('delete', toCaslResource('Role', role)), + ); async function deleteRoles(userIds: string[]) { setSelectedRowKeys([]); + setSelectedRows([]); await Promise.allSettled(userIds.map((id) => deleteRole({ params: { path: { id } } }))); } @@ -63,8 +65,8 @@ const RolesPage: FC = () => { deleteRoles([id])} >