upd: dashboard admin
Deskripsi: - login input - login redirect sesuai dg akses - tampilan jika tidak ada data ttd pada setting desa - disable button pada list kategori pengaduan dg value id == lainnya - disable button aksi pada list role dg value id == developer - tidak menampilkan list data menu akses pada modal tambah dan edi role - tampilan list permission pada table role - order data permission yg telah terpilih sesuai dengan data json menu NO Issues
This commit is contained in:
@@ -206,9 +206,12 @@ export default function DesaSetting({ permissions }: { permissions: JsonValue[]
|
||||
{
|
||||
v.name == "TTD"
|
||||
?
|
||||
<Anchor href="#" onClick={() => { setViewImg(v.value); setOpenedPreview(true); }} underline="always">
|
||||
Lihat
|
||||
</Anchor>
|
||||
v.value ?
|
||||
<Anchor href="#" onClick={() => { setViewImg(v.value); setOpenedPreview(true); }} underline="always">
|
||||
Lihat
|
||||
</Anchor>
|
||||
:
|
||||
"-"
|
||||
:
|
||||
v.value
|
||||
}
|
||||
|
||||
@@ -329,7 +329,7 @@ export default function KategoriPengaduan({ permissions }: { permissions: JsonVa
|
||||
size="sm"
|
||||
style={{ boxShadow: "0 0 8px rgba(0,255,200,0.2)" }}
|
||||
onClick={() => chooseEdit({ data: v })}
|
||||
disabled={!permissions.includes("setting.kategori_pengaduan.edit")}
|
||||
disabled={!permissions.includes("setting.kategori_pengaduan.edit") || v.id == "lainnya"}
|
||||
>
|
||||
<IconEdit size={20} />
|
||||
</ActionIcon>
|
||||
@@ -344,7 +344,7 @@ export default function KategoriPengaduan({ permissions }: { permissions: JsonVa
|
||||
setDataDelete(v.id);
|
||||
openDelete();
|
||||
}}
|
||||
disabled={!permissions.includes("setting.kategori_pengaduan.delete")}
|
||||
disabled={!permissions.includes("setting.kategori_pengaduan.delete") || v.id == "lainnya"}
|
||||
>
|
||||
<IconTrash size={20} />
|
||||
</ActionIcon>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { groupPermissions } from "@/lib/groupPermission";
|
||||
import { Button, Stack, Text } from "@mantine/core";
|
||||
import { Anchor, Flex, Stack, Text } from "@mantine/core";
|
||||
import { useState } from "react";
|
||||
|
||||
interface Node {
|
||||
@@ -14,7 +14,7 @@ function RenderNode({ node }: { node: Node }) {
|
||||
return (
|
||||
<Stack pl="md" gap={6}>
|
||||
{/* Title */}
|
||||
<Text fw={600}>- {node.label}</Text>
|
||||
<Text size="sm">- {node.label}</Text>
|
||||
|
||||
{/* Children */}
|
||||
{sub.map((child: any, i) => (
|
||||
@@ -24,6 +24,22 @@ function RenderNode({ node }: { node: Node }) {
|
||||
);
|
||||
}
|
||||
|
||||
function RenderNode2({ node }: { node: Node }) {
|
||||
const sub = Object.values(node.children || {});
|
||||
|
||||
return (
|
||||
<Flex direction={"row"} wrap={'wrap'} gap={6}>
|
||||
{/* Title */}
|
||||
<Text size="sm">{node.label},</Text>
|
||||
|
||||
{/* Children */}
|
||||
{sub.map((child: any, i) => (
|
||||
<RenderNode2 key={i} node={child} />
|
||||
))}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
export default function PermissionRole({ permissions }: { permissions: string[] }) {
|
||||
const [showAll, setShowAll] = useState(false);
|
||||
if (!permissions?.length) return <Text c="dimmed">-</Text>;
|
||||
@@ -32,7 +48,7 @@ export default function PermissionRole({ permissions }: { permissions: string[]
|
||||
const rootNodes = Object.values(groups);
|
||||
|
||||
return (
|
||||
<Stack gap="lg">
|
||||
<Stack gap="sm">
|
||||
{
|
||||
showAll ?
|
||||
rootNodes.map((node: any, idx) => (
|
||||
@@ -40,18 +56,12 @@ export default function PermissionRole({ permissions }: { permissions: string[]
|
||||
))
|
||||
:
|
||||
rootNodes.slice(0, 2).map((node: any, idx) => (
|
||||
<RenderNode key={idx} node={node} />
|
||||
<RenderNode2 key={idx} node={node} />
|
||||
))
|
||||
}
|
||||
<Button
|
||||
variant="subtle"
|
||||
size="xs"
|
||||
onClick={() => setShowAll(!showAll)}
|
||||
w="fit-content"
|
||||
ml="md"
|
||||
>
|
||||
<Anchor size="xs" onClick={() => setShowAll(!showAll)} >
|
||||
{showAll ? "View less" : "View more"}
|
||||
</Button>
|
||||
</Anchor>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -169,7 +169,7 @@ export default function PermissionTree({
|
||||
return (
|
||||
<Stack>
|
||||
<Text size="sm">Hak Akses</Text>
|
||||
{permissionConfig.menus.map((menu: Node) => (
|
||||
{permissionConfig.menus.filter((menu: Node) => !menu.key.startsWith("api") && !menu.key.startsWith("credential")).map((menu: Node) => (
|
||||
<RenderMenu key={menu.key} menu={menu} />
|
||||
))}
|
||||
</Stack>
|
||||
|
||||
@@ -18,10 +18,18 @@ 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);
|
||||
@@ -179,6 +187,27 @@ export default function UserRoleSetting({ permissions }: { permissions: JsonValu
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -212,7 +241,7 @@ export default function UserRoleSetting({ permissions }: { permissions: JsonValu
|
||||
<PermissionTree
|
||||
selected={dataEdit.permissions}
|
||||
onChange={(permissions) => {
|
||||
setDataEdit({ ...dataEdit, permissions: permissions as never[] });
|
||||
setDataEdit({ ...dataEdit, permissions: sortByJsonOrder(permissions) as never[] });
|
||||
}}
|
||||
/>
|
||||
<Group justify="center" grow>
|
||||
@@ -263,7 +292,7 @@ export default function UserRoleSetting({ permissions }: { permissions: JsonValu
|
||||
<PermissionTree
|
||||
selected={dataTambah.permissions}
|
||||
onChange={(permissions) => {
|
||||
setDataTambah({ ...dataTambah, permissions: permissions as never[] });
|
||||
setDataTambah({ ...dataTambah, permissions: sortByJsonOrder(permissions) as never[] });
|
||||
}}
|
||||
/>
|
||||
<Group justify="center" grow>
|
||||
@@ -346,11 +375,11 @@ export default function UserRoleSetting({ permissions }: { permissions: JsonValu
|
||||
{list.length > 0 ? (
|
||||
list?.map((v: any) => (
|
||||
<Table.Tr key={v.id}>
|
||||
<Table.Td>{v.name}</Table.Td>
|
||||
<Table.Td w={"150"}>{v.name}</Table.Td>
|
||||
<Table.Td>
|
||||
<PermissionRole permissions={v.permissions} />
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<Table.Td w={"100"}>
|
||||
<Group>
|
||||
<Tooltip label={permissions.includes('setting.user_role.edit') ? "Edit Role" : "Edit Role - Anda tidak memiliki akses"}>
|
||||
<ActionIcon
|
||||
@@ -358,7 +387,7 @@ export default function UserRoleSetting({ permissions }: { permissions: JsonValu
|
||||
size="sm"
|
||||
style={{ boxShadow: "0 0 8px rgba(0,255,200,0.2)" }}
|
||||
onClick={() => chooseEdit({ data: v })}
|
||||
disabled={!permissions.includes('setting.user_role.edit')}
|
||||
disabled={!permissions.includes('setting.user_role.edit') || v.id == "developer"}
|
||||
>
|
||||
<IconEdit size={20} />
|
||||
</ActionIcon>
|
||||
@@ -373,7 +402,7 @@ export default function UserRoleSetting({ permissions }: { permissions: JsonValu
|
||||
setDataDelete(v.id);
|
||||
openDelete();
|
||||
}}
|
||||
disabled={!permissions.includes('setting.user_role.delete')}
|
||||
disabled={!permissions.includes('setting.user_role.delete') || v.id == "developer"}
|
||||
>
|
||||
<IconTrash size={20} />
|
||||
</ActionIcon>
|
||||
|
||||
@@ -107,19 +107,19 @@ export default function UserSetting({ permissions }: { permissions: JsonValue[]
|
||||
async function handleEdit() {
|
||||
try {
|
||||
setBtnLoading(true);
|
||||
const res = await apiFetch.api.pengaduan.category.update.post(dataEdit);
|
||||
const res = await apiFetch.api.user.update.post(dataEdit);
|
||||
if (res.status === 200) {
|
||||
mutate();
|
||||
close();
|
||||
notification({
|
||||
title: "Success",
|
||||
message: "Your category have been saved",
|
||||
message: "Your data have been saved",
|
||||
type: "success",
|
||||
});
|
||||
} else {
|
||||
notification({
|
||||
title: "Error",
|
||||
message: "Failed to edit category",
|
||||
message: "Failed to edit user",
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
@@ -127,7 +127,7 @@ export default function UserSetting({ permissions }: { permissions: JsonValue[]
|
||||
console.error(error);
|
||||
notification({
|
||||
title: "Error",
|
||||
message: "Failed to edit category",
|
||||
message: "Failed to edit user2",
|
||||
type: "error",
|
||||
});
|
||||
} finally {
|
||||
@@ -222,9 +222,10 @@ export default function UserSetting({ permissions }: { permissions: JsonValue[]
|
||||
overlayProps={{ backgroundOpacity: 0.55, blur: 3 }}
|
||||
>
|
||||
<Stack gap="ld">
|
||||
<Input.Wrapper label="Edit Kategori">
|
||||
<Input.Wrapper label="Nama">
|
||||
<Input
|
||||
value={dataEdit.name}
|
||||
error={error.name ? "Field is required" : ""}
|
||||
onChange={(e) =>
|
||||
onValidation({
|
||||
kat: "name",
|
||||
@@ -234,6 +235,51 @@ export default function UserSetting({ permissions }: { permissions: JsonValue[]
|
||||
}
|
||||
/>
|
||||
</Input.Wrapper>
|
||||
<Select
|
||||
label="Role"
|
||||
placeholder="Pilih Role"
|
||||
data={listRole.map((r: any) => ({
|
||||
value: r.id,
|
||||
label: r.name,
|
||||
}))}
|
||||
value={dataEdit.roleId || null}
|
||||
error={error.roleId ? "Field is required" : ""}
|
||||
onChange={(_value, option) => {
|
||||
onValidation({
|
||||
kat: "roleId",
|
||||
value: option?.value,
|
||||
aksi: "edit",
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Input.Wrapper label="Phone" description="">
|
||||
<Input
|
||||
value={dataEdit.phone}
|
||||
onChange={(e) =>
|
||||
onValidation({
|
||||
kat: "phone",
|
||||
value: e.target.value,
|
||||
aksi: "edit",
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Input.Wrapper>
|
||||
<Input.Wrapper
|
||||
label="Email"
|
||||
description=""
|
||||
error={error.email ? "Field is required" : ""}
|
||||
>
|
||||
<Input
|
||||
value={dataEdit.email}
|
||||
onChange={(e) =>
|
||||
onValidation({
|
||||
kat: "email",
|
||||
value: e.target.value,
|
||||
aksi: "edit",
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Input.Wrapper>
|
||||
<Group justify="center" grow>
|
||||
<Button variant="light" onClick={close}>
|
||||
Batal
|
||||
@@ -434,7 +480,7 @@ export default function UserSetting({ permissions }: { permissions: JsonValue[]
|
||||
size="sm"
|
||||
style={{ boxShadow: "0 0 8px rgba(0,255,200,0.2)" }}
|
||||
onClick={() => chooseEdit({ data: v })}
|
||||
disabled={!permissions.includes('setting.user.edit')}
|
||||
disabled={!permissions.includes('setting.user.edit') || v.roleId == "developer"}
|
||||
>
|
||||
<IconEdit size={20} />
|
||||
</ActionIcon>
|
||||
@@ -449,7 +495,7 @@ export default function UserSetting({ permissions }: { permissions: JsonValue[]
|
||||
setDataDelete(v.id);
|
||||
openDelete();
|
||||
}}
|
||||
disabled={!permissions.includes('setting.user.delete')}
|
||||
disabled={!permissions.includes('setting.user.delete') || v.roleId == "developer"}
|
||||
>
|
||||
<IconTrash size={20} />
|
||||
</ActionIcon>
|
||||
|
||||
@@ -1,20 +1,50 @@
|
||||
import clientRoutes from "@/clientRoutes";
|
||||
import {
|
||||
Button,
|
||||
Container,
|
||||
Group,
|
||||
PasswordInput,
|
||||
Stack,
|
||||
Text,
|
||||
TextInput,
|
||||
} from "@mantine/core";
|
||||
import { useState } from "react";
|
||||
import apiFetch from "../lib/apiFetch";
|
||||
import clientRoutes from "@/clientRoutes";
|
||||
|
||||
export default function Login() {
|
||||
const [email, setEmail] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
function navigateToRoute(akses: string) {
|
||||
switch (akses) {
|
||||
case "dashboard":
|
||||
window.location.href = clientRoutes["/scr/dashboard/dashboard-home"];
|
||||
break;
|
||||
case "pengaduan":
|
||||
window.location.href = clientRoutes["/scr/dashboard/pengaduan/list"];
|
||||
break;
|
||||
case "warga":
|
||||
window.location.href = clientRoutes["/scr/dashboard/warga/list-warga"];
|
||||
break;
|
||||
case "credential":
|
||||
window.location.href = clientRoutes["/scr/dashboard/credential/credential"];
|
||||
break;
|
||||
case "setting":
|
||||
window.location.href = clientRoutes["/scr/dashboard/setting/detail-setting"];
|
||||
break;
|
||||
case "api_key":
|
||||
window.location.href = clientRoutes["/scr/dashboard/apikey/apikey"];
|
||||
break;
|
||||
case "pelayanan":
|
||||
window.location.href = clientRoutes["/scr/dashboard/pelayanan-surat/list-pelayanan"];
|
||||
break;
|
||||
default:
|
||||
window.location.href = clientRoutes["/scr/dashboard"];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const handleSubmit = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
@@ -25,7 +55,7 @@ export default function Login() {
|
||||
|
||||
if (response.data?.token) {
|
||||
localStorage.setItem("token", response.data.token);
|
||||
window.location.href = clientRoutes["/scr/dashboard"];
|
||||
navigateToRoute(response.data.akses || "dashboard");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -48,7 +78,7 @@ export default function Login() {
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
/>
|
||||
<TextInput
|
||||
<PasswordInput
|
||||
placeholder="Password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { prisma } from '@/server/lib/prisma'
|
||||
import { jwt as jwtPlugin, type JWTPayloadSpec } from '@elysiajs/jwt'
|
||||
import Elysia, { t, type Cookie, type HTTPHeaders, type StatusMap } from 'elysia'
|
||||
import { type ElysiaCookie } from 'elysia/cookies'
|
||||
import { prisma } from '@/server/lib/prisma'
|
||||
|
||||
const secret = process.env.JWT_SECRET
|
||||
if (!secret) {
|
||||
@@ -75,6 +75,15 @@ async function login({
|
||||
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { email },
|
||||
select: {
|
||||
id: true,
|
||||
password: true,
|
||||
Role: {
|
||||
select: {
|
||||
permissions: true
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (!user) {
|
||||
@@ -87,6 +96,12 @@ async function login({
|
||||
return { message: 'Invalid password' }
|
||||
}
|
||||
|
||||
const rawPermissions = user.Role?.permissions;
|
||||
|
||||
const akses = Array.isArray(rawPermissions)
|
||||
? rawPermissions[0]?.toString()
|
||||
: undefined;
|
||||
|
||||
const token = await issueToken({
|
||||
jwt,
|
||||
cookie,
|
||||
@@ -94,7 +109,7 @@ async function login({
|
||||
role: 'user',
|
||||
expiresAt: Math.floor(Date.now() / 1000) + NINETY_YEARS,
|
||||
})
|
||||
return { token }
|
||||
return { token, akses }
|
||||
} catch (error) {
|
||||
console.error('Error logging in:', error)
|
||||
return {
|
||||
@@ -146,7 +161,7 @@ const Auth = new Elysia({
|
||||
detail: {
|
||||
summary: 'logout',
|
||||
description: 'Logout (clear token cookie)',
|
||||
|
||||
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user