From 6c6ee02cf0392518b78aa5f090007ac6af36383f Mon Sep 17 00:00:00 2001 From: amaliadwiy Date: Fri, 14 Nov 2025 14:32:32 +0800 Subject: [PATCH] upd: dahsboar admin Deskripsi: - list kategori pelayanan surat - edit kategori pelayanan surat - tambah kategori pelayanan surat - hapush kategori pelayanan surat No Issues --- src/components/KategoriPelayananSurat.tsx | 492 ++++++++++++++++++ .../dashboard/setting/detail_setting_page.tsx | 49 +- src/server/routes/pelayanan_surat_route.ts | 16 +- 3 files changed, 503 insertions(+), 54 deletions(-) create mode 100644 src/components/KategoriPelayananSurat.tsx diff --git a/src/components/KategoriPelayananSurat.tsx b/src/components/KategoriPelayananSurat.tsx new file mode 100644 index 0000000..94f90d7 --- /dev/null +++ b/src/components/KategoriPelayananSurat.tsx @@ -0,0 +1,492 @@ +import apiFetch from "@/lib/apiFetch"; +import { + 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"; +import { useState } from "react"; +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: [""], + }) + + 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 !== ""); + + 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 !== ""); + + 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); + } + } + + + function handleAddSyarat() { + setDataChoose({ + ...dataChoose, + syaratDokumen: [...dataChoose.syaratDokumen, { name: "", desc: "" }], + }); + } + + 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 + + + + + + + + { + 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() }} + > + + + + + { setDataChoose(v); open(); }} + > + + + + + { + setDataDelete(v.id) + openDelete() + }} + > + + + + + + + ))} + +
+
+
+ + ); +} diff --git a/src/pages/scr/dashboard/setting/detail_setting_page.tsx b/src/pages/scr/dashboard/setting/detail_setting_page.tsx index 8baa579..46fb1ed 100644 --- a/src/pages/scr/dashboard/setting/detail_setting_page.tsx +++ b/src/pages/scr/dashboard/setting/detail_setting_page.tsx @@ -1,4 +1,5 @@ import DesaSetting from "@/components/DesaSetting"; +import KategoriPelayananSurat from "@/components/KategoriPelayananSurat"; import KategoriPengaduan from "@/components/KategoriPengaduan"; import ProfileUser from "@/components/ProfileUser"; import UserSetting from "@/components/UserSetting"; @@ -90,7 +91,7 @@ export default function DetailSettingPage() { {type === "cat-pengaduan" ? ( ) : type === "cat-pelayanan" ? ( - + ) : type === "desa" ? ( ) : type === "user" ? ( @@ -103,48 +104,4 @@ export default function DetailSettingPage() { ); -} - - -function KategoriPengaduanPage() { - 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 rows = elements.map((element) => ( - - {element.position} - {element.name} - {element.symbol} - {element.mass} - - )); - return ( - - - - Kategori Pengaduan - - - - - - - - - Tanggal - Deskripsi - Status - User - - - {rows} -
-
-
- ); -} +} \ No newline at end of file diff --git a/src/server/routes/pelayanan_surat_route.ts b/src/server/routes/pelayanan_surat_route.ts index 90b9ed4..c403491 100644 --- a/src/server/routes/pelayanan_surat_route.ts +++ b/src/server/routes/pelayanan_surat_route.ts @@ -1,8 +1,8 @@ -import Elysia, { StatusMap, t } from "elysia" -import { generateNoPengajuanSurat } from "../lib/no-pengajuan-surat" -import { prisma } from "../lib/prisma" +import Elysia, { t } from "elysia" import type { StatusPengaduan } from "generated/prisma" +import { generateNoPengajuanSurat } from "../lib/no-pengajuan-surat" import { normalizePhoneNumber } from "../lib/normalizePhone" +import { prisma } from "../lib/prisma" const PelayananRoute = new Elysia({ prefix: "pelayanan", @@ -15,7 +15,7 @@ const PelayananRoute = new Elysia({ where: { isActive: true }, - orderBy:{ + orderBy: { name: "asc" } }) @@ -42,8 +42,8 @@ const PelayananRoute = new Elysia({ }, { body: t.Object({ name: t.String({ minLength: 1, error: "name harus diisi" }), - syaratDokumen: t.Array(t.String({ minLength: 1, error: "syaratDokumen harus diisi" })), - dataText: t.Array(t.String({ minLength: 1, error: "dataText harus diisi" })), + syaratDokumen: t.Any(), + dataText: t.Any(), }), detail: { summary: "buat kategori pelayanan surat", @@ -69,8 +69,8 @@ const PelayananRoute = new Elysia({ body: t.Object({ id: t.String({ minLength: 1, error: "id harus diisi" }), name: t.String({ minLength: 1, error: "name harus diisi" }), - syaratDokumen: t.Array(t.String({ minLength: 1, error: "syaratDokumen harus diisi" })), - dataText: t.Array(t.String({ minLength: 1, error: "dataText harus diisi" })), + syaratDokumen: t.Any(), + dataText: t.Any(), }), detail: { summary: "update kategori pelayanan surat",