diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index 97bdedff..4fba70fc 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -672,17 +672,18 @@ model GalleryVideo {
// ========================================= LAYANAN DESA ========================================= //
model PelayananSuratKeterangan {
- id String @id @default(cuid())
- name String
- deskripsi String @db.Text
- image FileStorage? @relation("PelayananSuratKeteranganImage", fields: [imageId], references: [id])
- imageId String?
- image2 FileStorage? @relation("PelayananSuratKeteranganImage2", fields: [image2Id], references: [id])
- image2Id String?
- createdAt DateTime @default(now())
- updatedAt DateTime @updatedAt
- deletedAt DateTime @default(now())
- isActive Boolean @default(true)
+ id String @id @default(cuid())
+ name String
+ deskripsi String @db.Text
+ image FileStorage? @relation("PelayananSuratKeteranganImage", fields: [imageId], references: [id])
+ imageId String?
+ image2 FileStorage? @relation("PelayananSuratKeteranganImage2", fields: [image2Id], references: [id])
+ image2Id String?
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+ deletedAt DateTime @default(now())
+ isActive Boolean @default(true)
+ AjukanPermohonan AjukanPermohonan[]
}
model PelayananTelunjukSaktiDesa {
@@ -717,6 +718,20 @@ model PelayananPendudukNonPermanen {
isActive Boolean @default(true)
}
+model AjukanPermohonan {
+ id String @id @default(cuid())
+ nama String
+ nik String
+ alamat String
+ nomorKk String
+ kategori PelayananSuratKeterangan @relation(fields: [kategoriId], references: [id])
+ kategoriId String
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+ deletedAt DateTime @default(now())
+ isActive Boolean @default(true)
+}
+
// ========================================= PENGHARGAAN ========================================= //
model Penghargaan {
id String @id @default(cuid())
@@ -1254,15 +1269,15 @@ model KontakDaruratToItem {
// ========================================= PENCEGAHAN KRIMINALITAS ========================================= //
model PencegahanKriminalitas {
- id String @id @default(cuid())
- judul String
- deskripsi String
- deskripsiSingkat String
- linkVideo String
- createdAt DateTime @default(now())
- updatedAt DateTime @updatedAt
- deletedAt DateTime @default(now())
- isActive Boolean @default(true)
+ id String @id @default(cuid())
+ judul String
+ deskripsi String
+ deskripsiSingkat String
+ linkVideo String
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+ deletedAt DateTime @default(now())
+ isActive Boolean @default(true)
}
// ========================================= LAPORAN PUBLIK ========================================= //
diff --git a/public/pudak-icon.png b/public/pudak-icon.png
index 7aa60c17..c54c6062 100644
Binary files a/public/pudak-icon.png and b/public/pudak-icon.png differ
diff --git a/src/app/admin/(dashboard)/_state/desa/layananDesa.ts b/src/app/admin/(dashboard)/_state/desa/layananDesa.ts
index 3cc787a3..b11b67a8 100644
--- a/src/app/admin/(dashboard)/_state/desa/layananDesa.ts
+++ b/src/app/admin/(dashboard)/_state/desa/layananDesa.ts
@@ -71,6 +71,22 @@ const pelayananPendudukNonPermanenForm = {
deskripsi: "",
};
+const templateAjukanForm = z.object({
+ nama: z.string().min(1).max(5000),
+ nik: z.string().min(1).max(5000),
+ alamat: z.string().min(1).max(5000),
+ nomorKk: z.string().min(1).max(5000),
+ kategoriId: z.string().min(1).max(5000),
+});
+
+const defaultAjukanForm = {
+ nama: "",
+ nik: "",
+ alamat: "",
+ nomorKk: "",
+ kategoriId: "",
+};
+
const suratKeterangan = proxy({
create: {
form: { ...suratKeteranganForm },
@@ -146,6 +162,30 @@ const suratKeterangan = proxy({
}
},
},
+ findManyAll: {
+ data: null as Prisma.PelayananSuratKeteranganGetPayload<{
+ omit: { isActive: true };
+ }>[] | null,
+ loading: false,
+ load: async () => {
+ suratKeterangan.findManyAll.loading = true;
+ try {
+ const res = await ApiFetch.api.desa.layanan.pelayanansuratketerangan["findManyAll"].get();
+
+ if (res.status === 200 && res.data?.success) {
+ suratKeterangan.findManyAll.data = res.data.data || [];
+ } else {
+ suratKeterangan.findManyAll.data = [];
+ console.error("Failed to load surat keterangan all:", res.data?.message);
+ }
+ } catch (error) {
+ console.error("Error loading surat keterangan all:", error);
+ suratKeterangan.findManyAll.data = [];
+ } finally {
+ suratKeterangan.findManyAll.loading = false;
+ }
+ },
+ },
findUnique: {
data: null as Prisma.PelayananSuratKeteranganGetPayload<{
include: {
@@ -769,11 +809,250 @@ const pelayananPendudukNonPermanen = proxy({
},
});
+const ajukanPermohonan = proxy({
+ create: {
+ form: { ...defaultAjukanForm },
+ loading: false,
+ async create() {
+ const cek = templateAjukanForm.safeParse(
+ ajukanPermohonan.create.form
+ );
+ if (!cek.success) {
+ const err = `[${cek.error.issues
+ .map((v) => `${v.path.join(".")}`)
+ .join("\n")}] required`;
+ return toast.error(err);
+ }
+ try {
+ ajukanPermohonan.create.loading = true;
+ const res = await ApiFetch.api.desa.ajukanpermohonan[
+ "create"
+ ].post(ajukanPermohonan.create.form);
+ if (res.status === 200) {
+ ajukanPermohonan.findMany.load();
+ return toast.success("Ajukan permohonan berhasil disimpan!");
+ }
+ return toast.error("Gagal menyimpan ajukan permohonan");
+ } catch (error) {
+ console.log((error as Error).message);
+ } finally {
+ ajukanPermohonan.create.loading = false;
+ }
+ },
+ resetForm() {
+ ajukanPermohonan.create.form = { ...defaultAjukanForm };
+ },
+ },
+ findMany: {
+ data: null as Prisma.AjukanPermohonanGetPayload<{
+ include: {
+ kategori: true;
+ };
+ }>[] | null,
+ page: 1,
+ totalPages: 1,
+ total: 0,
+ loading: false,
+ search: "",
+ load: async (page = 1, limit = 10, search = "") => {
+ // Change to arrow function
+ ajukanPermohonan.findMany.loading = true; // Use the full path to access the property
+ ajukanPermohonan.findMany.page = page;
+ ajukanPermohonan.findMany.search = search;
+ try {
+ const query: any = { page, limit };
+ if (search) query.search = search;
+ const res = await ApiFetch.api.desa.ajukanpermohonan[
+ "findMany"
+ ].get({
+ query,
+ });
+
+ if (res.status === 200 && res.data?.success) {
+ ajukanPermohonan.findMany.data = res.data.data || [];
+ ajukanPermohonan.findMany.total = res.data.total || 0;
+ ajukanPermohonan.findMany.totalPages = res.data.totalPages || 1;
+ } else {
+ console.error("Failed to load ajukan permohonan:", res.data?.message);
+ ajukanPermohonan.findMany.data = [];
+ ajukanPermohonan.findMany.total = 0;
+ ajukanPermohonan.findMany.totalPages = 1;
+ }
+ } catch (error) {
+ console.error("Error loading ajukan permohonan:", error);
+ ajukanPermohonan.findMany.data = [];
+ ajukanPermohonan.findMany.total = 0;
+ ajukanPermohonan.findMany.totalPages = 1;
+ } finally {
+ ajukanPermohonan.findMany.loading = false;
+ }
+ },
+ },
+ findUnique: {
+ data: null as Prisma.AjukanPermohonanGetPayload<{
+ include: {
+ kategori: true;
+ }
+ }> | null,
+ async load(id: string) {
+ try {
+ const res = await fetch(
+ `/api/desa/ajukanpermohonan/${id}`
+ );
+ if (res.ok) {
+ const data = await res.json();
+ ajukanPermohonan.findUnique.data = data.data ?? null;
+ } else {
+ console.error("Failed to fetch ajukan permohonan:", res.statusText);
+ ajukanPermohonan.findUnique.data = null;
+ }
+ } catch (error) {
+ console.error("Error fetching ajukan permohonan:", error);
+ ajukanPermohonan.findUnique.data = null;
+ }
+ },
+ },
+ delete: {
+ loading: false,
+ async byId(id: string) {
+ if (!id) return toast.warn("ID tidak valid");
+ try {
+ ajukanPermohonan.delete.loading = true;
+ const response = await fetch(
+ `/api/desa/ajukanpermohonan/del/${id}`,
+ {
+ method: "DELETE",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ }
+ );
+ const result = await response.json();
+ if (response.ok) {
+ toast.success(result.message || "Ajukan permohonan berhasil dihapus");
+ await ajukanPermohonan.findMany.load(); // refresh list
+ } else {
+ toast.error(result.message || "Gagal menghapus ajukan permohonan");
+ }
+ } catch (error) {
+ console.error("Gagal delete:", error);
+ toast.error("Terjadi kesalahan saat menghapus ajukan permohonan");
+ } finally {
+ ajukanPermohonan.delete.loading = false;
+ }
+ },
+ },
+ edit: {
+ id: "",
+ form: { ...defaultAjukanForm },
+ loading: false,
+
+ async load(id: string) {
+ if (!id) {
+ toast.warn("ID tidak valid");
+ return null;
+ }
+ try {
+ const response = await fetch(
+ `/api/desa/ajukanpermohonan/${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 = {
+ nama: data.nama,
+ nik: data.nik,
+ alamat: data.alamat,
+ nomorKk: data.nomorKk,
+ kategoriId: data.kategoriId,
+ };
+ return data;
+ } else {
+ throw new Error(result.message || "Gagal memuat data");
+ }
+ } catch (error) {
+ console.error("Error fetching ajukan permohonan:", error);
+ toast.error(
+ error instanceof Error ? error.message : "Gagal memuat data"
+ );
+ return null;
+ }
+ },
+ async update() {
+ const cek = templateAjukanForm.safeParse(
+ ajukanPermohonan.edit.form
+ );
+ if (!cek.success) {
+ const err = `[${cek.error.issues
+ .map((v) => `${v.path.join(".")}`)
+ .join("\n")}] required`;
+ return toast.error(err);
+ }
+ try {
+ ajukanPermohonan.edit.loading = true;
+ const response = await fetch(
+ `/api/desa/ajukanpermohonan/${this.id}`,
+ {
+ method: "PUT",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({
+ nama: this.form.nama,
+ nik: this.form.nik,
+ alamat: this.form.alamat,
+ nomorKk: this.form.nomorKk,
+ kategoriId: this.form.kategoriId,
+ }),
+ }
+ );
+ 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 || "Ajukan permohonan berhasil diupdate");
+ await ajukanPermohonan.findMany.load(); // refresh list
+ return true;
+ } else {
+ throw new Error(
+ result.message || "Gagal mengupdate ajukan permohonan"
+ );
+ }
+ } catch (error) {
+ console.error("Error updating ajukan permohonan:", error);
+ toast.error(
+ error instanceof Error
+ ? error.message
+ : "Terjadi kesalahan saat update ajukan permohonan"
+ );
+ return false;
+ } finally {
+ ajukanPermohonan.edit.loading = false;
+ }
+ },
+ },
+});
+
const stateLayananDesa = proxy({
suratKeterangan,
pelayananPerizinanBerusaha,
pelayananTelunjukSaktiDesa,
pelayananPendudukNonPermanen,
+ ajukanPermohonan,
});
export default stateLayananDesa;
diff --git a/src/app/admin/(dashboard)/desa/_com/layoutTabLayanan.tsx b/src/app/admin/(dashboard)/desa/_com/layoutTabLayanan.tsx
index 94d786a9..4ec40e47 100644
--- a/src/app/admin/(dashboard)/desa/_com/layoutTabLayanan.tsx
+++ b/src/app/admin/(dashboard)/desa/_com/layoutTabLayanan.tsx
@@ -4,7 +4,7 @@ import colors from '@/con/colors';
import { ScrollArea, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title, Tooltip } from '@mantine/core';
import { usePathname, useRouter } from 'next/navigation';
import React, { useEffect, useState } from 'react';
-import { IconFileText, IconBuildingStore, IconSparkles, IconUsers } from '@tabler/icons-react';
+import { IconFileText, IconBuildingStore, IconSparkles, IconUsers, IconUsersPlus } from '@tabler/icons-react';
function LayoutTabsLayanan({ children }: { children: React.ReactNode }) {
const router = useRouter()
@@ -37,6 +37,13 @@ function LayoutTabsLayanan({ children }: { children: React.ReactNode }) {
href: "/admin/desa/layanan/pelayanan_penduduk_non_permanent",
icon: ,
tooltip: "Pendataan penduduk non-permanent"
+ },
+ {
+ label: "Ajukan Permohonan",
+ value: "ajukanpermohonan",
+ href: "/admin/desa/layanan/ajukan_permohonan",
+ icon: ,
+ tooltip: "Ajukan permohonan"
}
];
diff --git a/src/app/admin/(dashboard)/desa/berita/list-berita/page.tsx b/src/app/admin/(dashboard)/desa/berita/list-berita/page.tsx
index 1e632b96..47b628d8 100644
--- a/src/app/admin/(dashboard)/desa/berita/list-berita/page.tsx
+++ b/src/app/admin/(dashboard)/desa/berita/list-berita/page.tsx
@@ -5,7 +5,6 @@ import {
Button,
Center,
Group,
- Image,
Pagination,
Paper,
Skeleton,
@@ -18,7 +17,7 @@ import {
TableTr,
Text,
Title,
- Tooltip,
+ Tooltip
} from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconCircleDashedPlus, IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
@@ -87,7 +86,6 @@ function ListBerita({ search }: { search: string }) {
Judul
Kategori
- Gambar
Aksi
@@ -96,7 +94,7 @@ function ListBerita({ search }: { search: string }) {
filteredData.map((item) => (
-
+
{item.judul}
@@ -107,19 +105,6 @@ function ListBerita({ search }: { search: string }) {
{item.kategoriBerita?.name || '-'}
-
-
- {item.image?.link ? (
-
- ) : (
-
- )}
-
-