diff --git a/src/components/KategoriPengaduan.tsx b/src/components/KategoriPengaduan.tsx
new file mode 100644
index 0000000..ffd3f54
--- /dev/null
+++ b/src/components/KategoriPengaduan.tsx
@@ -0,0 +1,234 @@
+import apiFetch from "@/lib/apiFetch";
+import {
+ ActionIcon,
+ Button,
+ Divider,
+ Flex,
+ Group,
+ Input,
+ Modal,
+ Stack,
+ Table,
+ Title,
+ Tooltip
+} from "@mantine/core";
+import { useDisclosure, useShallowEffect } from "@mantine/hooks";
+import { IconEdit, IconPlus } from "@tabler/icons-react";
+import { useState } from "react";
+import useSWR from "swr";
+import notification from "./notificationGlobal";
+
+export default function KategoriPengaduan() {
+ 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 { data, mutate, isLoading } = useSWR("/", () =>
+ apiFetch.api.pengaduan.category.get(),
+ );
+ const list = data?.data || [];
+ const [dataEdit, setDataEdit] = useState({
+ id: "",
+ name: "",
+ });
+ const [dataTambah, setDataTambah] = useState("")
+
+ 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.log(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.log(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);
+ } else {
+ setBtnDisable(false);
+ }
+
+ if (kat === 'name') {
+ if (aksi === 'edit') {
+ setDataEdit({ ...dataEdit, name: value });
+ } else {
+ setDataTambah(value);
+ }
+ }
+ }
+
+ useShallowEffect(() => {
+ if (dataEdit.name.length > 0) {
+ setBtnDisable(false);
+ }
+ }, [dataEdit.id]);
+
+ useShallowEffect(() => {
+ if (dataTambah.length > 0) {
+ setBtnDisable(false);
+ }
+ }, [dataTambah]);
+
+ return (
+ <>
+ {/* Modal Edit */}
+
+
+
+ onValidation({ kat: 'name', value: e.target.value, aksi: 'edit' })} />
+
+
+
+
+
+
+
+
+ {/* Modal Tambah */}
+
+
+
+ onValidation({ kat: 'name', value: e.target.value, aksi: 'tambah' })} />
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Kategori Pengaduan
+
+
+ }
+ onClick={openTambah}
+ >
+ Tambah
+
+
+
+
+
+
+
+
+ Kategori
+ Aksi
+
+
+
+ {list?.map((v: any) => (
+
+ {v.name}
+
+
+ chooseEdit({ data: v })}
+ >
+
+
+
+
+
+ ))}
+
+
+
+
+ >
+ );
+}
diff --git a/src/pages/scr/dashboard/setting/detail_setting_page.tsx b/src/pages/scr/dashboard/setting/detail_setting_page.tsx
index 230e5ae..767f4cb 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 KategoriPengaduan from "@/components/KategoriPengaduan";
import {
Button,
Card,
@@ -80,7 +81,7 @@ export default function DetailSettingPage() {
}}
>
{type === "cat-pengaduan" ? (
-
+
) : type === "cat-pelayanan" ? (
) : type === "desa" ? (
diff --git a/src/server/routes/pengaduan_route.ts b/src/server/routes/pengaduan_route.ts
index 97c6021..f4543e5 100644
--- a/src/server/routes/pengaduan_route.ts
+++ b/src/server/routes/pengaduan_route.ts
@@ -6,7 +6,7 @@ import { mimeToExtension } from "../lib/mimetypeToExtension"
import { generateNoPengaduan } from "../lib/no-pengaduan"
import { normalizePhoneNumber } from "../lib/normalizePhone"
import { prisma } from "../lib/prisma"
-import { catFile, defaultConfigSF, testConnection, uploadFile, uploadFileBase64, uploadFileToFolder } from "../lib/seafile"
+import { catFile, defaultConfigSF, testConnection, uploadFile, uploadFileBase64 } from "../lib/seafile"
const PengaduanRoute = new Elysia({
prefix: "pengaduan",
@@ -100,27 +100,21 @@ const PengaduanRoute = new Elysia({
// --- PENGADUAN ---
.post("/create", async ({ body }) => {
- const { title, detail, location, imageName, idCategory, idWarga, phone } = body
- let imageFix = imageName
+ const { judulPengaduan, detailPengaduan, lokasi, namaGambar, kategoriId, wargaId, noTelepon } = body
+ let imageFix = namaGambar
const noPengaduan = await generateNoPengaduan()
- let idCategoryFix = idCategory
- let idWargaFix = idWarga
+ let idCategoryFix = kategoriId
+ let idWargaFix = wargaId
const category = await prisma.categoryPengaduan.findUnique({
where: {
- id: idCategory,
+ id: kategoriId,
}
})
- // if (!imageData && !imageMime) {
- // const ext = mimeToExtension(imageMime)
- // imageFix = `${uuidv4()}.${ext}`
- // await uploadFileToFolder(defaultConfigSF, { name: imageFix, data: imageData }, "pengaduan")
- // }
-
if (!category) {
const cariCategory = await prisma.categoryPengaduan.findFirst({
where: {
- name: idCategory,
+ name: kategoriId,
}
})
@@ -134,12 +128,12 @@ const PengaduanRoute = new Elysia({
const warga = await prisma.warga.findUnique({
where: {
- id: idWarga,
+ id: wargaId,
}
})
if (!warga) {
- const nomorHP = normalizePhoneNumber({ phone })
+ const nomorHP = normalizePhoneNumber({ phone: noTelepon })
const cariWarga = await prisma.warga.findUnique({
where: {
phone: nomorHP,
@@ -149,7 +143,7 @@ const PengaduanRoute = new Elysia({
if (!cariWarga) {
const wargaCreate = await prisma.warga.create({
data: {
- name: idWarga,
+ name: wargaId,
phone: nomorHP,
},
select: {
@@ -165,11 +159,11 @@ const PengaduanRoute = new Elysia({
const pengaduan = await prisma.pengaduan.create({
data: {
- title,
- detail,
+ title: judulPengaduan,
+ detail: detailPengaduan,
idCategory: idCategoryFix,
idWarga: idWargaFix,
- location,
+ location: lokasi,
image: imageFix,
noPengaduan,
},
@@ -189,20 +183,77 @@ const PengaduanRoute = new Elysia({
}
})
- return { success: true, message: 'pengaduan sudah dibuat' }
+ return { success: true, message: 'pengaduan sudah dibuat dengan nomer ' + noPengaduan + ', nomer ini akan digunakan untuk mengakses pengaduan ini' }
}, {
body: t.Object({
- title: t.String({ minLength: 1, error: "title harus diisi" }),
- detail: t.String({ minLength: 1, error: "detail harus diisi" }),
- location: t.String({ minLength: 1, error: "location harus diisi" }),
- imageName: t.String({ optional: true, description: "nama file gambar yg telah diupload" }),
- idCategory: t.String({ minLength: 1, error: "idCategory harus diisi" }),
- idWarga: t.String({ minLength: 1, error: "idWarga harus diisi" }),
- phone: t.String({ minLength: 1, error: "phone harus diisi" }),
+ judulPengaduan: t.String({
+ minLength: 3,
+ error: "Judul pengaduan harus diisi dan minimal 3 karakter",
+ examples: ["Sampah menumpuk di depan rumah"],
+ description: "Judul singkat dari pengaduan warga"
+ }),
+
+ detailPengaduan: t.String({
+ minLength: 5,
+ error: "Deskripsi pengaduan harus diisi dan minimal 10 karakter",
+ examples: ["Terdapat sampah yang menumpuk selama seminggu di depan rumah saya"],
+ description: "Penjelasan lebih detail mengenai pengaduan"
+ }),
+
+ lokasi: t.String({
+ minLength: 5,
+ error: "Lokasi pengaduan harus diisi",
+ examples: ["Jl. Raya No. 1, RT 01 RW 02, Darmasaba"],
+ description: "Alamat atau titik lokasi pengaduan"
+ }),
+
+ namaGambar: t.String({
+ optional: true,
+ examples: ["sampah.jpg"],
+ description: "Nama file gambar yang telah diupload (opsional)"
+ }),
+
+ kategoriId: t.String({
+ minLength: 1,
+ error: "ID kategori pengaduan harus diisi",
+ examples: ["kebersihan"],
+ description: "ID atau nama kategori pengaduan (contoh: kebersihan, keamanan, lainnya)"
+ }),
+
+ wargaId: t.String({
+ minLength: 1,
+ error: "ID warga harus diisi",
+ examples: ["budiman"],
+ description: "ID unik warga yang melapor (jika sudah terdaftar)"
+ }),
+
+ noTelepon: t.String({
+ minLength: 1,
+ error: "Nomor telepon harus diisi",
+ examples: ["08123456789", "+628123456789"],
+ description: "Nomor telepon warga pelapor"
+ }),
}),
+
detail: {
- summary: "Create Pengaduan Warga",
- description: `tool untuk membuat pengaduan warga`,
+ summary: "Buat Pengaduan Warga",
+ description: `
+Endpoint ini digunakan untuk membuat data pengaduan (laporan) baru dari warga.
+
+Alur proses:
+1. Sistem memvalidasi kategori pengaduan berdasarkan ID.
+ - Jika ID kategori tidak ditemukan, sistem akan mencari berdasarkan nama kategori.
+ - Jika tetap tidak ditemukan, kategori akan diset menjadi "lainnya".
+2. Sistem memvalidasi data warga berdasarkan ID.
+ - Jika warga tidak ditemukan, sistem akan mencari berdasarkan nomor telepon.
+ - Jika tetap tidak ditemukan, data warga baru akan dibuat secara otomatis.
+3. Sistem menghasilkan nomor pengaduan unik (noPengaduan).
+4. Data pengaduan akan disimpan ke database, termasuk judul, detail, lokasi, gambar (opsional), dan data warga.
+5. Sistem juga membuat catatan riwayat awal pengaduan dengan deskripsi "Pengaduan dibuat".
+
+Respon:
+- success: true jika pengaduan berhasil dibuat.
+- message: berisi pesan sukses dan nomor pengaduan yang dapat digunakan untuk melacak status pengaduan.`,
tags: ["mcp"]
}
})
@@ -262,6 +313,11 @@ const PengaduanRoute = new Elysia({
const data = await prisma.pengaduan.findUnique({
where: {
id,
+ OR: [
+ {
+ noPengaduan: id
+ }
+ ]
},
select: {
id: true,
@@ -339,7 +395,7 @@ const PengaduanRoute = new Elysia({
}, {
detail: {
summary: "Detail Pengaduan Warga",
- description: `tool untuk mendapatkan detail pengaduan warga / history pengaduan / mengecek status pengaduan`,
+ description: `tool untuk mendapatkan detail pengaduan warga / history pengaduan / mengecek status pengaduan berdasarkan id atau nomer Pengaduan`,
tags: ["mcp"]
}
})