upd: dashboard admin
Deskripsi: - list user - tambah usr - delete user - beserta api NO Issues
This commit is contained in:
365
src/components/UserSetting.tsx
Normal file
365
src/components/UserSetting.tsx
Normal file
@@ -0,0 +1,365 @@
|
|||||||
|
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";
|
||||||
|
|
||||||
|
export default function UserSetting() {
|
||||||
|
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("user-list", () =>
|
||||||
|
apiFetch.api.user.list.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 User
|
||||||
|
</Title>
|
||||||
|
<Tooltip label="Tambah User">
|
||||||
|
<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>Nama</Table.Th>
|
||||||
|
<Table.Th>Telepon</Table.Th>
|
||||||
|
<Table.Th>Email</Table.Th>
|
||||||
|
<Table.Th>Role</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>{v.phone}</Table.Td>
|
||||||
|
<Table.Td>{v.email}</Table.Td>
|
||||||
|
<Table.Td>{v.roleId}</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 User Tidak Ditemukan
|
||||||
|
</Table.Td>
|
||||||
|
</Table.Tr>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</Table.Tbody>
|
||||||
|
</Table>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import DesaSetting from "@/components/DesaSetting";
|
import DesaSetting from "@/components/DesaSetting";
|
||||||
import KategoriPengaduan from "@/components/KategoriPengaduan";
|
import KategoriPengaduan from "@/components/KategoriPengaduan";
|
||||||
import ProfileUser from "@/components/ProfileUser";
|
import ProfileUser from "@/components/ProfileUser";
|
||||||
|
import UserSetting from "@/components/UserSetting";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Card,
|
Card,
|
||||||
@@ -18,6 +19,7 @@ import {
|
|||||||
IconCategory2,
|
IconCategory2,
|
||||||
IconMailSpark,
|
IconMailSpark,
|
||||||
IconUserCog,
|
IconUserCog,
|
||||||
|
IconUsersGroup
|
||||||
} from "@tabler/icons-react";
|
} from "@tabler/icons-react";
|
||||||
import { useLocation } from "react-router-dom";
|
import { useLocation } from "react-router-dom";
|
||||||
|
|
||||||
@@ -47,6 +49,12 @@ export default function DetailSettingPage() {
|
|||||||
leftSection={<IconUserCog size={16} stroke={1.5} />}
|
leftSection={<IconUserCog size={16} stroke={1.5} />}
|
||||||
active={type === "profile" || !type}
|
active={type === "profile" || !type}
|
||||||
/>
|
/>
|
||||||
|
<NavLink
|
||||||
|
href={`?type=user`}
|
||||||
|
label="User"
|
||||||
|
leftSection={<IconUsersGroup size={16} stroke={1.5} />}
|
||||||
|
active={type === "user"}
|
||||||
|
/>
|
||||||
<NavLink
|
<NavLink
|
||||||
href={`?type=cat-pengaduan`}
|
href={`?type=cat-pengaduan`}
|
||||||
label="Kategori Pengaduan"
|
label="Kategori Pengaduan"
|
||||||
@@ -85,6 +93,8 @@ export default function DetailSettingPage() {
|
|||||||
<KategoriPengaduanPage />
|
<KategoriPengaduanPage />
|
||||||
) : type === "desa" ? (
|
) : type === "desa" ? (
|
||||||
<DesaSetting />
|
<DesaSetting />
|
||||||
|
) : type === "user" ? (
|
||||||
|
<UserSetting />
|
||||||
) : (
|
) : (
|
||||||
<ProfileUser />
|
<ProfileUser />
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -101,5 +101,86 @@ const UserRoute = new Elysia({
|
|||||||
description: "update user",
|
description: "update user",
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
.post("/create", async ({ body }) => {
|
||||||
|
const { name, phone, roleId, email, password } = body
|
||||||
|
const create = await prisma.user.create({
|
||||||
|
data: {
|
||||||
|
name,
|
||||||
|
phone,
|
||||||
|
roleId,
|
||||||
|
email,
|
||||||
|
password
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: "User created successfully",
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
body: t.Object({
|
||||||
|
name: t.String({ minLength: 1, error: "name is required" }),
|
||||||
|
phone: t.String({ minLength: 1, error: "phone is required" }),
|
||||||
|
roleId: t.String({ minLength: 1, error: "roleId is required" }),
|
||||||
|
email: t.String({ minLength: 1, error: "email is required" }),
|
||||||
|
password: t.String({ minLength: 1, error: "password is required" })
|
||||||
|
}),
|
||||||
|
detail: {
|
||||||
|
summary: "create",
|
||||||
|
description: "create user",
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.get("/list", async (ctx) => {
|
||||||
|
const { user } = ctx as any
|
||||||
|
|
||||||
|
const data = await prisma.user.findMany({
|
||||||
|
where: {
|
||||||
|
isActive: true,
|
||||||
|
NOT: {
|
||||||
|
id: user.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return data
|
||||||
|
}, {
|
||||||
|
detail: {
|
||||||
|
summary: "list",
|
||||||
|
description: "list user",
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.get("/role", async () => {
|
||||||
|
const data = await prisma.role.findMany()
|
||||||
|
return data
|
||||||
|
}, {
|
||||||
|
detail: {
|
||||||
|
summary: "role",
|
||||||
|
description: "role user",
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.post("/delete", async ({ body }) => {
|
||||||
|
const { id } = body
|
||||||
|
const deleteData = await prisma.user.update({
|
||||||
|
where: {
|
||||||
|
id
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
isActive: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: "User deleted successfully",
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
body: t.Object({
|
||||||
|
id: t.String({ minLength: 1, error: "id is required" })
|
||||||
|
}),
|
||||||
|
detail: {
|
||||||
|
summary: "delete",
|
||||||
|
description: "delete user",
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
export default UserRoute
|
export default UserRoute
|
||||||
Reference in New Issue
Block a user