From 4c047324bc3b4e769cebead2d8e2ab3fa2c784cd Mon Sep 17 00:00:00 2001 From: amaliadwiy Date: Mon, 24 Nov 2025 10:56:28 +0800 Subject: [PATCH 1/5] upd: dashboard admin Deskripsi: - view file seafile No Issuesg --- src/components/ModalFile.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ModalFile.tsx b/src/components/ModalFile.tsx index 71766c8..f77759f 100644 --- a/src/components/ModalFile.tsx +++ b/src/components/ModalFile.tsx @@ -64,7 +64,7 @@ export default function ModalFile({ open, onClose, folder, fileName }: { open: b {viewFile && ( <> {typeFile == "pdf" ? ( - + ) : ( Date: Mon, 24 Nov 2025 11:15:14 +0800 Subject: [PATCH 2/5] fix: dashboard admin Deskripsi - list warga - list pelayanan No Issues --- .../scr/dashboard/pelayanan-surat/list_pelayanan_page.tsx | 2 +- src/pages/scr/dashboard/warga/list_warga_page.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/scr/dashboard/pelayanan-surat/list_pelayanan_page.tsx b/src/pages/scr/dashboard/pelayanan-surat/list_pelayanan_page.tsx index 4128259..0d6a5cd 100644 --- a/src/pages/scr/dashboard/pelayanan-surat/list_pelayanan_page.tsx +++ b/src/pages/scr/dashboard/pelayanan-surat/list_pelayanan_page.tsx @@ -175,7 +175,7 @@ function ListPelayananSurat({ status }: { status: StatusKey }) { } /> - {list?.length === 0 ? ( + {Array.isArray(list) && list?.length === 0 ? ( diff --git a/src/pages/scr/dashboard/warga/list_warga_page.tsx b/src/pages/scr/dashboard/warga/list_warga_page.tsx index 149d10a..a19d578 100644 --- a/src/pages/scr/dashboard/warga/list_warga_page.tsx +++ b/src/pages/scr/dashboard/warga/list_warga_page.tsx @@ -78,12 +78,12 @@ export default function ListWargaPage() { { - list?.length === 0 ? ( + Array.isArray(list) && list?.length === 0 ? ( Tidak ada data ) : ( - list?.map((item, i) => ( + Array.isArray(list) && list?.map((item, i) => ( {item.name} {item.phone} From 0a3afb7b9c4138057d0c5a4cd31da8e12c31250a Mon Sep 17 00:00:00 2001 From: amaliadwiy Date: Mon, 24 Nov 2025 14:27:19 +0800 Subject: [PATCH 3/5] upd: dashboard admin Deskripsi: - databse - seeder - list user role NO Issues --- prisma/schema.prisma | 11 +- prisma/seed.ts | 32 +- src/components/PermissionRole.tsx | 57 +++ src/components/UserRoleSetting.tsx | 465 ++++++++++++++++++ src/lib/groupPermission.ts | 59 +++ src/lib/listPermission.json | 178 +++++++ .../dashboard/setting/detail_setting_page.tsx | 12 +- 7 files changed, 798 insertions(+), 16 deletions(-) create mode 100644 src/components/PermissionRole.tsx create mode 100644 src/components/UserRoleSetting.tsx create mode 100644 src/lib/groupPermission.ts create mode 100644 src/lib/listPermission.json diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 1e7f556..ee65913 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -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 { diff --git a/prisma/seed.ts b/prisma/seed.ts index 56b5941..a20e41f 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -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`) diff --git a/src/components/PermissionRole.tsx b/src/components/PermissionRole.tsx new file mode 100644 index 0000000..7218355 --- /dev/null +++ b/src/components/PermissionRole.tsx @@ -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 ( + + {/* Title */} + - {node.label} + + {/* Children */} + {sub.map((child: any, i) => ( + + ))} + + ); +} + +export default function PermissionRole({ permissions }: { permissions: string[] }) { + const [showAll, setShowAll] = useState(false); + if (!permissions?.length) return -; + + const groups = groupPermissions(permissions); + const rootNodes = Object.values(groups); + + return ( + + { + showAll ? + rootNodes.map((node: any, idx) => ( + + )) + : + rootNodes.slice(0, 2).map((node: any, idx) => ( + + )) + } + + + ); +} diff --git a/src/components/UserRoleSetting.tsx b/src/components/UserRoleSetting.tsx new file mode 100644 index 0000000..1cafc1e --- /dev/null +++ b/src/components/UserRoleSetting.tsx @@ -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 */} + + + + + onValidation({ + kat: "name", + value: e.target.value, + aksi: "edit", + }) + } + /> + + + + + + + + + {/* Modal Tambah */} + + + + + onValidation({ + kat: "name", + value: e.target.value, + aksi: "tambah", + }) + } + /> + + + onValidation({ + kat: "phone", + value: e.target.value, + aksi: "tambah", + }) + } + /> + + + + onValidation({ + kat: "email", + value: e.target.value, + aksi: "tambah", + }) + } + /> + + + + onValidation({ + kat: "password", + value: e.target.value, + aksi: "tambah", + }) + } + /> + + + + + + + + + + {/* Modal Delete */} + + + + Apakah anda yakin ingin menghapus user ini? + + + + + + + + + + + + Daftar Role + + + + + + + + + + + Role + Permission + Aksi + + + + {list.length > 0 ? ( + list?.map((v: any) => ( + + {v.name} + + + + + + + chooseEdit({ data: v })} + > + + + + + { + setDataDelete(v.id); + openDelete(); + }} + > + + + + + + + )) + ) : ( + + + Data Role Tidak Ditemukan + + + )} + +
+
+
+ + ); +} diff --git a/src/lib/groupPermission.ts b/src/lib/groupPermission.ts new file mode 100644 index 0000000..f9500f7 --- /dev/null +++ b/src/lib/groupPermission.ts @@ -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 = {}; + +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; +} diff --git a/src/lib/listPermission.json b/src/lib/listPermission.json new file mode 100644 index 0000000..b746a2e --- /dev/null +++ b/src/lib/listPermission.json @@ -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": [] + } + ] +} diff --git a/src/pages/scr/dashboard/setting/detail_setting_page.tsx b/src/pages/scr/dashboard/setting/detail_setting_page.tsx index b7ac9e1..d618371 100644 --- a/src/pages/scr/dashboard/setting/detail_setting_page.tsx +++ b/src/pages/scr/dashboard/setting/detail_setting_page.tsx @@ -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={} active={type === "user"} /> + } + active={type === "role"} + /> ) : type === "user" ? ( + ) : type === "role" ? ( + ) : ( )} From 10db3f922e07b22846bc4e61400e730fa505c69b Mon Sep 17 00:00:00 2001 From: amaliadwiy Date: Mon, 24 Nov 2025 16:27:35 +0800 Subject: [PATCH 4/5] up: dashboard admin Deskripsi: - akses role pada menu dashboard - akses role pada setting - akses role pada pelayanan surat - akses role pada pengaduan warga - akses role pada warga NO Issues --- src/components/DesaSetting.tsx | 6 +- src/components/KategoriPelayananSurat.tsx | 32 +-- src/components/KategoriPengaduan.tsx | 31 +-- src/components/ProfileUser.tsx | 24 ++- src/components/UserRoleSetting.tsx | 31 +-- src/components/UserSetting.tsx | 32 +-- src/lib/listPermission.json | 198 +++++++++++++++--- src/pages/scr/dashboard/dashboard_layout.tsx | 23 +- .../pelayanan-surat/detail_pelayanan_page.tsx | 11 + .../scr/dashboard/pengaduan/detail_page.tsx | 11 + .../dashboard/setting/detail_setting_page.tsx | 123 +++++++---- src/server/routes/user_route.ts | 10 +- 12 files changed, 397 insertions(+), 135 deletions(-) diff --git a/src/components/DesaSetting.tsx b/src/components/DesaSetting.tsx index 9bb41ce..938cd96 100644 --- a/src/components/DesaSetting.tsx +++ b/src/components/DesaSetting.tsx @@ -16,13 +16,14 @@ import { } from "@mantine/core"; import { useDisclosure, useShallowEffect } from "@mantine/hooks"; import { IconEdit } from "@tabler/icons-react"; +import type { JsonValue } from "generated/prisma/runtime/library"; import _ from "lodash"; import { useState } from "react"; import useSWR from "swr"; import ModalFile from "./ModalFile"; import notification from "./notificationGlobal"; -export default function DesaSetting() { +export default function DesaSetting({ permissions }: { permissions: JsonValue[] }) { const [btnDisable, setBtnDisable] = useState(false); const [btnLoading, setBtnLoading] = useState(false); const [opened, { open, close }] = useDisclosure(false); @@ -213,12 +214,13 @@ export default function DesaSetting() { } - + chooseEdit({ data: v })} + disabled={!permissions.includes("setting.desa.edit")} > diff --git a/src/components/KategoriPelayananSurat.tsx b/src/components/KategoriPelayananSurat.tsx index 1982bf8..0121ea2 100644 --- a/src/components/KategoriPelayananSurat.tsx +++ b/src/components/KategoriPelayananSurat.tsx @@ -18,11 +18,12 @@ import { } from "@mantine/core"; import { useDisclosure, useShallowEffect } from "@mantine/hooks"; import { IconEdit, IconEye, IconPlus, IconTrash } from "@tabler/icons-react"; +import type { JsonValue } from "generated/prisma/runtime/library"; import { useState } from "react"; import useSWR from "swr"; import notification from "./notificationGlobal"; -export default function KategoriPelayananSurat() { +export default function KategoriPelayananSurat({ permissions }: { permissions: JsonValue[] }) { const [openedDelete, { open: openDelete, close: closeDelete }] = useDisclosure(false); const [openedDetail, { open: openDetail, close: closeDetail }] = @@ -52,6 +53,7 @@ export default function KategoriPelayananSurat() { mutate(); }, []); + async function handleCreate() { try { setBtnLoading(true); @@ -533,15 +535,19 @@ export default function KategoriPelayananSurat() { Kategori Pelayanan Surat - - - + { + permissions.includes("setting.kategori_pelayanan.tambah") && ( + + + + ) + }
@@ -572,7 +578,7 @@ export default function KategoriPelayananSurat() { - + - + diff --git a/src/components/KategoriPengaduan.tsx b/src/components/KategoriPengaduan.tsx index fd2ea73..0f85343 100644 --- a/src/components/KategoriPengaduan.tsx +++ b/src/components/KategoriPengaduan.tsx @@ -15,11 +15,12 @@ import { } 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 notification from "./notificationGlobal"; -export default function KategoriPengaduan() { +export default function KategoriPengaduan({ permissions }: { permissions: JsonValue[] }) { const [openedDelete, { open: openDelete, close: closeDelete }] = useDisclosure(false); const [btnDisable, setBtnDisable] = useState(true); @@ -293,15 +294,19 @@ export default function KategoriPengaduan() { Kategori Pengaduan - - - + { + permissions.includes("setting.kategori_pengaduan.tambah") && ( + + + + ) + } @@ -318,17 +323,18 @@ export default function KategoriPengaduan() { {v.name} - + chooseEdit({ data: v })} + disabled={!permissions.includes("setting.kategori_pengaduan.edit")} > - + diff --git a/src/components/ProfileUser.tsx b/src/components/ProfileUser.tsx index d622062..9f0250f 100644 --- a/src/components/ProfileUser.tsx +++ b/src/components/ProfileUser.tsx @@ -9,10 +9,11 @@ import { Stack, Title, } from "@mantine/core"; +import type { JsonValue } from "generated/prisma/runtime/library"; import { useEffect, useState } from "react"; import notification from "./notificationGlobal"; -export default function ProfileUser() { +export default function ProfileUser({ permissions }: { permissions: JsonValue[] }) { const [opened, setOpened] = useState(false); const [openedPassword, setOpenedPassword] = useState(false); const [pwdBaru, setPwdBaru] = useState(""); @@ -126,12 +127,21 @@ export default function ProfileUser() { Profile Pengguna - - + { + permissions.includes("setting.profile.edit") && ( + + ) + } + + { + permissions.includes("setting.profile.password") && ( + + ) + } diff --git a/src/components/UserRoleSetting.tsx b/src/components/UserRoleSetting.tsx index 1cafc1e..8c9624b 100644 --- a/src/components/UserRoleSetting.tsx +++ b/src/components/UserRoleSetting.tsx @@ -16,12 +16,13 @@ import { } 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 notification from "./notificationGlobal"; import PermissionRole from "./PermissionRole"; -export default function UserRoleSetting() { +export default function UserRoleSetting({ permissions }: { permissions: JsonValue[] }) { const [btnDisable, setBtnDisable] = useState(true); const [btnLoading, setBtnLoading] = useState(false); const [opened, { open, close }] = useDisclosure(false); @@ -391,15 +392,19 @@ export default function UserRoleSetting() { Daftar Role - - - + { + permissions.includes('setting.user_role.tambah') && ( + + + + ) + } @@ -421,17 +426,18 @@ export default function UserRoleSetting() { - + chooseEdit({ data: v })} + disabled={!permissions.includes('setting.user_role.edit')} > - + diff --git a/src/components/UserSetting.tsx b/src/components/UserSetting.tsx index 1861a15..65e7acc 100644 --- a/src/components/UserSetting.tsx +++ b/src/components/UserSetting.tsx @@ -16,11 +16,12 @@ import { } 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 notification from "./notificationGlobal"; -export default function UserSetting() { +export default function UserSetting({ permissions }: { permissions: JsonValue[] }) { const [btnDisable, setBtnDisable] = useState(true); const [btnLoading, setBtnLoading] = useState(false); const [opened, { open, close }] = useDisclosure(false); @@ -390,15 +391,20 @@ export default function UserSetting() { Daftar User - - - + { + permissions.includes('setting.user.tambah') && ( + + + + ) + } + @@ -422,17 +428,18 @@ export default function UserSetting() { {v.roleId} - + chooseEdit({ data: v })} + disabled={!permissions.includes('setting.user.edit')} > - + diff --git a/src/lib/listPermission.json b/src/lib/listPermission.json index b746a2e..148ed4d 100644 --- a/src/lib/listPermission.json +++ b/src/lib/listPermission.json @@ -27,8 +27,16 @@ "label": "Antrian", "default": true, "children": [ - { "key": "pengaduan.antrian.tolak", "label": "Menolak", "default": true }, - { "key": "pengaduan.antrian.terima", "label": "Menerima", "default": true } + { + "key": "pengaduan.antrian.tolak", + "label": "Menolak", + "default": true + }, + { + "key": "pengaduan.antrian.terima", + "label": "Menerima", + "default": true + } ] }, { @@ -36,7 +44,11 @@ "label": "Diterima", "default": true, "children": [ - { "key": "pengaduan.diterima.dikerjakan", "label": "Dikerjakan", "default": true } + { + "key": "pengaduan.diterima.dikerjakan", + "label": "Dikerjakan", + "default": true + } ] }, { @@ -44,7 +56,11 @@ "label": "Dikerjakan", "default": true, "children": [ - { "key": "pengaduan.dikerjakan.selesai", "label": "Diselesaikan", "default": true } + { + "key": "pengaduan.dikerjakan.selesai", + "label": "Diselesaikan", + "default": true + } ] } ] @@ -64,8 +80,16 @@ "label": "Antrian", "default": true, "children": [ - { "key": "pelayanan.antrian.tolak", "label": "Menolak", "default": true }, - { "key": "pelayanan.antrian.terima", "label": "Menerima", "default": true } + { + "key": "pelayanan.antrian.tolak", + "label": "Menolak", + "default": true + }, + { + "key": "pelayanan.antrian.terima", + "label": "Menerima", + "default": true + } ] }, { @@ -73,8 +97,16 @@ "label": "Diterima", "default": true, "children": [ - { "key": "pelayanan.diterima.tolak", "label": "Menolak", "default": true }, - { "key": "pelayanan.diterima.setujui", "label": "Menyetujui", "default": true } + { + "key": "pelayanan.diterima.tolak", + "label": "Menolak", + "default": true + }, + { + "key": "pelayanan.diterima.setujui", + "label": "Menyetujui", + "default": true + } ] } ] @@ -101,9 +133,21 @@ "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.profile.view", + "label": "View", + "default": true + }, + { + "key": "setting.profile.edit", + "label": "Edit", + "default": true + }, + { + "key": "setting.profile.password", + "label": "Ubah Password", + "default": true + } ] }, { @@ -111,10 +155,26 @@ "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.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 + } ] }, { @@ -122,10 +182,26 @@ "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.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 + } ] }, { @@ -133,10 +209,26 @@ "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_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 + } ] }, { @@ -144,11 +236,31 @@ "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.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 + } ] }, { @@ -156,8 +268,16 @@ "label": "Desa", "default": true, "children": [ - { "key": "setting.desa.view", "label": "View List", "default": true }, - { "key": "setting.desa.edit", "label": "Edit", "default": true } + { + "key": "setting.desa.view", + "label": "View List", + "default": true + }, + { + "key": "setting.desa.edit", + "label": "Edit", + "default": true + } ] } ] @@ -166,13 +286,25 @@ "key": "api_key", "label": "API Key", "default": true, - "children": [] + "children": [ + { + "key": "api_key.view", + "label": "View List", + "default": true + } + ] }, { "key": "credential", "label": "Credential", "default": true, - "children": [] + "children": [ + { + "key": "credential.viewØ", + "label": "View List", + "default": true + } + ] } ] -} +} \ No newline at end of file diff --git a/src/pages/scr/dashboard/dashboard_layout.tsx b/src/pages/scr/dashboard/dashboard_layout.tsx index 2fc3115..ca3f687 100644 --- a/src/pages/scr/dashboard/dashboard_layout.tsx +++ b/src/pages/scr/dashboard/dashboard_layout.tsx @@ -35,6 +35,7 @@ import { IconUsersGroup, } from "@tabler/icons-react"; import type { User } from "generated/prisma"; +import type { JsonValue } from "generated/prisma/runtime/library"; import { useEffect, useState } from "react"; import { Outlet, useLocation, useNavigate } from "react-router-dom"; @@ -212,36 +213,54 @@ function HostView() { function NavigationDashboard() { const navigate = useNavigate(); const location = useLocation(); + const [permissions, setPermissions] = useState([]); + + useEffect(() => { + async function fetchPermissions() { + const { data } = await apiFetch.api.user.find.get(); + if (Array.isArray(data?.permissions)) { + setPermissions(data.permissions); + } else { + setPermissions([]); + } + } + fetchPermissions(); + }, []); const isActive = (path: keyof typeof clientRoute) => location.pathname.startsWith(clientRoute[path]); const navItems = [ { + key: "dashboard", path: "/scr/dashboard/dashboard-home", icon: , label: "Dashboard Overview", description: "Quick summary and insights", }, { + key: "pengaduan", path: "/scr/dashboard/pengaduan/list", icon: , label: "Pengaduan Warga", description: "Manage pengaduan warga", }, { + key: "pelayanan", path: "/scr/dashboard/pelayanan-surat/list-pelayanan", icon: , label: "Pelayanan Surat", description: "Manage pelayanan surat", }, { + key: "warga", path: "/scr/dashboard/warga/list-warga", icon: , label: "Warga", description: "Manage warga", }, { + key: "setting", path: "/scr/dashboard/setting/detail-setting", icon: , label: "Setting", @@ -249,12 +268,14 @@ function NavigationDashboard() { "Manage setting (category pengaduan dan pelayanan surat, desa, etc)", }, { + key: "api_key", path: "/scr/dashboard/apikey/apikey", icon: , label: "API Key Manager", description: "Create and manage API keys", }, { + key: "credential", path: "/scr/dashboard/credential/credential", icon: , label: "Credentials", @@ -264,7 +285,7 @@ function NavigationDashboard() { return ( - {navItems.map((item) => ( + {navItems.filter((item) => permissions.includes(item.key)).map((item) => ( (null); const [noSurat, setNoSurat] = useState(""); const [openedPreview, setOpenedPreview] = useState(false); + const [permissions, setPermissions] = useState([]); useEffect(() => { async function fetchHost() { const { data } = await apiFetch.api.user.find.get(); setHost(data?.user ?? null); + + if (data?.permissions && Array.isArray(data.permissions)) { + const onlySetting = data.permissions.filter((p: any) => p.startsWith("pelayanan")); + setPermissions(onlySetting); + } } fetchHost(); }, []); @@ -276,6 +283,7 @@ function DetailDataPengajuan({ data, syaratDokumen, dataText, onAction }: { data data?.status === "antrian" ? (