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' })} /> + + 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 User + + + + + + + + + + + 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