diff --git a/src/app/dashboard/(auth)/permissions/page.tsx b/src/app/dashboard/(auth)/permissions/page.tsx index 88fd333..6d7e669 100644 --- a/src/app/dashboard/(auth)/permissions/page.tsx +++ b/src/app/dashboard/(auth)/permissions/page.tsx @@ -28,7 +28,7 @@ export default async function RolesPage({ searchParams }: Props) { }); const res = await getAllPermissions(); - if (!res.success) throw new Error(); + if (!res.success) throw new Error("Error while fetch permission"); return ( diff --git a/src/app/dashboard/(auth)/roles/page.tsx b/src/app/dashboard/(auth)/roles/page.tsx index 2895fec..a04a0f7 100644 --- a/src/app/dashboard/(auth)/roles/page.tsx +++ b/src/app/dashboard/(auth)/roles/page.tsx @@ -30,13 +30,14 @@ export default async function RolesPage() { if (!permissions.readAll) unauthorized() - const roles = await getAllRoles(); + const res = await getAllRoles(); + if (!res.success) throw new Error("Error while fetch roles"); return ( Roles - + ); diff --git a/src/modules/dashboard/components/DashboardLayout.tsx b/src/modules/dashboard/components/DashboardLayout.tsx index 4b8c320..40b5745 100644 --- a/src/modules/dashboard/components/DashboardLayout.tsx +++ b/src/modules/dashboard/components/DashboardLayout.tsx @@ -45,7 +45,7 @@ export default function DashboardLayout(props: Props) { {/* Navbar */} - + {props.children} diff --git a/src/modules/role/actions/getAllRoles.ts b/src/modules/role/actions/getAllRoles.ts index f2bffd4..ed2ad82 100644 --- a/src/modules/role/actions/getAllRoles.ts +++ b/src/modules/role/actions/getAllRoles.ts @@ -1,8 +1,11 @@ -"use server" +"use server"; import prisma from "@/db"; import checkPermission from "@/modules/dashboard/services/checkPermission"; +import ServerResponseAction from "@/modules/dashboard/types/ServerResponseAction"; +import handleCatch from "@/modules/dashboard/utils/handleCatch"; import unauthorized from "@/modules/dashboard/utils/unauthorized"; import "server-only"; +import Role from "../types/Role"; /** * Retrieves all roles along with the count of associated permissions and users. @@ -10,37 +13,45 @@ import "server-only"; * * @returns An array of role objects each including details and counts of related permissions and users. */ -export default async function getAllRoles() { - // Authorization check - if (!await checkPermission("roles.getAll")) { - unauthorized(); - } +export default async function getAllRoles(): Promise< + ServerResponseAction +> { + try { + // Authorization check + if (!(await checkPermission("roles.getAll"))) { + unauthorized(); + } - try { - // Fetch roles from the database - const roles = await prisma.role.findMany({ - include: { - _count: { - select: { - permissions: true, - users: true - } - } - } - }); + // Fetch roles from the database + const roles = await prisma.role.findMany({ + include: { + _count: { + select: { + permissions: true, + users: true, + }, + }, + }, + }); - // Transform the data into the desired format - return roles.map(({ id, code, name, description, isActive, _count }) => ({ - id, - code, - name, - description, - isActive, - permissionCount: _count.permissions, - userCount: _count.users - })); - } catch (error) { - console.error('Error retrieving roles', error); - throw error; - } + // Transform the data into the desired format + const result = roles.map( + ({ id, code, name, description, isActive, _count }) => ({ + id, + code, + name, + description, + isActive, + permissionCount: _count.permissions, + userCount: _count.users, + }) + ); + + return { + success: true, + data: result + } + } catch (error) { + return handleCatch(error) + } } diff --git a/src/modules/role/tables/RolesTable/RolesTable.tsx b/src/modules/role/tables/RolesTable/RolesTable.tsx index 1eff0b5..c9ff6e1 100644 --- a/src/modules/role/tables/RolesTable/RolesTable.tsx +++ b/src/modules/role/tables/RolesTable/RolesTable.tsx @@ -9,10 +9,11 @@ import FormModal, { ModalProps } from "../../modals/FormModal"; import DeleteModal, { DeleteModalProps } from "../../modals/DeleteModal"; import createColumns from "./columns"; import DashboardTable from "@/modules/dashboard/components/DashboardTable"; +import Role from "../../types/Role"; interface Props { permissions: Partial; - roles: Awaited>; + roles: Role[]; } export default function RolesTable(props: Props) { diff --git a/src/modules/role/types/Role.d.ts b/src/modules/role/types/Role.d.ts new file mode 100644 index 0000000..cc38ef4 --- /dev/null +++ b/src/modules/role/types/Role.d.ts @@ -0,0 +1,9 @@ +export default interface Role { + id: string; + code: string; + name: string; + description: string; + isActive: boolean; + permissionCount: number; + userCount: number; +} diff --git a/src/modules/userManagement/actions/getAllUsers.ts b/src/modules/userManagement/actions/getAllUsers.ts index 1fe2c10..5b6fc51 100644 --- a/src/modules/userManagement/actions/getAllUsers.ts +++ b/src/modules/userManagement/actions/getAllUsers.ts @@ -6,27 +6,32 @@ import "server-only"; const getAllUsers = async () => { if (!(await checkPermission("users.readAll"))) unauthorized(); - try { - - const users = await prisma.user.findMany({ - select: { - id: true, - email: true, - photoProfile: true, - name: true, - }, - }); - - const result = users.map((user) => ({ - ...user, - photoUrl: user.photoProfile ?? null, - photoProfile: undefined, - })); - - return result; - } catch (e){ - throw e; - } + try { + const users = await prisma.user.findMany({ + select: { + id: true, + email: true, + photoProfile: true, + name: true, + roles: { + select: { + name: true, + code: true, + }, + }, + }, + }); + + const result = users.map((user) => ({ + ...user, + photoUrl: user.photoProfile ?? null, + photoProfile: undefined, + })); + + return result; + } catch (e) { + throw e; + } }; export default getAllUsers; diff --git a/src/modules/userManagement/actions/getUserDetailById.ts b/src/modules/userManagement/actions/getUserDetailById.ts index a02780e..3c8dc53 100644 --- a/src/modules/userManagement/actions/getUserDetailById.ts +++ b/src/modules/userManagement/actions/getUserDetailById.ts @@ -1,4 +1,4 @@ -"use server" +"use server"; import "server-only"; import prisma from "@/db"; import checkPermission from "@/modules/dashboard/services/checkPermission"; @@ -6,11 +6,15 @@ import unauthorized from "@/modules/dashboard/utils/unauthorized"; import ServerResponseAction from "@/modules/dashboard/types/ServerResponseAction"; type UserData = { - id: string, - email: string, - name: string, - photoProfileUrl: string -} + id: string; + email: string; + name: string; + photoProfileUrl: string; + roles: { + code: string, + name: string + }[] +}; /** * Retrieves detailed information of a user by their ID. @@ -18,7 +22,9 @@ type UserData = { * @param id The unique identifier of the user. * @returns The user's detailed information or an error response. */ -export default async function getUserDetailById(id: string): Promise> { +export default async function getUserDetailById( + id: string +): Promise> { // Check user permission if (!checkPermission("users.read")) return unauthorized(); @@ -30,6 +36,12 @@ export default async function getUserDetailById(id: string): Promise ({ id: role.id })), + }, + }, + }); } else { await prisma.user.update({ where: { id: validatedFields.data.id! }, - data: userData, + data: { + ...userData, + roles: { + set: roles.map((role) => ({ id: role.id })), + }, + }, }); } diff --git a/src/modules/userManagement/formSchemas/userFormSchema.ts b/src/modules/userManagement/formSchemas/userFormSchema.ts index c080296..0b19a1c 100644 --- a/src/modules/userManagement/formSchemas/userFormSchema.ts +++ b/src/modules/userManagement/formSchemas/userFormSchema.ts @@ -5,15 +5,19 @@ export interface UserFormData { name: string; photoProfileUrl: string; email: string; - password: string | undefined + password: string | undefined; + roles: string[] } const userFormDataSchema = z.object({ id: z.string().optional(), name: z.string(), - photoProfileUrl: z.union([z.string(), z.null()]), + photoProfileUrl: z.union([z.string().url(), z.null(), z.string()]), email: z.string().email(), - password: z.string().min(8).optional(), -}); - + password: z.string().optional(), + roles: z.array(z.string()) + }).refine((data) => data.id || data.password || data.password!.length >= 8, { + message: "Password is required and must be at least 8 characters long if id is empty", + path: ["password"], + }); export default userFormDataSchema; diff --git a/src/modules/userManagement/modals/UserFormModal.tsx b/src/modules/userManagement/modals/UserFormModal.tsx index b4c20ed..4f4bb65 100644 --- a/src/modules/userManagement/modals/UserFormModal.tsx +++ b/src/modules/userManagement/modals/UserFormModal.tsx @@ -16,6 +16,7 @@ import { Center, Avatar, PasswordInput, + MultiSelect, } from "@mantine/core"; import { useForm, zodResolver } from "@mantine/form"; import { useRouter } from "next/navigation"; @@ -29,6 +30,8 @@ import withServerAction from "@/modules/dashboard/utils/withServerAction"; import upsertUser from "../actions/upsertUser"; import DashboardError from "@/modules/dashboard/errors/DashboardError"; import stringToColorHex from "@/core/utils/stringToColorHex"; +import getAllRoles from "@/modules/role/actions/getAllRoles"; +import Role from "@/modules/role/types/Role"; export interface ModalProps { title: string; @@ -49,6 +52,7 @@ export default function UserFormModal(props: ModalProps) { const [isSubmitting, setSubmitting] = useState(false); const [isFetching, setFetching] = useState(false); const [errorMessage, setErrorMessage] = useState(""); + const [roles, setRoles] = useState([]); const form = useForm({ initialValues: { @@ -57,6 +61,7 @@ export default function UserFormModal(props: ModalProps) { name: "", photoProfileUrl: "", password: "", + roles: [], }, validate: zodResolver(userFormDataSchema), validateInputOnChange: false, @@ -80,6 +85,7 @@ export default function UserFormModal(props: ModalProps) { id: data.id, name: data.name, photoProfileUrl: data.photoProfileUrl, + roles: data.roles.map(role => role.code) }); } }) @@ -92,6 +98,17 @@ export default function UserFormModal(props: ModalProps) { }); }, [props.opened, props.id]); + // Fetch Roles + useEffect(() => { + withServerAction(getAllRoles) + .then((response) => { + setRoles(response.data); + }) + .catch((e) => { + console.log(e); + }); + }, []); + const closeModal = () => { form.reset(); props.onClose ? props.onClose() : router.replace("?"); @@ -190,6 +207,19 @@ export default function UserFormModal(props: ModalProps) { /> )} + {/* Role */} + + form.setFieldValue("roles", values) + } + data={roles.map((role) => role.code)} + error={form.errors.roles} + /> + {/* Buttons */}