From 928ecb4c76f9a385fa4e1a6f289f7afafc02e87c Mon Sep 17 00:00:00 2001 From: amaliadwiy Date: Fri, 7 Nov 2025 15:19:23 +0800 Subject: [PATCH 1/3] upd: list pengaduan Deskripsi: - pencarian data list pengaduan No Issues --- src/AppRoutes.tsx | 38 +-- src/index.tsx | 14 +- .../scr/dashboard/pengaduan/list_page.tsx | 281 +++++++++++------- src/server/routes/pengaduan_route.ts | 4 +- 4 files changed, 206 insertions(+), 131 deletions(-) diff --git a/src/AppRoutes.tsx b/src/AppRoutes.tsx index 18589c7..163c790 100644 --- a/src/AppRoutes.tsx +++ b/src/AppRoutes.tsx @@ -1,28 +1,28 @@ // ⚡ Auto-generated by generateRoutes.ts — DO NOT EDIT MANUALLY -import { BrowserRouter, Route, Routes } from "react-router-dom"; -import DarmasabaHome from "./pages/darmasaba/darmasaba_home"; -import DarmasabaLayout from "./pages/darmasaba/darmasaba_layout"; -import FormKartuKeluarga from "./pages/darmasaba/form_kartu_keluarga"; -import FormKartuTandaPenduduk from "./pages/darmasaba/form_kartu_tanda_penduduk"; -import FormKeteranganKelahiran from "./pages/darmasaba/form_keterangan_kelahiran"; -import FormLaporanSampah from "./pages/darmasaba/form_laporan_sampah"; -import FormSuratKeteranganBelumKawin from "./pages/darmasaba/form_surat_keterangan_belum_kawin"; -import FormSuratKeteranganDomisiliOrganisasi from "./pages/darmasaba/form_surat_keterangan_domisili_organisasi"; -import FormSuratKeteranganKelakuanBaik from "./pages/darmasaba/form_surat_keterangan_kelakuan_baik"; -import FormSuratKeteranganPenghasilan from "./pages/darmasaba/form_surat_keterangan_penghasilan"; -import FormSuratKeteranganTempatUsaha from "./pages/darmasaba/form_surat_keterangan_tempat_usaha"; -import FormSuratKeteranganTidakMampu from "./pages/darmasaba/form_surat_keterangan_tidak_mampu"; -import FormSuratKeteranganUsaha from "./pages/darmasaba/form_surat_keterangan_usaha"; -import DirPage from "./pages/dir/dir_page"; -import Home from "./pages/Home"; +import { BrowserRouter, Routes, Route } from "react-router-dom"; import Login from "./pages/Login"; -import NotFound from "./pages/NotFound"; -import ApikeyPage from "./pages/scr/dashboard/apikey/apikey_page"; +import DarmasabaLayout from "./pages/darmasaba/darmasaba_layout"; +import FormSuratKeteranganUsaha from "./pages/darmasaba/form_surat_keterangan_usaha"; +import FormSuratKeteranganTidakMampu from "./pages/darmasaba/form_surat_keterangan_tidak_mampu"; +import DarmasabaHome from "./pages/darmasaba/darmasaba_home"; +import FormKartuTandaPenduduk from "./pages/darmasaba/form_kartu_tanda_penduduk"; +import FormKartuKeluarga from "./pages/darmasaba/form_kartu_keluarga"; +import FormLaporanSampah from "./pages/darmasaba/form_laporan_sampah"; +import FormSuratKeteranganPenghasilan from "./pages/darmasaba/form_surat_keterangan_penghasilan"; +import FormSuratKeteranganDomisiliOrganisasi from "./pages/darmasaba/form_surat_keterangan_domisili_organisasi"; +import FormSuratKeteranganBelumKawin from "./pages/darmasaba/form_surat_keterangan_belum_kawin"; +import FormKeteranganKelahiran from "./pages/darmasaba/form_keterangan_kelahiran"; +import FormSuratKeteranganTempatUsaha from "./pages/darmasaba/form_surat_keterangan_tempat_usaha"; +import FormSuratKeteranganKelakuanBaik from "./pages/darmasaba/form_surat_keterangan_kelakuan_baik"; +import Home from "./pages/Home"; import CredentialPage from "./pages/scr/dashboard/credential/credential_page"; import DashboardHome from "./pages/scr/dashboard/dashboard_home"; -import DashboardLayout from "./pages/scr/dashboard/dashboard_layout"; import ListPage from "./pages/scr/dashboard/pengaduan/list_page"; +import ApikeyPage from "./pages/scr/dashboard/apikey/apikey_page"; +import DashboardLayout from "./pages/scr/dashboard/dashboard_layout"; import ScrLayout from "./pages/scr/scr_layout"; +import DirPage from "./pages/dir/dir_page"; +import NotFound from "./pages/NotFound"; export default function AppRoutes() { return ( diff --git a/src/index.tsx b/src/index.tsx index 83c1edd..688cff5 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -13,7 +13,7 @@ import { MCPRoute } from "./server/routes/mcp_route"; import PelayananRoute from "./server/routes/pelayanan_surat_route"; import PengaduanRoute from "./server/routes/pengaduan_route"; import UserRoute from "./server/routes/user_route"; -import cors from "@elysiajs/cors" +import cors from "@elysiajs/cors"; const Docs = new Elysia({ tags: ["docs"], @@ -41,11 +41,13 @@ const app = new Elysia() .use(Api) .use(Docs) .use(Auth) - .use(cors({ - origin: "*", - methods: ["GET", "POST", "OPTIONS"], - allowedHeaders: ["Content-Type"], - })) + .use( + cors({ + origin: "*", + methods: ["GET", "POST", "OPTIONS"], + allowedHeaders: ["Content-Type"], + }), + ) .get( "/.well-known/mcp.json", async () => { diff --git a/src/pages/scr/dashboard/pengaduan/list_page.tsx b/src/pages/scr/dashboard/pengaduan/list_page.tsx index e5fdd04..8bf33e8 100644 --- a/src/pages/scr/dashboard/pengaduan/list_page.tsx +++ b/src/pages/scr/dashboard/pengaduan/list_page.tsx @@ -2,17 +2,26 @@ import apiFetch from "@/lib/apiFetch"; import { Badge, Card, + CloseButton, Container, Divider, Flex, Group, + Input, Stack, Tabs, Text, Title } from "@mantine/core"; import { useShallowEffect } from "@mantine/hooks"; -import { IconAlignJustified, IconClockHour3, IconFileSad, IconMapPin } from "@tabler/icons-react"; +import { + IconAlignJustified, + IconClockHour3, + IconFileSad, + IconMapPin, + IconSearch, +} from "@tabler/icons-react"; +import { useState } from "react"; import { useLocation, useNavigate } from "react-router-dom"; import useSwr from "swr"; import { proxy } from "valtio"; @@ -25,17 +34,13 @@ function reloadState() { export default function PengaduanListPage() { const { search } = useLocation(); const query = new URLSearchParams(search); - const status = query.get("status"); + const status = query.get("status") as StatusKey; return ( - + - - + + ); @@ -44,39 +49,83 @@ export default function PengaduanListPage() { function TabListPengaduan({ status }: { status: string }) { const navigate = useNavigate(); const dataCount = useSwr("/pengaduan/count", () => - apiFetch.api.pengaduan.count.get().then((res) => res.data) + apiFetch.api.pengaduan.count.get().then((res) => res.data), ); return ( - + - { navigate("?status=all") }}>Semua ({dataCount?.data?.semua || 0}) - { navigate("?status=antrian") }}>Antrian ({dataCount?.data?.antrian || 0}) - { navigate("?status=diterima") }}>Diterima ({dataCount?.data?.diterima || 0}) - { navigate("?status=dikerjakan") }}>Dikerjakan ({dataCount?.data?.dikerjakan || 0}) - { navigate("?status=selesai") }}>Selesai ({dataCount?.data?.selesai || 0}) - { navigate("?status=ditolak") }}>Ditolak ({dataCount?.data?.ditolak || 0}) + { + navigate("?status=semua"); + }} + > + Semua ({dataCount?.data?.semua || 0}) + + { + navigate("?status=antrian"); + }} + > + Antrian ({dataCount?.data?.antrian || 0}) + + { + navigate("?status=diterima"); + }} + > + Diterima ({dataCount?.data?.diterima || 0}) + + { + navigate("?status=dikerjakan"); + }} + > + Dikerjakan ({dataCount?.data?.dikerjakan || 0}) + + { + navigate("?status=selesai"); + }} + > + Selesai ({dataCount?.data?.selesai || 0}) + + { + navigate("?status=ditolak"); + }} + > + Ditolak ({dataCount?.data?.ditolak || 0}) + ); } -function ListPengaduan({ status }: { status: string }) { +type StatusKey = "antrian" | "diterima" | "dikerjakan" | "ditolak" | "selesai" | "semua"; +function ListPengaduan({ status }: { status: StatusKey }) { + const [page, setPage] = useState(1); + const [value, setValue] = useState(""); const { data, mutate, isLoading } = useSwr("/", () => apiFetch.api.pengaduan.list.get({ query: { status, - search: "", + search: value, take: "", - page: "" + page: "", }, }), ); useShallowEffect(() => { mutate(); - }, [status]); - + }, [status, value]); if (isLoading) return ( @@ -99,94 +148,118 @@ function ListPengaduan({ status }: { status: string }) { return ( - { - list.length === 0 ? ( - - - - - No pengaduan have been added yet. - - - - ) : - list.map((v: any) => ( - - - - - - {v.title} + <Group grow> + <Input + value={value} + placeholder="Cari pengaduan..." + onChange={(event) => setValue(event.currentTarget.value)} + leftSection={<IconSearch size={16} />} + rightSectionPointerEvents="all" + rightSection={ + <CloseButton + aria-label="Clear input" + onClick={() => setValue('')} + style={{ display: value ? undefined : 'none' }} + /> + } + /> + {/* <Group justify="flex-end"> + <Text size="sm">Menampilkan {Number(data?.data?.length) * (page - 1) + 1} – {Math.min(10, Number(data?.data?.length) * page)} dari {Number(data?.data?.length)}</Text> + <Pagination total={Number(data?.data?.length)} value={page} onChange={setPage} withPages={false} /> + </Group> */} + </Group> + {list.length === 0 ? ( + <Flex justify="center" align="center" py={"xl"}> + <Stack gap={4} align="center"> + <IconFileSad size={32} color="gray" /> + <Text c="dimmed" size="sm"> + No pengaduan have been added yet. + </Text> + </Stack> + </Flex> + ) : ( + list.map((v: any) => ( + <Card + key={v.id} + radius="lg" + p="xl" + withBorder + style={{ + background: + "linear-gradient(145deg, rgba(25,25,25,0.95), rgba(45,45,45,0.85))", + borderColor: "rgba(100,100,100,0.2)", + boxShadow: "0 0 20px rgba(0,255,200,0.08)", + }} + > + <Stack gap="md"> + <Flex align="center" justify="space-between"> + <Flex direction={"column"}> + <Title order={3} c="gray.2"> + {v.title} + + + + #{v.noPengaduan} - - - #{v.noPengaduan} - - - {v.updatedAt} - - - - - {v.status} - + + {v.updatedAt} + + - - - - - - - Tanggal Aduan - - - - {v.createdAt} + + {v.status} + + + + + + + + + Tanggal Aduan - - - - - - Lokasi - - - - {v.location} + + {v.createdAt} + + + + + + Lokasi - - - - - - Detail - - - - {v.detail} + + {v.location} + + + + + + Detail - - + + {v.detail} + - - ))} + + + )) + )} ); } diff --git a/src/server/routes/pengaduan_route.ts b/src/server/routes/pengaduan_route.ts index 80efa46..392274c 100644 --- a/src/server/routes/pengaduan_route.ts +++ b/src/server/routes/pengaduan_route.ts @@ -500,7 +500,7 @@ const PengaduanRoute = new Elysia({ ] } - if (status && status !== "all") { + if (status && status !== "semua") { where = { ...where, status: status @@ -511,7 +511,7 @@ const PengaduanRoute = new Elysia({ skip, take: !take ? 10 : Number(take), orderBy: { - createdAt: "asc" + createdAt: "desc" }, where, select: { From 621cfc931a91b9dc706f29b80c6e49c4f9e2b985 Mon Sep 17 00:00:00 2001 From: amaliadwiy Date: Fri, 7 Nov 2025 16:22:56 +0800 Subject: [PATCH 2/3] upd: upload base64 Deskripsi: - api upload base64 test No Issues --- src/server/lib/seafile.ts | 73 ++++++++++++++++------ src/server/routes/pengaduan_route.ts | 91 +++++++++++++++++++--------- upload_base64.sh | 2 + 3 files changed, 117 insertions(+), 49 deletions(-) create mode 100644 upload_base64.sh diff --git a/src/server/lib/seafile.ts b/src/server/lib/seafile.ts index 776478f..6f42299 100644 --- a/src/server/lib/seafile.ts +++ b/src/server/lib/seafile.ts @@ -136,32 +136,65 @@ export async function catFile(config: Config, fileName: string): Promise } export async function uploadFile(config: Config, file: File): Promise { - const remoteName = path.basename(file.name); + const remoteName = path.basename(file.name); - // 1. Dapatkan upload link (pakai Authorization) - const uploadUrlResponse = await fetchWithAuth( - config, - `${config.URL}/${config.REPO}/upload-link/` - ); - const uploadUrl = (await uploadUrlResponse.text()).replace(/"/g, ""); + // 1. Dapatkan upload link (pakai Authorization) + const uploadUrlResponse = await fetchWithAuth( + config, + `${config.URL}/${config.REPO}/upload-link/` + ); + const uploadUrl = (await uploadUrlResponse.text()).replace(/"/g, ""); - // 2. Siapkan form-data - const formData = new FormData(); - formData.append("parent_dir", "/"); - formData.append("relative_path", "syarat-dokumen"); // tanpa slash di akhir - formData.append("file", file, remoteName); // file langsung, jangan pakai Blob + // 2. Siapkan form-data + const formData = new FormData(); + formData.append("parent_dir", "/"); + formData.append("relative_path", "syarat-dokumen"); // tanpa slash di akhir + formData.append("file", file, remoteName); // file langsung, jangan pakai Blob - // 3. Upload file TANPA Authorization header, token di query param - const res = await fetch(`${uploadUrl}?token=${config.TOKEN}`, { - method: "POST", - body: formData, - }); + // 3. Upload file TANPA Authorization header, token di query param + const res = await fetch(`${uploadUrl}?token=${config.TOKEN}`, { + method: "POST", + body: formData, + }); - const text = await res.text(); + const text = await res.text(); - if (!res.ok) throw new Error(`Upload failed: ${text}`); - return `✅ Uploaded ${file.name} successfully`; + if (!res.ok) throw new Error(`Upload failed: ${text}`); + return `✅ Uploaded ${file.name} successfully`; } +export async function uploadFileBase64(config: Config, base64File: { name: string; data: string }): Promise { + const remoteName = path.basename(base64File.name); + + // 1. Dapatkan upload link (pakai Authorization) + const uploadUrlResponse = await fetchWithAuth( + config, + `${config.URL}/${config.REPO}/upload-link/` + ); + const uploadUrl = (await uploadUrlResponse.text()).replace(/"/g, ""); + + // 2. Konversi base64 ke Blob + const binary = Buffer.from(base64File.data, "base64"); + const blob = new Blob([binary]); + + // 3. Siapkan form-data + const formData = new FormData(); + formData.append("parent_dir", "/"); + formData.append("relative_path", "syarat-dokumen"); // tanpa slash di akhir + formData.append("file", blob, remoteName); + + // 4. Upload file TANPA Authorization header, token di query param + const res = await fetch(`${uploadUrl}?token=${config.TOKEN}`, { + method: "POST", + body: formData, + }); + + const text = await res.text(); + + if (!res.ok) throw new Error(`Upload failed: ${text}`); + return `✅ Uploaded ${base64File.name} successfully`; +} + + export async function removeFile(config: Config, fileName: string): Promise { diff --git a/src/server/routes/pengaduan_route.ts b/src/server/routes/pengaduan_route.ts index 392274c..314afb9 100644 --- a/src/server/routes/pengaduan_route.ts +++ b/src/server/routes/pengaduan_route.ts @@ -4,7 +4,7 @@ import { getLastUpdated } from "../lib/get-last-updated" import { generateNoPengaduan } from "../lib/no-pengaduan" import { normalizePhoneNumber } from "../lib/normalizePhone" import { prisma } from "../lib/prisma" -import { defaultConfigSF, uploadFile } from "../lib/seafile" +import { defaultConfigSF, uploadFile, uploadFileBase64 } from "../lib/seafile" const PengaduanRoute = new Elysia({ prefix: "pengaduan", @@ -432,38 +432,71 @@ const PengaduanRoute = new Elysia({ tags: ["mcp"] } }) - .post("/upload", - async ({ body }) => { - const { file } = body; + .post("/upload", async ({ body }) => { + const { file } = body; - // Validasi file - if (!file) { - return { success: false, message: "File tidak ditemukan" }; - } + // Validasi file + if (!file) { + return { success: false, message: "File tidak ditemukan" }; + } - // Upload ke Seafile (pastikan uploadFile menerima Blob atau ArrayBuffer) - // const buffer = await file.arrayBuffer(); - const result = await uploadFile(defaultConfigSF, file); + // Upload ke Seafile (pastikan uploadFile menerima Blob atau ArrayBuffer) + // const buffer = await file.arrayBuffer(); + const result = await uploadFile(defaultConfigSF, file); - return { - success: true, - message: "Upload berhasil", - filename: file.name, - size: file.size, - seafileResult: result - }; + return { + success: true, + message: "Upload berhasil", + filename: file.name, + size: file.size, + seafileResult: result + }; + }, { + body: t.Object({ + file: t.File({ format: "binary" }) + }), + detail: { + summary: "Upload File", + description: "Tool untuk upload file ke Seafile", + tags: ["mcp"], + consumes: ["multipart/form-data"] }, - { - body: t.Object({ - file: t.File({ format: "binary" }) - }), - detail: { - summary: "Upload File", - description: "Tool untuk upload file ke Seafile", - tags: ["mcp"], - consumes: ["multipart/form-data"] - }, - }) + }) + .post("/upload-base64", async ({ body }) => { + const { file } = body; + + // Validasi file + if (!file) { + return { success: false, message: "File tidak ditemukan" }; + } + + // Konversi file ke base64 + const buffer = await file.arrayBuffer(); + const base64String = Buffer.from(buffer).toString("base64"); + + // (Opsional) jika perlu dikirim ke Seafile sebagai base64 + const result = await uploadFileBase64(defaultConfigSF, { name: file.name, data: base64String }); + + return { + success: true, + message: "Upload berhasil", + filename: file.name, + size: file.size, + base64Preview: base64String.slice(0, 100) + "...", // hanya preview + seafileResult: result + }; + }, { + body: t.Object({ + file: t.File({ format: "binary" }) + }), + detail: { + summary: "Upload File (Base64)", + description: "Tool untuk upload file ke Seafile dalam format Base64", + tags: ["mcp"], + consumes: ["multipart/form-data"] + }, + }) + .get("/list", async ({ query }) => { const { take, page, search, status } = query const skip = !page ? 0 : (Number(page) - 1) * (!take ? 10 : Number(take)) diff --git a/upload_base64.sh b/upload_base64.sh new file mode 100644 index 0000000..19b94c1 --- /dev/null +++ b/upload_base64.sh @@ -0,0 +1,2 @@ +curl -X POST http://localhost:3000/api/pengaduan/upload-base64 \ + -F file=@package.json From 5c71d000f60185a3863200e470ee9f5df9029909 Mon Sep 17 00:00:00 2001 From: amaliadwiy Date: Fri, 7 Nov 2025 17:34:07 +0800 Subject: [PATCH 3/3] tampilan pelayanan surat Deskripsi: - tampilan list pelayanan surat No Issues --- src/AppRoutes.tsx | 5 + src/clientRoutes.ts | 1 + src/pages/scr/dashboard/dashboard_layout.tsx | 2 +- .../pelayanan-surat/list_pelayanan_page.tsx | 271 ++++++++++++++++++ .../scr/dashboard/pengaduan/list_page.tsx | 14 +- 5 files changed, 288 insertions(+), 5 deletions(-) create mode 100644 src/pages/scr/dashboard/pelayanan-surat/list_pelayanan_page.tsx diff --git a/src/AppRoutes.tsx b/src/AppRoutes.tsx index 163c790..74aebc0 100644 --- a/src/AppRoutes.tsx +++ b/src/AppRoutes.tsx @@ -17,6 +17,7 @@ import FormSuratKeteranganKelakuanBaik from "./pages/darmasaba/form_surat_ketera import Home from "./pages/Home"; import CredentialPage from "./pages/scr/dashboard/credential/credential_page"; import DashboardHome from "./pages/scr/dashboard/dashboard_home"; +import ListPelayananPage from "./pages/scr/dashboard/pelayanan-surat/list_pelayanan_page"; import ListPage from "./pages/scr/dashboard/pengaduan/list_page"; import ApikeyPage from "./pages/scr/dashboard/apikey/apikey_page"; import DashboardLayout from "./pages/scr/dashboard/dashboard_layout"; @@ -93,6 +94,10 @@ export default function AppRoutes() { path="/scr/dashboard/dashboard-home" element={} /> + } + /> } diff --git a/src/clientRoutes.ts b/src/clientRoutes.ts index 5db3d11..d141a3b 100644 --- a/src/clientRoutes.ts +++ b/src/clientRoutes.ts @@ -19,6 +19,7 @@ const clientRoutes = { "/scr/dashboard": "/scr/dashboard", "/scr/dashboard/credential/credential": "/scr/dashboard/credential/credential", "/scr/dashboard/dashboard-home": "/scr/dashboard/dashboard-home", + "/scr/dashboard/pelayanan-surat/list-pelayanan": "/scr/dashboard/pelayanan-surat/list-pelayanan", "/scr/dashboard/pengaduan/list": "/scr/dashboard/pengaduan/list", "/scr/dashboard/apikey/apikey": "/scr/dashboard/apikey/apikey", "/dir/dir": "/dir/dir", diff --git a/src/pages/scr/dashboard/dashboard_layout.tsx b/src/pages/scr/dashboard/dashboard_layout.tsx index 21c53bb..f4241b1 100644 --- a/src/pages/scr/dashboard/dashboard_layout.tsx +++ b/src/pages/scr/dashboard/dashboard_layout.tsx @@ -230,7 +230,7 @@ function NavigationDashboard() { description: "Manage pengaduan warga", }, { - path: "/scr/dashboard/pelayanan", + path: "/scr/dashboard/pelayanan-surat/list-pelayanan", icon: , label: "Pelayanan Surat", description: "Manage pelayanan surat", diff --git a/src/pages/scr/dashboard/pelayanan-surat/list_pelayanan_page.tsx b/src/pages/scr/dashboard/pelayanan-surat/list_pelayanan_page.tsx new file mode 100644 index 0000000..6a5c0aa --- /dev/null +++ b/src/pages/scr/dashboard/pelayanan-surat/list_pelayanan_page.tsx @@ -0,0 +1,271 @@ +import apiFetch from "@/lib/apiFetch"; +import { + Badge, + Card, + CloseButton, + Container, + Divider, + Flex, + Group, + Input, + Stack, + Tabs, + Text, + Title, +} from "@mantine/core"; +import { useShallowEffect } from "@mantine/hooks"; +import { + IconAlignJustified, + IconClockHour3, + IconFileSad, + IconMapPin, + IconSearch, +} from "@tabler/icons-react"; +import { useState } from "react"; +import { useLocation, useNavigate } from "react-router-dom"; +import useSwr from "swr"; +import { proxy } from "valtio"; + +const state = proxy({ reload: "" }); +function reloadState() { + state.reload = Math.random().toString(); +} + +export default function PelayananSuratListPage() { + const { search } = useLocation(); + const query = new URLSearchParams(search); + const status = query.get("status") as StatusKey; + + return ( + + + + + + + ); +} + +function TabListPelayananSurat({ status }: { status: string }) { + const navigate = useNavigate(); + const dataCount = useSwr("/pelayanan-surat/count", () => + apiFetch.api.pengaduan.count.get().then((res) => res.data), + ); + + return ( + + + { + navigate("?status=semua"); + }} + > + Semua ({dataCount?.data?.semua || 0}) + + { + navigate("?status=antrian"); + }} + > + Antrian ({dataCount?.data?.antrian || 0}) + + { + navigate("?status=diterima"); + }} + > + Diterima ({dataCount?.data?.diterima || 0}) + + { + navigate("?status=dikerjakan"); + }} + > + Dikerjakan ({dataCount?.data?.dikerjakan || 0}) + + { + navigate("?status=selesai"); + }} + > + Selesai ({dataCount?.data?.selesai || 0}) + + { + navigate("?status=ditolak"); + }} + > + Ditolak ({dataCount?.data?.ditolak || 0}) + + + + ); +} + +type StatusKey = + | "antrian" + | "diterima" + | "dikerjakan" + | "ditolak" + | "selesai" + | "semua"; +function ListPelayananSurat({ status }: { status: StatusKey }) { + 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]); + + if (isLoading) + return ( + + + Loading pengaduan... + + + ); + + const list = data?.data || []; + + return ( + + + setValue(event.currentTarget.value)} + leftSection={} + rightSectionPointerEvents="all" + rightSection={ + setValue("")} + style={{ display: value ? undefined : "none" }} + /> + } + /> + {/* + Menampilkan {Number(data?.data?.length) * (page - 1) + 1} – {Math.min(10, Number(data?.data?.length) * page)} dari {Number(data?.data?.length)} + + */} + + {list.length === 0 ? ( + + + + + No pengaduan have been added yet. + + + + ) : ( + list.map((v: any) => ( + + + + + + {v.title} + + + + #{v.noPengaduan} + + + {v.updatedAt} + + + + + {v.status} + + + + + + + + + Tanggal Aduan + + + {v.createdAt} + + + + + + Lokasi + + + {v.location} + + + + + + Detail + + + {v.detail} + + + + + )) + )} + + ); +} diff --git a/src/pages/scr/dashboard/pengaduan/list_page.tsx b/src/pages/scr/dashboard/pengaduan/list_page.tsx index 8bf33e8..547b79d 100644 --- a/src/pages/scr/dashboard/pengaduan/list_page.tsx +++ b/src/pages/scr/dashboard/pengaduan/list_page.tsx @@ -11,7 +11,7 @@ import { Stack, Tabs, Text, - Title + Title, } from "@mantine/core"; import { useShallowEffect } from "@mantine/hooks"; import { @@ -108,7 +108,13 @@ function TabListPengaduan({ status }: { status: string }) { ); } -type StatusKey = "antrian" | "diterima" | "dikerjakan" | "ditolak" | "selesai" | "semua"; +type StatusKey = + | "antrian" + | "diterima" + | "dikerjakan" + | "ditolak" + | "selesai" + | "semua"; function ListPengaduan({ status }: { status: StatusKey }) { const [page, setPage] = useState(1); const [value, setValue] = useState(""); @@ -158,8 +164,8 @@ function ListPengaduan({ status }: { status: StatusKey }) { rightSection={ setValue('')} - style={{ display: value ? undefined : 'none' }} + onClick={() => setValue("")} + style={{ display: value ? undefined : "none" }} /> } />