diff --git a/prisma/data/desa/layanan/pelayanaPendudukNonPermanen.json b/prisma/data/desa/layanan/pelayanaPendudukNonPermanen.json
new file mode 100644
index 00000000..2259bea4
--- /dev/null
+++ b/prisma/data/desa/layanan/pelayanaPendudukNonPermanen.json
@@ -0,0 +1,7 @@
+[
+ {
+ "id": "1",
+ "name": "Pelayanan Penduduk Non-Permanent",
+ "deskripsi": "
Surat Keterangan Penduduk Non-Permanent adalah dokumen yang dikeluarkan oleh pihak berwenang untuk memberikan keterangan bahwa seseorang atau kelompok orang memiliki status penduduk non-permanent di suatu wilayah. Dokumen ini biasanya digunakan untuk keperluan administratif atau legal, seperti mendapatkan akses ke layanan kesehatan, pendidikan, atau pelayanan publik lainnya.
"
+ }
+]
\ No newline at end of file
diff --git a/prisma/data/desa/layanan/pelayananPerizinanBerusaha.json b/prisma/data/desa/layanan/pelayananPerizinanBerusaha.json
index 1e7d9025..8df36cf0 100644
--- a/prisma/data/desa/layanan/pelayananPerizinanBerusaha.json
+++ b/prisma/data/desa/layanan/pelayananPerizinanBerusaha.json
@@ -1,8 +1,8 @@
[
{
"id": "1",
- "name": "I.B Surya Prabhawa Manuaba, S.H., M.H.",
- "deskripsi": "I.B Surya Prabhawa Manuaba, S.H., M.H., adalah Perbekel Darmasaba periode 2021-2027, seorang advokat, pendiri Mantra Legal Consultants & Advocates, serta aktif di bidang musik dan akademis. Dia menempuh pendidikan hukum di Universitas Udayana dan Universitas Mahasaraswati Denpasar, serta memiliki pengalaman luas di berbagai organisasi dan kepemimpinan.
",
+ "name": "Pelayanan Perizinan Berusaha Berbasis Risiko Melalui Sistem ONLINE SINGLE SUBMISSION (OSS)",
+ "deskripsi": "Penyelenggaraan Perizinan Berusaha Berbasis Risiko melalui Sistem Online Single Submission (OSS) merupakan pelaksanaan Undang-Undang Nomor 11 Tahun 2020 Tentang Cipta Kerja. OSS Berbasis Risiko wajib digunakan oleh Pelaku Usaha, Kementerian/Lembaga, Pemerintah Daerah, Administrator Kawasan Ekonomi Khusus (KEK), dan Badan Pengusahaan Kawasan Perdagangan Bebas Pelabuhan Bebas (KPBPB).Berdasarkan Peraturan Pemerintah Nomor 5 Tahun 2021 terdapat 1.702 kegiatan usaha yang terdiri atas 1.349 Klasifikasi Baku Lapangan Usaha Indonesia (KBLI) yang sudah diimplementasikan dalam Sistem OSS Berbasis Risiko.
",
"link" : "https://oss.go.id/"
}
]
\ No newline at end of file
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index e6dacf37..374834dd 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -69,6 +69,8 @@ model FileStorage {
GalleryFoto GalleryFoto[]
PelayananSuratKeterangan PelayananSuratKeterangan[]
+
+ Penghargaan Penghargaan[]
}
//========================================= MENU PPID ========================================= //
@@ -408,6 +410,20 @@ model PelayananPendudukNonPermanen {
isActive Boolean @default(true)
}
+// ========================================= PENGHARGAAN ========================================= //
+model Penghargaan {
+ id String @id @default(cuid())
+ name String
+ juara String
+ deskripsi String @db.Text
+ image FileStorage @relation(fields: [imageId], references: [id])
+ imageId String
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+ deletedAt DateTime @default(now())
+ isActive Boolean @default(true)
+}
+
// ========================================= MENU KESEHATAN ========================================= //
// ========================================= DATA KESEHATAN WARGA ========================================= //
diff --git a/prisma/seed.ts b/prisma/seed.ts
index 361c7ee2..82a63aec 100644
--- a/prisma/seed.ts
+++ b/prisma/seed.ts
@@ -11,6 +11,7 @@ import profilePPID from "./data/ppid/profile-ppid/profilePPid.json";
import visiMisiPPID from "./data/ppid/visi-misi-ppid/visimisiPPID.json";
import strukturPPID from "./data/ppid/struktur-ppid/strukturPPID.json";
import pelayananPerizinanBerusaha from "./data/desa/layanan/pelayananPerizinanBerusaha.json";
+import pelayananPendudukNonPermanen from "./data/desa/layanan/pelayanaPendudukNonPermanen.json";
(async () => {
for (const l of layanan) {
@@ -50,6 +51,25 @@ import pelayananPerizinanBerusaha from "./data/desa/layanan/pelayananPerizinanBe
console.log("pelayanan perizinan berusaha success ...");
+ for (const l of pelayananPendudukNonPermanen) {
+ await prisma.pelayananPendudukNonPermanen.upsert({
+ where: {
+ id: l.id,
+ },
+ update: {
+ name: l.name,
+ deskripsi: l.deskripsi,
+ },
+ create: {
+ id: l.id,
+ name: l.name,
+ deskripsi: l.deskripsi,
+ },
+ });
+ }
+
+ console.log("pelayanan perizinan berusaha success ...");
+
for (const s of strukturPPID) {
await prisma.strukturPPID.upsert({
where: {
diff --git a/src/app/admin/(dashboard)/_state/desa/layananDesa.ts b/src/app/admin/(dashboard)/_state/desa/layananDesa.ts
index 9d48cfc4..558d4672 100644
--- a/src/app/admin/(dashboard)/_state/desa/layananDesa.ts
+++ b/src/app/admin/(dashboard)/_state/desa/layananDesa.ts
@@ -50,6 +50,25 @@ const pelayananPerizinanBerusahaForm = {
link: "",
};
+const templatePelayananPendudukNonPermanen = z.object({
+ name: z.string().min(3, "Nama minimal 3 karakter"),
+ deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"),
+});
+
+type pelayananPendudukNonPermanenForm =
+ Prisma.PelayananPendudukNonPermanenGetPayload<{
+ select: {
+ id: true;
+ name: true;
+ deskripsi: true;
+ };
+ }>;
+
+const pelayananPendudukNonPermanenForm = {
+ name: "",
+ deskripsi: "",
+};
+
const suratKeterangan = proxy({
create: {
@@ -572,10 +591,128 @@ const pelayananPerizinanBerusaha = proxy({
},
});
+const pelayananPendudukNonPermanen = proxy({
+ findById: {
+ data: null as pelayananPendudukNonPermanenForm | null,
+ loading: false,
+ initialize() {
+ pelayananPendudukNonPermanen.findById.data = {
+ id: "",
+ name: "",
+ deskripsi: "",
+ } as pelayananPendudukNonPermanenForm;
+ },
+ async load(id: string) {
+ try {
+ pelayananPendudukNonPermanen.findById.loading = true;
+ const res = await fetch(
+ `/api/desa/layanan/pelayananpenduduknonpermanen/${id}`
+ );
+ if (res.ok) {
+ const data = await res.json();
+ pelayananPendudukNonPermanen.findById.data = data.data ?? null;
+ } else {
+ console.error(
+ "Failed to fetch pelayanan penduduk non permanen:",
+ res.statusText
+ );
+ pelayananPendudukNonPermanen.findById.data = null;
+ }
+ } catch (error) {
+ console.error("Error fetching pelayanan penduduk non permanen:", error);
+ pelayananPendudukNonPermanen.findById.data = null;
+ }
+ },
+ },
+ update: {
+ id: "",
+ form: { ...pelayananPendudukNonPermanenForm },
+ loading: false,
+ async load(id: string) {
+ if (!id) {
+ toast.warn("ID tidak boleh kosong");
+ return null;
+ }
+ try {
+ const response = await fetch(
+ `/api/desa/layanan/pelayananpenduduknonpermanen/${id}`,
+ {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ }
+ );
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+ const result = await response.json();
+ if (result?.success) {
+ const data = result.data;
+ pelayananPendudukNonPermanen.update.id = data.id;
+ pelayananPendudukNonPermanen.update.form = {
+ name: data.name,
+ deskripsi: data.deskripsi,
+ };
+ return data;
+ } else {
+ throw new Error(result.message || "Gagal memuat data");
+ }
+ } catch (error) {
+ console.error("Error fetching pelayanan penduduk non permanen:", error);
+ toast.error(
+ error instanceof Error
+ ? error.message
+ : "Gagal memuat data"
+ );
+ return null;
+ }
+ },
+ async update(data: pelayananPendudukNonPermanenForm) {
+ const cek = templatePelayananPendudukNonPermanen.safeParse(data);
+ if (!cek.success) {
+ const errors = cek.error.issues
+ .map((issue) => `${issue.path.join(".")}: ${issue.message}`)
+ .join(", ");
+ toast.error(`Form tidak valid: ${errors}`);
+ return;
+ }
+
+ try {
+ pelayananPendudukNonPermanen.update.loading = true;
+ const res = await fetch(
+ `/api/desa/layanan/pelayananpenduduknonpermanen/${data.id}`,
+ {
+ method: "PUT",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify(data),
+ }
+ );
+ if (res.ok) {
+ toast.success("Pelayanan penduduk non permanen berhasil diupdate");
+ await pelayananPendudukNonPermanen.findById.load(data.id);
+ } else {
+ toast.error("Gagal mengupdate pelayanan penduduk non permanen");
+ }
+ } catch (error) {
+ console.error("Error updating pelayanan penduduk non permanen:", error);
+ toast.error(
+ "Terjadi kesalahan saat mengupdate pelayanan penduduk non permanen"
+ );
+ } finally {
+ pelayananPendudukNonPermanen.update.loading = false;
+ }
+ },
+ },
+});
+
const stateLayananDesa = proxy({
suratKeterangan,
pelayananPerizinanBerusaha,
pelayananTelunjukSaktiDesa,
+ pelayananPendudukNonPermanen,
});
export default stateLayananDesa;
diff --git a/src/app/admin/(dashboard)/_state/desa/penghargaan.ts b/src/app/admin/(dashboard)/_state/desa/penghargaan.ts
new file mode 100644
index 00000000..2545ae1a
--- /dev/null
+++ b/src/app/admin/(dashboard)/_state/desa/penghargaan.ts
@@ -0,0 +1,221 @@
+import ApiFetch from "@/lib/api-fetch";
+import { Prisma } from "@prisma/client";
+import { toast } from "react-toastify";
+import { proxy } from "valtio";
+import { z } from "zod";
+
+const templateForm = z.object({
+ name: z.string().min(1).max(50),
+ juara: z.string().min(1).max(50),
+ deskripsi: z.string().min(1).max(5000),
+ imageId: z.string().min(1).max(50),
+});
+
+const defaultForm = {
+ name: "",
+ juara: "",
+ deskripsi: "",
+ imageId: "",
+};
+
+const penghargaanState = proxy({
+ create: {
+ form: { ...defaultForm },
+ loading: false,
+ async create() {
+ const cek = templateForm.safeParse(penghargaanState.create.form);
+ if (!cek.success) {
+ const err = `[${cek.error.issues
+ .map((v) => `${v.path.join(".")}`)
+ .join("\n")}] required`;
+ return toast.error(err);
+ }
+
+ try {
+ penghargaanState.create.loading = true;
+ const res = await ApiFetch.api.desa.penghargaan["create"].post(
+ penghargaanState.create.form
+ );
+ if (res.status === 200) {
+ penghargaanState.findMany.load();
+ return toast.success("success create");
+ }
+ console.log(res);
+ return toast.error("failed create");
+ } catch (error) {
+ console.log((error as Error).message);
+ } finally {
+ penghargaanState.create.loading = false;
+ }
+ },
+ },
+ findMany: {
+ data: null as
+ | Prisma.PenghargaanGetPayload<{
+ include: {
+ image: true;
+ };
+ }>[]
+ | null,
+ async load() {
+ const res = await ApiFetch.api.desa.penghargaan["find-many"].get();
+ if (res.status === 200) {
+ penghargaanState.findMany.data = res.data?.data ?? [];
+ }
+ },
+ },
+ findUnique: {
+ data: null as Prisma.PenghargaanGetPayload<{
+ include: {
+ image: true;
+ };
+ }> | null,
+ async load(id: string) {
+ try {
+ const res = await fetch(`/api/desa/penghargaan/${id}`);
+ if (res.ok) {
+ const data = await res.json();
+ penghargaanState.findUnique.data = data.data ?? null;
+ } else {
+ console.error("Failed to fetch data", res.status, res.statusText);
+ penghargaanState.findUnique.data = null;
+ }
+ } catch (error) {
+ console.error("Error loading penghargaan:", error);
+ penghargaanState.findUnique.data = null;
+ }
+ },
+ },
+ delete: {
+ loading: false,
+ async byId(id: string) {
+ if (!id) return toast.warn("ID tidak valid");
+
+ try {
+ penghargaanState.delete.loading = true;
+ const response = await fetch(`/api/desa/penghargaan/del/${id}`, {
+ method: "DELETE",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ });
+ const result = await response.json();
+
+ if (response.ok) {
+ toast.success(result.message || "Penghargaan berhasil dihapus");
+ await penghargaanState.findMany.load();
+ } else {
+ toast.error(result?.message || "Gagal menghapus penghargaan");
+ }
+ } catch (error) {
+ console.log((error as Error).message);
+ toast.error("Terjadi kesalahan saat menghapus penghargaan");
+ } finally {
+ penghargaanState.delete.loading = false;
+ }
+ },
+ },
+ edit: {
+ id: "",
+ form: { ...defaultForm },
+ loading: false,
+
+ async load(id: string) {
+ if (!id) {
+ toast.warn("ID tidak valid");
+ return null;
+ }
+
+ try {
+ const response = await fetch(`/api/desa/penghargaan/${id}`, {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ });
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+ const result = await response.json();
+ if (result?.success) {
+ const data = result.data;
+ this.id = data.id;
+ this.form = {
+ name: data.name,
+ juara: data.juara,
+ deskripsi: data.deskripsi,
+ imageId: data.imageId,
+ };
+ return data;
+ } else {
+ throw new Error(result?.message || "Gagal memuat data");
+ }
+ } catch (error) {
+ console.error("Error loading penghargaan:", error);
+ toast.error(
+ error instanceof Error ? error.message : "Gagal memuat data"
+ );
+ return null;
+ }
+ },
+ async update() {
+ const cek = templateForm.safeParse(penghargaanState.edit.form);
+ if (!cek.success) {
+ const err = `[${cek.error.issues
+ .map((v) => `${v.path.join(".")}`)
+ .join("\n")}] required`;
+ toast.error(err);
+ return false;
+ }
+
+ try {
+ penghargaanState.edit.loading = true;
+ const response = await fetch(
+ `/api/desa/penghargaan/${penghargaanState.edit.id}`,
+ {
+ method: "PUT",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({
+ name: this.form.name,
+ juara: this.form.juara,
+ deskripsi: this.form.deskripsi,
+ imageId: this.form.imageId,
+ }),
+ }
+ );
+
+ if (!response.ok) {
+ const errorData = await response.json().catch(() => ({}));
+ throw new Error(
+ errorData.message || `HTTP error! status: ${response.status}`
+ );
+ }
+ const result = await response.json();
+ if (result.success) {
+ toast.success("Berhasil update penghargaan");
+ await penghargaanState.findMany.load();
+ return true;
+ } else {
+ throw new Error(result?.message || "Gagal update penghargaan");
+ }
+ } catch (error) {
+ console.error("Error updating penghargaan:", error);
+ toast.error(
+ error instanceof Error
+ ? error.message
+ : "Terjadi kesalahan saat update penghargaan"
+ );
+ return false;
+ } finally {
+ penghargaanState.edit.loading = false;
+ }
+ },
+ reset() {
+ penghargaanState.edit.id = "";
+ penghargaanState.edit.form = { ...defaultForm };
+ },
+ },
+});
+export default penghargaanState;
\ No newline at end of file
diff --git a/src/app/admin/(dashboard)/desa/layanan/pelayanan_penduduk_non_permanent/edit/page.tsx b/src/app/admin/(dashboard)/desa/layanan/pelayanan_penduduk_non_permanent/edit/page.tsx
new file mode 100644
index 00000000..80841411
--- /dev/null
+++ b/src/app/admin/(dashboard)/desa/layanan/pelayanan_penduduk_non_permanent/edit/page.tsx
@@ -0,0 +1,103 @@
+'use client'
+/* eslint-disable react-hooks/exhaustive-deps */
+import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
+import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
+import colors from '@/con/colors';
+import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
+import { IconArrowBack } from '@tabler/icons-react';
+import { useParams, useRouter } from 'next/navigation';
+import { useEffect, useState } from 'react';
+import { toast } from 'react-toastify';
+import { useProxy } from 'valtio/utils';
+
+function EditPelayananPendudukNonPermanent() {
+ const router = useRouter();
+ const params = useParams()
+ const statePendudukNonPermanent = useProxy(stateLayananDesa.pelayananPendudukNonPermanen)
+ const [formData, setFormData] = useState({
+ name: statePendudukNonPermanent.findById.data?.name || '',
+ deskripsi: statePendudukNonPermanent.findById.data?.deskripsi || '',
+ })
+
+ useEffect(() => {
+ const loadPelayananPerizinan = async () => {
+ const id = params?.id as string;
+ if (!id) return;
+ try {
+ const data = await statePendudukNonPermanent.update.load(id);
+ if (data) {
+ setFormData({
+ name: data.name || '',
+ deskripsi: data.deskripsi || '',
+ });
+ }
+ } catch (error) {
+ console.error("Error loading pelayanan perizinan berusaha:", error);
+ toast.error("Gagal memuat data pelayanan perizinan berusaha");
+ }
+ };
+ loadPelayananPerizinan();
+ }, [params?.id]);
+
+ const handleSubmit = async () => {
+ if (statePendudukNonPermanent.findById.data) {
+ statePendudukNonPermanent.findById.data.name = formData.name;
+ statePendudukNonPermanent.findById.data.deskripsi = formData.deskripsi;
+ statePendudukNonPermanent.update.update(statePendudukNonPermanent.findById.data)
+ }
+ router.push('/admin/desa/layanan/pelayanan_penduduk_non_permanent')
+ }
+ return (
+
+
+
+ router.back()}
+ >
+
+
+
+
+
+
+ Edit Pelayanan Penduduk Non Permanent
+ Judul
+ {
+ setFormData({
+ ...formData,
+ name: val.target.value,
+ })
+ }}
+ />
+ Deskripsi
+ {
+ setFormData({
+ ...formData,
+ deskripsi: val,
+ })
+ }}
+ />
+
+
+
+ Submit
+
+
+
+
+
+
+
+ );
+}
+
+export default EditPelayananPendudukNonPermanent;
diff --git a/src/app/admin/(dashboard)/desa/layanan/pelayanan_penduduk_non_permanent/page.tsx b/src/app/admin/(dashboard)/desa/layanan/pelayanan_penduduk_non_permanent/page.tsx
index 9813c4cf..3d9065f3 100644
--- a/src/app/admin/(dashboard)/desa/layanan/pelayanan_penduduk_non_permanent/page.tsx
+++ b/src/app/admin/(dashboard)/desa/layanan/pelayanan_penduduk_non_permanent/page.tsx
@@ -1,40 +1,48 @@
-import JudulListTab from '@/app/admin/(dashboard)/_com/jusulListTab';
+'use client'
import colors from '@/con/colors';
-import { Box, Button, Paper, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
-import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
+import { Box, Button, Grid, GridCol, Paper, Skeleton, Stack, Text } from '@mantine/core';
+import { useShallowEffect } from '@mantine/hooks';
+import { IconEdit } from '@tabler/icons-react';
+import { useRouter } from 'next/navigation';
+import { useProxy } from 'valtio/utils';
+import stateLayananDesa from '../../../_state/desa/layananDesa';
function SuratKeterangan() {
+ const router = useRouter()
+ const pelayananPendudukNonPermanen = useProxy(stateLayananDesa.pelayananPendudukNonPermanen)
+
+ useShallowEffect(() => {
+ pelayananPendudukNonPermanen.findById.load('1')
+ }, [])
+
+ if (!pelayananPendudukNonPermanen.findById.data) {
+ return (
+
+
+
+ )
+ }
return (
- }
- />
-
-
-
- Nama
- Deskripsi
- Detail
-
-
-
-
- Pelayanan Penduduk Non-Permanent
- Deskripsi Pelayanan Penduduk Non-Permanent
-
-
-
-
+
+
+
+
+
+ Preview Pelayanan Perizinan Berusaha
+
+
+ router.push('/admin/desa/layanan/pelayanan_penduduk_non_permanent/edit')}>
+
-
-
-
-
-
+
+
+
+
+ {pelayananPendudukNonPermanen.findById.data.name}
+
+
);
diff --git a/src/app/admin/(dashboard)/desa/penghargaan/[id]/edit/page.tsx b/src/app/admin/(dashboard)/desa/penghargaan/[id]/edit/page.tsx
new file mode 100644
index 00000000..761d2ee9
--- /dev/null
+++ b/src/app/admin/(dashboard)/desa/penghargaan/[id]/edit/page.tsx
@@ -0,0 +1,147 @@
+'use client'
+/* eslint-disable react-hooks/exhaustive-deps */
+import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
+import penghargaanState from '@/app/admin/(dashboard)/_state/desa/penghargaan';
+import colors from '@/con/colors';
+import ApiFetch from '@/lib/api-fetch';
+import { Box, Button, Paper, Stack, Title, TextInput, FileInput, Center, Text, Image } from '@mantine/core';
+import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
+import { useParams, useRouter } from 'next/navigation';
+import React, { useEffect, useState } from 'react';
+import { toast } from 'react-toastify';
+import { useProxy } from 'valtio/utils';
+
+function EditPenghargaan() {
+ const statePenghargaan = useProxy(penghargaanState)
+ const router = useRouter()
+ const params = useParams()
+ const [previewImage, setPreviewImage] = useState(null)
+ const [file, setFile] = useState(null)
+ const [formData, setFormData] = useState({
+ name: statePenghargaan.findUnique.data?.name || '',
+ juara: statePenghargaan.findUnique.data?.juara || '',
+ deskripsi: statePenghargaan.findUnique.data?.deskripsi || '',
+ imageId: statePenghargaan.findUnique.data?.imageId || '',
+ })
+
+ useEffect(() => {
+ const loadPenghargaan = async () => {
+ const id = params?.id as string;
+ if (!id) return;
+
+ try {
+ const data = await statePenghargaan.edit.load(id);
+ if (data) {
+ setFormData({
+ name: data.name || '',
+ juara: data.juara || '',
+ deskripsi: data.deskripsi || '',
+ imageId: data.imageId || '',
+ });
+
+ if (data?.image?.link) {
+ setPreviewImage(data.image.link);
+ }
+ }
+ } catch (error) {
+ console.error("Error loading penghargaan:", error);
+ toast.error("Gagal memuat data penghargaan");
+ }
+ };
+
+ loadPenghargaan();
+ }, [params?.id]);
+
+ const handleSubmit = async () => {
+ try {
+ statePenghargaan.edit.form = {
+ ...statePenghargaan.edit.form,
+ name: formData.name,
+ juara: formData.juara,
+ deskripsi: formData.deskripsi,
+ imageId: formData.imageId,
+ }
+
+ if (file) {
+ const res = await ApiFetch.api.fileStorage.create.post({ file, name: file.name });
+ const uploaded = res.data?.data;
+
+ if (!uploaded?.id) {
+ return toast.error("Gagal upload gambar");
+ }
+
+ statePenghargaan.edit.form.imageId = uploaded.id;
+ }
+
+ await statePenghargaan.edit.update();
+ toast.success("Penghargaan berhasil diperbarui!");
+ router.push("/admin/desa/penghargaan");
+ } catch (error) {
+ console.error("Error updating penghargaan:", error);
+ toast.error("Terjadi kesalahan saat memperbarui penghargaan");
+ }
+ }
+
+ return (
+
+
+ router.back()}>
+
+
+
+
+
+ Edit Penghargaan
+ setFormData({ ...formData, name: e.target.value })}
+ label={Judul }
+ placeholder="masukkan judul"
+ />
+
+ setFormData({ ...formData, juara: e.target.value })}
+ label={Juara }
+ placeholder="masukkan juara"
+ />
+ Upload Gambar Baru (Opsional)}
+ value={file}
+ onChange={async (e) => {
+ if (!e) return;
+ setFile(e);
+ const base64 = await e.arrayBuffer().then((buf) =>
+ "data:image/png;base64," + Buffer.from(buf).toString("base64")
+ );
+ setPreviewImage(base64);
+ }}
+ />
+
+ {previewImage ? (
+
+ ) : (
+
+
+
+ )}
+
+
+ Deskripsi
+ {
+ setFormData((prev) => ({ ...prev, deskripsi: htmlContent }));
+ statePenghargaan.edit.form.deskripsi = htmlContent;
+ }}
+ />
+
+
+ Edit Penghargaan
+
+
+
+ );
+}
+
+export default EditPenghargaan;
diff --git a/src/app/admin/(dashboard)/desa/penghargaan/[id]/page.tsx b/src/app/admin/(dashboard)/desa/penghargaan/[id]/page.tsx
new file mode 100644
index 00000000..4b094730
--- /dev/null
+++ b/src/app/admin/(dashboard)/desa/penghargaan/[id]/page.tsx
@@ -0,0 +1,111 @@
+'use client'
+import React, { useState } from 'react';
+import penghargaanState from '../../../_state/desa/penghargaan';
+import { useProxy } from 'valtio/utils';
+import { useParams, useRouter } from 'next/navigation';
+import { useShallowEffect } from '@mantine/hooks';
+import { Box, Button, Flex, Image, Paper, Skeleton, Stack, Text } from '@mantine/core';
+import colors from '@/con/colors';
+import { IconArrowBack, IconX, IconEdit } from '@tabler/icons-react';
+import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
+
+function DetailPenghargaan() {
+ const statePenghargaan = useProxy(penghargaanState)
+ const [modalHapus, setModalHapus] = useState(false);
+ const [selectedId, setSelectedId] = useState(null);
+ const router = useRouter()
+ const params = useParams()
+
+ useShallowEffect(() => {
+ statePenghargaan.findUnique.load(params?.id as string)
+ }, [])
+
+ const handleHapus = () => {
+ if (selectedId) {
+ statePenghargaan.delete.byId(selectedId)
+ setModalHapus(false)
+ setSelectedId(null)
+ router.push("/admin/desa/penghargaan")
+ }
+ }
+
+ if (!statePenghargaan.findUnique.data) {
+ return (
+
+
+
+ )
+ }
+
+ return (
+
+
+ router.back()}>
+
+
+
+
+
+ Detail Penghargaan
+ {statePenghargaan.findUnique.data ? (
+
+
+
+ Judul
+ {statePenghargaan.findUnique.data?.name}
+
+
+ Juara
+ {statePenghargaan.findUnique.data?.juara}
+
+
+ Deskripsi
+
+
+
+ Gambar
+
+
+
+ {
+ if (statePenghargaan.findUnique.data) {
+ setSelectedId(statePenghargaan.findUnique.data.id);
+ setModalHapus(true);
+ }
+ }}
+ disabled={statePenghargaan.delete.loading || !statePenghargaan.findUnique.data}
+ color={"red"}
+ >
+
+
+ {
+ if (statePenghargaan.findUnique.data) {
+ router.push(`/admin/desa/penghargaan/${statePenghargaan.findUnique.data.id}/edit`);
+ }
+ }}
+ disabled={!statePenghargaan.findUnique.data}
+ color={"green"}
+ >
+
+
+
+
+
+ ) : null}
+
+
+
+ {/* Modal Konfirmasi Hapus */}
+ setModalHapus(false)}
+ onConfirm={handleHapus}
+ text='Apakah anda yakin ingin menghapus penghargaan ini?'
+ />
+
+ );
+}
+
+export default DetailPenghargaan;
diff --git a/src/app/admin/(dashboard)/desa/penghargaan/create/page.tsx b/src/app/admin/(dashboard)/desa/penghargaan/create/page.tsx
new file mode 100644
index 00000000..838aa368
--- /dev/null
+++ b/src/app/admin/(dashboard)/desa/penghargaan/create/page.tsx
@@ -0,0 +1,114 @@
+'use client'
+import colors from '@/con/colors';
+import { Box, Button, Center, FileInput, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
+import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
+import { useRouter } from 'next/navigation';
+import { useState } from 'react';
+import { toast } from 'react-toastify';
+import { useProxy } from 'valtio/utils';
+import penghargaanState from '../../../_state/desa/penghargaan';
+import ApiFetch from '@/lib/api-fetch';
+import CreateEditor from '../../../_com/createEditor';
+
+
+function CreatePenghargaan() {
+ const statePenghargaan = useProxy(penghargaanState)
+ const [previewImage, setPreviewImage] = useState(null);
+ const [file, setFile] = useState(null);
+ const router = useRouter()
+
+ const resetForm = () => {
+ statePenghargaan.create.form = {
+ name: "",
+ juara: "",
+ deskripsi: "",
+ imageId: "",
+ }
+ setPreviewImage(null)
+ setFile(null)
+ }
+
+ const handleSubmit = async () => {
+ if (!file) {
+ return toast.error("Silahkan pilih file gambar terlebih dahulu")
+ }
+
+ const res = await ApiFetch.api.fileStorage.create.post({
+ file: file,
+ name: file.name
+ })
+
+ const uploaded = res.data?.data
+ if (!uploaded?.id) {
+ return toast.error("Gagal upload gambar")
+ }
+
+ statePenghargaan.create.form.imageId = uploaded.id
+
+ await statePenghargaan.create.create()
+ resetForm()
+ router.push("/admin/desa/penghargaan")
+
+ }
+ return (
+
+
+ router.back()}>
+
+
+
+
+
+ Create Penghargaan
+ {
+ statePenghargaan.create.form.name = val.target.value;
+ }}
+ label={Nama Penghargaan }
+ placeholder="masukkan nama penghargaan"
+ />
+ {
+ statePenghargaan.create.form.juara = val.target.value;
+ }}
+ label={Juara }
+ placeholder="masukkan juara"
+ />
+
+ Deskripsi
+ {
+ statePenghargaan.create.form.deskripsi = htmlContent;
+ }}
+ />
+
+ Upload Gambar Konten}
+ value={file}
+ onChange={async (e) => {
+ if (!e) return;
+ setFile(e);
+ const base64 = await e.arrayBuffer().then((buf) =>
+ "data:image/png;base64," + Buffer.from(buf).toString("base64")
+ );
+ setPreviewImage(base64);
+ }}
+ />
+ {previewImage ? (
+
+ ) : (
+
+
+
+ )}
+ Simpan
+
+
+
+ );
+}
+
+export default CreatePenghargaan;
diff --git a/src/app/admin/(dashboard)/desa/penghargaan/page.tsx b/src/app/admin/(dashboard)/desa/penghargaan/page.tsx
index 40e01edb..6d839be6 100644
--- a/src/app/admin/(dashboard)/desa/penghargaan/page.tsx
+++ b/src/app/admin/(dashboard)/desa/penghargaan/page.tsx
@@ -1,34 +1,69 @@
+'use client'
+import penghargaanState from '@/app/admin/(dashboard)/_state/desa/penghargaan';
import colors from '@/con/colors';
-import { Box, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
-import React from 'react';
-import Penghargaan from './ui/penghargaan/page';
-import GambarPerhargaan from './ui/gambar_perhargaan/page';
+import { Box, Button, Image, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
+import { useShallowEffect } from '@mantine/hooks';
+import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
+import { useRouter } from 'next/navigation';
+import { useProxy } from 'valtio/utils';
+import JudulListTab from '../../_com/jusulListTab';
-function Page() {
+function Penghargaan() {
+ const state = useProxy(penghargaanState)
+ const router = useRouter()
+ useShallowEffect(() => {
+ state.findMany.load()
+ }, [])
+
+ if (!state.findMany.data) {
+ return(
+
+
+
+ )
+ }
return (
-
- Penghargaan
-
-
-
- Penghargaan
-
-
- Gambar Penghargaan
-
-
-
-
-
-
-
-
-
-
-
+
+ }
+ />
+
+
+
+ Nama
+ Deskripsi
+ Image
+ Detail
+
+
+
+ {state.findMany.data?.map((item) => (
+
+ {item.name}
+
+
+
+
+
+
+
+
+ router.push(`/admin/desa/penghargaan/${item.id}`)}>
+
+
+
+
+
+ ))}
+
+
+
);
}
-export default Page;
+export default Penghargaan;
diff --git a/src/app/admin/(dashboard)/desa/penghargaan/ui/gambar_perhargaan/listPage.tsx b/src/app/admin/(dashboard)/desa/penghargaan/ui/gambar_perhargaan/listPage.tsx
deleted file mode 100644
index cfdaf4c7..00000000
--- a/src/app/admin/(dashboard)/desa/penghargaan/ui/gambar_perhargaan/listPage.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import colors from '@/con/colors';
-import { Box, Paper, Stack, Title } from '@mantine/core';
-import React from 'react';
-
-function ListGambarPenghargaan() {
- return (
-
-
-
- List Gambar Penghargaan
-
-
-
- );
-}
-
-export default ListGambarPenghargaan;
diff --git a/src/app/admin/(dashboard)/desa/penghargaan/ui/gambar_perhargaan/page.tsx b/src/app/admin/(dashboard)/desa/penghargaan/ui/gambar_perhargaan/page.tsx
deleted file mode 100644
index 93bfcbe7..00000000
--- a/src/app/admin/(dashboard)/desa/penghargaan/ui/gambar_perhargaan/page.tsx
+++ /dev/null
@@ -1,50 +0,0 @@
-import colors from '@/con/colors';
-import { Box, Paper, SimpleGrid, Stack, Title, Text, Group, Button, TextInput, Center } from '@mantine/core';
-import React from 'react';
-import { DesaEditor } from '../../../_com/desaEditor';
-import ListGambarPenghargaan from './listPage';
-import { IconUpload } from '@tabler/icons-react';
-
-
-function GambarPerhargaan() {
- return (
-
-
-
-
-
-
- Tambah Gambar Penghargaan
-
- Deskripsi Gambar Penghargaan
-
-
- Upload Gambar Penghargaan
-
-
-
-
-
-
-
-
- Submit
-
-
-
-
-
-
-
-
-
- );
-}
-
-export default GambarPerhargaan;
diff --git a/src/app/admin/(dashboard)/desa/penghargaan/ui/penghargaan/listPage.tsx b/src/app/admin/(dashboard)/desa/penghargaan/ui/penghargaan/listPage.tsx
deleted file mode 100644
index d217eb80..00000000
--- a/src/app/admin/(dashboard)/desa/penghargaan/ui/penghargaan/listPage.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import colors from '@/con/colors';
-import { Box, Paper, Stack, Title } from '@mantine/core';
-import React from 'react';
-
-function ListPenghargaan() {
- return (
-
-
-
- List Penghargaan
-
-
-
- );
-}
-
-export default ListPenghargaan;
diff --git a/src/app/admin/(dashboard)/desa/penghargaan/ui/penghargaan/page.tsx b/src/app/admin/(dashboard)/desa/penghargaan/ui/penghargaan/page.tsx
deleted file mode 100644
index ba63a677..00000000
--- a/src/app/admin/(dashboard)/desa/penghargaan/ui/penghargaan/page.tsx
+++ /dev/null
@@ -1,40 +0,0 @@
-import colors from '@/con/colors';
-import { Box, Button, Group, Paper, SimpleGrid, Stack, Text, Title } from '@mantine/core';
-import React from 'react';
-import { DesaEditor } from '../../../_com/desaEditor';
-import ListPenghargaan from './listPage';
-
-function Penghargaan() {
- return (
-
-
-
-
-
-
- Penghargaan
-
- Deskripsi Penghargaan
-
-
-
-
- Submit
-
-
-
-
-
-
-
-
-
- );
-}
-
-export default Penghargaan;
diff --git a/src/app/api/[[...slugs]]/_lib/desa/index.ts b/src/app/api/[[...slugs]]/_lib/desa/index.ts
index 9769536c..1955ed81 100644
--- a/src/app/api/[[...slugs]]/_lib/desa/index.ts
+++ b/src/app/api/[[...slugs]]/_lib/desa/index.ts
@@ -6,6 +6,7 @@ import PotensiDesa from "./potensi";
import GalleryFoto from "./gallery/foto";
import GalleryVideo from "./gallery/video";
import LayananDesa from "./layanan";
+import Penghargaan from "./penghargaan";
const Desa = new Elysia({ prefix: "/api/desa", tags: ["Desa"] })
@@ -16,5 +17,6 @@ const Desa = new Elysia({ prefix: "/api/desa", tags: ["Desa"] })
.use(GalleryFoto)
.use(GalleryVideo)
.use(LayananDesa)
+ .use(Penghargaan)
export default Desa;
diff --git a/src/app/api/[[...slugs]]/_lib/desa/penghargaan/create.ts b/src/app/api/[[...slugs]]/_lib/desa/penghargaan/create.ts
new file mode 100644
index 00000000..199a8f75
--- /dev/null
+++ b/src/app/api/[[...slugs]]/_lib/desa/penghargaan/create.ts
@@ -0,0 +1,32 @@
+import prisma from "@/lib/prisma";
+import { Prisma } from "@prisma/client";
+import { Context } from "elysia";
+
+type FormCreate = Prisma.PenghargaanGetPayload<{
+ select: {
+ juara: true;
+ name: true;
+ deskripsi: true;
+ imageId: true;
+ }
+}>
+export default async function penghargaanCreate(context: Context){
+ const body = context.body as FormCreate;
+
+ await prisma.penghargaan.create({
+ data: {
+ juara: body.juara,
+ name: body.name,
+ deskripsi: body.deskripsi,
+ imageId: body.imageId,
+ }
+ })
+
+ return {
+ success: true,
+ message: "Success create penghargaan",
+ data: {
+ ...body,
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/app/api/[[...slugs]]/_lib/desa/penghargaan/del.ts b/src/app/api/[[...slugs]]/_lib/desa/penghargaan/del.ts
new file mode 100644
index 00000000..72f8222a
--- /dev/null
+++ b/src/app/api/[[...slugs]]/_lib/desa/penghargaan/del.ts
@@ -0,0 +1,53 @@
+import prisma from "@/lib/prisma";
+import { Context } from "elysia";
+import fs from "fs/promises";
+import path from "path";
+
+const penghargaanDelete = async (context: Context) => {
+ const id = context.params?.id as string;
+
+ if (!id) {
+ return {
+ status: 400,
+ body: "ID tidak diberikan",
+ };
+ }
+
+ const penghargaan = await prisma.penghargaan.findUnique({
+ where: { id },
+ include: {
+ image: true,
+ }
+ });
+
+ if (!penghargaan) {
+ return {
+ status: 404,
+ body: "Penghargaan tidak ditemukan",
+ };
+ }
+
+ if (penghargaan.image) {
+ try {
+ const filePath = path.join(penghargaan.image.path, penghargaan.image.name);
+ await fs.unlink(filePath);
+ await prisma.fileStorage.delete({
+ where: { id: penghargaan.image.id },
+ });
+ } catch (error) {
+ console.error("Gagal hapus file image:", error);
+ }
+ }
+
+ await prisma.penghargaan.delete({
+ where: { id },
+ });
+
+ return {
+ success: true,
+ message: "Penghargaan berhasil dihapus",
+ status: 200,
+ };
+}
+
+export default penghargaanDelete
diff --git a/src/app/api/[[...slugs]]/_lib/desa/penghargaan/find-many.ts b/src/app/api/[[...slugs]]/_lib/desa/penghargaan/find-many.ts
new file mode 100644
index 00000000..9c2f44ec
--- /dev/null
+++ b/src/app/api/[[...slugs]]/_lib/desa/penghargaan/find-many.ts
@@ -0,0 +1,23 @@
+import prisma from "@/lib/prisma";
+
+export default async function penghargaanFindMany() {
+ try {
+ const data = await prisma.penghargaan.findMany({
+ include: {
+ image: true,
+ },
+ });
+
+ return {
+ success: true,
+ message: "Success fetch penghargaan",
+ data,
+ };
+ } catch (error) {
+ console.error("Find many error:", error);
+ return {
+ success: false,
+ message: "Failed fetch penghargaan",
+ };
+ }
+}
diff --git a/src/app/api/[[...slugs]]/_lib/desa/penghargaan/findUnique.ts b/src/app/api/[[...slugs]]/_lib/desa/penghargaan/findUnique.ts
new file mode 100644
index 00000000..5bf8e5c3
--- /dev/null
+++ b/src/app/api/[[...slugs]]/_lib/desa/penghargaan/findUnique.ts
@@ -0,0 +1,49 @@
+import prisma from "@/lib/prisma";
+
+export default async function penghargaanFindUnique(request: Request) {
+ const url = new URL(request.url);
+ const pathSegments = url.pathname.split("/");
+ const id = pathSegments[pathSegments.length - 1];
+
+ if (!id) {
+ return Response.json({
+ success: false,
+ message: "ID tidak ditemukan",
+ }, {status: 400});
+ }
+
+ try {
+ if (typeof id !== 'string') {
+ return Response.json({
+ success: false,
+ message: "ID tidak valid",
+ }, {status: 400});
+ }
+
+ const data = await prisma.penghargaan.findUnique({
+ where: { id },
+ include: {
+ image: true,
+ },
+ });
+
+ if (!data) {
+ return Response.json({
+ success: false,
+ message: "Penghargaan tidak ditemukan",
+ }, {status: 404});
+ }
+
+ return Response.json({
+ success: true,
+ message: "Success fetch penghargaan by ID",
+ data,
+ }, {status: 200});
+ } catch (error) {
+ console.error("Find by ID error:", error);
+ return Response.json({
+ success: false,
+ message: "Gagal mengambil penghargaan: " + (error instanceof Error ? error.message : 'Unknown error'),
+ }, {status: 500});
+ }
+}
diff --git a/src/app/api/[[...slugs]]/_lib/desa/penghargaan/index.ts b/src/app/api/[[...slugs]]/_lib/desa/penghargaan/index.ts
new file mode 100644
index 00000000..223fa815
--- /dev/null
+++ b/src/app/api/[[...slugs]]/_lib/desa/penghargaan/index.ts
@@ -0,0 +1,35 @@
+import Elysia, { t } from "elysia";
+import penghargaanFindMany from "./find-many";
+import penghargaanFindUnique from "./findUnique";
+import penghargaanCreate from "./create";
+import penghargaanDelete from "./del";
+import penghargaanUpdate from "./updt";
+
+
+const Penghargaan = new Elysia({prefix: "/penghargaan", tags: ["Desa/Penghargaan"]})
+ .get("/find-many", penghargaanFindMany)
+ .get("/:id", async (context) => {
+ const response = await penghargaanFindUnique(context.request);
+ return response;
+ })
+ .post("/create", penghargaanCreate, {
+ body: t.Object({
+ juara: t.String(),
+ name: t.String(),
+ deskripsi: t.String(),
+ imageId: t.String(),
+ }),
+ })
+ .delete("/del/:id", penghargaanDelete)
+ .put("/:id", async (context) => {
+ const response = await penghargaanUpdate(context);
+ return response;
+ }, {
+ body: t.Object({
+ juara: t.String(),
+ name: t.String(),
+ deskripsi: t.String(),
+ imageId: t.String(),
+ }),
+ });
+export default Penghargaan;
\ No newline at end of file
diff --git a/src/app/api/[[...slugs]]/_lib/desa/penghargaan/updt.ts b/src/app/api/[[...slugs]]/_lib/desa/penghargaan/updt.ts
new file mode 100644
index 00000000..82a4e1a3
--- /dev/null
+++ b/src/app/api/[[...slugs]]/_lib/desa/penghargaan/updt.ts
@@ -0,0 +1,106 @@
+import prisma from "@/lib/prisma";
+import { Prisma } from "@prisma/client";
+import path from "path";
+import fs from "fs/promises";
+import { Context } from "elysia";
+
+type FormUpdate = Prisma.PenghargaanGetPayload<{
+ select: {
+ id: true;
+ juara: true;
+ name: true;
+ deskripsi: true;
+ imageId: true;
+ }
+}>
+export default async function penghargaanUpdate(context: Context) {
+try {
+ const id = context.params?.id as string;
+ const body = (await context.body) as Omit;
+
+ const {
+ juara,
+ name,
+ deskripsi,
+ imageId
+ } = body;
+
+ if (!id) {
+ return new Response(JSON.stringify({
+ success: false,
+ message: "ID tidak ditemukan",
+ }), {
+ status: 400,
+ headers: {
+ 'Content-Type': 'application/json'
+ }
+ })
+ }
+
+ const existing = await prisma.penghargaan.findUnique({
+ where: {id},
+ include: {
+ image: true,
+ }
+ })
+
+ if (!existing) {
+ return new Response(JSON.stringify({
+ success: false,
+ message: "Penghargaan tidak ditemukan",
+ }), {
+ status: 404,
+ headers: {
+ 'Content-Type': 'application/json'
+ }
+ })
+ }
+
+ if (existing.imageId && existing.imageId !== imageId) {
+ const oldImage = existing.image;
+ if (oldImage) {
+ try {
+ const filePath = path.join(oldImage.path, oldImage.name);
+ await fs.unlink(filePath);
+ await prisma.fileStorage.delete({
+ where: { id: oldImage.id },
+ });
+ } catch (error) {
+ console.error("Gagal hapus gambar lama:", error);
+ }
+ }
+ }
+
+ const updated = await prisma.penghargaan.update({
+ where: { id },
+ data: {
+ juara,
+ name,
+ deskripsi,
+ imageId,
+ }
+ })
+
+ return new Response(JSON.stringify({
+ success: true,
+ message: "Penghargaan berhasil diupdate",
+ data: updated,
+ }), {
+ status: 200,
+ headers: {
+ 'Content-Type': 'application/json'
+ }
+ })
+} catch (error) {
+ console.error("Error updating penghargaan:", error);
+ return new Response(JSON.stringify({
+ success: false,
+ message: "Terjadi kesalahan saat mengupdate penghargaan",
+ }), {
+ status: 500,
+ headers: {
+ 'Content-Type': 'application/json'
+ }
+ })
+}
+}
\ No newline at end of file