upd: dashboard admin
Deskripsi: - databse - seeder - list user role NO Issues
This commit is contained in:
@@ -9,11 +9,12 @@ datasource db {
|
||||
}
|
||||
|
||||
model Role {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
User User[]
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
permissions Json?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
User User[]
|
||||
}
|
||||
|
||||
model User {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { categoryPelayananSurat } from "@/lib/categoryPelayananSurat";
|
||||
import { confDesa } from "@/lib/configurationDesa";
|
||||
import permissionConfig from "@/lib/listPermission.json"; // JSON yang kita buat
|
||||
import { prisma } from "@/server/lib/prisma";
|
||||
|
||||
const category = [
|
||||
@@ -29,14 +30,6 @@ const role = [
|
||||
{
|
||||
id: "developer",
|
||||
name: "developer"
|
||||
},
|
||||
{
|
||||
id: "admin",
|
||||
name: "admin"
|
||||
},
|
||||
{
|
||||
id: "pelaksana",
|
||||
name: "pelaksana"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -51,11 +44,30 @@ const user = [
|
||||
];
|
||||
|
||||
(async () => {
|
||||
const allKeys: string[] = [];
|
||||
|
||||
function collectKeys(items: any[]) {
|
||||
items.forEach((item) => {
|
||||
allKeys.push(item.key);
|
||||
if (item.children) collectKeys(item.children);
|
||||
});
|
||||
}
|
||||
|
||||
collectKeys(permissionConfig.menus);
|
||||
|
||||
|
||||
for (const r of role) {
|
||||
await prisma.role.upsert({
|
||||
where: { id: r.id },
|
||||
create: r,
|
||||
update: r
|
||||
create: {
|
||||
id: r.id,
|
||||
name: r.name,
|
||||
permissions: allKeys as any,
|
||||
},
|
||||
update: {
|
||||
name: r.name,
|
||||
permissions: allKeys as any,
|
||||
}
|
||||
})
|
||||
|
||||
console.log(`✅ Role ${r.name} seeded successfully`)
|
||||
|
||||
57
src/components/PermissionRole.tsx
Normal file
57
src/components/PermissionRole.tsx
Normal file
@@ -0,0 +1,57 @@
|
||||
import { groupPermissions } from "@/lib/groupPermission";
|
||||
import { Button, Stack, Text } from "@mantine/core";
|
||||
import { useState } from "react";
|
||||
|
||||
interface Node {
|
||||
label: string;
|
||||
children: any;
|
||||
actions: string[];
|
||||
}
|
||||
|
||||
function RenderNode({ node }: { node: Node }) {
|
||||
const sub = Object.values(node.children || {});
|
||||
|
||||
return (
|
||||
<Stack pl="md" gap={6}>
|
||||
{/* Title */}
|
||||
<Text fw={600}>- {node.label}</Text>
|
||||
|
||||
{/* Children */}
|
||||
{sub.map((child: any, i) => (
|
||||
<RenderNode key={i} node={child} />
|
||||
))}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
export default function PermissionRole({ permissions }: { permissions: string[] }) {
|
||||
const [showAll, setShowAll] = useState(false);
|
||||
if (!permissions?.length) return <Text c="dimmed">-</Text>;
|
||||
|
||||
const groups = groupPermissions(permissions);
|
||||
const rootNodes = Object.values(groups);
|
||||
|
||||
return (
|
||||
<Stack gap="lg">
|
||||
{
|
||||
showAll ?
|
||||
rootNodes.map((node: any, idx) => (
|
||||
<RenderNode key={idx} node={node} />
|
||||
))
|
||||
:
|
||||
rootNodes.slice(0, 2).map((node: any, idx) => (
|
||||
<RenderNode key={idx} node={node} />
|
||||
))
|
||||
}
|
||||
<Button
|
||||
variant="subtle"
|
||||
size="xs"
|
||||
onClick={() => setShowAll(!showAll)}
|
||||
w="fit-content"
|
||||
ml="md"
|
||||
>
|
||||
{showAll ? "View less" : "View more"}
|
||||
</Button>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
465
src/components/UserRoleSetting.tsx
Normal file
465
src/components/UserRoleSetting.tsx
Normal file
@@ -0,0 +1,465 @@
|
||||
import apiFetch from "@/lib/apiFetch";
|
||||
import {
|
||||
ActionIcon,
|
||||
Button,
|
||||
Divider,
|
||||
Flex,
|
||||
Group,
|
||||
Input,
|
||||
Modal,
|
||||
Select,
|
||||
Stack,
|
||||
Table,
|
||||
Text,
|
||||
Title,
|
||||
Tooltip,
|
||||
} from "@mantine/core";
|
||||
import { useDisclosure, useShallowEffect } from "@mantine/hooks";
|
||||
import { IconEdit, IconPlus, IconTrash } from "@tabler/icons-react";
|
||||
import { useState } from "react";
|
||||
import useSWR from "swr";
|
||||
import notification from "./notificationGlobal";
|
||||
import PermissionRole from "./PermissionRole";
|
||||
|
||||
export default function UserRoleSetting() {
|
||||
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: "",
|
||||
phone: "",
|
||||
email: "",
|
||||
roleId: "",
|
||||
});
|
||||
const [dataTambah, setDataTambah] = useState({
|
||||
name: "",
|
||||
email: "",
|
||||
roleId: "",
|
||||
password: "",
|
||||
phone: "",
|
||||
});
|
||||
const [error, setError] = useState({
|
||||
name: false,
|
||||
email: false,
|
||||
roleId: false,
|
||||
password: false,
|
||||
phone: false,
|
||||
});
|
||||
|
||||
useShallowEffect(() => {
|
||||
mutate();
|
||||
}, []);
|
||||
|
||||
async function handleCreate() {
|
||||
try {
|
||||
setBtnLoading(true);
|
||||
const res = await apiFetch.api.user.create.post(dataTambah);
|
||||
if (res.status === 200) {
|
||||
mutate();
|
||||
closeTambah();
|
||||
setDataTambah({
|
||||
name: "",
|
||||
email: "",
|
||||
roleId: "",
|
||||
password: "",
|
||||
phone: "",
|
||||
});
|
||||
notification({
|
||||
title: "Success",
|
||||
message: "Your user have been saved",
|
||||
type: "success",
|
||||
});
|
||||
} else {
|
||||
notification({
|
||||
title: "Error",
|
||||
message: "Failed to create user ",
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
notification({
|
||||
title: "Error",
|
||||
message: "Failed to create user",
|
||||
type: "error",
|
||||
});
|
||||
} finally {
|
||||
setBtnLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleEdit() {
|
||||
try {
|
||||
setBtnLoading(true);
|
||||
const res = await apiFetch.api.pengaduan.category.update.post(dataEdit);
|
||||
if (res.status === 200) {
|
||||
mutate();
|
||||
close();
|
||||
notification({
|
||||
title: "Success",
|
||||
message: "Your category have been saved",
|
||||
type: "success",
|
||||
});
|
||||
} else {
|
||||
notification({
|
||||
title: "Error",
|
||||
message: "Failed to edit category",
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
notification({
|
||||
title: "Error",
|
||||
message: "Failed to edit category",
|
||||
type: "error",
|
||||
});
|
||||
} finally {
|
||||
setBtnLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDelete() {
|
||||
try {
|
||||
setBtnLoading(true);
|
||||
const res = await apiFetch.api.user.delete.post({ id: dataDelete });
|
||||
if (res.status === 200) {
|
||||
mutate();
|
||||
closeDelete();
|
||||
notification({
|
||||
title: "Success",
|
||||
message: "Your user have been deleted",
|
||||
type: "success",
|
||||
});
|
||||
} else {
|
||||
notification({
|
||||
title: "Error",
|
||||
message: "Failed to delete user",
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
notification({
|
||||
title: "Error",
|
||||
message: "Failed to delete user",
|
||||
type: "error",
|
||||
});
|
||||
} finally {
|
||||
setBtnLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
function chooseEdit({
|
||||
data,
|
||||
}: {
|
||||
data: {
|
||||
id: string;
|
||||
name: string;
|
||||
phone: string;
|
||||
email: string;
|
||||
roleId: string;
|
||||
};
|
||||
}) {
|
||||
setDataEdit(data);
|
||||
open();
|
||||
}
|
||||
|
||||
function onValidation({
|
||||
kat,
|
||||
value,
|
||||
aksi,
|
||||
}: {
|
||||
kat: "name" | "email" | "roleId" | "password" | "phone";
|
||||
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 });
|
||||
}
|
||||
}
|
||||
|
||||
useShallowEffect(() => {
|
||||
if (dataEdit.name.length > 0) {
|
||||
setBtnDisable(false);
|
||||
}
|
||||
}, [dataEdit.id]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Modal Edit */}
|
||||
<Modal
|
||||
opened={opened}
|
||||
onClose={close}
|
||||
title={"Edit"}
|
||||
centered
|
||||
overlayProps={{ backgroundOpacity: 0.55, blur: 3 }}
|
||||
>
|
||||
<Stack gap="ld">
|
||||
<Input.Wrapper label="Edit Kategori">
|
||||
<Input
|
||||
value={dataEdit.name}
|
||||
onChange={(e) =>
|
||||
onValidation({
|
||||
kat: "name",
|
||||
value: e.target.value,
|
||||
aksi: "edit",
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Input.Wrapper>
|
||||
<Group justify="center" grow>
|
||||
<Button variant="light" onClick={close}>
|
||||
Batal
|
||||
</Button>
|
||||
<Button
|
||||
variant="filled"
|
||||
onClick={handleEdit}
|
||||
disabled={btnDisable}
|
||||
loading={btnLoading}
|
||||
>
|
||||
Simpan
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Modal>
|
||||
|
||||
{/* Modal Tambah */}
|
||||
<Modal
|
||||
opened={openedTambah}
|
||||
onClose={closeTambah}
|
||||
title={"Tambah"}
|
||||
overlayProps={{ backgroundOpacity: 0.55, blur: 3 }}
|
||||
>
|
||||
<Stack gap="ld">
|
||||
<Input.Wrapper
|
||||
label="Nama"
|
||||
description=""
|
||||
error={error.name ? "Field is required" : ""}
|
||||
>
|
||||
<Input
|
||||
value={dataTambah.name}
|
||||
onChange={(e) =>
|
||||
onValidation({
|
||||
kat: "name",
|
||||
value: e.target.value,
|
||||
aksi: "tambah",
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Input.Wrapper>
|
||||
<Select
|
||||
label="Role"
|
||||
placeholder="Pilih Role"
|
||||
data={listRole.map((r: any) => ({
|
||||
value: r.id,
|
||||
label: r.name,
|
||||
}))}
|
||||
value={dataTambah.roleId || null}
|
||||
error={error.roleId ? "Field is required" : ""}
|
||||
onChange={(_value, option) => {
|
||||
onValidation({
|
||||
kat: "roleId",
|
||||
value: option?.value,
|
||||
aksi: "tambah",
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Input.Wrapper label="Phone" description="">
|
||||
<Input
|
||||
value={dataTambah.phone}
|
||||
onChange={(e) =>
|
||||
onValidation({
|
||||
kat: "phone",
|
||||
value: e.target.value,
|
||||
aksi: "tambah",
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Input.Wrapper>
|
||||
<Input.Wrapper
|
||||
label="Email"
|
||||
description=""
|
||||
error={error.email ? "Field is required" : ""}
|
||||
>
|
||||
<Input
|
||||
value={dataTambah.email}
|
||||
onChange={(e) =>
|
||||
onValidation({
|
||||
kat: "email",
|
||||
value: e.target.value,
|
||||
aksi: "tambah",
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Input.Wrapper>
|
||||
<Input.Wrapper
|
||||
label="Password"
|
||||
description=""
|
||||
error={error.password ? "Field is required" : ""}
|
||||
>
|
||||
<Input
|
||||
value={dataTambah.password}
|
||||
onChange={(e) =>
|
||||
onValidation({
|
||||
kat: "password",
|
||||
value: e.target.value,
|
||||
aksi: "tambah",
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Input.Wrapper>
|
||||
|
||||
<Group justify="center" grow>
|
||||
<Button variant="light" onClick={closeTambah}>
|
||||
Batal
|
||||
</Button>
|
||||
<Button
|
||||
variant="filled"
|
||||
onClick={handleCreate}
|
||||
disabled={
|
||||
btnDisable ||
|
||||
dataTambah.name.length < 1 ||
|
||||
dataTambah.email.length < 1 ||
|
||||
dataTambah.password.length < 1 ||
|
||||
dataTambah.roleId.length < 1 ||
|
||||
dataTambah.phone.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 user 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>
|
||||
<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>{v.name}</Table.Td>
|
||||
<Table.Td>
|
||||
<PermissionRole permissions={v.permissions} />
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<Group>
|
||||
<Tooltip label="Edit User">
|
||||
<ActionIcon
|
||||
variant="light"
|
||||
size="sm"
|
||||
style={{ boxShadow: "0 0 8px rgba(0,255,200,0.2)" }}
|
||||
onClick={() => chooseEdit({ data: v })}
|
||||
>
|
||||
<IconEdit size={20} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<Tooltip label="Delete User">
|
||||
<ActionIcon
|
||||
variant="light"
|
||||
size="sm"
|
||||
color="red"
|
||||
style={{ boxShadow: "0 0 8px rgba(0,255,200,0.2)" }}
|
||||
onClick={() => {
|
||||
setDataDelete(v.id);
|
||||
openDelete();
|
||||
}}
|
||||
>
|
||||
<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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
59
src/lib/groupPermission.ts
Normal file
59
src/lib/groupPermission.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import config from "@/lib/listPermission.json";
|
||||
|
||||
export interface PermissionNode {
|
||||
key: string;
|
||||
label: string;
|
||||
children?: PermissionNode[];
|
||||
}
|
||||
|
||||
interface Grouped {
|
||||
[key: string]: {
|
||||
label: string;
|
||||
children: Grouped;
|
||||
actions: string[];
|
||||
};
|
||||
}
|
||||
|
||||
/* --- Build lookup table --- */
|
||||
const permissionMap: Record<string, string[]> = {};
|
||||
|
||||
function walk(nodes: PermissionNode[], path: string[] = []) {
|
||||
nodes.forEach((n) => {
|
||||
const full = [...path, n.label];
|
||||
permissionMap[n.key] = full;
|
||||
if (n.children) walk(n.children, full);
|
||||
});
|
||||
}
|
||||
|
||||
walk(config.menus);
|
||||
|
||||
/* --- Convert keys → hierarchical grouped --- */
|
||||
export function groupPermissions(keys: string[]) {
|
||||
const tree: Grouped = {};
|
||||
|
||||
keys.forEach((key) => {
|
||||
const path = permissionMap[key];
|
||||
if (!path) return;
|
||||
|
||||
let pointer = tree;
|
||||
|
||||
path.forEach((label, idx) => {
|
||||
if (!pointer[label]) {
|
||||
pointer[label] = {
|
||||
label,
|
||||
children: {},
|
||||
actions: []
|
||||
};
|
||||
}
|
||||
|
||||
// last item = actual permission action
|
||||
if (idx === path.length - 1) {
|
||||
pointer[label].actions.push(label);
|
||||
}
|
||||
|
||||
pointer = pointer[label].children;
|
||||
});
|
||||
});
|
||||
|
||||
return tree;
|
||||
}
|
||||
178
src/lib/listPermission.json
Normal file
178
src/lib/listPermission.json
Normal file
@@ -0,0 +1,178 @@
|
||||
{
|
||||
"menus": [
|
||||
{
|
||||
"key": "dashboard",
|
||||
"label": "Dashboard",
|
||||
"default": true,
|
||||
"children": [
|
||||
{
|
||||
"key": "dashboard.view",
|
||||
"label": "Melihat Dashboard",
|
||||
"default": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "pengaduan",
|
||||
"label": "Pengaduan",
|
||||
"default": true,
|
||||
"children": [
|
||||
{
|
||||
"key": "pengaduan.view",
|
||||
"label": "Melihat List & Detail",
|
||||
"default": true
|
||||
},
|
||||
{
|
||||
"key": "pengaduan.antrian",
|
||||
"label": "Antrian",
|
||||
"default": true,
|
||||
"children": [
|
||||
{ "key": "pengaduan.antrian.tolak", "label": "Menolak", "default": true },
|
||||
{ "key": "pengaduan.antrian.terima", "label": "Menerima", "default": true }
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "pengaduan.diterima",
|
||||
"label": "Diterima",
|
||||
"default": true,
|
||||
"children": [
|
||||
{ "key": "pengaduan.diterima.dikerjakan", "label": "Dikerjakan", "default": true }
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "pengaduan.dikerjakan",
|
||||
"label": "Dikerjakan",
|
||||
"default": true,
|
||||
"children": [
|
||||
{ "key": "pengaduan.dikerjakan.selesai", "label": "Diselesaikan", "default": true }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "pelayanan",
|
||||
"label": "Pelayanan",
|
||||
"default": true,
|
||||
"children": [
|
||||
{
|
||||
"key": "pelayanan.view",
|
||||
"label": "Melihat List & Detail",
|
||||
"default": true
|
||||
},
|
||||
{
|
||||
"key": "pelayanan.antrian",
|
||||
"label": "Antrian",
|
||||
"default": true,
|
||||
"children": [
|
||||
{ "key": "pelayanan.antrian.tolak", "label": "Menolak", "default": true },
|
||||
{ "key": "pelayanan.antrian.terima", "label": "Menerima", "default": true }
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "pelayanan.diterima",
|
||||
"label": "Diterima",
|
||||
"default": true,
|
||||
"children": [
|
||||
{ "key": "pelayanan.diterima.tolak", "label": "Menolak", "default": true },
|
||||
{ "key": "pelayanan.diterima.setujui", "label": "Menyetujui", "default": true }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "warga",
|
||||
"label": "Warga",
|
||||
"default": true,
|
||||
"children": [
|
||||
{
|
||||
"key": "warga.view",
|
||||
"label": "Melihat List & Detail",
|
||||
"default": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "setting",
|
||||
"label": "Setting",
|
||||
"default": true,
|
||||
"children": [
|
||||
{
|
||||
"key": "setting.profile",
|
||||
"label": "Profile",
|
||||
"default": true,
|
||||
"children": [
|
||||
{ "key": "setting.profile.view", "label": "View", "default": true },
|
||||
{ "key": "setting.profile.edit", "label": "Edit", "default": true },
|
||||
{ "key": "setting.profile.password", "label": "Ubah Password", "default": true }
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "setting.user",
|
||||
"label": "User",
|
||||
"default": true,
|
||||
"children": [
|
||||
{ "key": "setting.user.view", "label": "View List", "default": true },
|
||||
{ "key": "setting.user.tambah", "label": "Tambah", "default": true },
|
||||
{ "key": "setting.user.edit", "label": "Edit", "default": true },
|
||||
{ "key": "setting.user.delete", "label": "Delete", "default": true }
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "setting.user_role",
|
||||
"label": "User Role",
|
||||
"default": true,
|
||||
"children": [
|
||||
{ "key": "setting.user_role.view", "label": "View List", "default": true },
|
||||
{ "key": "setting.user_role.tambah", "label": "Tambah", "default": true },
|
||||
{ "key": "setting.user_role.edit", "label": "Edit", "default": true },
|
||||
{ "key": "setting.user_role.delete", "label": "Delete", "default": true }
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "setting.kategori_pengaduan",
|
||||
"label": "Kategori Pengaduan",
|
||||
"default": true,
|
||||
"children": [
|
||||
{ "key": "setting.kategori_pengaduan.view", "label": "View List", "default": true },
|
||||
{ "key": "setting.kategori_pengaduan.tambah", "label": "Tambah", "default": true },
|
||||
{ "key": "setting.kategori_pengaduan.edit", "label": "Edit", "default": true },
|
||||
{ "key": "setting.kategori_pengaduan.delete", "label": "Delete", "default": true }
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "setting.kategori_pelayanan",
|
||||
"label": "Kategori Pelayanan Surat",
|
||||
"default": true,
|
||||
"children": [
|
||||
{ "key": "setting.kategori_pelayanan.view", "label": "View List", "default": true },
|
||||
{ "key": "setting.kategori_pelayanan.detail", "label": "View Detail", "default": true },
|
||||
{ "key": "setting.kategori_pelayanan.tambah", "label": "Tambah", "default": true },
|
||||
{ "key": "setting.kategori_pelayanan.edit", "label": "Edit", "default": true },
|
||||
{ "key": "setting.kategori_pelayanan.delete", "label": "Delete", "default": true }
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "setting.desa",
|
||||
"label": "Desa",
|
||||
"default": true,
|
||||
"children": [
|
||||
{ "key": "setting.desa.view", "label": "View List", "default": true },
|
||||
{ "key": "setting.desa.edit", "label": "Edit", "default": true }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "api_key",
|
||||
"label": "API Key",
|
||||
"default": true,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"key": "credential",
|
||||
"label": "Credential",
|
||||
"default": true,
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import DesaSetting from "@/components/DesaSetting";
|
||||
import KategoriPelayananSurat from "@/components/KategoriPelayananSurat";
|
||||
import KategoriPengaduan from "@/components/KategoriPengaduan";
|
||||
import ProfileUser from "@/components/ProfileUser";
|
||||
import UserRoleSetting from "@/components/UserRoleSetting";
|
||||
import UserSetting from "@/components/UserSetting";
|
||||
import {
|
||||
Card,
|
||||
@@ -14,7 +15,8 @@ import {
|
||||
IconCategory2,
|
||||
IconMailSpark,
|
||||
IconUserCog,
|
||||
IconUsersGroup,
|
||||
IconUserScreen,
|
||||
IconUsersGroup
|
||||
} from "@tabler/icons-react";
|
||||
import { useLocation } from "react-router-dom";
|
||||
|
||||
@@ -50,6 +52,12 @@ export default function DetailSettingPage() {
|
||||
leftSection={<IconUsersGroup size={16} stroke={1.5} />}
|
||||
active={type === "user"}
|
||||
/>
|
||||
<NavLink
|
||||
href={`?type=role`}
|
||||
label="Role"
|
||||
leftSection={<IconUserScreen size={16} stroke={1.5} />}
|
||||
active={type === "role"}
|
||||
/>
|
||||
<NavLink
|
||||
href={`?type=cat-pengaduan`}
|
||||
label="Kategori Pengaduan"
|
||||
@@ -90,6 +98,8 @@ export default function DetailSettingPage() {
|
||||
<DesaSetting />
|
||||
) : type === "user" ? (
|
||||
<UserSetting />
|
||||
) : type === "role" ? (
|
||||
<UserRoleSetting />
|
||||
) : (
|
||||
<ProfileUser />
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user