upd: list pengaduan
Deskripsi: - pencarian data list pengaduan No Issues
This commit is contained in:
@@ -1,28 +1,28 @@
|
|||||||
// ⚡ Auto-generated by generateRoutes.ts — DO NOT EDIT MANUALLY
|
// ⚡ Auto-generated by generateRoutes.ts — DO NOT EDIT MANUALLY
|
||||||
import { BrowserRouter, Route, Routes } from "react-router-dom";
|
import { BrowserRouter, Routes, Route } 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 Login from "./pages/Login";
|
import Login from "./pages/Login";
|
||||||
import NotFound from "./pages/NotFound";
|
import DarmasabaLayout from "./pages/darmasaba/darmasaba_layout";
|
||||||
import ApikeyPage from "./pages/scr/dashboard/apikey/apikey_page";
|
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 CredentialPage from "./pages/scr/dashboard/credential/credential_page";
|
||||||
import DashboardHome from "./pages/scr/dashboard/dashboard_home";
|
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 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 ScrLayout from "./pages/scr/scr_layout";
|
||||||
|
import DirPage from "./pages/dir/dir_page";
|
||||||
|
import NotFound from "./pages/NotFound";
|
||||||
|
|
||||||
export default function AppRoutes() {
|
export default function AppRoutes() {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { MCPRoute } from "./server/routes/mcp_route";
|
|||||||
import PelayananRoute from "./server/routes/pelayanan_surat_route";
|
import PelayananRoute from "./server/routes/pelayanan_surat_route";
|
||||||
import PengaduanRoute from "./server/routes/pengaduan_route";
|
import PengaduanRoute from "./server/routes/pengaduan_route";
|
||||||
import UserRoute from "./server/routes/user_route";
|
import UserRoute from "./server/routes/user_route";
|
||||||
import cors from "@elysiajs/cors"
|
import cors from "@elysiajs/cors";
|
||||||
|
|
||||||
const Docs = new Elysia({
|
const Docs = new Elysia({
|
||||||
tags: ["docs"],
|
tags: ["docs"],
|
||||||
@@ -41,11 +41,13 @@ const app = new Elysia()
|
|||||||
.use(Api)
|
.use(Api)
|
||||||
.use(Docs)
|
.use(Docs)
|
||||||
.use(Auth)
|
.use(Auth)
|
||||||
.use(cors({
|
.use(
|
||||||
origin: "*",
|
cors({
|
||||||
methods: ["GET", "POST", "OPTIONS"],
|
origin: "*",
|
||||||
allowedHeaders: ["Content-Type"],
|
methods: ["GET", "POST", "OPTIONS"],
|
||||||
}))
|
allowedHeaders: ["Content-Type"],
|
||||||
|
}),
|
||||||
|
)
|
||||||
.get(
|
.get(
|
||||||
"/.well-known/mcp.json",
|
"/.well-known/mcp.json",
|
||||||
async () => {
|
async () => {
|
||||||
|
|||||||
@@ -2,17 +2,26 @@ import apiFetch from "@/lib/apiFetch";
|
|||||||
import {
|
import {
|
||||||
Badge,
|
Badge,
|
||||||
Card,
|
Card,
|
||||||
|
CloseButton,
|
||||||
Container,
|
Container,
|
||||||
Divider,
|
Divider,
|
||||||
Flex,
|
Flex,
|
||||||
Group,
|
Group,
|
||||||
|
Input,
|
||||||
Stack,
|
Stack,
|
||||||
Tabs,
|
Tabs,
|
||||||
Text,
|
Text,
|
||||||
Title
|
Title
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import { useShallowEffect } from "@mantine/hooks";
|
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 { useLocation, useNavigate } from "react-router-dom";
|
||||||
import useSwr from "swr";
|
import useSwr from "swr";
|
||||||
import { proxy } from "valtio";
|
import { proxy } from "valtio";
|
||||||
@@ -25,17 +34,13 @@ function reloadState() {
|
|||||||
export default function PengaduanListPage() {
|
export default function PengaduanListPage() {
|
||||||
const { search } = useLocation();
|
const { search } = useLocation();
|
||||||
const query = new URLSearchParams(search);
|
const query = new URLSearchParams(search);
|
||||||
const status = query.get("status");
|
const status = query.get("status") as StatusKey;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container
|
<Container size="xl" py="xl" w={"100%"}>
|
||||||
size="xl"
|
|
||||||
py="xl"
|
|
||||||
w={"100%"}
|
|
||||||
>
|
|
||||||
<Stack gap="xl">
|
<Stack gap="xl">
|
||||||
<TabListPengaduan status={status || "all"} />
|
<TabListPengaduan status={status || "semua"} />
|
||||||
<ListPengaduan status={status || "all"} />
|
<ListPengaduan status={status || "semua"} />
|
||||||
</Stack>
|
</Stack>
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
@@ -44,39 +49,83 @@ export default function PengaduanListPage() {
|
|||||||
function TabListPengaduan({ status }: { status: string }) {
|
function TabListPengaduan({ status }: { status: string }) {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const dataCount = useSwr("/pengaduan/count", () =>
|
const dataCount = useSwr("/pengaduan/count", () =>
|
||||||
apiFetch.api.pengaduan.count.get().then((res) => res.data)
|
apiFetch.api.pengaduan.count.get().then((res) => res.data),
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tabs defaultValue={status || "all"} color="teal">
|
<Tabs defaultValue={status || "semua"} color="teal">
|
||||||
<Tabs.List grow>
|
<Tabs.List grow>
|
||||||
<Tabs.Tab value="all" onClick={() => { navigate("?status=all") }}>Semua ({dataCount?.data?.semua || 0})</Tabs.Tab>
|
<Tabs.Tab
|
||||||
<Tabs.Tab value="antrian" onClick={() => { navigate("?status=antrian") }}>Antrian ({dataCount?.data?.antrian || 0})</Tabs.Tab>
|
value="all"
|
||||||
<Tabs.Tab value="diterima" onClick={() => { navigate("?status=diterima") }}>Diterima ({dataCount?.data?.diterima || 0})</Tabs.Tab>
|
onClick={() => {
|
||||||
<Tabs.Tab value="dikerjakan" onClick={() => { navigate("?status=dikerjakan") }}>Dikerjakan ({dataCount?.data?.dikerjakan || 0})</Tabs.Tab>
|
navigate("?status=semua");
|
||||||
<Tabs.Tab value="selesai" onClick={() => { navigate("?status=selesai") }}>Selesai ({dataCount?.data?.selesai || 0})</Tabs.Tab>
|
}}
|
||||||
<Tabs.Tab value="ditolak" onClick={() => { navigate("?status=ditolak") }}>Ditolak ({dataCount?.data?.ditolak || 0})</Tabs.Tab>
|
>
|
||||||
|
Semua ({dataCount?.data?.semua || 0})
|
||||||
|
</Tabs.Tab>
|
||||||
|
<Tabs.Tab
|
||||||
|
value="antrian"
|
||||||
|
onClick={() => {
|
||||||
|
navigate("?status=antrian");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Antrian ({dataCount?.data?.antrian || 0})
|
||||||
|
</Tabs.Tab>
|
||||||
|
<Tabs.Tab
|
||||||
|
value="diterima"
|
||||||
|
onClick={() => {
|
||||||
|
navigate("?status=diterima");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Diterima ({dataCount?.data?.diterima || 0})
|
||||||
|
</Tabs.Tab>
|
||||||
|
<Tabs.Tab
|
||||||
|
value="dikerjakan"
|
||||||
|
onClick={() => {
|
||||||
|
navigate("?status=dikerjakan");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Dikerjakan ({dataCount?.data?.dikerjakan || 0})
|
||||||
|
</Tabs.Tab>
|
||||||
|
<Tabs.Tab
|
||||||
|
value="selesai"
|
||||||
|
onClick={() => {
|
||||||
|
navigate("?status=selesai");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Selesai ({dataCount?.data?.selesai || 0})
|
||||||
|
</Tabs.Tab>
|
||||||
|
<Tabs.Tab
|
||||||
|
value="ditolak"
|
||||||
|
onClick={() => {
|
||||||
|
navigate("?status=ditolak");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Ditolak ({dataCount?.data?.ditolak || 0})
|
||||||
|
</Tabs.Tab>
|
||||||
</Tabs.List>
|
</Tabs.List>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
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("/", () =>
|
const { data, mutate, isLoading } = useSwr("/", () =>
|
||||||
apiFetch.api.pengaduan.list.get({
|
apiFetch.api.pengaduan.list.get({
|
||||||
query: {
|
query: {
|
||||||
status,
|
status,
|
||||||
search: "",
|
search: value,
|
||||||
take: "",
|
take: "",
|
||||||
page: ""
|
page: "",
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
mutate();
|
mutate();
|
||||||
}, [status]);
|
}, [status, value]);
|
||||||
|
|
||||||
|
|
||||||
if (isLoading)
|
if (isLoading)
|
||||||
return (
|
return (
|
||||||
@@ -99,94 +148,118 @@ function ListPengaduan({ status }: { status: string }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack gap="xl">
|
<Stack gap="xl">
|
||||||
{
|
<Group grow>
|
||||||
list.length === 0 ? (
|
<Input
|
||||||
<Flex justify="center" align="center" py={"xl"}>
|
value={value}
|
||||||
<Stack gap={4} align="center">
|
placeholder="Cari pengaduan..."
|
||||||
<IconFileSad size={32} color="gray" />
|
onChange={(event) => setValue(event.currentTarget.value)}
|
||||||
<Text c="dimmed" size="sm">
|
leftSection={<IconSearch size={16} />}
|
||||||
No pengaduan have been added yet.
|
rightSectionPointerEvents="all"
|
||||||
</Text>
|
rightSection={
|
||||||
</Stack>
|
<CloseButton
|
||||||
</Flex>
|
aria-label="Clear input"
|
||||||
) :
|
onClick={() => setValue('')}
|
||||||
list.map((v: any) => (
|
style={{ display: value ? undefined : 'none' }}
|
||||||
<Card
|
/>
|
||||||
key={v.id}
|
}
|
||||||
radius="lg"
|
/>
|
||||||
p="xl"
|
{/* <Group justify="flex-end">
|
||||||
withBorder
|
<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>
|
||||||
style={{
|
<Pagination total={Number(data?.data?.length)} value={page} onChange={setPage} withPages={false} />
|
||||||
background:
|
</Group> */}
|
||||||
"linear-gradient(145deg, rgba(25,25,25,0.95), rgba(45,45,45,0.85))",
|
</Group>
|
||||||
borderColor: "rgba(100,100,100,0.2)",
|
{list.length === 0 ? (
|
||||||
boxShadow: "0 0 20px rgba(0,255,200,0.08)",
|
<Flex justify="center" align="center" py={"xl"}>
|
||||||
}}
|
<Stack gap={4} align="center">
|
||||||
>
|
<IconFileSad size={32} color="gray" />
|
||||||
<Stack gap="md">
|
<Text c="dimmed" size="sm">
|
||||||
<Flex align="center" justify="space-between">
|
No pengaduan have been added yet.
|
||||||
<Flex direction={"column"}>
|
</Text>
|
||||||
<Title order={3} c="gray.2">
|
</Stack>
|
||||||
{v.title}
|
</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}
|
||||||
|
</Title>
|
||||||
|
<Group>
|
||||||
|
<Title order={6} c="gray.5">
|
||||||
|
#{v.noPengaduan}
|
||||||
</Title>
|
</Title>
|
||||||
<Group>
|
<Text size="sm" c="dimmed">
|
||||||
<Title order={6} c="gray.5">
|
{v.updatedAt}
|
||||||
#{v.noPengaduan}
|
</Text>
|
||||||
</Title>
|
</Group>
|
||||||
<Text size="sm" c="dimmed">
|
|
||||||
{v.updatedAt}
|
|
||||||
</Text>
|
|
||||||
</Group>
|
|
||||||
</Flex>
|
|
||||||
<Badge
|
|
||||||
size="xl"
|
|
||||||
variant="light"
|
|
||||||
radius="sm"
|
|
||||||
color={v.status === "diterima" ? "green" : v.status === "ditolak" ? "red" : v.status === "selesai" ? "blue" : v.status === "dikerjakan" ? "purple" : "yellow"}
|
|
||||||
style={{ textTransform: "none" }}
|
|
||||||
>
|
|
||||||
{v.status}
|
|
||||||
</Badge>
|
|
||||||
</Flex>
|
</Flex>
|
||||||
<Divider my={0} />
|
<Badge
|
||||||
<Stack gap="sm">
|
size="xl"
|
||||||
<Flex direction={"column"} justify="flex-start">
|
variant="light"
|
||||||
<Group gap="xs">
|
radius="sm"
|
||||||
<IconClockHour3 size={20} color="white" />
|
color={
|
||||||
<Text size="md" c="white">
|
v.status === "diterima"
|
||||||
Tanggal Aduan
|
? "green"
|
||||||
</Text>
|
: v.status === "ditolak"
|
||||||
</Group>
|
? "red"
|
||||||
<Text size="md">
|
: v.status === "selesai"
|
||||||
{v.createdAt}
|
? "blue"
|
||||||
|
: v.status === "dikerjakan"
|
||||||
|
? "purple"
|
||||||
|
: "yellow"
|
||||||
|
}
|
||||||
|
style={{ textTransform: "none" }}
|
||||||
|
>
|
||||||
|
{v.status}
|
||||||
|
</Badge>
|
||||||
|
</Flex>
|
||||||
|
<Divider my={0} />
|
||||||
|
<Stack gap="sm">
|
||||||
|
<Flex direction={"column"} justify="flex-start">
|
||||||
|
<Group gap="xs">
|
||||||
|
<IconClockHour3 size={20} color="white" />
|
||||||
|
<Text size="md" c="white">
|
||||||
|
Tanggal Aduan
|
||||||
</Text>
|
</Text>
|
||||||
</Flex>
|
</Group>
|
||||||
<Flex direction={"column"} justify="flex-start">
|
<Text size="md">{v.createdAt}</Text>
|
||||||
<Group gap="xs">
|
</Flex>
|
||||||
<IconMapPin size={20} color="white" />
|
<Flex direction={"column"} justify="flex-start">
|
||||||
<Text size="md" c="white">
|
<Group gap="xs">
|
||||||
Lokasi
|
<IconMapPin size={20} color="white" />
|
||||||
</Text>
|
<Text size="md" c="white">
|
||||||
</Group>
|
Lokasi
|
||||||
<Text size="md">
|
|
||||||
{v.location}
|
|
||||||
</Text>
|
</Text>
|
||||||
</Flex>
|
</Group>
|
||||||
<Flex direction={"column"} justify="flex-start">
|
<Text size="md">{v.location}</Text>
|
||||||
<Group gap="xs">
|
</Flex>
|
||||||
<IconAlignJustified size={20} color="white" />
|
<Flex direction={"column"} justify="flex-start">
|
||||||
<Text size="md" c="white">
|
<Group gap="xs">
|
||||||
Detail
|
<IconAlignJustified size={20} color="white" />
|
||||||
</Text>
|
<Text size="md" c="white">
|
||||||
</Group>
|
Detail
|
||||||
<Text size="md">
|
|
||||||
{v.detail}
|
|
||||||
</Text>
|
</Text>
|
||||||
</Flex>
|
</Group>
|
||||||
</Stack>
|
<Text size="md">{v.detail}</Text>
|
||||||
|
</Flex>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Card>
|
</Stack>
|
||||||
))}
|
</Card>
|
||||||
|
))
|
||||||
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -500,7 +500,7 @@ const PengaduanRoute = new Elysia({
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status && status !== "all") {
|
if (status && status !== "semua") {
|
||||||
where = {
|
where = {
|
||||||
...where,
|
...where,
|
||||||
status: status
|
status: status
|
||||||
@@ -511,7 +511,7 @@ const PengaduanRoute = new Elysia({
|
|||||||
skip,
|
skip,
|
||||||
take: !take ? 10 : Number(take),
|
take: !take ? 10 : Number(take),
|
||||||
orderBy: {
|
orderBy: {
|
||||||
createdAt: "asc"
|
createdAt: "desc"
|
||||||
},
|
},
|
||||||
where,
|
where,
|
||||||
select: {
|
select: {
|
||||||
|
|||||||
Reference in New Issue
Block a user