From 67c066990e9968e07cb827910ee2b8438669ef03 Mon Sep 17 00:00:00 2001 From: amaliadwiy Date: Fri, 14 Nov 2025 17:19:50 +0800 Subject: [PATCH] update dashboard admin Deskripsi: - list warga - detail warga No Issues --- src/components/DesaSetting.tsx | 292 ++--- src/components/KategoriPelayananSurat.tsx | 1042 +++++++++-------- src/components/KategoriPengaduan.tsx | 608 +++++----- src/components/ProfileUser.tsx | 404 ++++--- src/components/UserSetting.tsx | 775 ++++++------ src/index.tsx | 4 +- .../pelayanan-surat/list_pelayanan_page.tsx | 4 +- .../scr/dashboard/pengaduan/detail_page.tsx | 3 +- .../scr/dashboard/pengaduan/list_page.tsx | 2 +- .../dashboard/setting/detail_setting_page.tsx | 6 +- .../scr/dashboard/warga/detail_warga_page.tsx | 161 +-- .../scr/dashboard/warga/list_warga_page.tsx | 81 +- src/server/routes/warga_route.ts | 141 +++ 13 files changed, 2014 insertions(+), 1509 deletions(-) create mode 100644 src/server/routes/warga_route.ts diff --git a/src/components/DesaSetting.tsx b/src/components/DesaSetting.tsx index fa9e3f3..d136937 100644 --- a/src/components/DesaSetting.tsx +++ b/src/components/DesaSetting.tsx @@ -1,16 +1,16 @@ import apiFetch from "@/lib/apiFetch"; import { - ActionIcon, - Button, - Divider, - Flex, - Group, - Input, - Modal, - Stack, - Table, - Title, - Tooltip + ActionIcon, + Button, + Divider, + Flex, + Group, + Input, + Modal, + Stack, + Table, + Title, + Tooltip, } from "@mantine/core"; import { useDisclosure, useShallowEffect } from "@mantine/hooks"; import { IconEdit } from "@tabler/icons-react"; @@ -19,139 +19,153 @@ import useSWR from "swr"; import notification from "./notificationGlobal"; export default function DesaSetting() { - const [btnDisable, setBtnDisable] = useState(false); - const [btnLoading, setBtnLoading] = useState(false); - const [opened, { open, close }] = useDisclosure(false); - const { data, mutate, isLoading } = useSWR("/", () => - apiFetch.api["configuration-desa"].list.get(), - ); - const list = data?.data || []; - const [dataEdit, setDataEdit] = useState({ - id: "", - value: "", - name: "", - }); + const [btnDisable, setBtnDisable] = useState(false); + const [btnLoading, setBtnLoading] = useState(false); + const [opened, { open, close }] = useDisclosure(false); + const { data, mutate, isLoading } = useSWR("/", () => + apiFetch.api["configuration-desa"].list.get(), + ); + const list = data?.data || []; + const [dataEdit, setDataEdit] = useState({ + id: "", + value: "", + name: "", + }); - useShallowEffect(() => { - mutate(); - }, []); + useShallowEffect(() => { + mutate(); + }, []); - async function handleEdit() { - try { - setBtnLoading(true); - const res = await apiFetch.api["configuration-desa"].edit.post(dataEdit); - if (res.status === 200) { - mutate(); - close(); - notification({ - title: "Success", - message: "Your settings have been saved", - type: "success", - }) - } else { - notification({ - title: "Error", - message: "Failed to edit configuration", - type: "error", - }) - } - } catch (error) { - console.log(error); - notification({ - title: "Error", - message: "Failed to edit configuration", - type: "error", - }) - } finally { - setBtnLoading(false); - } - } - - function chooseEdit({ data }: { data: { id: string, value: string, name: string } }) { - setDataEdit(data); - open(); - } - - function onValidation({ kat, value }: { kat: 'value', value: string }) { - if (value.length < 1) { - setBtnDisable(true); + async function handleEdit() { + try { + setBtnLoading(true); + const res = await apiFetch.api["configuration-desa"].edit.post(dataEdit); + if (res.status === 200) { + mutate(); + close(); + notification({ + title: "Success", + message: "Your settings have been saved", + type: "success", + }); } else { - setBtnDisable(false); + notification({ + title: "Error", + message: "Failed to edit configuration", + type: "error", + }); } + } catch (error) { + console.error(error); + notification({ + title: "Error", + message: "Failed to edit configuration", + type: "error", + }); + } finally { + setBtnLoading(false); + } + } - if (kat === 'value') { - setDataEdit({ ...dataEdit, value: value }); - } - } + function chooseEdit({ + data, + }: { + data: { id: string; value: string; name: string }; + }) { + setDataEdit(data); + open(); + } - useShallowEffect(() => { - if (dataEdit.value.length > 0) { - setBtnDisable(false); - } - }, [dataEdit.id]); + function onValidation({ kat, value }: { kat: "value"; value: string }) { + if (value.length < 1) { + setBtnDisable(true); + } else { + setBtnDisable(false); + } - return ( - <> - - - - onValidation({ kat: 'value', value: e.target.value })} /> - - - - - - - - - - - Pengaturan Desa - - - - - - - - Nama - Value - Aksi - - - - {list?.map((v: any) => ( - - {v.name} - {v.value} - - - chooseEdit({ data: v })} - > - - - - - - ))} - -
-
-
- - ); + if (kat === "value") { + setDataEdit({ ...dataEdit, value: value }); + } + } + + useShallowEffect(() => { + if (dataEdit.value.length > 0) { + setBtnDisable(false); + } + }, [dataEdit.id]); + + return ( + <> + + + + + onValidation({ kat: "value", value: e.target.value }) + } + /> + + + + + + + + + + + Pengaturan Desa + + + + + + + + Nama + Value + Aksi + + + + {list?.map((v: any) => ( + + {v.name} + {v.value} + + + chooseEdit({ data: v })} + > + + + + + + ))} + +
+
+
+ + ); } diff --git a/src/components/KategoriPelayananSurat.tsx b/src/components/KategoriPelayananSurat.tsx index 94f90d7..1982bf8 100644 --- a/src/components/KategoriPelayananSurat.tsx +++ b/src/components/KategoriPelayananSurat.tsx @@ -1,20 +1,20 @@ import apiFetch from "@/lib/apiFetch"; import { - ActionIcon, - Button, - Divider, - Flex, - Grid, - Group, - Input, - List, - Modal, - Stack, - Table, - TagsInput, - Text, - Title, - Tooltip + ActionIcon, + Button, + Divider, + Flex, + Grid, + Group, + Input, + List, + Modal, + Stack, + Table, + TagsInput, + Text, + Title, + Tooltip, } from "@mantine/core"; import { useDisclosure, useShallowEffect } from "@mantine/hooks"; import { IconEdit, IconEye, IconPlus, IconTrash } from "@tabler/icons-react"; @@ -23,470 +23,590 @@ import useSWR from "swr"; import notification from "./notificationGlobal"; export default function KategoriPelayananSurat() { - const [openedDelete, { open: openDelete, close: closeDelete }] = useDisclosure(false); - const [openedDetail, { open: openDetail, close: closeDetail }] = useDisclosure(false); - const [btnLoading, setBtnLoading] = useState(false); - const [opened, { open, close }] = useDisclosure(false); - const [openedTambah, { open: openTambah, close: closeTambah }] = useDisclosure(false); - const [dataDelete, setDataDelete] = useState("") - const { data, mutate, isLoading } = useSWR("/", () => - apiFetch.api.pelayanan.category.get(), - ); - const list = data?.data || []; - const [dataChoose, setDataChoose] = useState({ - id: "", - name: "", - syaratDokumen: [{ name: "", desc: "" }], - dataText: [""], - }); - const [dataTambah, setDataTambah] = useState({ - name: "", - syaratDokumen: [{ name: "", desc: "" }], - dataText: [""], - }) + const [openedDelete, { open: openDelete, close: closeDelete }] = + useDisclosure(false); + const [openedDetail, { open: openDetail, close: closeDetail }] = + useDisclosure(false); + const [btnLoading, setBtnLoading] = useState(false); + const [opened, { open, close }] = useDisclosure(false); + const [openedTambah, { open: openTambah, close: closeTambah }] = + useDisclosure(false); + const [dataDelete, setDataDelete] = useState(""); + const { data, mutate, isLoading } = useSWR("/", () => + apiFetch.api.pelayanan.category.get(), + ); + const list = data?.data || []; + const [dataChoose, setDataChoose] = useState({ + id: "", + name: "", + syaratDokumen: [{ name: "", desc: "" }], + dataText: [""], + }); + const [dataTambah, setDataTambah] = useState({ + name: "", + syaratDokumen: [{ name: "", desc: "" }], + dataText: [""], + }); - useShallowEffect(() => { - mutate(); - }, []); + useShallowEffect(() => { + mutate(); + }, []); - async function handleCreate() { - try { - setBtnLoading(true); - const cleanedDataText = dataTambah.dataText.map(v => v.trim()).filter(v => v !== ""); - const cleanedSyarat = dataTambah.syaratDokumen.map((item) => ({ - name: item.name.trim(), - desc: item.desc.trim(), - })).filter(item => item.name !== "" && item.desc !== ""); + async function handleCreate() { + try { + setBtnLoading(true); + const cleanedDataText = dataTambah.dataText + .map((v) => v.trim()) + .filter((v) => v !== ""); + const cleanedSyarat = dataTambah.syaratDokumen + .map((item) => ({ + name: item.name.trim(), + desc: item.desc.trim(), + })) + .filter((item) => item.name !== "" && item.desc !== ""); - const cleanedTambah = { - name: dataTambah.name.trim(), - syaratDokumen: cleanedSyarat, - dataText: cleanedDataText, - } - const res = await apiFetch.api.pelayanan.category.create.post(cleanedTambah); - if (res.status === 200) { - mutate(); - closeTambah(); - setDataTambah({ - name: "", - syaratDokumen: [{ name: "", desc: "" }], - dataText: [""], - }); - notification({ - title: "Success", - message: "Your category have been saved", - type: "success", - }) - } else { - notification({ - title: "Error", - message: "Failed to create category", - type: "error", - }) - } - } catch (error) { - console.error(error); - notification({ - title: "Error", - message: "Failed to create category", - type: "error", - }) - } finally { - setBtnLoading(false); + const cleanedTambah = { + name: dataTambah.name.trim(), + syaratDokumen: cleanedSyarat, + dataText: cleanedDataText, + }; + const res = + await apiFetch.api.pelayanan.category.create.post(cleanedTambah); + if (res.status === 200) { + mutate(); + closeTambah(); + setDataTambah({ + name: "", + syaratDokumen: [{ name: "", desc: "" }], + dataText: [""], + }); + notification({ + title: "Success", + message: "Your category have been saved", + type: "success", + }); + } else { + notification({ + title: "Error", + message: "Failed to create category", + type: "error", + }); } - } + } catch (error) { + console.error(error); + notification({ + title: "Error", + message: "Failed to create category", + type: "error", + }); + } finally { + setBtnLoading(false); + } + } - async function handleEdit() { - try { - setBtnLoading(true); - const cleanedDataText = dataChoose.dataText.map(v => v.trim()).filter(v => v !== ""); - const cleanedSyarat = dataChoose.syaratDokumen.map((item) => ({ - name: item.name.trim(), - desc: item.desc.trim(), - })).filter(item => item.name !== "" && item.desc !== ""); + async function handleEdit() { + try { + setBtnLoading(true); + const cleanedDataText = dataChoose.dataText + .map((v) => v.trim()) + .filter((v) => v !== ""); + const cleanedSyarat = dataChoose.syaratDokumen + .map((item) => ({ + name: item.name.trim(), + desc: item.desc.trim(), + })) + .filter((item) => item.name !== "" && item.desc !== ""); - const res = await apiFetch.api.pelayanan.category.update.post({ - id: dataChoose.id, - name: dataChoose.name, - syaratDokumen: cleanedSyarat, - dataText: cleanedDataText, - }); - 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); + const res = await apiFetch.api.pelayanan.category.update.post({ + id: dataChoose.id, + name: dataChoose.name, + syaratDokumen: cleanedSyarat, + dataText: cleanedDataText, + }); + 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.pelayanan.category.delete.post({ id: dataDelete }); - if (res.status === 200) { - mutate(); - closeDelete(); - notification({ - title: "Success", - message: "Your category have been deleted", - type: "success", - }) - } else { - notification({ - title: "Error", - message: "Failed to delete category", - type: "error", - }) - } - } catch (error) { - console.error(error); - notification({ - title: "Error", - message: "Failed to delete category", - type: "error", - }) - } finally { - setBtnLoading(false); + async function handleDelete() { + try { + setBtnLoading(true); + const res = await apiFetch.api.pelayanan.category.delete.post({ + id: dataDelete, + }); + if (res.status === 200) { + mutate(); + closeDelete(); + notification({ + title: "Success", + message: "Your category have been deleted", + type: "success", + }); + } else { + notification({ + title: "Error", + message: "Failed to delete category", + type: "error", + }); } - } - - - function handleAddSyarat() { - setDataChoose({ - ...dataChoose, - syaratDokumen: [...dataChoose.syaratDokumen, { name: "", desc: "" }], + } catch (error) { + console.error(error); + notification({ + title: "Error", + message: "Failed to delete category", + type: "error", }); - } + } finally { + setBtnLoading(false); + } + } - function handleDeleteSyarat(index: number) { - setDataChoose({ - ...dataChoose, - syaratDokumen: dataChoose.syaratDokumen.filter((_, i) => i !== index), - }); - } + function handleAddSyarat() { + setDataChoose({ + ...dataChoose, + syaratDokumen: [...dataChoose.syaratDokumen, { name: "", desc: "" }], + }); + } - function handleEditSyarat(index: number, data: { name: string; desc: string }) { - setDataChoose({ - ...dataChoose, - syaratDokumen: dataChoose.syaratDokumen.map((v, i) => (i === index ? data : v)), - }); - } + function handleDeleteSyarat(index: number) { + setDataChoose({ + ...dataChoose, + syaratDokumen: dataChoose.syaratDokumen.filter((_, i) => i !== index), + }); + } + function handleEditSyarat( + index: number, + data: { name: string; desc: string }, + ) { + setDataChoose({ + ...dataChoose, + syaratDokumen: dataChoose.syaratDokumen.map((v, i) => + i === index ? data : v, + ), + }); + } - return ( - <> - {/* Modal Edit */} - - - - setDataChoose({ ...dataChoose, name: e.target.value })} /> - - setDataChoose({ ...dataChoose, dataText: value })} - /> - - - - Syarat dokumen - - + return ( + <> + {/* Modal Edit */} + + + + + setDataChoose({ ...dataChoose, name: e.target.value }) + } + /> + + + setDataChoose({ ...dataChoose, dataText: value }) + } + /> + + + + Syarat dokumen + + + + + + + + {dataChoose?.syaratDokumen?.map((v: any, i: number) => ( + + + + { + handleDeleteSyarat(i); + }} + > + + + + + + + + handleEditSyarat(i, { + name: e.target.value, + desc: v.desc, + }) + } + /> + + + + + + handleEditSyarat(i, { + name: v.name, + desc: e.target.value, + }) + } + /> + + + + ))} + + + + + + + + + + {/* Modal Tambah */} + + + + + setDataTambah({ ...dataTambah, name: e.target.value }) + } + /> + + + setDataTambah({ ...dataTambah, dataText: value }) + } + /> + + + + Syarat dokumen + + + { + setDataTambah({ + ...dataTambah, + syaratDokumen: [ + ...dataTambah.syaratDokumen, + { name: "", desc: "" }, + ], + }); + }} + > + + + + + {dataTambah?.syaratDokumen?.map((v: any, index: number) => ( + + + + { + setDataTambah({ + ...dataTambah, + syaratDokumen: dataTambah.syaratDokumen.filter( + (v: any, i: number) => i !== index, + ), + }); + }} + disabled={dataTambah?.syaratDokumen?.length === 1} + > + + + + + + + + setDataTambah({ + ...dataTambah, + syaratDokumen: dataTambah.syaratDokumen.map( + (v: any, i: number) => + i === index ? { ...v, name: e.target.value } : v, + ), + }) + } + /> + + + + + + setDataTambah({ + ...dataTambah, + syaratDokumen: dataTambah.syaratDokumen.map( + (v: any, i: number) => + i === index ? { ...v, desc: e.target.value } : v, + ), + }) + } + /> + + + + ))} + + + + + + + + + {/* Modal Delete */} + + + + Apakah anda yakin ingin menghapus kategori ini? + + + + + + + + + {/* Modal Detail */} + + + + + Kategori + + {dataChoose?.name ?? ""} + + + + Syarat Dokumen + + + {dataChoose?.syaratDokumen?.map((v: any) => ( + {v.desc} + ))} + + + + + Data Pelengkap + + + {dataChoose?.dataText?.map((v: any) => ( + {v} + ))} + + + + + + {/* Table */} + + + + Kategori Pelayanan Surat + + + + + + + + + + + Kategori + Aksi + + + + {list?.map((v: any) => ( + + {v.name} + + + { + setDataChoose(v); + openDetail(); + }} > - + - - - { - dataChoose?.syaratDokumen?.map((v: any, i: number) => ( - - - - { - handleDeleteSyarat(i) - }} - > - - - - - - - handleEditSyarat(i, { name: e.target.value, desc: v.desc })} /> - - - - - handleEditSyarat(i, { name: v.name, desc: e.target.value })} /> - - - - )) - } - - - - - - - - - - {/* Modal Tambah */} - - - - setDataTambah({ ...dataTambah, name: e.target.value })} /> - - setDataTambah({ ...dataTambah, dataText: value })} - /> - - - - Syarat dokumen - - + + { - setDataTambah({ ...dataTambah, syaratDokumen: [...dataTambah.syaratDokumen, { name: "", desc: "" }] }) - }} + variant="light" + size="sm" + style={{ boxShadow: "0 0 8px rgba(0,255,200,0.2)" }} + onClick={() => { + setDataChoose(v); + open(); + }} > - + - - - { - - dataTambah?.syaratDokumen?.map((v: any, index: number) => ( - - - - { - setDataTambah({ ...dataTambah, syaratDokumen: dataTambah.syaratDokumen.filter((v: any, i: number) => i !== index) }) - }} - disabled={dataTambah?.syaratDokumen?.length === 1} - > - - - - - - - setDataTambah({ ...dataTambah, syaratDokumen: dataTambah.syaratDokumen.map((v: any, i: number) => i === index ? { ...v, name: e.target.value } : v) })} /> - - - - - setDataTambah({ ...dataTambah, syaratDokumen: dataTambah.syaratDokumen.map((v: any, i: number) => i === index ? { ...v, desc: e.target.value } : v) })} /> - - - - )) - } - - - - - - - - - {/* Modal Delete */} - - - - Apakah anda yakin ingin menghapus kategori ini? - - - - - - - - - {/* Modal Detail */} - - - - Kategori - {dataChoose?.name ?? ""} - - - Syarat Dokumen - - {dataChoose?.syaratDokumen?.map((v: any) => ( - {v.desc} - ))} - - - - Data Pelengkap - - {dataChoose?.dataText?.map((v: any) => ( - {v} - ))} - - - - - - - {/* Table */} - - - - Kategori Pelayanan Surat - - - - - - - -
- - - Kategori - Aksi - - - - {list?.map((v: any) => ( - - {v.name} - - - - { setDataChoose(v); openDetail() }} - > - - - - - { setDataChoose(v); open(); }} - > - - - - - { - setDataDelete(v.id) - openDelete() - }} - > - - - - - - - ))} - -
-
-
- - ); +
+ + { + setDataDelete(v.id); + openDelete(); + }} + > + + + +
+ + + ))} + + +
+ + + ); } diff --git a/src/components/KategoriPengaduan.tsx b/src/components/KategoriPengaduan.tsx index 6fdbf00..fd2ea73 100644 --- a/src/components/KategoriPengaduan.tsx +++ b/src/components/KategoriPengaduan.tsx @@ -1,17 +1,17 @@ import apiFetch from "@/lib/apiFetch"; import { - ActionIcon, - Button, - Divider, - Flex, - Group, - Input, - Modal, - Stack, - Table, - Text, - Title, - Tooltip + ActionIcon, + Button, + Divider, + Flex, + Group, + Input, + Modal, + Stack, + Table, + Text, + Title, + Tooltip, } from "@mantine/core"; import { useDisclosure, useShallowEffect } from "@mantine/hooks"; import { IconEdit, IconPlus, IconTrash } from "@tabler/icons-react"; @@ -20,288 +20,336 @@ import useSWR from "swr"; import notification from "./notificationGlobal"; export default function KategoriPengaduan() { - const [openedDelete, { open: openDelete, close: closeDelete }] = useDisclosure(false); - const [btnDisable, setBtnDisable] = useState(true); - const [btnLoading, setBtnLoading] = useState(false); - const [opened, { open, close }] = useDisclosure(false); - const [openedTambah, { open: openTambah, close: closeTambah }] = useDisclosure(false); - const [dataDelete, setDataDelete] = useState("") - const { data, mutate, isLoading } = useSWR("/", () => - apiFetch.api.pengaduan.category.get(), - ); - const list = data?.data?.data || []; - const [dataEdit, setDataEdit] = useState({ - id: "", - name: "", - }); - const [dataTambah, setDataTambah] = useState("") + const [openedDelete, { open: openDelete, close: closeDelete }] = + useDisclosure(false); + const [btnDisable, setBtnDisable] = useState(true); + const [btnLoading, setBtnLoading] = useState(false); + const [opened, { open, close }] = useDisclosure(false); + const [openedTambah, { open: openTambah, close: closeTambah }] = + useDisclosure(false); + const [dataDelete, setDataDelete] = useState(""); + const { data, mutate, isLoading } = useSWR("/", () => + apiFetch.api.pengaduan.category.get(), + ); + const list = data?.data?.data || []; + const [dataEdit, setDataEdit] = useState({ + id: "", + name: "", + }); + const [dataTambah, setDataTambah] = useState(""); - useShallowEffect(() => { - mutate(); - }, []); + useShallowEffect(() => { + mutate(); + }, []); - async function handleCreate() { - try { - setBtnLoading(true); - const res = await apiFetch.api.pengaduan.category.create.post({ name: dataTambah }); - if (res.status === 200) { - mutate(); - closeTambah(); - setDataTambah(""); - notification({ - title: "Success", - message: "Your category have been saved", - type: "success", - }) - } else { - notification({ - title: "Error", - message: "Failed to create category", - type: "error", - }) - } - } catch (error) { - console.error(error); - notification({ - title: "Error", - message: "Failed to create category", - 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); - } - } - - function chooseEdit({ data }: { data: { id: string, value: string, name: string } }) { - setDataEdit(data); - open(); - } - - function onValidation({ kat, value, aksi }: { kat: 'name', value: string, aksi: 'edit' | 'tambah' }) { - if (value.length < 1) { - setBtnDisable(true); + async function handleCreate() { + try { + setBtnLoading(true); + const res = await apiFetch.api.pengaduan.category.create.post({ + name: dataTambah, + }); + if (res.status === 200) { + mutate(); + closeTambah(); + setDataTambah(""); + notification({ + title: "Success", + message: "Your category have been saved", + type: "success", + }); } else { - setBtnDisable(false); + notification({ + title: "Error", + message: "Failed to create category", + type: "error", + }); } + } catch (error) { + console.error(error); + notification({ + title: "Error", + message: "Failed to create category", + type: "error", + }); + } finally { + setBtnLoading(false); + } + } - if (kat === 'name') { - if (aksi === 'edit') { - setDataEdit({ ...dataEdit, name: value }); - } else { - setDataTambah(value); - } + 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.pengaduan.category.delete.post({ id: dataDelete }); - if (res.status === 200) { - mutate(); - closeDelete(); - notification({ - title: "Success", - message: "Your category have been deleted", - type: "success", - }) - } else { - notification({ - title: "Error", - message: "Failed to delete category", - type: "error", - }) - } - } catch (error) { - console.error(error); - notification({ - title: "Error", - message: "Failed to delete category", - type: "error", - }) - } finally { - setBtnLoading(false); + function chooseEdit({ + data, + }: { + data: { id: string; value: string; name: string }; + }) { + setDataEdit(data); + open(); + } + + function onValidation({ + kat, + value, + aksi, + }: { + kat: "name"; + value: string; + aksi: "edit" | "tambah"; + }) { + if (value.length < 1) { + setBtnDisable(true); + } else { + setBtnDisable(false); + } + + if (kat === "name") { + if (aksi === "edit") { + setDataEdit({ ...dataEdit, name: value }); + } else { + setDataTambah(value); } - } + } + } - useShallowEffect(() => { - if (dataEdit.name.length > 0) { - setBtnDisable(false); + async function handleDelete() { + try { + setBtnLoading(true); + const res = await apiFetch.api.pengaduan.category.delete.post({ + id: dataDelete, + }); + if (res.status === 200) { + mutate(); + closeDelete(); + notification({ + title: "Success", + message: "Your category have been deleted", + type: "success", + }); + } else { + notification({ + title: "Error", + message: "Failed to delete category", + type: "error", + }); } - }, [dataEdit.id]); + } catch (error) { + console.error(error); + notification({ + title: "Error", + message: "Failed to delete category", + type: "error", + }); + } finally { + setBtnLoading(false); + } + } - useShallowEffect(() => { - if (dataTambah.length > 0) { - setBtnDisable(false); - } - }, [dataTambah]); + useShallowEffect(() => { + if (dataEdit.name.length > 0) { + setBtnDisable(false); + } + }, [dataEdit.id]); - return ( - <> - {/* Modal Edit */} - - - - onValidation({ kat: 'name', value: e.target.value, aksi: 'edit' })} /> - - - - - - - + useShallowEffect(() => { + if (dataTambah.length > 0) { + setBtnDisable(false); + } + }, [dataTambah]); - {/* Modal Tambah */} - - - - onValidation({ kat: 'name', value: e.target.value, aksi: 'tambah' })} /> - - - - - - - + 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 kategori ini? - - - - - - - + {/* Modal Delete */} + + + + Apakah anda yakin ingin menghapus kategori ini? + + + + + + + - - - - - - Kategori Pengaduan - - - - - - - - - - - Kategori - Aksi - - - - {list?.map((v: any) => ( - - {v.name} - - - - chooseEdit({ data: v })} - > - - - - - { - setDataDelete(v.id) - openDelete() - }} - > - - - - - - - ))} - -
-
-
- - ); + + + + Kategori Pengaduan + + + + + + + + + + + Kategori + Aksi + + + + {list?.map((v: any) => ( + + {v.name} + + + + chooseEdit({ data: v })} + > + + + + + { + setDataDelete(v.id); + openDelete(); + }} + > + + + + + + + ))} + +
+
+
+ + ); } diff --git a/src/components/ProfileUser.tsx b/src/components/ProfileUser.tsx index 31b8d45..d622062 100644 --- a/src/components/ProfileUser.tsx +++ b/src/components/ProfileUser.tsx @@ -1,190 +1,246 @@ import apiFetch from "@/lib/apiFetch"; -import { Button, Divider, Flex, Group, Input, Modal, Stack, Title } from "@mantine/core"; +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 [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, - }); + 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(); - }, []); + 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 }); + } - function onValidation({ kat, value }: { kat: 'name' | 'email' | 'phone', value: string, }) { - if (value.length < 1) { - setError({ ...error, [kat]: true }); + 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 { - setError({ ...error, [kat]: false }); + notification({ + title: "Error", + message: "Failed to update profile", + type: "error", + }); } + } catch (error) { + console.error(error); + notification({ + title: "Error", + message: "Failed to update profile", + type: "error", + }); + } + } - 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.error(error); + notification({ + title: "Error", + message: "Failed to update password", + 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 }) + } + /> + + + + + + + - - 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 + setOpenedPassword(false)} + title={"Ubah Password"} + overlayProps={{ backgroundOpacity: 0.55, blur: 3 }} + > + + + setPwdBaru(e.target.value)} + /> + + + + + + + + + ); +} diff --git a/src/components/UserSetting.tsx b/src/components/UserSetting.tsx index 0dd1fd9..1861a15 100644 --- a/src/components/UserSetting.tsx +++ b/src/components/UserSetting.tsx @@ -1,18 +1,18 @@ import apiFetch from "@/lib/apiFetch"; import { - ActionIcon, - Button, - Divider, - Flex, - Group, - Input, - Modal, - Select, - Stack, - Table, - Text, - Title, - Tooltip + 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"; @@ -21,345 +21,446 @@ 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, - }) + 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(); - }, []); + 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 }); + 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 { - setBtnDisable(false); - setError({ ...error, [kat]: false }); + 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); + } + } - - if (aksi === 'edit') { - setDataEdit({ ...dataEdit, [kat]: value }); + 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 { - setDataTambah({ ...dataTambah, [kat]: value }); + 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); + } + } - useShallowEffect(() => { - if (dataEdit.name.length > 0) { - setBtnDisable(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", + }); } - }, [dataEdit.id]); + } 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(); + } - return ( - <> - {/* Modal Edit */} - - - - onValidation({ kat: 'name', value: e.target.value, aksi: 'edit' })} /> - - - - - - - + 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 }); + } - {/* 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' })} /> - + 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 Delete */} - - - - Apakah anda yakin ingin menghapus user ini? - - - - - - - + {/* 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", + }) + } + /> + + + + + + + - - - - 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 - - - ) - } - -
-
-
- - ); + {/* 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/index.tsx b/src/index.tsx index 202417e..fc83f18 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -7,6 +7,7 @@ import { apiAuth } from "./server/middlewares/apiAuth"; import AduanRoute from "./server/routes/aduan_route"; import ApiKeyRoute from "./server/routes/apikey_route"; import Auth from "./server/routes/auth_route"; +import ConfigurationDesaRoute from "./server/routes/configuration_desa_route"; import CredentialRoute from "./server/routes/credential_route"; import DarmasabaRoute from "./server/routes/darmasaba_route"; import LayananRoute from "./server/routes/layanan_route"; @@ -15,7 +16,7 @@ import PelayananRoute from "./server/routes/pelayanan_surat_route"; import PengaduanRoute from "./server/routes/pengaduan_route"; import TestRoute from "./server/routes/test"; import UserRoute from "./server/routes/user_route"; -import ConfigurationDesaRoute from "./server/routes/configuration_desa_route"; +import WargaRoute from "./server/routes/warga_route"; const Docs = new Elysia({ tags: ["docs"], @@ -32,6 +33,7 @@ const Api = new Elysia({ .use(PengaduanRoute) .use(PelayananRoute) .use(ConfigurationDesaRoute) + .use(WargaRoute) .use(TestRoute) .use(apiAuth) .use(ApiKeyRoute) 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 0a6d2bb..c23c3b1 100644 --- a/src/pages/scr/dashboard/pelayanan-surat/list_pelayanan_page.tsx +++ b/src/pages/scr/dashboard/pelayanan-surat/list_pelayanan_page.tsx @@ -172,7 +172,7 @@ function ListPelayananSurat({ status }: { status: StatusKey }) { } /> - {list.length === 0 ? ( + {list?.length === 0 ? ( @@ -182,7 +182,7 @@ function ListPelayananSurat({ status }: { status: StatusKey }) { ) : ( - list.map((v: any) => ( + list?.map((v: any) => ( ) : ( - list.map((v: any) => ( + list?.map((v: any) => ( ); -} \ No newline at end of file +} diff --git a/src/pages/scr/dashboard/warga/detail_warga_page.tsx b/src/pages/scr/dashboard/warga/detail_warga_page.tsx index a4a3d04..57c6f67 100644 --- a/src/pages/scr/dashboard/warga/detail_warga_page.tsx +++ b/src/pages/scr/dashboard/warga/detail_warga_page.tsx @@ -2,62 +2,66 @@ import apiFetch from "@/lib/apiFetch"; import { Avatar, Box, + Button, Card, Container, Divider, Flex, Grid, Group, + LoadingOverlay, Stack, Table, Text, Title, } from "@mantine/core"; import { useShallowEffect } from "@mantine/hooks"; -import { IconMail, IconMapPin, IconPhone } from "@tabler/icons-react"; -import { useState } from "react"; -import { useLocation } from "react-router-dom"; +import { IconPhone } from "@tabler/icons-react"; +import _ from "lodash"; +import { useLocation, useNavigate } from "react-router-dom"; import useSwr from "swr"; export default function DetailWargaPage() { const { search } = useLocation(); const query = new URLSearchParams(search); const id = query.get("id"); + const { data, mutate, isLoading } = useSwr("/", () => + apiFetch.api.warga.detail.get({ + query: { + id: id!, + }, + }), + ); + + useShallowEffect(() => { + mutate(); + }, []); + return ( - - - - - - - - - - - - - + <> + + + + + + + + + + + + + + + + ); } -function DetailDataHistori() { - const elements = [ - { position: 6, mass: 12.011, symbol: "C", name: "Carbon" }, - { position: 7, mass: 14.007, symbol: "N", name: "Nitrogen" }, - { position: 39, mass: 88.906, symbol: "Y", name: "Yttrium" }, - { position: 56, mass: 137.33, symbol: "Ba", name: "Barium" }, - { position: 58, mass: 140.12, symbol: "Ce", name: "Cerium" }, - ]; +function DetailDataHistori({ data, kategori }: { data: any, kategori: 'pengaduan' | 'pelayanan' }) { + const navigate = useNavigate(); - const rows = elements.map((element) => ( - - {element.position} - {element.name} - {element.symbol} - {element.mass} - - )); return ( - Histori Pengaduan + Histori {_.upperFirst(kategori)} - Tanggal - Deskripsi + No {_.upperFirst(kategori)} + {kategori == "pengaduan" ? "Judul" : "Kategori"} Status - User + - {rows} + + { + data?.length > 0 ? ( + data?.map((item: any, index: number) => ( + + {item.noPengaduan} + {kategori == "pengaduan" ? item.title : item.category} + {item.status} + + + + + )) + ) : ( + + Tidak ada data + + ) + } +
); } -function DetailWarga() { - const [page, setPage] = useState(1); - const [value, setValue] = useState(""); - const { data, mutate, isLoading } = useSwr("/", () => - apiFetch.api.pengaduan.list.get({ - query: { - status, - search: value, - take: "", - page: "", - }, - }), - ); - - useShallowEffect(() => { - mutate(); - }, [status, value]); - - const list = data?.data || []; - +function DetailWarga({ data }: { data: any }) { return ( - {/* Profile image */} + > + A + {/* Main content */} - Lizbeth Moore + {data?.name} - Social Media Strategies + Warga Desa @@ -161,19 +180,9 @@ function DetailWarga() { {/* Contact info */} - - - lizbeth.moore@email.com - - - +1 555-7788 - - - - - Greenway Ave, Los Angeles, CA, USA + {data?.phone} diff --git a/src/pages/scr/dashboard/warga/list_warga_page.tsx b/src/pages/scr/dashboard/warga/list_warga_page.tsx index cb34134..149d10a 100644 --- a/src/pages/scr/dashboard/warga/list_warga_page.tsx +++ b/src/pages/scr/dashboard/warga/list_warga_page.tsx @@ -1,3 +1,4 @@ +import apiFetch from "@/lib/apiFetch"; import { Button, Card, @@ -10,41 +11,30 @@ import { Table, Title, } from "@mantine/core"; +import { useShallowEffect } from "@mantine/hooks"; import { IconSearch } from "@tabler/icons-react"; import { useState } from "react"; import { useNavigate } from "react-router-dom"; +import useSWR from "swr"; export default function ListWargaPage() { const navigate = useNavigate(); - const [value, setValue] = useState(""); - const elements = [ - { position: 6, mass: 12.011, symbol: "C", name: "Carbon" }, - { position: 7, mass: 14.007, symbol: "N", name: "Nitrogen" }, - { position: 39, mass: 88.906, symbol: "Y", name: "Yttrium" }, - { position: 56, mass: 137.33, symbol: "Ba", name: "Barium" }, - { position: 58, mass: 140.12, symbol: "Ce", name: "Cerium" }, - ]; + const { data, mutate, isLoading } = useSWR("/", () => + apiFetch.api.warga.list.get({ + query: { + search: value, + }, + }), + ); + + const list = data?.data || []; + + const [value, setValue] = useState(""); + + useShallowEffect(() => { + mutate(); + }, [value]); - const rows = elements.map((element) => ( - - {element.position} - {element.name} - {element.symbol} - {element.mass} - - - - - )); return ( @@ -81,14 +71,39 @@ export default function ListWargaPage() { - Tanggal - Deskripsi - Status - User + Nama + No Telepon Aksi - {rows} + + { + list?.length === 0 ? ( + + Tidak ada data + + ) : ( + list?.map((item, i) => ( + + {item.name} + {item.phone} + + + + + )) + ) + } +
diff --git a/src/server/routes/warga_route.ts b/src/server/routes/warga_route.ts new file mode 100644 index 0000000..d5324ed --- /dev/null +++ b/src/server/routes/warga_route.ts @@ -0,0 +1,141 @@ +import Elysia, { t } from "elysia"; +import _ from "lodash"; +import { normalizePhoneNumber } from "../lib/normalizePhone"; +import { prisma } from "../lib/prisma"; + +const WargaRoute = new Elysia({ + prefix: "warga", + tags: ["warga"], +}) + + .get("/list", async ({ query }) => { + const { search } = query + + const data = await prisma.warga.findMany({ + where: { + OR: [ + { + name: { + contains: search, + mode: "insensitive" + } + }, + { + phone: { + contains: search, + mode: "insensitive" + } + } + ] + }, + orderBy: { + name: "asc" + } + }) + + return data + }, { + detail: { + summary: "List Warga", + description: `tool untuk mendapatkan list warga`, + } + }) + .post("/edit", async ({ body }) => { + const { id, name, phone } = body + + const nomorHP = normalizePhoneNumber({ phone }) + await prisma.warga.update({ + where: { + id, + }, + data: { + name, + phone: nomorHP + } + }) + + return { success: true, message: 'data warga sudah diperbarui' } + }, { + body: t.Object({ + id: t.String({ minLength: 1, error: "id harus diisi" }), + name: t.String({ minLength: 1, error: "value harus diisi" }), + phone: t.String({ minLength: 1 }) + }), + detail: { + summary: "edit konfigurasi desa", + description: `tool untuk edit konfigurasi desa` + } + }) + .get("/detail", async ({ query }) => { + const { id } = query + const dataWarga = await prisma.warga.findUnique({ + where: { + id + } + }) + + const dataPengaduan = await prisma.pengaduan.findMany({ + orderBy: { + createdAt: "desc" + }, + where: { + isActive: true, + idWarga: id + }, + select: { + id: true, + status: true, + noPengaduan: true, + title: true + } + }) + + + + const dataPelayanan = await prisma.pelayananAjuan.findMany({ + orderBy: { + createdAt: "desc" + }, + where: { + isActive: true, + idWarga: id + }, + select: { + id: true, + noPengajuan: true, + status: true, + CategoryPelayanan: { + select: { + name: true + } + } + } + }) + + const dataPelayanFix = dataPelayanan.map((v: any) => ({ + ..._.omit(v, ["CategoryPelayanan"]), + id: v.id, + noPengaduan: v.noPengajuan, + status: v.status, + category: v.CategoryPelayanan.name + })) + + return { + warga: dataWarga, + pengaduan: dataPengaduan, + pelayanan: dataPelayanFix + } + + }, { + query: t.Object({ + id: t.String({ minLength: 1, error: "id harus diisi" }) + }), + detail: { + summary: "Detail Warga", + description: `tool untuk mendapatkan detail warga`, + } + + }) + ; + +export default WargaRoute