473 lines
13 KiB
TypeScript
473 lines
13 KiB
TypeScript
import apiFetch from "@/lib/apiFetch";
|
|
import {
|
|
ActionIcon,
|
|
Button,
|
|
Divider,
|
|
Flex,
|
|
Group,
|
|
Input,
|
|
Modal,
|
|
Stack,
|
|
Table,
|
|
Text,
|
|
Title,
|
|
Tooltip,
|
|
} from "@mantine/core";
|
|
import { useDisclosure, useShallowEffect } from "@mantine/hooks";
|
|
import { IconEdit, IconPlus, IconTrash } from "@tabler/icons-react";
|
|
import type { JsonValue } from "generated/prisma/runtime/library";
|
|
import { useState } from "react";
|
|
import useSWR from "swr";
|
|
import listMenu from "../lib/listPermission.json";
|
|
import notification from "./notificationGlobal";
|
|
import PermissionRole from "./PermissionRole";
|
|
import PermissionTree from "./PermissionTree";
|
|
|
|
interface MenuNode {
|
|
key: string;
|
|
label: string;
|
|
default: boolean;
|
|
children?: MenuNode[];
|
|
}
|
|
|
|
export default function UserRoleSetting({
|
|
permissions,
|
|
}: {
|
|
permissions: JsonValue[];
|
|
}) {
|
|
const [btnDisable, setBtnDisable] = useState(true);
|
|
const [btnLoading, setBtnLoading] = useState(false);
|
|
const [opened, { open, close }] = useDisclosure(false);
|
|
const [openedDelete, { open: openDelete, close: closeDelete }] =
|
|
useDisclosure(false);
|
|
const [dataDelete, setDataDelete] = useState("");
|
|
const {
|
|
data: dataRole,
|
|
mutate: mutateRole,
|
|
isLoading: isLoadingRole,
|
|
} = useSWR("user-role", () => apiFetch.api.user.role.get());
|
|
const [openedTambah, { open: openTambah, close: closeTambah }] =
|
|
useDisclosure(false);
|
|
const { data, mutate, isLoading } = useSWR("role-list", () =>
|
|
apiFetch.api.user.role.get(),
|
|
);
|
|
const list = data?.data || [];
|
|
const listRole = dataRole?.data || [];
|
|
const [dataEdit, setDataEdit] = useState({
|
|
id: "",
|
|
name: "",
|
|
permissions: [],
|
|
});
|
|
const [dataTambah, setDataTambah] = useState({
|
|
name: "",
|
|
permissions: [],
|
|
});
|
|
const [error, setError] = useState({
|
|
name: false,
|
|
permissions: false,
|
|
});
|
|
|
|
useShallowEffect(() => {
|
|
mutate();
|
|
}, []);
|
|
|
|
async function handleCreate() {
|
|
try {
|
|
setBtnLoading(true);
|
|
const res = await apiFetch.api.user["role-create"].post(
|
|
dataTambah as any,
|
|
);
|
|
if (res.status === 200) {
|
|
mutate();
|
|
closeTambah();
|
|
setDataTambah({
|
|
name: "",
|
|
permissions: [],
|
|
});
|
|
notification({
|
|
title: "Success",
|
|
message: "Your role have been saved",
|
|
type: "success",
|
|
});
|
|
} else {
|
|
notification({
|
|
title: "Error",
|
|
message: "Failed to create role",
|
|
type: "error",
|
|
});
|
|
}
|
|
} catch (error) {
|
|
console.error(error);
|
|
notification({
|
|
title: "Error",
|
|
message: "Failed to create role",
|
|
type: "error",
|
|
});
|
|
} finally {
|
|
setBtnLoading(false);
|
|
}
|
|
}
|
|
|
|
async function handleEdit() {
|
|
try {
|
|
setBtnLoading(true);
|
|
const res = await apiFetch.api.user["role-update"].post(dataEdit as any);
|
|
if (res.status === 200) {
|
|
mutate();
|
|
close();
|
|
notification({
|
|
title: "Success",
|
|
message: "Your role have been saved",
|
|
type: "success",
|
|
});
|
|
} else {
|
|
notification({
|
|
title: "Error",
|
|
message: "Failed to edit role",
|
|
type: "error",
|
|
});
|
|
}
|
|
} catch (error) {
|
|
console.error(error);
|
|
notification({
|
|
title: "Error",
|
|
message: "Failed to edit role",
|
|
type: "error",
|
|
});
|
|
} finally {
|
|
setBtnLoading(false);
|
|
}
|
|
}
|
|
|
|
async function handleDelete() {
|
|
try {
|
|
setBtnLoading(true);
|
|
const res = await apiFetch.api.user["role-delete"].post({
|
|
id: dataDelete,
|
|
});
|
|
if (res.status === 200) {
|
|
mutate();
|
|
closeDelete();
|
|
notification({
|
|
title: "Success",
|
|
message: "Your role have been deleted",
|
|
type: "success",
|
|
});
|
|
} else {
|
|
notification({
|
|
title: "Error",
|
|
message: "Failed to delete role",
|
|
type: "error",
|
|
});
|
|
}
|
|
} catch (error) {
|
|
console.error(error);
|
|
notification({
|
|
title: "Error",
|
|
message: "Failed to delete role",
|
|
type: "error",
|
|
});
|
|
} finally {
|
|
setBtnLoading(false);
|
|
}
|
|
}
|
|
|
|
function chooseEdit({
|
|
data,
|
|
}: {
|
|
data: { id: string; name: string; permissions: [] };
|
|
}) {
|
|
setDataEdit({
|
|
id: data.id,
|
|
name: data.name,
|
|
permissions: data.permissions ? data.permissions : [],
|
|
});
|
|
open();
|
|
}
|
|
|
|
function onValidation({
|
|
kat,
|
|
value,
|
|
aksi,
|
|
}: {
|
|
kat: "name" | "permission";
|
|
value: string | null;
|
|
aksi: "edit" | "tambah";
|
|
}) {
|
|
if (value == null || value.length < 1) {
|
|
setBtnDisable(true);
|
|
setError({ ...error, [kat]: true });
|
|
} else {
|
|
setBtnDisable(false);
|
|
setError({ ...error, [kat]: false });
|
|
}
|
|
|
|
if (aksi === "edit") {
|
|
setDataEdit({ ...dataEdit, [kat]: value });
|
|
} else {
|
|
setDataTambah({ ...dataTambah, [kat]: value });
|
|
}
|
|
}
|
|
|
|
function buildOrderList(menus: MenuNode[]): string[] {
|
|
const list: string[] = [];
|
|
|
|
const traverse = (nodes: MenuNode[]) => {
|
|
nodes.forEach((node) => {
|
|
list.push(node.key);
|
|
if (node.children) traverse(node.children);
|
|
});
|
|
};
|
|
|
|
traverse(menus);
|
|
return list;
|
|
}
|
|
|
|
function sortByJsonOrder(arrayData: string[]): string[] {
|
|
const orderList = buildOrderList(listMenu.menus);
|
|
|
|
return arrayData.sort((a, b) => {
|
|
return orderList.indexOf(a) - orderList.indexOf(b);
|
|
});
|
|
}
|
|
|
|
useShallowEffect(() => {
|
|
if (dataEdit.name.length > 0) {
|
|
setBtnDisable(false);
|
|
}
|
|
}, [dataEdit.id]);
|
|
|
|
return (
|
|
<>
|
|
{/* Modal Edit */}
|
|
<Modal
|
|
opened={opened}
|
|
onClose={close}
|
|
title={"Edit"}
|
|
overlayProps={{ backgroundOpacity: 0.55, blur: 3 }}
|
|
size={"lg"}
|
|
>
|
|
<Stack gap="ld">
|
|
<Input.Wrapper label="Nama Role">
|
|
<Input
|
|
value={dataEdit.name}
|
|
onChange={(e) =>
|
|
onValidation({
|
|
kat: "name",
|
|
value: e.target.value,
|
|
aksi: "edit",
|
|
})
|
|
}
|
|
/>
|
|
</Input.Wrapper>
|
|
<PermissionTree
|
|
selected={dataEdit.permissions}
|
|
onChange={(permissions) => {
|
|
setDataEdit({
|
|
...dataEdit,
|
|
permissions: sortByJsonOrder(permissions) as never[],
|
|
});
|
|
}}
|
|
/>
|
|
<Group justify="center" grow>
|
|
<Button variant="light" onClick={close}>
|
|
Batal
|
|
</Button>
|
|
<Button
|
|
variant="filled"
|
|
onClick={handleEdit}
|
|
disabled={
|
|
btnDisable ||
|
|
dataEdit.name.length < 1 ||
|
|
dataEdit.permissions?.length < 1
|
|
}
|
|
loading={btnLoading}
|
|
>
|
|
Simpan
|
|
</Button>
|
|
</Group>
|
|
</Stack>
|
|
</Modal>
|
|
|
|
{/* Modal Tambah */}
|
|
<Modal
|
|
opened={openedTambah}
|
|
onClose={closeTambah}
|
|
title={"Tambah"}
|
|
overlayProps={{ backgroundOpacity: 0.55, blur: 3 }}
|
|
size={"lg"}
|
|
>
|
|
<Stack gap="ld">
|
|
<Input.Wrapper
|
|
label="Nama Role"
|
|
description=""
|
|
error={error.name ? "Field is required" : ""}
|
|
>
|
|
<Input
|
|
value={dataTambah.name}
|
|
onChange={(e) =>
|
|
onValidation({
|
|
kat: "name",
|
|
value: e.target.value,
|
|
aksi: "tambah",
|
|
})
|
|
}
|
|
/>
|
|
</Input.Wrapper>
|
|
<PermissionTree
|
|
selected={dataTambah.permissions}
|
|
onChange={(permissions) => {
|
|
setDataTambah({
|
|
...dataTambah,
|
|
permissions: sortByJsonOrder(permissions) as never[],
|
|
});
|
|
}}
|
|
/>
|
|
<Group justify="center" grow>
|
|
<Button variant="light" onClick={closeTambah}>
|
|
Batal
|
|
</Button>
|
|
<Button
|
|
variant="filled"
|
|
onClick={handleCreate}
|
|
disabled={
|
|
btnDisable ||
|
|
dataTambah.name.length < 1 ||
|
|
dataTambah.permissions.length < 1
|
|
}
|
|
loading={btnLoading}
|
|
>
|
|
Simpan
|
|
</Button>
|
|
</Group>
|
|
</Stack>
|
|
</Modal>
|
|
|
|
{/* Modal Delete */}
|
|
<Modal
|
|
opened={openedDelete}
|
|
onClose={closeDelete}
|
|
title={"Delete"}
|
|
overlayProps={{ backgroundOpacity: 0.55, blur: 3 }}
|
|
>
|
|
<Stack gap="md">
|
|
<Text size="md" color="gray.6">
|
|
Apakah anda yakin ingin menghapus role ini?
|
|
</Text>
|
|
<Group justify="center" grow>
|
|
<Button variant="light" onClick={closeDelete}>
|
|
Batal
|
|
</Button>
|
|
<Button
|
|
variant="filled"
|
|
color="red"
|
|
onClick={handleDelete}
|
|
loading={btnLoading}
|
|
>
|
|
Hapus
|
|
</Button>
|
|
</Group>
|
|
</Stack>
|
|
</Modal>
|
|
|
|
<Stack gap={"md"}>
|
|
<Flex align="center" justify="space-between">
|
|
<Title order={4} c="gray.2">
|
|
Daftar Role
|
|
</Title>
|
|
{permissions.includes("setting.user_role.tambah") && (
|
|
<Tooltip label="Tambah Role">
|
|
<Button
|
|
variant="light"
|
|
leftSection={<IconPlus size={20} />}
|
|
onClick={openTambah}
|
|
>
|
|
Tambah
|
|
</Button>
|
|
</Tooltip>
|
|
)}
|
|
</Flex>
|
|
<Divider my={0} />
|
|
<Stack gap={"md"}>
|
|
<Table highlightOnHover>
|
|
<Table.Thead>
|
|
<Table.Tr>
|
|
<Table.Th>Role</Table.Th>
|
|
<Table.Th>Permission</Table.Th>
|
|
<Table.Th>Aksi</Table.Th>
|
|
</Table.Tr>
|
|
</Table.Thead>
|
|
<Table.Tbody>
|
|
{list.length > 0 ? (
|
|
list?.map((v: any) => (
|
|
<Table.Tr key={v.id}>
|
|
<Table.Td w={"150"}>{v.name}</Table.Td>
|
|
<Table.Td>
|
|
<PermissionRole permissions={v.permissions} />
|
|
</Table.Td>
|
|
<Table.Td w={"100"}>
|
|
<Group>
|
|
<Tooltip
|
|
label={
|
|
permissions.includes("setting.user_role.edit")
|
|
? "Edit Role"
|
|
: "Edit Role - Anda tidak memiliki akses"
|
|
}
|
|
>
|
|
<ActionIcon
|
|
variant="light"
|
|
size="sm"
|
|
style={{ boxShadow: "0 0 8px rgba(0,255,200,0.2)" }}
|
|
onClick={() => chooseEdit({ data: v })}
|
|
disabled={
|
|
!permissions.includes("setting.user_role.edit") ||
|
|
v.id == "developer"
|
|
}
|
|
>
|
|
<IconEdit size={20} />
|
|
</ActionIcon>
|
|
</Tooltip>
|
|
<Tooltip
|
|
label={
|
|
permissions.includes("setting.user_role.delete")
|
|
? "Delete Role"
|
|
: "Delete Role - Anda tidak memiliki akses"
|
|
}
|
|
>
|
|
<ActionIcon
|
|
variant="light"
|
|
size="sm"
|
|
color="red"
|
|
style={{ boxShadow: "0 0 8px rgba(0,255,200,0.2)" }}
|
|
onClick={() => {
|
|
setDataDelete(v.id);
|
|
openDelete();
|
|
}}
|
|
disabled={
|
|
!permissions.includes(
|
|
"setting.user_role.delete",
|
|
) || v.id == "developer"
|
|
}
|
|
>
|
|
<IconTrash size={20} />
|
|
</ActionIcon>
|
|
</Tooltip>
|
|
</Group>
|
|
</Table.Td>
|
|
</Table.Tr>
|
|
))
|
|
) : (
|
|
<Table.Tr>
|
|
<Table.Td colSpan={5} align="center">
|
|
Data Role Tidak Ditemukan
|
|
</Table.Td>
|
|
</Table.Tr>
|
|
)}
|
|
</Table.Tbody>
|
|
</Table>
|
|
</Stack>
|
|
</Stack>
|
|
</>
|
|
);
|
|
}
|