diff --git a/src/_features/dashboard/permissions/actions/upsertPermission.ts b/src/_features/dashboard/permissions/actions/upsertPermission.ts
index 4a92f7e..e9cb39f 100644
--- a/src/_features/dashboard/permissions/actions/upsertPermission.ts
+++ b/src/_features/dashboard/permissions/actions/upsertPermission.ts
@@ -1,7 +1,7 @@
"use server";
import checkPermission from "@/features/auth/tools/checkPermission";
-import permissionFormDataSchema, { PermissionFormData } from "../formSchemas/PermissionFormData";
+import permissionFormDataSchema, { PermissionFormData } from "../../../../modules/permission/formSchemas/PermissionFormData";
import mapObjectToFirstValue from "@/utils/mapObjectToFirstValue";
import prisma from "@/db";
import { revalidatePath } from "next/cache";
diff --git a/src/_features/dashboard/users/actions/deleteUser.ts b/src/_features/dashboard/users/actions/deleteUser.ts
deleted file mode 100644
index 9ad686b..0000000
--- a/src/_features/dashboard/users/actions/deleteUser.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-"use server";
-
-import checkPermission from "@/features/auth/tools/checkPermission";
-import prisma from "@/db";
-import { revalidatePath } from "next/cache";
-
-/**
- * Deletes a user from the database based on their ID.
- *
- * @param {string} id The unique identifier of the user to be deleted.
- * @returns A promise that resolves to an object indicating the success or failure of the operation.
- */
-export default async function deleteUser(id: string) {
-
- // Check user permission
- if (!(await checkPermission())) return {
- success: false,
- message: "Unauthorized"
- } as const;
-
- // Find the user in the database
- const user = await prisma.user.findFirst({
- where: { id },
- });
-
- // Handle case where user is not found
- if (!user) return {
- success: false,
- message: "User not found"
- } as const;
-
- // Delete the user
- await prisma.user.delete({
- where: { id }
- });
-
- // Revalidate cache
- revalidatePath(".");
-
- return {
- success: true,
- message: `User ${user.name} has been successfully deleted`
- } as const;
-}
diff --git a/src/_features/dashboard/users/actions/editUser.ts b/src/_features/dashboard/users/actions/editUser.ts
deleted file mode 100644
index c3de03f..0000000
--- a/src/_features/dashboard/users/actions/editUser.ts
+++ /dev/null
@@ -1,69 +0,0 @@
-"use server";
-
-import prisma from "@/db";
-import userFormDataSchema, { UserFormData } from "../formSchemas/userFormDataSchema";
-import checkPermission from "@/features/auth/tools/checkPermission";
-import { revalidatePath } from "next/cache";
-import mapObjectToFirstValue from "@/utils/mapObjectToFirstValue";
-
-/**
- * Edits user data in the database based on the provided form data.
- *
- * @param formData The user data to be updated.
- * @returns A promise that resolves to an object indicating the success or failure of the operation.
- */
-export default async function editUser(formData: UserFormData) {
-
- // Check user permission
- if (!await checkPermission("authenticated-only")) return {
- success: false,
- message: "Unauthorized"
- }
-
- // Validate form data
- const validatedFields = userFormDataSchema.safeParse(formData);
- if (!validatedFields.success){
- return {
- success: false,
- message: "Invalid Form Data",
- errors: mapObjectToFirstValue(validatedFields.error.flatten().fieldErrors)
- } as const
- }
-
- // Check for valid ID
- if (!validatedFields.data.id){
- return {
- success: false,
- message: "Invalid Form Data",
- errors: {
- id: "Invalid ID"
- }
- } as const
- }
-
- // Update user data in the database
- try {
- await prisma.user.update({
- where: { id: validatedFields.data.id },
- data: {
- email: validatedFields.data.email,
- name: validatedFields.data.name,
- }
- });
-
- // Revalidate the cache
- revalidatePath(".");
-
- return {
- success: true,
- message: `User ${validatedFields.data.name} has been successfully updated`
- };
- } catch (error) {
- // Consider handling specific database errors here
- console.error('Error updating user data', error);
- return {
- success: false,
- message: "Error updating user data"
- };
- }
-}
diff --git a/src/_features/dashboard/users/data/getUserDetailById.ts b/src/_features/dashboard/users/data/getUserDetailById.ts
deleted file mode 100644
index b61427e..0000000
--- a/src/_features/dashboard/users/data/getUserDetailById.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-import "server-only"
-import prisma from "@/db";
-import { notFound } from "next/navigation";
-import checkPermission from "@/features/auth/tools/checkPermission";
-import { unauthorized } from "@/BaseError";
-
-/**
- * Retrieves detailed information of a user by their ID.
- *
- * @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){
-
- // Check user permission
- if (!checkPermission("authenticated-only")) return unauthorized();
-
- // Retrieve user data from the database
- const user = await prisma.user.findFirst({
- where: { id },
- select: {
- id: true,
- email: true,
- name: true,
- photoProfile: {
- select: {
- path: true
- }
- },
- }
- })
-
- // Check if user exists
- if (!user) return notFound();
-
- // Format user data
- const formattedUser = {
- id: user.id,
- email: user.email ?? "",
- name: user.name ?? "",
- photoProfileUrl: user.photoProfile?.path ?? ""
- }
-
- return formattedUser;
-}
diff --git a/src/_features/dashboard/users/data/getUsers.ts b/src/_features/dashboard/users/data/getUsers.ts
deleted file mode 100644
index f086209..0000000
--- a/src/_features/dashboard/users/data/getUsers.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-import { unauthorized } from "@/BaseError"
-import prisma from "@/db";
-import checkPermission from "@/features/auth/tools/checkPermission"
-import "server-only"
-
-const getUsers = async () => {
-
- if (!await checkPermission("authenticated-only")) unauthorized();
-
- const users = await prisma.user.findMany({
- select: {
- id: true,
- email: true,
- photoProfile: {
- select: {
- path: true
- }
- },
- name: true,
- },
- })
-
- const result = users.map((user) => ({
- ...user,
- photoUrl: user.photoProfile?.path ?? null,
- photoProfile: undefined
- }))
-
- return result;
-}
-
-export default getUsers;
diff --git a/src/_features/dashboard/users/formSchemas/userFormDataSchema.ts b/src/_features/dashboard/users/formSchemas/userFormDataSchema.ts
deleted file mode 100644
index 24b1f0e..0000000
--- a/src/_features/dashboard/users/formSchemas/userFormDataSchema.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import { z } from "zod";
-
-export interface UserFormData {
- id: string;
- name: string;
- photoProfileUrl: string;
- email: string;
-}
-
-const userFormDataSchema = z.object({
- id: z.string().nullable(),
- name: z.string(),
- photoProfileUrl: z.union([z.string(), z.null()]),
- email: z.string().email(),
-});
-
-export default userFormDataSchema;
diff --git a/src/app/dashboard/(auth)/permissions/page.tsx b/src/app/dashboard/(auth)/permissions/page.tsx
index 3b76d8a..88fd333 100644
--- a/src/app/dashboard/(auth)/permissions/page.tsx
+++ b/src/app/dashboard/(auth)/permissions/page.tsx
@@ -1,11 +1,9 @@
+import checkMultiplePermissions from "@/modules/dashboard/services/checkMultiplePermissions";
+import getAllPermissions from "@/modules/permission/actions/getAllPermissions";
+import PermissionsTable from "@/modules/permission/tables/PermissionTable/PermissionTable";
import { Card, Stack, Title } from "@mantine/core";
import { Metadata } from "next";
import React from "react";
-// import RolesTable from "./_tables/RolesTable/RolesTable";
-// import getRoles from "@/features/dashboard/roles/data/getRoles";
-import checkMultiplePermissions from "@/features/auth/tools/checkMultiplePermissions";
-import { PermissionTable } from "@/features/dashboard/permissions/tables";
-import getPermissions from "@/features/dashboard/permissions/data/getPermissions";
interface Props {
searchParams: {
@@ -28,14 +26,15 @@ export default async function RolesPage({ searchParams }: Props) {
update: "permission.update",
delete: "permission.delete",
});
-
- const permissionData = await getPermissions()
+
+ const res = await getAllPermissions();
+ if (!res.success) throw new Error();
return (
Permissions
-
+
);
diff --git a/src/modules/permission/actions/deletePermission.ts b/src/modules/permission/actions/deletePermission.ts
new file mode 100644
index 0000000..4e4e187
--- /dev/null
+++ b/src/modules/permission/actions/deletePermission.ts
@@ -0,0 +1,27 @@
+"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 { revalidatePath } from "next/cache";
+
+export default async function deletePermission(id: string): Promise {
+ try {
+ if (!(await checkPermission("permission.delete"))) unauthorized();
+ const permission = await prisma.permission.delete({
+ where: { id },
+ });
+
+
+ revalidatePath(".")
+
+ return {
+ success: true,
+ message: "The permission has been deleted successfully",
+ };
+ } catch (e: unknown) {
+ return handleCatch(e)
+ }
+}
diff --git a/src/modules/permission/actions/getAllPermissions.ts b/src/modules/permission/actions/getAllPermissions.ts
new file mode 100644
index 0000000..419c60a
--- /dev/null
+++ b/src/modules/permission/actions/getAllPermissions.ts
@@ -0,0 +1,56 @@
+"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 Permission from "../types/Permission";
+
+/**
+ * Retrieves all permissions along with the count of associated permissions and users.
+ * Authorization check is performed for the operation.
+ *
+ * @returns An array of permission objects each including details and counts of related permissions and users.
+ */
+export default async function getAllPermissions(): Promise> {
+ // Authorization check
+ if (!(await checkPermission("permissions.readAll"))) {
+ unauthorized();
+ }
+
+ try {
+ // Fetch permissions from the database
+ const permissions = await prisma.permission.findMany({
+ include: {
+ _count: {
+ select: {
+ roles: true,
+ directUsers: true,
+ },
+ },
+ },
+ });
+
+ // Transform the data into the desired format
+ const permissionsData = permissions.map(
+ ({ id, code, name, description, isActive, _count }) => ({
+ id,
+ code,
+ name,
+ description,
+ isActive,
+ roleCount: _count.roles,
+ //User count counts only direct user
+ userCount: _count.directUsers,
+ })
+ );
+
+ return {
+ success: true,
+ data: permissionsData
+ }
+ } catch (error) {
+ return handleCatch(error)
+ }
+}
diff --git a/src/modules/permission/actions/getPermissionById.ts b/src/modules/permission/actions/getPermissionById.ts
new file mode 100644
index 0000000..1ed5f81
--- /dev/null
+++ b/src/modules/permission/actions/getPermissionById.ts
@@ -0,0 +1,49 @@
+"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";
+
+interface Permission {
+ id: string;
+ code: string;
+ name: string;
+ description: string;
+ isActive: boolean;
+}
+
+export default async function getPermissionById(
+ id: string
+): Promise> {
+ try {
+ if (!(await checkPermission("permissions.read"))) unauthorized();
+
+ const permission = await prisma.permission.findFirst({
+ where: { id },
+ select: {
+ code: true,
+ description: true,
+ id: true,
+ isActive: true,
+ name: true,
+ },
+ });
+
+ if (!permission) {
+ return {
+ success: false,
+ message: "Permission not found",
+ } as const;
+ }
+
+ return {
+ success: true,
+ message: "Permission fetched successfully",
+ data: permission,
+ } as const;
+ } catch (e) {
+ return handleCatch(e);
+ }
+}
diff --git a/src/modules/permission/actions/upsertPermission.ts b/src/modules/permission/actions/upsertPermission.ts
new file mode 100644
index 0000000..51a6258
--- /dev/null
+++ b/src/modules/permission/actions/upsertPermission.ts
@@ -0,0 +1,83 @@
+"use server";
+
+import permissionFormDataSchema, { PermissionFormData } from "../formSchemas/PermissionFormData";
+import mapObjectToFirstValue from "@/utils/mapObjectToFirstValue";
+import prisma from "@/db";
+import { revalidatePath } from "next/cache";
+import ServerResponseAction from "@/modules/dashboard/types/ServerResponseAction";
+import checkPermission from "@/modules/dashboard/services/checkPermission";
+import unauthorized from "@/modules/dashboard/utils/unauthorized";
+import DashboardError from "@/modules/dashboard/errors/DashboardError";
+import handleCatch from "@/modules/dashboard/utils/handleCatch";
+
+/**
+ * Upserts a permission based on the provided PermissionFormData.
+ * If the permission already exists (determined by `id`), it updates the permission; otherwise, it creates a new permission.
+ * Authorization checks are performed based on whether it's a create or update operation.
+ *
+ * @param data - The data for creating or updating the permission.
+ * @returns An object containing the success status, message, and any errors.
+ */
+export default async function upsertPermission(
+ data: PermissionFormData
+): Promise {
+ try {
+ const isInsert = !data.id;
+
+ // Authorization check
+ const permissionType = isInsert ? "permission.create" : "permission.update";
+ if (!(await checkPermission(permissionType))) {
+ unauthorized();
+ }
+
+ // Validate form data
+ const validatedFields = permissionFormDataSchema.safeParse(data);
+ if (!validatedFields.success) {
+ throw new DashboardError({
+ errorCode: "INVALID_FORM_DATA",
+ formErrors: mapObjectToFirstValue(validatedFields.error.flatten().fieldErrors)
+ })
+ }
+ const permissionData = {
+ code: validatedFields.data.code,
+ description: validatedFields.data.description,
+ name: validatedFields.data.name,
+ isActive: validatedFields.data.isActive,
+ };
+
+ // Database operation
+ if (isInsert) {
+ if (await prisma.permission.findFirst({
+ where: {
+ code: permissionData.code
+ }
+ })){
+ throw new DashboardError({
+ errorCode: "INVALID_FORM_DATA",
+ formErrors: {
+ code: "The code is already exists"
+ }
+ })
+ }
+ await prisma.permission.create({ data: permissionData });
+ } else {
+ await prisma.permission.update({
+ where: { id: validatedFields.data.id! },
+ data: permissionData,
+ });
+ }
+
+ // Revalidate the cache
+ revalidatePath(".");
+
+ // Return success message
+ return {
+ success: true,
+ message: `Permission ${validatedFields.data.name} has been successfully ${
+ isInsert ? "created" : "updated"
+ }.`,
+ };
+ } catch (error) {
+ return handleCatch(error)
+ }
+}
diff --git a/src/_features/dashboard/permissions/formSchemas/PermissionFormData.ts b/src/modules/permission/formSchemas/PermissionFormData.ts
similarity index 100%
rename from src/_features/dashboard/permissions/formSchemas/PermissionFormData.ts
rename to src/modules/permission/formSchemas/PermissionFormData.ts
diff --git a/src/modules/permission/modals/PermissionDeleteModal.tsx b/src/modules/permission/modals/PermissionDeleteModal.tsx
new file mode 100644
index 0000000..910a402
--- /dev/null
+++ b/src/modules/permission/modals/PermissionDeleteModal.tsx
@@ -0,0 +1,105 @@
+"use client";
+import { useRouter } from "next/navigation";
+import React, { useState } from "react";
+import {
+ Button,
+ Flex,
+ Modal,
+ Text,
+ Alert,
+} from "@mantine/core";
+import { showNotification } from "@/utils/notifications";
+import { error } from "console";
+import { revalidatePath } from "next/cache";
+import withServerAction from "@/modules/dashboard/utils/withServerAction";
+import deletePermission from "../actions/deletePermission";
+import DashboardError from "@/modules/dashboard/errors/DashboardError";
+
+export interface DeleteModalProps {
+ data?: {
+ id: string;
+ name: string;
+ };
+ onClose: () => void;
+}
+
+export default function DeleteModal(props: DeleteModalProps) {
+ const router = useRouter();
+
+ const [isSubmitting, setSubmitting] = useState(false);
+ const [errorMessage, setErrorMessage] = useState("");
+
+ /**
+ * Closes the modal. It won't close if a submission is in progress.
+ */
+ const closeModal = () => {
+ if (isSubmitting) return;
+ setErrorMessage("")
+ props.onClose();
+ };
+
+ const confirmAction = () => {
+ if (!props.data?.id) return;
+ setSubmitting(true);
+
+ withServerAction(deletePermission, props.data!.id)
+ .then((response) => {
+ showNotification(
+ response.message ?? "Permission deleted successfully"
+ );
+ setSubmitting(false);
+ props.onClose()
+ })
+ .catch((e) => {
+ if (e instanceof DashboardError){
+ setErrorMessage(`ERROR: ${e.message} (${e.errorCode})`)
+ }
+ else if (e instanceof Error) {
+ setErrorMessage(`ERROR: ${e.message}`)
+ } else {
+ setErrorMessage(`Unkown error is occured. Please contact administrator`)
+ }
+ })
+ .finally(() => {
+ setSubmitting(false)
+ });
+ };
+
+ return (
+
+
+ Are you sure you want to delete permission{" "}
+
+ {props.data?.name}
+
+ ? This action is irreversible.
+
+
+ {errorMessage && {errorMessage}}
+ {/* Buttons */}
+
+
+ }
+ type="submit"
+ color="red"
+ loading={isSubmitting}
+ onClick={confirmAction}
+ >
+ Delete Permission
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/modules/permission/modals/PermissionFormModal.tsx b/src/modules/permission/modals/PermissionFormModal.tsx
new file mode 100644
index 0000000..3428bd6
--- /dev/null
+++ b/src/modules/permission/modals/PermissionFormModal.tsx
@@ -0,0 +1,213 @@
+/* eslint-disable react-hooks/exhaustive-deps */
+import { showNotification } from "@/utils/notifications";
+import {
+ Flex,
+ Modal,
+ Stack,
+ Switch,
+ TextInput,
+ Textarea,
+ Button,
+ ScrollArea,
+ Checkbox,
+ Skeleton,
+ Fieldset,
+ Alert,
+} from "@mantine/core";
+import { useForm, zodResolver } from "@mantine/form";
+import { useRouter } from "next/navigation";
+import React, { useCallback, useEffect, useState } from "react";
+import { TbDeviceFloppy } from "react-icons/tb";
+import permissionFormDataSchema, { PermissionFormData } from "../formSchemas/PermissionFormData";
+import getPermissionById from "../actions/getPermissionById";
+import withServerAction from "@/modules/dashboard/utils/withServerAction";
+import upsertPermission from "../actions/upsertPermission";
+import DashboardError from "@/modules/dashboard/errors/DashboardError";
+
+export interface ModalProps {
+ title: string;
+ readonly?: boolean;
+ id?: string;
+ opened: boolean;
+ onClose?: () => void;
+}
+
+/**
+ * A component for rendering a modal with a form to create or edit a permission.
+ *
+ * @param props - The props for the component.
+ * @returns The rendered element.
+ */
+export default function FormModal(props: ModalProps) {
+ const router = useRouter();
+ const [isSubmitting, setSubmitting] = useState(false);
+ const [isFetching, setFetching] = useState(false);
+ const [errorMessage, setErrorMessage] = useState("");
+
+ const form = useForm({
+ initialValues: {
+ code: "",
+ description: "",
+ id: "",
+ isActive: false,
+ name: "",
+ },
+ validate: zodResolver(permissionFormDataSchema),
+ validateInputOnChange: false,
+ onValuesChange: (values) => {
+ console.log(values);
+ },
+ });
+
+ /**
+ * Fetches permission data by ID and populates the form if the modal is opened and an ID is provided.
+ */
+ useEffect(() => {
+ if (!props.opened || !props.id) {
+ return;
+ }
+
+ setFetching(true);
+ getPermissionById(props.id)
+ .then((response) => {
+ if (response.success) {
+ const data = response.data;
+ form.setValues({
+ code: data.code,
+ description: data.description,
+ id: data.id,
+ isActive: data.isActive,
+ name: data.name,
+ });
+ }
+ })
+ .catch((e) => {
+ //TODO: Handle error
+ console.log(e);
+ })
+ .finally(() => {
+ setFetching(false);
+ });
+ }, [props.opened, props.id]);
+
+ const closeModal = () => {
+ form.reset()
+ props.onClose ? props.onClose() : router.replace("?");
+ };
+
+ const handleSubmit = (values: PermissionFormData) => {
+ setSubmitting(true);
+ withServerAction(upsertPermission, values)
+ .then((response) => {
+ showNotification(response.message!, "success");
+ closeModal();
+ })
+ .catch((e) => {
+ if (e instanceof DashboardError) {
+ if (e.errorCode === "INVALID_FORM_DATA") {
+ form.setErrors(e.formErrors ?? {});
+ } else {
+ setErrorMessage(`ERROR: ${e.message} (${e.errorCode})`);
+ }
+ } else if (e instanceof Error) {
+ setErrorMessage(`ERROR: ${e.message}`);
+ } else {
+ setErrorMessage(
+ `Unkown error is occured. Please contact administrator`
+ );
+ }
+ })
+ .finally(() => {
+ setSubmitting(false);
+ });
+ };
+
+ return (
+
+
+
+ );
+}
diff --git a/src/modules/permission/tables/PermissionTable/PermissionTable.tsx b/src/modules/permission/tables/PermissionTable/PermissionTable.tsx
new file mode 100644
index 0000000..e939cb0
--- /dev/null
+++ b/src/modules/permission/tables/PermissionTable/PermissionTable.tsx
@@ -0,0 +1,126 @@
+"use client";
+import CrudPermissions from "@/modules/dashboard/types/CrudPermissions";
+import { Table, Text, Flex, Button, Center } from "@mantine/core";
+import {
+ flexRender,
+ getCoreRowModel,
+ useReactTable,
+} from "@tanstack/react-table";
+import React, { useState } from "react";
+import { TbPlus } from "react-icons/tb";
+import getPermissions from "../../actions/getAllPermissions";
+import getAllPermissions from "../../actions/getAllPermissions";
+import FormModal, { ModalProps } from "../../modals/PermissionFormModal";
+import DeleteModal, { DeleteModalProps } from "../../modals/PermissionDeleteModal";
+import Permission from "../../types/Permission";
+import createColumns from "./columns";
+import DashboardTable from "@/modules/dashboard/components/DashboardTable";
+
+interface Props {
+ permissions: Partial;
+ permissionData: Permission[];
+}
+
+export default function PermissionsTable(props: Props) {
+ const [modalProps, setModalProps] = useState({
+ opened: false,
+ title: "",
+ });
+
+ const [deleteModalProps, setDeleteModalProps] = useState<
+ Omit
+ >({
+ data: undefined,
+ });
+
+ const table = useReactTable({
+ data: props.permissionData,
+ columns: createColumns({
+ permissions: props.permissions,
+ actions: {
+ detail: (id: string) => openFormModal("detail", id),
+ edit: (id: string) => openFormModal("edit", id),
+ delete: (id: string, name: string) => openDeleteModal(id, name),
+ },
+ }),
+ getCoreRowModel: getCoreRowModel(),
+ defaultColumn: {
+ cell: (props) => {props.getValue() as React.ReactNode},
+ },
+ });
+
+ const openFormModal = (type: "create" | "edit" | "detail", id?: string) => {
+ const openCreateModal = () => {
+ setModalProps({
+ id,
+ opened: true,
+ title: "Create new permission",
+ });
+ };
+
+ const openDetailModal = () => {
+ setModalProps({
+ id,
+ opened: true,
+ title: "Permission detail",
+ readonly: true,
+ });
+ };
+
+ const openEditModal = () => {
+ setModalProps({
+ id,
+ opened: true,
+ title: "Edit permission",
+ });
+ };
+
+ type === "create"
+ ? openCreateModal()
+ : type === "detail"
+ ? openDetailModal()
+ : openEditModal();
+ };
+
+ const openDeleteModal = (id: string, name: string) => {
+ setDeleteModalProps({
+ data: {
+ id,
+ name,
+ },
+ });
+ };
+
+ const closeModal = () => {
+ setModalProps({
+ id: "",
+ opened: false,
+ title: "",
+ });
+ };
+
+ // TODO: Add view when data is empty
+
+ return (
+ <>
+
+ {props.permissions.create && (
+ }
+ onClick={() => openFormModal("create")}
+ >
+ New Permission
+
+ )}
+
+
+
+
+
+ setDeleteModalProps({})}
+ />
+ >
+ );
+}
diff --git a/src/modules/permission/tables/PermissionTable/columns.tsx b/src/modules/permission/tables/PermissionTable/columns.tsx
new file mode 100644
index 0000000..3220de8
--- /dev/null
+++ b/src/modules/permission/tables/PermissionTable/columns.tsx
@@ -0,0 +1,105 @@
+import { createColumnHelper } from "@tanstack/react-table";
+import { Badge, Flex } from "@mantine/core";
+import { TbEye, TbPencil, TbTrash } from "react-icons/tb";
+import CrudPermissions from "@/modules/dashboard/types/CrudPermissions";
+import createActionButtons from "@/modules/dashboard/utils/createActionButton";
+
+export interface PermissionRow {
+ id: string;
+ code: string;
+ name: string;
+ description: string;
+ isActive: boolean;
+ roleCount: number;
+ userCount: number;
+}
+
+interface ColumnOptions {
+ permissions: Partial;
+ actions: {
+ detail: (id: string) => void;
+ edit: (id: string) => void;
+ delete: (id: string, name: string) => void;
+ };
+}
+
+const createColumns = (options: ColumnOptions) => {
+ const columnHelper = createColumnHelper();
+
+ const columns = [
+ columnHelper.accessor("id", {
+ id: "sequence",
+ header: "#",
+ cell: (props) => props.row.index + 1,
+ }),
+
+ columnHelper.accessor("code", {
+ header: "Code",
+ }),
+
+ columnHelper.accessor("name", {
+ header: "Name",
+ }),
+
+ columnHelper.accessor("isActive", {
+ header: "Status",
+ cell: (props) => {
+ props.getValue() ? (
+ Enabled
+ ) : (
+ Disabled
+ );
+ },
+ }),
+
+ columnHelper.accessor("roleCount", {
+ header: "Roles",
+ }),
+
+ columnHelper.accessor("userCount", {
+ header: "Users",
+ }),
+
+ columnHelper.display({
+ id: "Actions",
+ header: "Actions",
+ cell: (props) => (
+
+ {createActionButtons([
+ {
+ label: "Detail",
+ permission: options.permissions.read,
+ action: () =>
+ options.actions.detail(props.row.original.id),
+ color: "green",
+ icon: ,
+ },
+ {
+ label: "Edit",
+ permission: options.permissions.update,
+ action: () =>
+ options.actions.edit(props.row.original.id),
+ color: "yellow",
+ icon: ,
+ },
+ {
+ label: "Delete",
+ permission: options.permissions.delete,
+ action: () =>
+ options.actions.delete(
+ props.row.original.id,
+ props.row.original.name
+ ),
+ color: "red",
+ icon: ,
+ },
+ ])}
+
+ ),
+ }),
+ ];
+
+ return columns;
+};
+
+export default createColumns;
diff --git a/src/modules/permission/types/Permission.d.ts b/src/modules/permission/types/Permission.d.ts
new file mode 100644
index 0000000..f41300a
--- /dev/null
+++ b/src/modules/permission/types/Permission.d.ts
@@ -0,0 +1,11 @@
+type Permission = {
+ id: string;
+ code: string;
+ name: string;
+ description: string;
+ isActive: boolean;
+ roleCount: number;
+ userCount: number;
+};
+
+export default Permission;