diff --git a/src/components/ProfileUser.tsx b/src/components/ProfileUser.tsx
new file mode 100644
index 0000000..31b8d45
--- /dev/null
+++ b/src/components/ProfileUser.tsx
@@ -0,0 +1,190 @@
+import apiFetch from "@/lib/apiFetch";
+import { Button, Divider, Flex, Group, Input, Modal, Stack, Title } from "@mantine/core";
+import { useEffect, useState } from "react";
+import notification from "./notificationGlobal";
+
+export default function ProfileUser() {
+ const [opened, setOpened] = useState(false);
+ const [openedPassword, setOpenedPassword] = useState(false);
+ const [pwdBaru, setPwdBaru] = useState("");
+ const [host, setHost] = useState({
+ id: "",
+ name: "",
+ phone: "",
+ roleId: "",
+ email: "",
+ });
+
+ const [error, setError] = useState({
+ name: false,
+ email: false,
+ phone: false,
+ });
+
+ useEffect(() => {
+ async function fetchHost() {
+ const { data } = await apiFetch.api.user.find.get();
+ setHost({
+ id: data?.user?.id ?? "",
+ name: data?.user?.name ?? "",
+ phone: data?.user?.phone ?? "",
+ roleId: data?.user?.roleId ?? "",
+ email: data?.user?.email ?? "",
+ });
+ }
+ fetchHost();
+ }, []);
+
+
+ function onValidation({ kat, value }: { kat: 'name' | 'email' | 'phone', value: string, }) {
+ if (value.length < 1) {
+ setError({ ...error, [kat]: true });
+ } else {
+ setError({ ...error, [kat]: false });
+ }
+
+ setHost({ ...host, [kat]: value });
+ }
+
+ async function handleUpdate() {
+ try {
+ const res = await apiFetch.api.user.update.post(host);
+ if (res.status === 200) {
+ setOpened(false);
+ notification({
+ title: "Success",
+ message: "Your profile have been saved",
+ type: "success",
+ })
+ } else {
+ notification({
+ title: "Error",
+ message: "Failed to update profile",
+ type: "error",
+ })
+ }
+ } catch (error) {
+ console.log(error);
+ notification({
+ title: "Error",
+ message: "Failed to update profile",
+ type: "error",
+ })
+ }
+ }
+
+ async function handleUpdatePassword() {
+ try {
+ const res = await apiFetch.api.user["update-password"].post({ password: pwdBaru, id: host.id });
+ if (res.status === 200) {
+ setPwdBaru("");
+ setOpenedPassword(false);
+ notification({
+ title: "Success",
+ message: "Your password have been saved",
+ type: "success",
+ })
+ } else {
+ notification({
+ title: "Error",
+ message: "Failed to update password",
+ type: "error",
+ })
+ }
+ } catch (error) {
+ console.log(error);
+ notification({
+ title: "Error",
+ message: "Failed to update password",
+ type: "error",
+ })
+ }
+ }
+
+
+
+ return (
+ <>
+
+
+
+ Profile Pengguna
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ setOpened(false)}
+ title={"Edit Profile"}
+
+ size={"lg"}
+ overlayProps={{ backgroundOpacity: 0.55, blur: 3 }}
+ >
+
+
+ onValidation({ kat: 'name', value: e.target.value })} />
+
+
+ onValidation({ kat: 'phone', value: e.target.value })} />
+
+
+ onValidation({ kat: 'email', value: e.target.value })} />
+
+
+
+
+
+
+
+
+ setOpenedPassword(false)}
+ title={"Ubah Password"}
+ overlayProps={{ backgroundOpacity: 0.55, blur: 3 }}
+ >
+
+
+ setPwdBaru(e.target.value)} />
+
+
+
+
+
+
+
+ >
+ )
+}
\ No newline at end of file
diff --git a/src/components/UserSetting.tsx b/src/components/UserSetting.tsx
new file mode 100644
index 0000000..0dd1fd9
--- /dev/null
+++ b/src/components/UserSetting.tsx
@@ -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 */}
+
+
+
+ onValidation({ kat: 'name', value: e.target.value, aksi: 'edit' })} />
+
+
+
+
+
+
+
+
+ {/* Modal Tambah */}
+
+
+
+ onValidation({ kat: 'name', value: e.target.value, aksi: 'tambah' })} />
+
+
+
+
+
+ {/* Modal Delete */}
+
+
+
+ Apakah anda yakin ingin menghapus user ini?
+
+
+
+
+
+
+
+
+
+
+
+
+ Daftar User
+
+
+ }
+ onClick={openTambah}
+ >
+ Tambah
+
+
+
+
+
+
+
+
+ Nama
+ Telepon
+ Email
+ Role
+ Aksi
+
+
+
+ {
+ list.length > 0 ? (
+ list?.map((v: any) => (
+
+ {v.name}
+ {v.phone}
+ {v.email}
+ {v.roleId}
+
+
+
+ chooseEdit({ data: v })}
+ >
+
+
+
+
+ {
+ setDataDelete(v.id)
+ openDelete()
+ }}
+ >
+
+
+
+
+
+
+ ))
+ ) : (
+
+
+ Data User Tidak Ditemukan
+
+
+ )
+ }
+
+
+
+
+ >
+ );
+}
diff --git a/src/pages/scr/dashboard/setting/detail_setting_page.tsx b/src/pages/scr/dashboard/setting/detail_setting_page.tsx
index 767f4cb..8baa579 100644
--- a/src/pages/scr/dashboard/setting/detail_setting_page.tsx
+++ b/src/pages/scr/dashboard/setting/detail_setting_page.tsx
@@ -1,5 +1,7 @@
import DesaSetting from "@/components/DesaSetting";
import KategoriPengaduan from "@/components/KategoriPengaduan";
+import ProfileUser from "@/components/ProfileUser";
+import UserSetting from "@/components/UserSetting";
import {
Button,
Card,
@@ -7,18 +9,17 @@ import {
Divider,
Flex,
Grid,
- Group,
- Input,
NavLink,
Stack,
Table,
- Title,
+ Title
} from "@mantine/core";
import {
IconBuildingBank,
IconCategory2,
IconMailSpark,
IconUserCog,
+ IconUsersGroup
} from "@tabler/icons-react";
import { useLocation } from "react-router-dom";
@@ -48,6 +49,12 @@ export default function DetailSettingPage() {
leftSection={}
active={type === "profile" || !type}
/>
+ }
+ active={type === "user"}
+ />
) : type === "desa" ? (
+ ) : type === "user" ? (
+
) : (
-
+
)}
@@ -96,37 +105,6 @@ export default function DetailSettingPage() {
);
}
-function ProfilePage() {
- return (
-
-
-
- Profile Pengguna
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-}
function KategoriPengaduanPage() {
const elements = [
diff --git a/src/server/routes/user_route.ts b/src/server/routes/user_route.ts
index 127e7d4..ad8911f 100644
--- a/src/server/routes/user_route.ts
+++ b/src/server/routes/user_route.ts
@@ -47,5 +47,140 @@ const UserRoute = new Elysia({
description: "upsert user",
}
})
+ .post("/update-password", async ({ body }) => {
+ const { password, id } = body
+ const update = await prisma.user.update({
+ where: {
+ id
+ },
+ data: {
+ password
+ }
+ })
+
+ return {
+ success: true,
+ message: "Password updated successfully",
+ }
+ }, {
+ body: t.Object({
+ password: t.String({ minLength: 1, error: "password is required" }),
+ id: t.String({ minLength: 1, error: "id is required" })
+ }),
+ detail: {
+ summary: "update password",
+ description: "update password user",
+ }
+ })
+ .post("/update", async ({ body }) => {
+ const { name, phone, id, roleId } = body
+ const update = await prisma.user.update({
+ where: {
+ id
+ },
+ data: {
+ name,
+ phone,
+ roleId
+ }
+ })
+
+ return {
+ success: true,
+ message: "User updated successfully",
+ }
+ }, {
+ body: t.Object({
+ name: t.String({ minLength: 1, error: "name is required" }),
+ phone: t.String({ minLength: 1, error: "phone is required" }),
+ id: t.String({ minLength: 1, error: "id is required" }),
+ roleId: t.String({ minLength: 1, error: "roleId is required" })
+ }),
+ detail: {
+ summary: "update",
+ 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
\ No newline at end of file