diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 238d68fc..ec702d5d 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -67,6 +67,8 @@ model FileStorage { StrukturPPID StrukturPPID[] GalleryFoto GalleryFoto[] + + PelayananSuratKeterangan PelayananSuratKeterangan[] } //========================================= MENU PPID ========================================= // @@ -361,6 +363,30 @@ model GalleryVideo { isActive Boolean @default(true) } +// ========================================= LAYANAN DESA ========================================= // +model PelayananSuratKeterangan { + id String @id @default(cuid()) + name 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) +} + +model PelayananTelunjukSaktiDesa { + id String @id @default(cuid()) + name String + deskripsi String @db.Text + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime @default(now()) + isActive Boolean @default(true) +} + + // ========================================= MENU KESEHATAN ========================================= // // ========================================= DATA KESEHATAN WARGA ========================================= // diff --git a/src/app/admin/(dashboard)/_state/desa/layananDesa.ts b/src/app/admin/(dashboard)/_state/desa/layananDesa.ts new file mode 100644 index 00000000..d518a9c4 --- /dev/null +++ b/src/app/admin/(dashboard)/_state/desa/layananDesa.ts @@ -0,0 +1,215 @@ +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 templateSuratKeteranganForm = z.object({ + name: z.string().min(3, "Nama minimal 3 karakter"), + deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"), + imageId: z.string().nonempty(), +}); + +const suratKeteranganForm = { + name: "", + deskripsi: "", + imageId: "", +}; + +const suratKeterangan = proxy({ + create: { + form: { ...suratKeteranganForm }, + loading: false, + async create() { + const cek = templateSuratKeteranganForm.safeParse( + suratKeterangan.create.form + ); + if (!cek.success) { + const err = `[${cek.error.issues + .map((v) => `${v.path.join(".")}`) + .join("\n")}] required`; + return toast.error(err); + } + try { + suratKeterangan.create.loading = true; + const res = await ApiFetch.api.desa.layanan.pelayanansuratketerangan[ + "create" + ].post(suratKeterangan.create.form); + if (res.status === 200) { + suratKeterangan.findMany.load(); + return toast.success("Surat Keterangan berhasil disimpan!"); + } + return toast.error("Gagal menyimpan surat keterangan"); + } catch (error) { + console.log((error as Error).message); + } finally { + suratKeterangan.create.loading = false; + } + }, + resetForm() { + suratKeterangan.create.form = { ...suratKeteranganForm }; + }, + }, + findMany: { + data: [] as Prisma.PelayananSuratKeteranganGetPayload<{ + include: { image: true }; + }>[], + async load() { + const res = await ApiFetch.api.desa.layanan.pelayanansuratketerangan[ + "find-many" + ].get(); + if (res.status === 200) { + suratKeterangan.findMany.data = res.data?.data ?? []; + } + }, + }, + findUnique: { + data: null as Prisma.PelayananSuratKeteranganGetPayload<{ + include: { + image: true; + }; + }> | null, + async load(id: string) { + try { + const res = await fetch( + `/api/desa/layanan/pelayanansuratketerangan/${id}` + ); + if (res.ok) { + const data = await res.json(); + suratKeterangan.findUnique.data = data.data ?? null; + } else { + console.error("Failed to fetch surat keterangan:", res.statusText); + suratKeterangan.findUnique.data = null; + } + } catch (error) { + console.error("Error fetching surat keterangan:", error); + suratKeterangan.findUnique.data = null; + } + }, + }, + delete: { + loading: false, + async byId(id: string) { + if (!id) return toast.warn("ID tidak valid"); + try { + suratKeterangan.delete.loading = true; + const response = await fetch( + `/api/desa/layanan/pelayanansuratketerangan/del/${id}`, + { + method: "DELETE", + headers: { + "Content-Type": "application/json", + }, + } + ); + const result = await response.json(); + if (response.ok) { + toast.success(result.message || "Surat Keterangan berhasil dihapus"); + await suratKeterangan.findMany.load(); // refresh list + } else { + toast.error(result.message || "Gagal menghapus surat keterangan"); + } + } catch (error) { + console.error("Gagal delete:", error); + toast.error("Terjadi kesalahan saat menghapus surat keterangan"); + } finally { + suratKeterangan.delete.loading = false; + } + }, + }, + edit: { + id: "", + form: { ...suratKeteranganForm }, + loading: false, + + async load(id: string) { + if (!id) { + toast.warn("ID tidak valid"); + return null; + } + try { + const response = await fetch( + `/api/desa/layanan/pelayanansuratketerangan/${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, + deskripsi: data.deskripsi, + imageId: data.imageId || "", + }; + return data; + } else { + throw new Error(result.message || "Gagal memuat data"); + } + } catch (error) { + console.error("Error fetching surat keterangan:", error); + toast.error( + error instanceof Error ? error.message : "Gagal memuat data" + ); + return null; + } + }, + async update() { + const cek = templateSuratKeteranganForm.safeParse( + suratKeterangan.edit.form + ); + if (!cek.success) { + const err = `[${cek.error.issues + .map((v) => `${v.path.join(".")}`) + .join("\n")}] required`; + return toast.error(err); + } + try { + suratKeterangan.edit.loading = true; + const response = await fetch(`/api/desa/layanan/pelayanansuratketerangan/${this.id}`, { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + name: this.form.name, + 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(result.message || "Surat Keterangan berhasil diupdate"); + await suratKeterangan.findMany.load(); // refresh list + return true; + } else { + throw new Error(result.message || "Gagal mengupdate surat keterangan"); + } + } catch (error) { + console.error("Error updating surat keterangan:", error); + toast.error(error instanceof Error ? error.message : "Terjadi kesalahan saat update surat keterangan"); + return false; + } finally { + suratKeterangan.edit.loading = false; + } + }, + }, +}); + +const stateLayananDesa = proxy({ + suratKeterangan, +}); + +export default stateLayananDesa; + diff --git a/src/app/admin/(dashboard)/desa/_com/layoutTabLayanan.tsx b/src/app/admin/(dashboard)/desa/_com/layoutTabLayanan.tsx new file mode 100644 index 00000000..47eea7f9 --- /dev/null +++ b/src/app/admin/(dashboard)/desa/_com/layoutTabLayanan.tsx @@ -0,0 +1,72 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +'use client' +import colors from '@/con/colors'; +import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core'; +import { usePathname, useRouter } from 'next/navigation'; +import React, { useEffect, useState } from 'react'; + +function LayoutTabsLayanan({ children }: { children: React.ReactNode }) { + const router = useRouter() + const pathname = usePathname() + const tabs = [ + { + label: "Pelayanan Surat Keterangan", + value: "pelayanansuratketerangan", + href: "/admin/desa/layanan/pelayanan_surat_keterangan" + }, + { + label: "Pelayanan Perizinan Berusaha", + value: "pelayananperizinanusaha", + href: "/admin/desa/layanan/pelayanan_perizinan_berusaha" + }, + { + label: "Pelayanan Telunjuk Sakti Desa", + value: "pelayanantelunjuksaktidesa", + href: "/admin/desa/layanan/pelayanan_telunjuk_sakti_desa" + }, + { + label: "Pelayanan Penduduk Non-Permanent", + value: "pelayanantelunjuknonpermanent", + href: "/admin/desa/layanan/pelayanan_penduduk_non_permanent" + } + ]; + const curentTab = tabs.find(tab => tab.href === pathname) + const [activeTab, setActiveTab] = useState(curentTab?.value || tabs[0].value); + + const handleTabChange = (value: string | null) => { + const tab = tabs.find(t => t.value === value) + if (tab) { + router.push(tab.href) + } + setActiveTab(value) + } + + useEffect(() => { + const match = tabs.find(tab => tab.href === pathname) + if (match) { + setActiveTab(match.value) + } + }, [pathname]) + + return ( + + Layanan + + + {tabs.map((e, i) => ( + {e.label} + ))} + + {tabs.map((e, i) => ( + + {/* Konten dummy, bisa diganti tergantung routing */} + <> + + ))} + + {children} + + ); +} + +export default LayoutTabsLayanan; \ No newline at end of file diff --git a/src/app/admin/(dashboard)/desa/layanan/layout.tsx b/src/app/admin/(dashboard)/desa/layanan/layout.tsx new file mode 100644 index 00000000..7113ca7e --- /dev/null +++ b/src/app/admin/(dashboard)/desa/layanan/layout.tsx @@ -0,0 +1,10 @@ +'use client' +import LayoutTabsLayanan from "../_com/layoutTabLayanan"; + +export default function Layout({children} : {children: React.ReactNode}) { + return ( + + {children} + + ) +} \ No newline at end of file diff --git a/src/app/admin/(dashboard)/desa/layanan/page.tsx b/src/app/admin/(dashboard)/desa/layanan/page.tsx deleted file mode 100644 index 83f6a69b..00000000 --- a/src/app/admin/(dashboard)/desa/layanan/page.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import colors from '@/con/colors'; -import { Box, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core'; -import SuratKeterangan from './ui/surat_keterangan/page'; -import PerizinanUsaha from './ui/perizinan_usaha/page'; -import TelunjukSaktiDesa from './ui/telunjuk_sakti_desa/page'; -import PendudukNonPermanent from './ui/penduduk_non_permanent/page'; - -function Page() { - return ( - - - Layanan - - - - Pelayanan Surat Keterangan - - - Pelayanan Perizinan Berusaha - - - Pelayanan Telunjuk Sakti Desa - - - Pelayanan Penduduk Non-Permanent - - - - - - - - - - - - - - - - - - - ); -} - -export default Page; - 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 new file mode 100644 index 00000000..9813c4cf --- /dev/null +++ b/src/app/admin/(dashboard)/desa/layanan/pelayanan_penduduk_non_permanent/page.tsx @@ -0,0 +1,43 @@ +import JudulListTab from '@/app/admin/(dashboard)/_com/jusulListTab'; +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'; + +function SuratKeterangan() { + return ( + + + } + /> + + + + Nama + Deskripsi + Detail + + + + + Pelayanan Penduduk Non-Permanent + Deskripsi Pelayanan Penduduk Non-Permanent + + + + + + + +
+
+
+ ); +} + +export default SuratKeterangan; diff --git a/src/app/admin/(dashboard)/desa/layanan/pelayanan_perizinan_berusaha/page.tsx b/src/app/admin/(dashboard)/desa/layanan/pelayanan_perizinan_berusaha/page.tsx new file mode 100644 index 00000000..39fe16e9 --- /dev/null +++ b/src/app/admin/(dashboard)/desa/layanan/pelayanan_perizinan_berusaha/page.tsx @@ -0,0 +1,43 @@ +import JudulListTab from '@/app/admin/(dashboard)/_com/jusulListTab'; +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'; + +function PerizinanBerusaha() { + return ( + + + } + /> + + + + Nama + Deskripsi + Detail + + + + + Pelayanan Perizinan Berusaha + Deskripsi Pelayanan Perizinan Berusaha + + + + + + + +
+
+
+ ); +} + +export default PerizinanBerusaha; diff --git a/src/app/admin/(dashboard)/desa/layanan/pelayanan_surat_keterangan/[id]/edit/page.tsx b/src/app/admin/(dashboard)/desa/layanan/pelayanan_surat_keterangan/[id]/edit/page.tsx new file mode 100644 index 00000000..fb2c0ad3 --- /dev/null +++ b/src/app/admin/(dashboard)/desa/layanan/pelayanan_surat_keterangan/[id]/edit/page.tsx @@ -0,0 +1,132 @@ +'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 ApiFetch from '@/lib/api-fetch'; +import { Box, Button, Center, FileInput, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core'; +import { IconArrowBack, IconImageInPicture } 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 EditSuratKeterangan() { + const router = useRouter() + const params = useParams() + const stateSurat = useProxy(stateLayananDesa.suratKeterangan) + const [previewImage, setPreviewImage] = useState(null); + const [file, setFile] = useState(null); + const [formData, setFormData] = useState({ + name: stateSurat.edit.form.name, + deskripsi: stateSurat.edit.form.deskripsi, + imageId: stateSurat.edit.form.imageId, + }) + + useEffect(() => { + const loadSurat = async () => { + const id = params?.id as string; + if (!id) return; + try { + const data = await stateSurat.edit.load(id); + if (data) { + setFormData({ + name: data.name, + deskripsi: data.deskripsi, + imageId: data.imageId, + }); + if (data?.image?.link) { + setPreviewImage(data.image.link); + } + } + } catch (error) { + console.error("Error loading surat:", error); + toast.error("Gagal memuat data surat"); + } + }; + loadSurat(); + }, [params?.id]); + + const handleSubmit = async () => { + try { + stateSurat.edit.form = { + ...stateSurat.edit.form, + name: formData.name, + 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"); + } + + stateSurat.edit.form.imageId = uploaded.id; + } + + await stateSurat.edit.update() + toast.success("Surat berhasil diperbarui!") + router.push("/admin/desa/layanan/pelayanan_surat_keterangan") + } catch (error) { + console.error("Error updating surat:", error); + toast.error("Terjadi kesalahan saat memperbarui surat"); + } + } + + + return ( + + + + + + + Edit Surat Keterangan + { + setFormData({ ...formData, name: val.target.value }); + }} + label={Nama Surat Keterangan} + placeholder="masukkan nama surat keterangan" + /> + + Konten + { + setFormData({ ...formData, 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 ? ( + + ) : ( +
+ +
+ )} + +
+
+
+ ); +} + +export default EditSuratKeterangan; diff --git a/src/app/admin/(dashboard)/desa/layanan/pelayanan_surat_keterangan/[id]/page.tsx b/src/app/admin/(dashboard)/desa/layanan/pelayanan_surat_keterangan/[id]/page.tsx new file mode 100644 index 00000000..70404c80 --- /dev/null +++ b/src/app/admin/(dashboard)/desa/layanan/pelayanan_surat_keterangan/[id]/page.tsx @@ -0,0 +1,109 @@ +'use client' +import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus'; +import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa'; +import colors from '@/con/colors'; +import { Box, Button, Flex, Image, Paper, Skeleton, Stack, Text } from '@mantine/core'; +import { useShallowEffect } from '@mantine/hooks'; +import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react'; +import { useParams, useRouter } from 'next/navigation'; +import { useState } from 'react'; +import { useProxy } from 'valtio/utils'; + +function DetailSuratKeterangan() { + const suratKeteranganState = useProxy(stateLayananDesa.suratKeterangan) + const [modalHapus, setModalHapus] = useState(false) + const [selectedId, setSelectedId] = useState(null) + const params = useParams() + const router = useRouter() + + useShallowEffect(() => { + suratKeteranganState.findUnique.load(params?.id as string) + }, []) + + const handleHapus = () => { + if (selectedId) { + suratKeteranganState.delete.byId(selectedId) + setModalHapus(false) + setSelectedId(null) + router.push("/admin/desa/layanan/pelayanan_surat_keterangan") + } + } + + if (!suratKeteranganState.findUnique.data) { + return ( + + {Array.from({ length: 10 }).map((_, k) => ( + + ))} + + ) + } + + return ( + + + + + + + Detail Surat Keterangan + {suratKeteranganState.findUnique.data ? ( + + + + Nama + {suratKeteranganState.findUnique.data?.name} + + + Deskripsi + + + + Gambar + gambar + + + + + + + + ) : null} + + + + {/* Modal Konfirmasi Hapus */} + setModalHapus(false)} + onConfirm={handleHapus} + text='Apakah anda yakin ingin menghapus berita ini?' + /> + + ); +} + +export default DetailSuratKeterangan; diff --git a/src/app/admin/(dashboard)/desa/layanan/pelayanan_surat_keterangan/create/page.tsx b/src/app/admin/(dashboard)/desa/layanan/pelayanan_surat_keterangan/create/page.tsx new file mode 100644 index 00000000..fa1a9c3a --- /dev/null +++ b/src/app/admin/(dashboard)/desa/layanan/pelayanan_surat_keterangan/create/page.tsx @@ -0,0 +1,104 @@ +'use client' +import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor'; +import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa'; +import colors from '@/con/colors'; +import ApiFetch from '@/lib/api-fetch'; +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'; + +function CreateSuratKeterangan() { + const stateSurat = useProxy(stateLayananDesa.suratKeterangan) + const [previewImage, setPreviewImage] = useState(null); + const [file, setFile] = useState(null); + const router = useRouter() + + const resetForm = () => { + stateSurat.create.form = { + name: "", + 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") + } + + stateSurat.create.form.imageId = uploaded.id + + await stateSurat.create.create() + resetForm() + router.push("/admin/desa/layanan/pelayanan_surat_keterangan") + + } + return ( + + + + + + + Create Surat Keterangan + { + stateSurat.create.form.name = val.target.value; + }} + label={Nama Surat Keterangan} + placeholder="masukkan nama surat keterangan" + /> + + Konten + { + stateSurat.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 ? ( + + ) : ( +
+ +
+ )} + +
+
+
+ ); +} + +export default CreateSuratKeterangan; diff --git a/src/app/admin/(dashboard)/desa/layanan/pelayanan_surat_keterangan/page.tsx b/src/app/admin/(dashboard)/desa/layanan/pelayanan_surat_keterangan/page.tsx new file mode 100644 index 00000000..9257c5d1 --- /dev/null +++ b/src/app/admin/(dashboard)/desa/layanan/pelayanan_surat_keterangan/page.tsx @@ -0,0 +1,71 @@ +'use client' +import JudulListTab from '@/app/admin/(dashboard)/_com/jusulListTab'; +import colors from '@/con/colors'; +import { Box, Button, Image, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core'; +import { IconDeviceImac, IconSearch } from '@tabler/icons-react'; +import { useProxy } from 'valtio/utils'; +import stateLayananDesa from '../../../_state/desa/layananDesa'; +import { useShallowEffect } from '@mantine/hooks'; +import { useRouter } from 'next/navigation'; + +function SuratKeterangan() { + const suratKeteranganState = useProxy(stateLayananDesa.suratKeterangan) + const router = useRouter() + + useShallowEffect(() => { + suratKeteranganState.findMany.load() + }, []) + + if (!suratKeteranganState.findMany.data) { + return ( + + + + ) + } + + return ( + + + } + /> + + + + Nama + Deskripsi + Image + Detail + + + + {suratKeteranganState.findMany.data?.map((item) => ( + + {item.name} + + + + + + + + + + + + + ))} + +
+
+
+ ); +} + +export default SuratKeterangan; diff --git a/src/app/admin/(dashboard)/desa/layanan/pelayanan_telunjuk_sakti_desa/page.tsx b/src/app/admin/(dashboard)/desa/layanan/pelayanan_telunjuk_sakti_desa/page.tsx new file mode 100644 index 00000000..33a847d9 --- /dev/null +++ b/src/app/admin/(dashboard)/desa/layanan/pelayanan_telunjuk_sakti_desa/page.tsx @@ -0,0 +1,43 @@ +import JudulListTab from '@/app/admin/(dashboard)/_com/jusulListTab'; +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'; + +function SuratKeterangan() { + return ( + + + } + /> + + + + Nama + Deskripsi + Detail + + + + + Pelayanan Telunjuk Sakti Desa + Deskripsi Pelayanan Telunjuk Sakti Desa + + + + + + + +
+
+
+ ); +} + +export default SuratKeterangan; diff --git a/src/app/admin/(dashboard)/desa/layanan/ui/penduduk_non_permanent/listPage.tsx b/src/app/admin/(dashboard)/desa/layanan/ui/penduduk_non_permanent/listPage.tsx deleted file mode 100644 index 93d7c0dc..00000000 --- a/src/app/admin/(dashboard)/desa/layanan/ui/penduduk_non_permanent/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 ListPendudukNonPermanent() { - return ( - - - - List Penduduk Non-Permanent - - - - ); -} - -export default ListPendudukNonPermanent; diff --git a/src/app/admin/(dashboard)/desa/layanan/ui/penduduk_non_permanent/page.tsx b/src/app/admin/(dashboard)/desa/layanan/ui/penduduk_non_permanent/page.tsx deleted file mode 100644 index a3585e50..00000000 --- a/src/app/admin/(dashboard)/desa/layanan/ui/penduduk_non_permanent/page.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import colors from '@/con/colors'; -import { Box, SimpleGrid, Paper, Stack, Title, Group, Button, Text } from '@mantine/core'; -import React from 'react'; -import { DesaEditor } from '../../../_com/desaEditor'; -import ListPendudukNonPermanent from './listPage'; - -function PendudukNonPermanent() { - return ( - - - - - - Penduduk Non-Permanent - Deskripsi Penduduk Non-Permanent - - - - - - - - - - - ); -} - -export default PendudukNonPermanent; - diff --git a/src/app/admin/(dashboard)/desa/layanan/ui/perizinan_usaha/listPage.tsx b/src/app/admin/(dashboard)/desa/layanan/ui/perizinan_usaha/listPage.tsx deleted file mode 100644 index 440f1a43..00000000 --- a/src/app/admin/(dashboard)/desa/layanan/ui/perizinan_usaha/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 ListPerizinanUsaha() { - return ( - - - - List Perizinan Usaha - - - - ); -} - -export default ListPerizinanUsaha; diff --git a/src/app/admin/(dashboard)/desa/layanan/ui/perizinan_usaha/page.tsx b/src/app/admin/(dashboard)/desa/layanan/ui/perizinan_usaha/page.tsx deleted file mode 100644 index aa85a19f..00000000 --- a/src/app/admin/(dashboard)/desa/layanan/ui/perizinan_usaha/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 ListPerizinanUsaha from './listPage'; -import { DesaEditor } from '../../../_com/desaEditor'; - -function PerizinanUsaha() { - return ( - - - - - - - Pelayanan Perizinan Usaha - - Deskripsi Perizinan Usaha - - - - - - - - - - - - - ); -} - -export default PerizinanUsaha; diff --git a/src/app/admin/(dashboard)/desa/layanan/ui/surat_keterangan/listPage.tsx b/src/app/admin/(dashboard)/desa/layanan/ui/surat_keterangan/listPage.tsx deleted file mode 100644 index bd4dd422..00000000 --- a/src/app/admin/(dashboard)/desa/layanan/ui/surat_keterangan/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 ListSuratKeterangan() { - return ( - - - - List Surat Keterangan - - - - ); -} - -export default ListSuratKeterangan; diff --git a/src/app/admin/(dashboard)/desa/layanan/ui/surat_keterangan/page.tsx b/src/app/admin/(dashboard)/desa/layanan/ui/surat_keterangan/page.tsx deleted file mode 100644 index 5ac1070e..00000000 --- a/src/app/admin/(dashboard)/desa/layanan/ui/surat_keterangan/page.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import colors from '@/con/colors'; -import { Box, SimpleGrid, Paper, Stack, Title, Button, Group, TextInput, Text, Center, Flex } from '@mantine/core'; -import { IconUpload } from '@tabler/icons-react'; -import React from 'react'; -import ListSuratKeterangan from './listPage'; - -function SuratKeterangan() { - return ( - - - - - - - Pelayanan Surat Keterangan - Nama Surat Keterangan} - placeholder='masukkan nama surat keterangan' - /> - Upload Gambar Surat Keterangan - -
- -
-
- - * - Upload foto untuk konten surat keterangan - - - - -
-
-
- -
-
-
- ); -} - -export default SuratKeterangan; diff --git a/src/app/admin/(dashboard)/desa/layanan/ui/telunjuk_sakti_desa/listPage.tsx b/src/app/admin/(dashboard)/desa/layanan/ui/telunjuk_sakti_desa/listPage.tsx deleted file mode 100644 index 42f8d68a..00000000 --- a/src/app/admin/(dashboard)/desa/layanan/ui/telunjuk_sakti_desa/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 ListTelunjukSaktiDesa() { - return ( - - - - List Telunjuk Sakti Desa - - - - ); -} - -export default ListTelunjukSaktiDesa; diff --git a/src/app/admin/(dashboard)/desa/layanan/ui/telunjuk_sakti_desa/page.tsx b/src/app/admin/(dashboard)/desa/layanan/ui/telunjuk_sakti_desa/page.tsx deleted file mode 100644 index 60a28a7c..00000000 --- a/src/app/admin/(dashboard)/desa/layanan/ui/telunjuk_sakti_desa/page.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import colors from '@/con/colors'; -import { Box, SimpleGrid, Paper, Stack, Title, Group, Button, Text } from '@mantine/core'; -import React from 'react'; -import { DesaEditor } from '../../../_com/desaEditor'; -import ListTelunjukSaktiDesa from './listPage'; - -function TelunjukSaktiDesa() { - return ( - - - - - - Telunjuk Sakti Desa - - Deskripsi Telunjuk Sakti Desa - - - - - - - - - - - - ); -} - -export default TelunjukSaktiDesa; diff --git a/src/app/admin/_com/list_PageAdmin.tsx b/src/app/admin/_com/list_PageAdmin.tsx index 8f532a82..5624c16d 100644 --- a/src/app/admin/_com/list_PageAdmin.tsx +++ b/src/app/admin/_com/list_PageAdmin.tsx @@ -133,7 +133,7 @@ export const navBar = [ { id: "Desa_6", name: "Layanan", - path: "/admin/desa/layanan" + path: "/admin/desa/layanan/pelayanan_surat_keterangan" }, { id: "Desa_7", diff --git a/src/app/api/[[...slugs]]/_lib/desa/index.ts b/src/app/api/[[...slugs]]/_lib/desa/index.ts index 276970f0..9769536c 100644 --- a/src/app/api/[[...slugs]]/_lib/desa/index.ts +++ b/src/app/api/[[...slugs]]/_lib/desa/index.ts @@ -5,6 +5,8 @@ import ProfileDesa from "./profile/profile_desa"; import PotensiDesa from "./potensi"; import GalleryFoto from "./gallery/foto"; import GalleryVideo from "./gallery/video"; +import LayananDesa from "./layanan"; + const Desa = new Elysia({ prefix: "/api/desa", tags: ["Desa"] }) .use(Berita) @@ -13,5 +15,6 @@ const Desa = new Elysia({ prefix: "/api/desa", tags: ["Desa"] }) .use(PotensiDesa) .use(GalleryFoto) .use(GalleryVideo) + .use(LayananDesa) export default Desa; diff --git a/src/app/api/[[...slugs]]/_lib/desa/layanan/index.ts b/src/app/api/[[...slugs]]/_lib/desa/layanan/index.ts new file mode 100644 index 00000000..ebe6345b --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/desa/layanan/index.ts @@ -0,0 +1,7 @@ +import Elysia from "elysia"; +import PelayananSuratKeterangan from "./pelayanan_surat_keterangan"; + +const LayananDesa = new Elysia({ prefix: "/layanan", tags: ["Desa/Layanan"] }) +.use(PelayananSuratKeterangan) + +export default LayananDesa diff --git a/src/app/api/[[...slugs]]/_lib/desa/layanan/pelayanan_penduduk_non_permanen/findUnique.ts b/src/app/api/[[...slugs]]/_lib/desa/layanan/pelayanan_penduduk_non_permanen/findUnique.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/app/api/[[...slugs]]/_lib/desa/layanan/pelayanan_penduduk_non_permanen/index.ts b/src/app/api/[[...slugs]]/_lib/desa/layanan/pelayanan_penduduk_non_permanen/index.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/app/api/[[...slugs]]/_lib/desa/layanan/pelayanan_penduduk_non_permanen/updt.ts b/src/app/api/[[...slugs]]/_lib/desa/layanan/pelayanan_penduduk_non_permanen/updt.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/app/api/[[...slugs]]/_lib/desa/layanan/pelayanan_perizinan_berusaha/findUnique.ts b/src/app/api/[[...slugs]]/_lib/desa/layanan/pelayanan_perizinan_berusaha/findUnique.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/app/api/[[...slugs]]/_lib/desa/layanan/pelayanan_perizinan_berusaha/index.ts b/src/app/api/[[...slugs]]/_lib/desa/layanan/pelayanan_perizinan_berusaha/index.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/app/api/[[...slugs]]/_lib/desa/layanan/pelayanan_perizinan_berusaha/updt.ts b/src/app/api/[[...slugs]]/_lib/desa/layanan/pelayanan_perizinan_berusaha/updt.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/app/api/[[...slugs]]/_lib/desa/layanan/pelayanan_surat_keterangan/create.ts b/src/app/api/[[...slugs]]/_lib/desa/layanan/pelayanan_surat_keterangan/create.ts new file mode 100644 index 00000000..1963e7b6 --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/desa/layanan/pelayanan_surat_keterangan/create.ts @@ -0,0 +1,30 @@ +import prisma from "@/lib/prisma"; +import { Prisma } from "@prisma/client"; +import { Context } from "elysia"; + +type FormCreate = Prisma.PelayananSuratKeteranganGetPayload<{ + select: { + name: true; + deskripsi: true; + imageId: true; + }; +}>; +async function createPelayananSuratKeterangan(context: Context) { + const body = context.body as FormCreate; + + await prisma.pelayananSuratKeterangan.create({ + data: { + name: body.name, + deskripsi: body.deskripsi, + imageId: body.imageId, + }, + }); + return { + success: true, + message: "Success create pelayanan surat keterangan", + data: { + ...body, + }, + }; +} +export default createPelayananSuratKeterangan \ No newline at end of file diff --git a/src/app/api/[[...slugs]]/_lib/desa/layanan/pelayanan_surat_keterangan/del.ts b/src/app/api/[[...slugs]]/_lib/desa/layanan/pelayanan_surat_keterangan/del.ts new file mode 100644 index 00000000..fcebb32e --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/desa/layanan/pelayanan_surat_keterangan/del.ts @@ -0,0 +1,52 @@ +import prisma from "@/lib/prisma"; +import { Context } from "elysia"; +import path from "path"; +import fs from "fs/promises"; + +const pelayananSuratKeteranganDelete = async (context: Context) => { + const id = context.params?.id as string; + + if (!id) { + return { + status: 400, + body: "ID tidak diberikan", + }; + } + + const pelayananSuratKeterangan = await prisma.pelayananSuratKeterangan.findUnique({ + where: { id }, + include: { + image: true, + }, + }); + + if (!pelayananSuratKeterangan) { + return { + status: 404, + body: "Pelayanan surat keterangan tidak ditemukan", + }; + } + + // Hapus file gambar dari filesystem jika ada + if (pelayananSuratKeterangan.image) { + try { + const filePath = path.join(pelayananSuratKeterangan.image.path, pelayananSuratKeterangan.image.name); + await fs.unlink(filePath); + await prisma.fileStorage.delete({ + where: { id: pelayananSuratKeterangan.image.id }, + }); + } catch (err) { + console.error("Gagal hapus gambar lama:", err); + } + } + + const deleted = await prisma.pelayananSuratKeterangan.delete({ + where: { id }, + }); + + return { + status: 200, + body: deleted, + }; +}; +export default pelayananSuratKeteranganDelete; \ No newline at end of file diff --git a/src/app/api/[[...slugs]]/_lib/desa/layanan/pelayanan_surat_keterangan/find-many.ts b/src/app/api/[[...slugs]]/_lib/desa/layanan/pelayanan_surat_keterangan/find-many.ts new file mode 100644 index 00000000..9581f2f1 --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/desa/layanan/pelayanan_surat_keterangan/find-many.ts @@ -0,0 +1,24 @@ +import prisma from "@/lib/prisma"; + +export default async function pelayananSuratKeteranganFindMany() { + try { + const data = await prisma.pelayananSuratKeterangan.findMany({ + where: { isActive: true }, + include: { + image: true, + }, + }); + + return { + success: true, + message: "Success fetch pelayanan surat keterangan", + data, + }; + } catch (e) { + console.error("Find many error:", e); + return { + success: false, + message: "Failed fetch pelayanan surat keterangan", + }; + } +} \ No newline at end of file diff --git a/src/app/api/[[...slugs]]/_lib/desa/layanan/pelayanan_surat_keterangan/findUnique.ts b/src/app/api/[[...slugs]]/_lib/desa/layanan/pelayanan_surat_keterangan/findUnique.ts new file mode 100644 index 00000000..364d5c7b --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/desa/layanan/pelayanan_surat_keterangan/findUnique.ts @@ -0,0 +1,49 @@ +import prisma from "@/lib/prisma"; + +export default async function pelayananSuratKeteranganFindUnique(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 boleh kosong", + }, { status: 400 }); + } + + try { + if (typeof id !== 'string') { + return Response.json({ + success: false, + message: "ID tidak valid", + }, { status: 400 }); + } + + const data = await prisma.pelayananSuratKeterangan.findUnique({ + where: { id }, + include: { + image: true, + }, + }); + + if (!data) { + return Response.json({ + success: false, + message: "Pelayanan surat keterangan tidak ditemukan", + }, { status: 404 }); + } + + return Response.json({ + success: true, + message: "Success fetch pelayanan surat keterangan by ID", + data, + }, { status: 200 }); + } catch (error) { + console.error("Find by ID error:", error); + return Response.json({ + success: false, + message: "Gagal mengambil pelayanan surat keterangan: " + (error instanceof Error ? error.message : 'Unknown error'), + }, { status: 500 }); + } +} \ No newline at end of file diff --git a/src/app/api/[[...slugs]]/_lib/desa/layanan/pelayanan_surat_keterangan/index.ts b/src/app/api/[[...slugs]]/_lib/desa/layanan/pelayanan_surat_keterangan/index.ts new file mode 100644 index 00000000..9c289902 --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/desa/layanan/pelayanan_surat_keterangan/index.ts @@ -0,0 +1,35 @@ +import Elysia from "elysia"; +import pelayananSuratKeteranganFindMany from "./find-many"; +import pelayananSuratKeteranganFindUnique from "./findUnique"; +import pelayananSuratKeteranganCreate from "./create"; +import pelayananSuratKeteranganUpdate from "./updt"; +import pelayananSuratKeteranganDelete from "./del"; + +import { t } from "elysia"; + +const PelayananSuratKeterangan = new Elysia({ prefix: "/pelayanansuratketerangan", tags: ["Desa/Layanan/Pelayanan Surat Keterangan"] }) +.get("/find-many", pelayananSuratKeteranganFindMany) +.get("/:id", async (context) => { + const response = await pelayananSuratKeteranganFindUnique(new Request(context.request)); + return response; +}) +.post("/create", pelayananSuratKeteranganCreate, { + body: t.Object({ + name: t.String(), + deskripsi: t.String(), + imageId: t.String(), + }), +}) +.delete("/del/:id", pelayananSuratKeteranganDelete) +.put("/:id", async (context) => { + const response = await pelayananSuratKeteranganUpdate(context); + return response; +}, +{ + body: t.Object({ + name: t.String(), + deskripsi: t.String(), + imageId: t.String(), + }), +}) +export default PelayananSuratKeterangan; \ No newline at end of file diff --git a/src/app/api/[[...slugs]]/_lib/desa/layanan/pelayanan_surat_keterangan/updt.ts b/src/app/api/[[...slugs]]/_lib/desa/layanan/pelayanan_surat_keterangan/updt.ts new file mode 100644 index 00000000..06b301f0 --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/desa/layanan/pelayanan_surat_keterangan/updt.ts @@ -0,0 +1,97 @@ +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.PelayananSuratKeteranganGetPayload<{ + select: { + name: true; + deskripsi: true; + imageId: true; + }; +}>; +export default async function updatePelayananSuratKeterangan(context: Context) { + try { + const id = context.params?.id; + const body = (await context.body) as Omit; + + const { name, deskripsi, imageId } = body; + + if (!id) { + return new Response(JSON.stringify({ + success: false, + message: "ID tidak diberikan", + }), { + status: 400, + headers: { + "Content-Type": "application/json", + }, + }); + } + const existing = await prisma.pelayananSuratKeterangan.findUnique({ + where: { id }, + include: { + image: true, + } + }); + + if (!existing) { + return new Response(JSON.stringify({ + success: false, + message: "Pelayanan surat keterangan 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 (err) { + console.error("Gagal hapus gambar lama:", err); + } + } + } + + const updated = await prisma.pelayananSuratKeterangan.update({ + where: { id }, + data: { + name, + deskripsi, + imageId, + }, + }) + + return new Response(JSON.stringify({ + success: true, + message: "Success update pelayanan surat keterangan", + data: updated, + }), { + status: 200, + headers: { + "Content-Type": "application/json", + }, + }); + } catch (error) { + console.error("Error updating pelayanan surat keterangan:", error); + return new Response(JSON.stringify({ + success: false, + message: "Terjadi kesalahan saat mengupdate pelayanan surat keterangan", + }), { + status: 500, + headers: { + "Content-Type": "application/json", + }, + }); + } +} \ No newline at end of file diff --git a/src/app/api/[[...slugs]]/_lib/desa/layanan/pelayanan_telunjuk_sakti_desa/create.ts b/src/app/api/[[...slugs]]/_lib/desa/layanan/pelayanan_telunjuk_sakti_desa/create.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/app/api/[[...slugs]]/_lib/desa/layanan/pelayanan_telunjuk_sakti_desa/del.ts b/src/app/api/[[...slugs]]/_lib/desa/layanan/pelayanan_telunjuk_sakti_desa/del.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/app/api/[[...slugs]]/_lib/desa/layanan/pelayanan_telunjuk_sakti_desa/find-many.ts b/src/app/api/[[...slugs]]/_lib/desa/layanan/pelayanan_telunjuk_sakti_desa/find-many.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/app/api/[[...slugs]]/_lib/desa/layanan/pelayanan_telunjuk_sakti_desa/findUnique.ts b/src/app/api/[[...slugs]]/_lib/desa/layanan/pelayanan_telunjuk_sakti_desa/findUnique.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/app/api/[[...slugs]]/_lib/desa/layanan/pelayanan_telunjuk_sakti_desa/index.ts b/src/app/api/[[...slugs]]/_lib/desa/layanan/pelayanan_telunjuk_sakti_desa/index.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/app/api/[[...slugs]]/_lib/desa/layanan/pelayanan_telunjuk_sakti_desa/updt.ts b/src/app/api/[[...slugs]]/_lib/desa/layanan/pelayanan_telunjuk_sakti_desa/updt.ts new file mode 100644 index 00000000..e69de29b