diff --git a/src/index.tsx b/src/index.tsx
index 10e4028..83c1edd 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -13,6 +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"
const Docs = new Elysia({
tags: ["docs"],
@@ -40,6 +41,11 @@ const app = new Elysia()
.use(Api)
.use(Docs)
.use(Auth)
+ .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 7682a34..e5fdd04 100644
--- a/src/pages/scr/dashboard/pengaduan/list_page.tsx
+++ b/src/pages/scr/dashboard/pengaduan/list_page.tsx
@@ -12,11 +12,10 @@ import {
Title
} from "@mantine/core";
import { useShallowEffect } from "@mantine/hooks";
-import { showNotification } from "@mantine/notifications";
-import { IconAlignJustified, IconClockHour3, IconMapPin } from "@tabler/icons-react";
+import { IconAlignJustified, IconClockHour3, IconFileSad, IconMapPin } from "@tabler/icons-react";
import { useLocation, useNavigate } from "react-router-dom";
import useSwr from "swr";
-import { proxy, subscribe } from "valtio";
+import { proxy } from "valtio";
const state = proxy({ reload: "" });
function reloadState() {
@@ -26,9 +25,7 @@ function reloadState() {
export default function PengaduanListPage() {
const { search } = useLocation();
const query = new URLSearchParams(search);
-
const status = query.get("status");
- console.log(status, "status");
return (
-
+
);
@@ -46,47 +43,40 @@ 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)
+ );
+
return (
- { navigate("?status=all") }}>Semua
- { navigate("?status=antrian") }}>Antrian
- { navigate("?status=diterima") }}>Diterima
- { navigate("?status=dikerjakan") }}>Dikerjakan
- { navigate("?status=ditolak") }}>Ditolak
- { navigate("?status=selesai") }}>Selesai
+ { 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})
);
}
-function ListPengaduan() {
+function ListPengaduan({ status }: { status: string }) {
const { data, mutate, isLoading } = useSwr("/", () =>
- apiFetch.api.credential.list.get(),
+ apiFetch.api.pengaduan.list.get({
+ query: {
+ status,
+ search: "",
+ take: "",
+ page: ""
+ },
+ }),
);
useShallowEffect(() => {
- const unsubscribe = subscribe(state, () => mutate());
- return () => unsubscribe();
- }, []);
+ mutate();
+ }, [status]);
- async function handleRemove(id: string) {
- try {
- await apiFetch.api.credential.rm.delete({ id });
- showNotification({
- color: "teal",
- title: "Credential Deleted",
- message: "The credential was successfully removed.",
- });
- reloadState();
- } catch {
- showNotification({
- color: "red",
- title: "Error",
- message: "Failed to delete credential. Please try again.",
- });
- }
- }
if (isLoading)
return (
@@ -100,87 +90,103 @@ function ListPengaduan() {
}}
>
- Loading credentials...
+ Loading pengaduan...
);
- const list = data?.data?.list || [];
+ const list = data?.data || [];
return (
-
-
-
-
-
- Dompet Hilang
-
-
-
- #PGD-061125-001
-
-
- updated 2 minutes ago
+
+ {
+ list.length === 0 ? (
+
+
+
+
+ No pengaduan have been added yet.
-
+
-
- Antrian
-
-
-
-
-
-
-
-
- Tanggal Aduan
-
-
-
- 05 November 2025
-
-
-
-
-
-
- Lokasi
-
-
-
- Jalan Darmasaba Raya no 77
-
-
-
-
-
-
- Detail
-
-
-
- Lorem, ipsum dolor sit amet consectetur adipisicing elit. Quis, obcaecati. Sint natus culpa temporibus neque quasi expedita ratione, facere optio incidunt quibusdam suscipit nam nemo delectus beatae similique velit obcaecati?
-
-
-
-
-
+ ) :
+ 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/server/lib/get-last-updated.ts b/src/server/lib/get-last-updated.ts
new file mode 100644
index 0000000..7936390
--- /dev/null
+++ b/src/server/lib/get-last-updated.ts
@@ -0,0 +1,21 @@
+export function getLastUpdated(date: string | Date): string {
+ const now = new Date();
+ const updated = new Date(date);
+ const diffMs = now.getTime() - updated.getTime();
+
+ const diffMinutes = Math.floor(diffMs / (1000 * 60));
+ const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
+ const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
+
+ if (diffMinutes < 1) return "baru saja";
+ if (diffMinutes < 60) return `${diffMinutes} menit lalu`;
+ if (diffHours < 24) return `${diffHours} jam lalu`;
+ if (diffDays < 7) return `${diffDays} hari lalu`;
+
+ // kalau sudah lebih dari seminggu, tampilkan tanggal
+ return updated.toLocaleDateString("id-ID", {
+ day: "numeric",
+ month: "long",
+ year: "numeric",
+ });
+}
diff --git a/src/server/routes/pengaduan_route.ts b/src/server/routes/pengaduan_route.ts
index cf62aa9..80efa46 100644
--- a/src/server/routes/pengaduan_route.ts
+++ b/src/server/routes/pengaduan_route.ts
@@ -1,5 +1,6 @@
import Elysia, { t } from "elysia"
import type { StatusPengaduan } from "generated/prisma"
+import { getLastUpdated } from "../lib/get-last-updated"
import { generateNoPengaduan } from "../lib/no-pengaduan"
import { normalizePhoneNumber } from "../lib/normalizePhone"
import { prisma } from "../lib/prisma"
@@ -462,7 +463,137 @@ const PengaduanRoute = new Elysia({
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))
+
+ let where: any = {
+ isActive: true,
+ OR: [
+ {
+ title: {
+ contains: search ?? "",
+ mode: "insensitive"
+ },
+ },
+ {
+ noPengaduan: {
+ contains: search ?? "",
+ mode: "insensitive"
+ },
+ },
+ {
+ detail: {
+ contains: search ?? "",
+ mode: "insensitive"
+ },
+ },
+ {
+ Warga: {
+ phone: {
+ contains: search ?? "",
+ mode: "insensitive"
+ },
+ },
+ }
+ ]
}
- );
+
+ if (status && status !== "all") {
+ where = {
+ ...where,
+ status: status
+ }
+ }
+
+ const data = await prisma.pengaduan.findMany({
+ skip,
+ take: !take ? 10 : Number(take),
+ orderBy: {
+ createdAt: "asc"
+ },
+ where,
+ select: {
+ id: true,
+ noPengaduan: true,
+ title: true,
+ detail: true,
+ location: true,
+ status: true,
+ createdAt: true,
+ updatedAt: true,
+ CategoryPengaduan: {
+ select: {
+ name: true
+ }
+ },
+ Warga: {
+ select: {
+ name: true,
+ }
+ }
+ }
+ })
+
+ const dataFix = data.map((item) => {
+ return {
+ noPengaduan: item.noPengaduan,
+ title: item.title,
+ detail: item.detail,
+ status: item.status,
+ location: item.location,
+ createdAt: item.createdAt.toLocaleDateString("id-ID", { day: "numeric", month: "long", year: "numeric" }),
+ updatedAt: 'terakhir diperbarui ' + getLastUpdated(item.updatedAt),
+ }
+ })
+
+ return dataFix
+ }, {
+ query: t.Object({
+ take: t.String({ optional: true }),
+ page: t.String({ optional: true }),
+ search: t.String({ optional: true }),
+ status: t.String({ optional: true }),
+ }),
+ detail: {
+ summary: "List Pengaduan Warga",
+ description: `tool untuk mendapatkan list pengaduan warga`,
+ }
+ })
+ .get("/count", async ({ query }) => {
+ const counts = await prisma.pengaduan.groupBy({
+ by: ['status'],
+ where: {
+ isActive: true,
+ },
+ _count: {
+ status: true,
+ },
+ });
+
+ const grouped = Object.fromEntries(
+ counts.map(c => [c.status, c._count.status])
+ );
+
+ const total = await prisma.pengaduan.count({
+ where: { isActive: true },
+ });
+
+ return {
+ antrian: grouped?.antrian || 0,
+ diterima: grouped?.diterima || 0,
+ dikerjakan: grouped?.dikerjakan || 0,
+ ditolak: grouped?.ditolak || 0,
+ selesai: grouped?.selesai || 0,
+ semua: total,
+ };
+ }, {
+ detail: {
+ summary: "Jumlah Pengaduan Warga",
+ description: `tool untuk mendapatkan jumlah pengaduan warga`,
+ }
+ })
+ ;
export default PengaduanRoute