diff --git a/package.json b/package.json
index 4fc1956b..7b9450f8 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "desa-darmasaba",
- "version": "0.1.3",
+ "version": "0.1.4",
"private": true,
"scripts": {
"dev": "next dev --turbopack",
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index 7bbc4c7c..e93d4996 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -1341,3 +1341,15 @@ model DesaDigital {
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
+// ========================================= PROGRAM KREATIF ========================================= //
+model ProgramKreatif {
+ id String @id @default(cuid())
+ name String
+ slug String @db.Text //deskripsi singkat
+ deskripsi String @db.Text //deskripsi panjang
+ icon String
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+ deletedAt DateTime @default(now())
+ isActive Boolean @default(true)
+}
diff --git a/src/app/admin/(dashboard)/_state/ekonomi/desa-digital.ts b/src/app/admin/(dashboard)/_state/inovasi/desa-digital.ts
similarity index 100%
rename from src/app/admin/(dashboard)/_state/ekonomi/desa-digital.ts
rename to src/app/admin/(dashboard)/_state/inovasi/desa-digital.ts
diff --git a/src/app/admin/(dashboard)/_state/inovasi/program-kreatif.ts b/src/app/admin/(dashboard)/_state/inovasi/program-kreatif.ts
new file mode 100644
index 00000000..f96e79b3
--- /dev/null
+++ b/src/app/admin/(dashboard)/_state/inovasi/program-kreatif.ts
@@ -0,0 +1,227 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+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, "Nama minimal 1 karakter"),
+ deskripsi: z.string().min(1, "Deskripsi minimal 1 karakter"),
+ slug: z.string().min(1, "Deskripsi singkat minimal 1 karakter"),
+ icon: z.string().min(1, "Icon minimal 1 karakter"),
+});
+
+const defaultForm = {
+ name: "",
+ deskripsi: "",
+ slug: "",
+ icon: "",
+};
+
+const programKreatifState = proxy({
+ create: {
+ form: { ...defaultForm },
+ loading: false,
+ async create() {
+ const cek = templateForm.safeParse(programKreatifState.create.form);
+ if (!cek.success) {
+ const err = `[${cek.error.issues
+ .map((v) => `${v.path.join(".")}`)
+ .join("\n")}] required`;
+ return toast.error(err);
+ }
+
+ try {
+ programKreatifState.create.loading = true;
+ const res = await ApiFetch.api.inovasi.programkreatif["create"].post(
+ programKreatifState.create.form
+ );
+ if (res.status === 200) {
+ programKreatifState.findMany.load();
+ return toast.success("success create");
+ }
+ console.log(res);
+ return toast.error("failed create");
+ } catch (error) {
+ console.log((error as Error).message);
+ } finally {
+ programKreatifState.create.loading = false;
+ }
+ },
+ },
+ findMany: {
+ data: null as any[] | null,
+ page: 1,
+ totalPages: 1,
+ total: 0,
+ loading: false,
+ load: async (page = 1, limit = 10) => {
+ // Change to arrow function
+ programKreatifState.findMany.loading = true; // Use the full path to access the property
+ programKreatifState.findMany.page = page;
+ try {
+ const res = await ApiFetch.api.inovasi.programkreatif["find-many"].get({
+ query: { page, limit },
+ });
+
+ if (res.status === 200 && res.data?.success) {
+ programKreatifState.findMany.data = res.data.data || [];
+ programKreatifState.findMany.total = res.data.total || 0;
+ programKreatifState.findMany.totalPages = res.data.totalPages || 1;
+ } else {
+ console.error(
+ "Failed to load grafik berdasarkan jenis kelamin:",
+ res.data?.message
+ );
+ programKreatifState.findMany.data = [];
+ programKreatifState.findMany.total = 0;
+ programKreatifState.findMany.totalPages = 1;
+ }
+ } catch (error) {
+ console.error("Error loading grafik berdasarkan jenis kelamin:", error);
+ programKreatifState.findMany.data = [];
+ programKreatifState.findMany.total = 0;
+ programKreatifState.findMany.totalPages = 1;
+ } finally {
+ programKreatifState.findMany.loading = false;
+ }
+ },
+ },
+ update: {
+ id: "",
+ form: { ...defaultForm },
+ loading: false,
+ async load(id: string) {
+ if (!id) {
+ toast.warn("ID tidak valid");
+ return null;
+ }
+
+ try {
+ const response = await fetch(`/api/inovasi/programkreatif/${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,
+ slug: data.slug,
+ icon: data.icon,
+ };
+ return data;
+ } else {
+ throw new Error(result?.message || "Gagal mengambil data");
+ }
+ } catch (error) {
+ console.error("Error loading program kreatif:", error);
+ toast.error(
+ error instanceof Error ? error.message : "Gagal memuat data"
+ );
+ return null;
+ }
+ },
+
+ async submit() {
+ const id = this.id;
+ if (!id) {
+ toast.warn("ID tidak valid");
+ return null;
+ }
+ const cek = templateForm.safeParse(this.form);
+ if (!cek.success) {
+ const err = `[${cek.error.issues
+ .map((v) => `${v.path.join(".")}`)
+ .join("\n")}] required`;
+ toast.error(err);
+ return null;
+ }
+ this.loading = true;
+ try {
+ const response = await fetch(`/api/inovasi/programkreatif/${id}`, {
+ method: "PUT",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify(this.form),
+ });
+ const result = await response.json();
+ if (!response.ok || !result?.success) {
+ throw new Error(result?.message || "Gagal update data");
+ }
+ toast.success("Berhasil update data!");
+ await programKreatifState.findMany.load();
+ return result.data;
+ } catch (error) {
+ console.error("Error update data:", error);
+ toast.error("Gagal update data program kreatif");
+ } finally {
+ this.loading = false;
+ }
+ },
+ },
+ findUnique: {
+ data: null as Prisma.ProgramKreatifGetPayload<{
+ omit: { isActive: true };
+ }> | null,
+ async load(id: string) {
+ try {
+ const res = await fetch(`/api/inovasi/programkreatif/${id}`);
+ if (res.ok) {
+ const data = await res.json();
+ programKreatifState.findUnique.data = data.data ?? null;
+ } else {
+ console.error("Failed to fetch data", res.status, res.statusText);
+ programKreatifState.findUnique.data = null;
+ }
+ } catch (error) {
+ console.error("Error loading program kreatif:", error);
+ programKreatifState.findUnique.data = null;
+ }
+ },
+ },
+ delete: {
+ loading: false,
+ async byId(id: string) {
+ if (!id) return toast.warn("ID tidak valid");
+
+ try {
+ programKreatifState.delete.loading = true;
+
+ const response = await fetch(`/api/inovasi/programkreatif/del/${id}`, {
+ method: "DELETE",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ });
+
+ const result = await response.json();
+
+ if (response.ok && result?.success) {
+ toast.success(result.message || "Program kreatif berhasil dihapus");
+ await programKreatifState.findMany.load(); // refresh list
+ } else {
+ toast.error(result?.message || "Gagal menghapus program kreatif");
+ }
+ } catch (error) {
+ console.error("Gagal delete:", error);
+ toast.error("Terjadi kesalahan saat menghapus program kreatif");
+ } finally {
+ programKreatifState.delete.loading = false;
+ }
+ },
+ },
+});
+
+export default programKreatifState;
diff --git a/src/app/admin/(dashboard)/desa/berita/[id]/edit/page.tsx b/src/app/admin/(dashboard)/desa/berita/[id]/edit/page.tsx
index b5ea38d1..eaf05ddb 100644
--- a/src/app/admin/(dashboard)/desa/berita/[id]/edit/page.tsx
+++ b/src/app/admin/(dashboard)/desa/berita/[id]/edit/page.tsx
@@ -18,8 +18,6 @@ import { useParams, useRouter } from "next/navigation";
import { useEffect, useState } from "react";
import { toast } from "react-toastify";
import { useProxy } from "valtio/utils";
-
-
import EditEditor from "@/app/admin/(dashboard)/_com/editEditor";
import colors from "@/con/colors";
import ApiFetch from "@/lib/api-fetch";
diff --git a/src/app/admin/(dashboard)/inovasi/desa-digital-smart-village/[id]/edit/page.tsx b/src/app/admin/(dashboard)/inovasi/desa-digital-smart-village/[id]/edit/page.tsx
index 8cf11b2c..1ef111a4 100644
--- a/src/app/admin/(dashboard)/inovasi/desa-digital-smart-village/[id]/edit/page.tsx
+++ b/src/app/admin/(dashboard)/inovasi/desa-digital-smart-village/[id]/edit/page.tsx
@@ -1,7 +1,7 @@
'use client'
/* eslint-disable react-hooks/exhaustive-deps */
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
-import desaDigitalState from '@/app/admin/(dashboard)/_state/ekonomi/desa-digital';
+import desaDigitalState from '@/app/admin/(dashboard)/_state/inovasi/desa-digital';
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';
diff --git a/src/app/admin/(dashboard)/inovasi/desa-digital-smart-village/[id]/page.tsx b/src/app/admin/(dashboard)/inovasi/desa-digital-smart-village/[id]/page.tsx
index 50c3d8b1..a5bcd748 100644
--- a/src/app/admin/(dashboard)/inovasi/desa-digital-smart-village/[id]/page.tsx
+++ b/src/app/admin/(dashboard)/inovasi/desa-digital-smart-village/[id]/page.tsx
@@ -7,7 +7,7 @@ import { useParams, useRouter } from 'next/navigation';
import { useState } from 'react';
import { useProxy } from 'valtio/utils';
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
-import desaDigitalState from '../../../_state/ekonomi/desa-digital';
+import desaDigitalState from '../../../_state/inovasi/desa-digital';
function DetailDesaDigital() {
const stateDesaDigital = useProxy(desaDigitalState)
diff --git a/src/app/admin/(dashboard)/inovasi/desa-digital-smart-village/create/page.tsx b/src/app/admin/(dashboard)/inovasi/desa-digital-smart-village/create/page.tsx
index bcf97ee7..96666e6d 100644
--- a/src/app/admin/(dashboard)/inovasi/desa-digital-smart-village/create/page.tsx
+++ b/src/app/admin/(dashboard)/inovasi/desa-digital-smart-village/create/page.tsx
@@ -8,7 +8,7 @@ import { useState } from 'react';
import { toast } from 'react-toastify';
import { useProxy } from 'valtio/utils';
import CreateEditor from '../../../_com/createEditor';
-import desaDigitalState from '../../../_state/ekonomi/desa-digital';
+import desaDigitalState from '../../../_state/inovasi/desa-digital';
function CreateDesaDigital() {
diff --git a/src/app/admin/(dashboard)/inovasi/desa-digital-smart-village/page.tsx b/src/app/admin/(dashboard)/inovasi/desa-digital-smart-village/page.tsx
index 9cbea679..bfc71f1e 100644
--- a/src/app/admin/(dashboard)/inovasi/desa-digital-smart-village/page.tsx
+++ b/src/app/admin/(dashboard)/inovasi/desa-digital-smart-village/page.tsx
@@ -8,7 +8,7 @@ import { useState } from 'react';
import { useProxy } from 'valtio/utils';
import HeaderSearch from '../../_com/header';
import JudulList from '../../_com/judulList';
-import desaDigitalState from '../../_state/ekonomi/desa-digital';
+import desaDigitalState from '../../_state/inovasi/desa-digital';
function DesaDigitalSmartVillage() {
const [search, setSearch] = useState("");
diff --git a/src/app/admin/(dashboard)/inovasi/layanan-online-desa/edit/page.tsx b/src/app/admin/(dashboard)/inovasi/layanan-online-desa/[id]/edit/page.tsx
similarity index 91%
rename from src/app/admin/(dashboard)/inovasi/layanan-online-desa/edit/page.tsx
rename to src/app/admin/(dashboard)/inovasi/layanan-online-desa/[id]/edit/page.tsx
index 3f844e7f..558fd83d 100644
--- a/src/app/admin/(dashboard)/inovasi/layanan-online-desa/edit/page.tsx
+++ b/src/app/admin/(dashboard)/inovasi/layanan-online-desa/[id]/edit/page.tsx
@@ -3,7 +3,7 @@ import colors from "@/con/colors";
import { Box, Button, Paper, Stack, Title, TextInput, Group, Text } from "@mantine/core";
import { IconArrowBack, IconImageInPicture } from "@tabler/icons-react";
import { useRouter } from "next/navigation";
-import { KeamananEditor } from "../../../keamanan/_com/keamananEditor";
+// import { KeamananEditor } from "../../../keamanan/_com/keamananEditor";
export default function EditLayananOnlineDesa() {
const router = useRouter();
@@ -28,9 +28,9 @@ export default function EditLayananOnlineDesa() {
/>
Deskripsi Layanan Online Desa
-
+ /> */}
diff --git a/src/app/admin/(dashboard)/inovasi/layanan-online-desa/detail/page.tsx b/src/app/admin/(dashboard)/inovasi/layanan-online-desa/[id]/page.tsx
similarity index 100%
rename from src/app/admin/(dashboard)/inovasi/layanan-online-desa/detail/page.tsx
rename to src/app/admin/(dashboard)/inovasi/layanan-online-desa/[id]/page.tsx
diff --git a/src/app/admin/(dashboard)/inovasi/program-kreatif-desa/[id]/edit/page.tsx b/src/app/admin/(dashboard)/inovasi/program-kreatif-desa/[id]/edit/page.tsx
new file mode 100644
index 00000000..72168726
--- /dev/null
+++ b/src/app/admin/(dashboard)/inovasi/program-kreatif-desa/[id]/edit/page.tsx
@@ -0,0 +1,149 @@
+/* eslint-disable react-hooks/exhaustive-deps */
+'use client'
+import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
+import programKreatifState from '@/app/admin/(dashboard)/_state/inovasi/program-kreatif';
+import colors from '@/con/colors';
+import { Box, Button, 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';
+import SelectIconProgramEdit from '../../_lib/selectIconEdit';
+
+interface FormProgramKreatif {
+ name: string;
+ deskripsi: string;
+ slug: string;
+ icon: string;
+}
+
+type IconKey = 'ekowisata' | 'kompetisi' | 'wisata' | 'ekonomi' | 'sampah';
+
+
+function EditProgramKreatifDesa() {
+ const stateProgramKreatif = useProxy(programKreatifState)
+ const params = useParams()
+ const router = useRouter();
+ const [formData, setFormData] = useState({
+ name: '',
+ deskripsi: '',
+ slug: '',
+ icon: '',
+ })
+
+ useEffect(() => {
+ const loadProgramKreatif = async () => {
+ const id = params?.id as string;
+ if (!id) return;
+
+ try {
+ const data = await stateProgramKreatif.update.load(id);
+ if (data) {
+ // ⬇️ FIX PENTING: tambahkan ini
+ stateProgramKreatif.update.id = id;
+
+ stateProgramKreatif.update.form = {
+ name: data.name,
+ slug: data.slug,
+ deskripsi: data.deskripsi,
+ icon: data.icon,
+ };
+
+ setFormData({
+ name: data.name,
+ slug: data.slug,
+ deskripsi: data.deskripsi,
+ icon: data.icon,
+ });
+ }
+ } catch (error) {
+ console.error("Error loading program kreatif:", error);
+ toast.error("Gagal memuat data program kreatif");
+ }
+ }
+
+ loadProgramKreatif();
+ }, [params?.id]);
+
+
+
+ const handleSubmit = async () => {
+ try {
+ stateProgramKreatif.update.form = {
+ ...stateProgramKreatif.update.form,
+ name: formData.name.trim(),
+ deskripsi: formData.deskripsi.trim(),
+ slug: formData.slug.trim(),
+ icon: formData.icon.trim(),
+ }
+ await stateProgramKreatif.update.submit();
+ router.push("/admin/inovasi/program-kreatif-desa");
+ } catch (error) {
+ console.error("Error updating program kreatif:", error);
+ toast.error("Gagal memuat data program kreatif");
+ }
+ }
+ return (
+
+
+
+
+
+
+
+ Edit Program Kreatif Desa
+ Nama Program Kreatif Desa}
+ placeholder="masukkan nama program kreatif desa"
+ onChange={(val) => {
+ setFormData({
+ ...formData,
+ name: val.target.value
+ })
+ }}
+ />
+ Deskripsi Singkat Program Kreatif Desa}
+ placeholder="masukkan deskripsi singkat program kreatif desa"
+ onChange={(val) => {
+ setFormData({
+ ...formData,
+ slug: val.target.value
+ })
+ }}
+ />
+
+ Deskripsi
+ {
+ setFormData((prev) => ({ ...prev, deskripsi: htmlContent }));
+ stateProgramKreatif.update.form.deskripsi = htmlContent;
+ }}
+ />
+
+
+ Ikon Program Kreatif Desa
+ {
+ setFormData((prev) => ({ ...prev, icon: value }));
+ stateProgramKreatif.update.form.icon = value;
+ }}
+/>
+
+
+
+
+
+
+
+ );
+}
+
+export default EditProgramKreatifDesa;
diff --git a/src/app/admin/(dashboard)/inovasi/program-kreatif-desa/[id]/page.tsx b/src/app/admin/(dashboard)/inovasi/program-kreatif-desa/[id]/page.tsx
new file mode 100644
index 00000000..f5c15480
--- /dev/null
+++ b/src/app/admin/(dashboard)/inovasi/program-kreatif-desa/[id]/page.tsx
@@ -0,0 +1,127 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+'use client'
+import colors from '@/con/colors';
+import { Box, Button, Flex, Paper, Skeleton, Stack, Text } from '@mantine/core';
+import { useShallowEffect } from '@mantine/hooks';
+import { IconArrowBack, IconChartLine, IconEdit, IconLeaf, IconRecycle, IconTent, IconTrophy, IconX } from '@tabler/icons-react';
+import { useParams, useRouter } from 'next/navigation';
+import React, { useState } from 'react';
+import { useProxy } from 'valtio/utils';
+import programKreatifState from '../../../_state/inovasi/program-kreatif';
+import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
+
+// import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
+
+function DetailProgramKreatifDesa() {
+ const [modalHapus, setModalHapus] = useState(false)
+ const stateProgramKreatif = useProxy(programKreatifState)
+ const router = useRouter()
+ const params = useParams()
+ const [selectedId, setSelectedId] = useState(null)
+
+ const iconMap: Record> = {
+ ekowisata: IconLeaf,
+ kompetisi: IconTrophy,
+ wisata: IconTent,
+ ekonomi: IconChartLine,
+ sampah: IconRecycle,
+ };
+
+ useShallowEffect(() => {
+ stateProgramKreatif.findUnique.load(params?.id as string)
+ }, [params?.id])
+
+ const handleHapus = () => {
+ if (selectedId) {
+ stateProgramKreatif.delete.byId(selectedId)
+ setModalHapus(false)
+ setSelectedId(null)
+ router.push("/admin/inovasi/program-kreatif-desa")
+ }
+ }
+
+ if (!stateProgramKreatif.findUnique.data) {
+ return (
+
+
+
+ )
+ }
+
+ return (
+
+
+
+
+
+
+ Detail Program Kreatif Desa
+
+
+
+
+ Nama Program Kreatif Desa
+ {stateProgramKreatif.findUnique.data?.name}
+
+
+ Ikon Program Kreatif Desa
+ {iconMap[stateProgramKreatif.findUnique.data?.icon] && (
+
+ {React.createElement(iconMap[stateProgramKreatif.findUnique.data?.icon], { size: 24 })}
+
+ )}
+
+
+ Deskripsi Singkat
+ {stateProgramKreatif.findUnique.data?.slug}
+
+
+ Deskripsi
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Modal Hapus */}
+ setModalHapus(false)}
+ onConfirm={handleHapus}
+ text="Apakah anda yakin ingin menghapus program kreatif desa ini?"
+ />
+
+ );
+}
+
+export default DetailProgramKreatifDesa;
diff --git a/src/app/admin/(dashboard)/inovasi/program-kreatif-desa/_lib/selectIcon.tsx b/src/app/admin/(dashboard)/inovasi/program-kreatif-desa/_lib/selectIcon.tsx
new file mode 100644
index 00000000..0d92dc75
--- /dev/null
+++ b/src/app/admin/(dashboard)/inovasi/program-kreatif-desa/_lib/selectIcon.tsx
@@ -0,0 +1,75 @@
+/* eslint-disable react-hooks/exhaustive-deps */
+'use client'
+
+import { Box, rem, Select } from '@mantine/core';
+import {
+ IconChartLine,
+ IconLeaf,
+ IconRecycle,
+ IconTent,
+ IconTrophy,
+} from '@tabler/icons-react';
+import { useEffect, useState } from 'react';
+
+const iconMap = {
+ ekowisata: { label: 'Ekowisata', icon: IconLeaf },
+ kompetisi: { label: 'Kompetisi', icon: IconTrophy },
+ wisata: { label: 'Wisata', icon: IconTent },
+ ekonomi: { label: 'Ekonomi', icon: IconChartLine },
+ sampah: { label: 'Sampah', icon: IconRecycle },
+};
+
+type IconKey = keyof typeof iconMap;
+
+const iconList = Object.entries(iconMap).map(([value, data]) => ({
+ value,
+ label: data.label,
+}));
+
+export default function SelectIconProgram(
+ { onChange }: { onChange: (value: IconKey) => void }) {
+ const [selectedIcon, setSelectedIcon] = useState('ekowisata');
+ const IconComponent = iconMap[selectedIcon]?.icon || null;
+
+ // Push default icon ke state saat render awal
+ useEffect(() => {
+ onChange(selectedIcon);
+ }, []);
+
+ return (
+
+
+ );
+}
diff --git a/src/app/admin/(dashboard)/inovasi/program-kreatif-desa/_lib/selectIconEdit.tsx b/src/app/admin/(dashboard)/inovasi/program-kreatif-desa/_lib/selectIconEdit.tsx
new file mode 100644
index 00000000..eff26d48
--- /dev/null
+++ b/src/app/admin/(dashboard)/inovasi/program-kreatif-desa/_lib/selectIconEdit.tsx
@@ -0,0 +1,70 @@
+'use client'
+
+import { Box, rem, Select } from '@mantine/core';
+import {
+ IconChartLine,
+ IconLeaf,
+ IconRecycle,
+ IconTent,
+ IconTrophy,
+} from '@tabler/icons-react';
+
+const iconMap = {
+ ekowisata: { label: 'Ekowisata', icon: IconLeaf },
+ kompetisi: { label: 'Kompetisi', icon: IconTrophy },
+ wisata: { label: 'Wisata', icon: IconTent },
+ ekonomi: { label: 'Ekonomi', icon: IconChartLine },
+ sampah: { label: 'Sampah', icon: IconRecycle },
+};
+
+type IconKey = keyof typeof iconMap;
+
+const iconList = Object.entries(iconMap).map(([value, data]) => ({
+ value,
+ label: data.label,
+}));
+
+export default function SelectIconProgramEdit({
+ onChange,
+ value,
+}: {
+ onChange: (value: IconKey) => void;
+ value: IconKey;
+}) {
+ const IconComponent = iconMap[value]?.icon || null;
+
+ return (
+
+
+ );
+}
+
diff --git a/src/app/admin/(dashboard)/inovasi/program-kreatif-desa/create/page.tsx b/src/app/admin/(dashboard)/inovasi/program-kreatif-desa/create/page.tsx
index 9894191c..7194adb9 100644
--- a/src/app/admin/(dashboard)/inovasi/program-kreatif-desa/create/page.tsx
+++ b/src/app/admin/(dashboard)/inovasi/program-kreatif-desa/create/page.tsx
@@ -1,48 +1,70 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
-import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
+import { IconArrowBack } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
-import { KeamananEditor } from '../../../keamanan/_com/keamananEditor';
+import { useProxy } from 'valtio/utils';
+import CreateEditor from '../../../_com/createEditor';
+import programKreatifState from '../../../_state/inovasi/program-kreatif';
+import SelectIconProgram from '../_lib/selectIcon';
function CreateProgramKreatifDesa() {
+ const stateCreate = useProxy(programKreatifState)
const router = useRouter();
+
+ const resetForm = () => {
+ stateCreate.create.form = {
+ name: "",
+ slug: "",
+ deskripsi: "",
+ icon: "",
+ }
+ }
+
+ const handleSubmit = async () => {
+ await stateCreate.create.create();
+ resetForm();
+ router.push("/admin/inovasi/program-kreatif-desa")
+ }
return (
-
+
- Create Program Kreatif Desa
+ Create Program Kreatif Desa
+ Nama Program Kreatif Desa}
+ placeholder="masukkan nama program kreatif desa"
+ onChange={(val) => stateCreate.create.form.name = val.target.value}
+ />
- Masukkan Image
-
+ Ikon Program Kreatif Desa
+ stateCreate.create.form.icon = value} />
Nama Program Kreatif Desa}
- placeholder='Masukkan nama program kreatif desa'
- />
- Deskripsi Singkat Program Kreatif Desa}
- placeholder='Masukkan deskripsi singkat program kreatif desa'
+ onChange={(e) => stateCreate.create.form.slug = e.currentTarget.value}
+ label={Deskripsi Singkat Program Kreatif Desa}
+ placeholder='Masukkan deskripsi singkat program kreatif desa'
/>
Deskripsi Program Kreatif Desa
- stateCreate.create.form.deskripsi = htmlContent}
/>
-
+
-
+
-
+
);
}
diff --git a/src/app/admin/(dashboard)/inovasi/program-kreatif-desa/detail/page.tsx b/src/app/admin/(dashboard)/inovasi/program-kreatif-desa/detail/page.tsx
deleted file mode 100644
index aa154fd2..00000000
--- a/src/app/admin/(dashboard)/inovasi/program-kreatif-desa/detail/page.tsx
+++ /dev/null
@@ -1,66 +0,0 @@
-'use client'
-import colors from '@/con/colors';
-import { Box, Button, Paper, Stack, Flex, Text, Image } from '@mantine/core';
-import { IconArrowBack, IconX, IconEdit } from '@tabler/icons-react';
-import { useRouter } from 'next/navigation';
-import React from 'react';
-// import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
-
-function DetailProgramKreatifDesa() {
- const router = useRouter();
- return (
-
-
-
-
-
-
- Detail Program Kreatif Desa
-
-
-
-
- Nama Program Kreatif Desa
- Test Judul
-
-
- Gambar
-
-
-
- Deskripsi Singkat
- Test Deskripsi Singkat
-
-
- Deskripsi
- Test Deskripsi
-
-
-
-
-
-
-
-
-
-
-
-
- {/* Modal Hapus
- setModalHapus(false)}
- onConfirm={handleHapus}
- text="Apakah anda yakin ingin menghapus potensi ini?"
- /> */}
-
- );
-}
-
-export default DetailProgramKreatifDesa;
diff --git a/src/app/admin/(dashboard)/inovasi/program-kreatif-desa/edit/page.tsx b/src/app/admin/(dashboard)/inovasi/program-kreatif-desa/edit/page.tsx
deleted file mode 100644
index 7368cb26..00000000
--- a/src/app/admin/(dashboard)/inovasi/program-kreatif-desa/edit/page.tsx
+++ /dev/null
@@ -1,49 +0,0 @@
-'use client'
-import colors from '@/con/colors';
-import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
-import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
-import { useRouter } from 'next/navigation';
-import { KeamananEditor } from '../../../keamanan/_com/keamananEditor';
-
-
-function EditProgramKreatifDesa() {
- const router = useRouter();
- return (
-
-
-
-
-
-
-
- Edit Program Kreatif Desa
-
- Masukkan Image
-
-
- Nama Program Kreatif Desa}
- placeholder='Masukkan nama Program Kreatif Desa'
- />
- Deskripsi Singkat Program Kreatif Desa}
- placeholder='Masukkan deskripsi singkat program kreatif desa'
- />
-
- Deskripsi Program Kreatif Desa
-
-
-
-
-
-
-
-
- );
-}
-
-export default EditProgramKreatifDesa;
diff --git a/src/app/admin/(dashboard)/inovasi/program-kreatif-desa/page.tsx b/src/app/admin/(dashboard)/inovasi/program-kreatif-desa/page.tsx
index 50ed41fc..5b5bfb2c 100644
--- a/src/app/admin/(dashboard)/inovasi/program-kreatif-desa/page.tsx
+++ b/src/app/admin/(dashboard)/inovasi/program-kreatif-desa/page.tsx
@@ -1,58 +1,155 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+/* eslint-disable react-hooks/exhaustive-deps */
'use client'
+import React from 'react';
import colors from '@/con/colors';
-import { Box, Button, Paper, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
+import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
import HeaderSearch from '../../_com/header';
import JudulList from '../../_com/judulList';
import { useRouter } from 'next/navigation';
+import { useEffect, useState } from 'react';
+import programKreatifState from '../../_state/inovasi/program-kreatif';
+import { useProxy } from 'valtio/utils';
+import {
+ IconChartLine,
+ IconLeaf,
+ IconRecycle,
+ IconTent,
+ IconTrophy,
+} from '@tabler/icons-react';
function ProgramKreatifDesa() {
+ const [search, setSearch] = useState("");
return (
}
+ value={search}
+ onChange={(e) => setSearch(e.currentTarget.value)}
/>
-
+
);
}
-function ListProgramKreatifDesa() {
+function ListProgramKreatifDesa({ search }: { search: string }) {
+ const listState = useProxy(programKreatifState)
+ const { data, loading, page, totalPages, load } = listState.findMany
const router = useRouter();
+
+ useEffect(() => {
+ load(page, 10)
+ }, [page])
+
+ const filteredData = (data || []).filter(item => {
+ const keyword = search.toLowerCase();
+ return (
+ item.name.toLowerCase().includes(keyword) ||
+ item.deskripsi.toLowerCase().includes(keyword) ||
+ item.slug.toLowerCase().includes(keyword) ||
+ item.icon.toLowerCase().includes(keyword)
+ );
+ });
+
+ const iconMap: Record> = {
+ ekowisata: IconLeaf,
+ kompetisi: IconTrophy,
+ wisata: IconTent,
+ ekonomi: IconChartLine,
+ sampah: IconRecycle,
+ };
+
+ if (loading || !data) {
+ return (
+
+
+
+ );
+ }
+ if (data.length === 0) {
+ return (
+
+
+
+
+
+
+
+ No
+ Nama Program Kreatif Desa
+ Deskripsi Singkat
+ Ikon
+ Detail
+
+
+
+ Tidak ada data program kreatif desa yang tersedia
+
+
+
+ );
+ }
return (
-
+
-
-
-
- Nama Program Kreatif Desa
- Image
- Deskripsi Singkat
- Detail
+
+
+
+
+ No
+ Nama Program Kreatif Desa
+ Deskripsi Singkat
+ Ikon
+ Detail
-
-
-
- Program Kreatif Desa 1
- Image
- Deskripsi Singkat
-
-
-
-
-
-
+
+
+ {filteredData.map((item, index) => (
+
+ {index + 1}
+ {item.name}
+
+
+ {iconMap[item.icon] && (
+
+ {React.createElement(iconMap[item.icon], { size: 24 })}
+
+ )}
+
+
+
+
+
+ ))}
+
+
+
+
+ {
+ load(newPage, 10);
+ window.scrollTo(0, 0);
+ }}
+ total={totalPages}
+ mt="md"
+ mb="md"
+ />
+
);
}
-
export default ProgramKreatifDesa;
diff --git a/src/app/api/[[...slugs]]/_lib/inovasi/index.ts b/src/app/api/[[...slugs]]/_lib/inovasi/index.ts
index 62a5965c..758d4203 100644
--- a/src/app/api/[[...slugs]]/_lib/inovasi/index.ts
+++ b/src/app/api/[[...slugs]]/_lib/inovasi/index.ts
@@ -1,10 +1,12 @@
import Elysia from "elysia";
import DesaDigital from "./desa-digital";
+import ProgramKreatif from "./program-kreatif";
const Inovasi = new Elysia({
prefix: "/api/inovasi",
tags: ["Inovasi"],
})
.use(DesaDigital)
+ .use(ProgramKreatif)
export default Inovasi;
diff --git a/src/app/api/[[...slugs]]/_lib/inovasi/program-kreatif/create.ts b/src/app/api/[[...slugs]]/_lib/inovasi/program-kreatif/create.ts
new file mode 100644
index 00000000..c73ee4c2
--- /dev/null
+++ b/src/app/api/[[...slugs]]/_lib/inovasi/program-kreatif/create.ts
@@ -0,0 +1,30 @@
+import prisma from "@/lib/prisma";
+import { Context } from "elysia";
+
+type FormCreateProgramKreatif = {
+ name: string;
+ slug: string;
+ deskripsi: string;
+ icon: string;
+}
+
+export default async function programKreatifCreate(context: Context){
+ const body = context.body as FormCreateProgramKreatif;
+
+ await prisma.programKreatif.create({
+ data: {
+ name: body.name,
+ slug: body.slug,
+ deskripsi: body.deskripsi,
+ icon: body.icon,
+ }
+ })
+
+ return {
+ success: true,
+ message: "Success create program kreatif",
+ data: {
+ ...body,
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/app/api/[[...slugs]]/_lib/inovasi/program-kreatif/del.ts b/src/app/api/[[...slugs]]/_lib/inovasi/program-kreatif/del.ts
new file mode 100644
index 00000000..5004f5c3
--- /dev/null
+++ b/src/app/api/[[...slugs]]/_lib/inovasi/program-kreatif/del.ts
@@ -0,0 +1,33 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import prisma from "@/lib/prisma";
+import { Context } from "elysia";
+
+export default async function programKreatifDelete(context: Context) {
+ const { id } = context.params as { id: string };
+
+ if (!id) {
+ return {
+ success: false,
+ message: "ID program kreatif tidak ditemukan",
+ };
+ }
+
+ try {
+ const deleted = await prisma.programKreatif.delete({
+ where: { id },
+ });
+
+ return {
+ success: true,
+ message: "Program kreatif berhasil dihapus",
+ data: deleted,
+ };
+ } catch (error: any) {
+ console.error("Error delete program kreatif:", error);
+ return {
+ success: false,
+ message: "Terjadi kesalahan saat menghapus program kreatif",
+ error: error.message,
+ };
+ }
+}
diff --git a/src/app/api/[[...slugs]]/_lib/inovasi/program-kreatif/findMany.ts b/src/app/api/[[...slugs]]/_lib/inovasi/program-kreatif/findMany.ts
new file mode 100644
index 00000000..d4bf8709
--- /dev/null
+++ b/src/app/api/[[...slugs]]/_lib/inovasi/program-kreatif/findMany.ts
@@ -0,0 +1,44 @@
+import prisma from "@/lib/prisma";
+import { Context } from "elysia";
+
+// Di findMany.ts
+export default async function programKreatifFindMany(context: Context) {
+ const page = Number(context.query.page) || 1;
+ const limit = Number(context.query.limit) || 10;
+ const skip = (page - 1) * limit;
+
+ try {
+ const [data, total] = await Promise.all([
+ prisma.programKreatif.findMany({
+ where: { isActive: true },
+ skip,
+ take: limit,
+ orderBy: { createdAt: 'desc' },
+ }),
+ prisma.programKreatif.count({
+ where: { isActive: true }
+ })
+ ]);
+
+ const totalPages = Math.ceil(total / limit);
+
+ return {
+ success: true,
+ message: "Success fetch program kreatif with pagination",
+ data,
+ page,
+ totalPages,
+ total,
+ };
+ } catch (e) {
+ console.error("Find many paginated error:", e);
+ return {
+ success: false,
+ message: "Failed fetch program kreatif with pagination",
+ data: [],
+ page: 1,
+ totalPages: 1,
+ total: 0,
+ };
+ }
+}
\ No newline at end of file
diff --git a/src/app/api/[[...slugs]]/_lib/inovasi/program-kreatif/findUnique.ts b/src/app/api/[[...slugs]]/_lib/inovasi/program-kreatif/findUnique.ts
new file mode 100644
index 00000000..1d9d036e
--- /dev/null
+++ b/src/app/api/[[...slugs]]/_lib/inovasi/program-kreatif/findUnique.ts
@@ -0,0 +1,39 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import prisma from "@/lib/prisma";
+import { Context } from "elysia";
+
+export default async function programKreatifFindUnique(context: Context) {
+ const { id } = context.params as { id: string };
+
+ if (!id) {
+ return {
+ success: false,
+ message: "ID program kreatif diperlukan",
+ };
+ }
+
+ try {
+ const programKreatif = await prisma.programKreatif.findUnique({
+ where: { id },
+ });
+
+ if (!programKreatif) {
+ return {
+ success: false,
+ message: "Program kreatif tidak ditemukan",
+ };
+ }
+
+ return {
+ success: true,
+ data: programKreatif,
+ };
+ } catch (error: any) {
+ console.error("Error findUnique program kreatif:", error);
+ return {
+ success: false,
+ message: "Gagal mengambil data program kreatif",
+ error: error.message,
+ };
+ }
+}
\ No newline at end of file
diff --git a/src/app/api/[[...slugs]]/_lib/inovasi/program-kreatif/index.ts b/src/app/api/[[...slugs]]/_lib/inovasi/program-kreatif/index.ts
new file mode 100644
index 00000000..dad708dd
--- /dev/null
+++ b/src/app/api/[[...slugs]]/_lib/inovasi/program-kreatif/index.ts
@@ -0,0 +1,41 @@
+import Elysia, { t } from "elysia";
+import programKreatifFindMany from "./findMany";
+import programKreatifFindUnique from "./findUnique";
+import programKreatifCreate from "./create";
+import programKreatifUpdate from "./updt";
+import programKreatifDelete from "./del";
+
+const ProgramKreatif = new Elysia({
+ prefix: "/programkreatif",
+ tags: ["Inovasi/Program Kreatif"],
+})
+ .get("/find-many", programKreatifFindMany)
+ .get("/:id", async (context) => {
+ const response = await programKreatifFindUnique(context);
+ return response;
+ })
+ .post("/create", programKreatifCreate, {
+ body: t.Object({
+ name: t.String(),
+ slug: t.String(),
+ deskripsi: t.String(),
+ icon: t.String(),
+ }),
+ })
+ .put(
+ "/:id",
+ async (context) => {
+ const response = await programKreatifUpdate(context);
+ return response;
+ },
+ {
+ body: t.Object({
+ name: t.String(),
+ slug: t.String(),
+ deskripsi: t.String(),
+ icon: t.String(),
+ }),
+ }
+ )
+ .delete("/del/:id", programKreatifDelete);
+ export default ProgramKreatif;
diff --git a/src/app/api/[[...slugs]]/_lib/inovasi/program-kreatif/updt.ts b/src/app/api/[[...slugs]]/_lib/inovasi/program-kreatif/updt.ts
new file mode 100644
index 00000000..2ce0c770
--- /dev/null
+++ b/src/app/api/[[...slugs]]/_lib/inovasi/program-kreatif/updt.ts
@@ -0,0 +1,48 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import prisma from "@/lib/prisma";
+import { Context } from "elysia";
+
+type FormUpdateProgramKreatif = {
+ id: string;
+ name?: string;
+ slug?: string;
+ deskripsi?: string;
+ icon?: string;
+};
+export default async function programKreatifUpdate(context: Context) {
+ const body = context.body as FormUpdateProgramKreatif;
+ const id = context.params?.id; // ambil dari URL param
+
+ if (!id) {
+ return {
+ success: false,
+ message: "ID program kreatif wajib diisi",
+ };
+ }
+
+ try {
+ const updated = await prisma.programKreatif.update({
+ where: { id },
+ data: {
+ name: body.name,
+ slug: body.slug,
+ deskripsi: body.deskripsi,
+ icon: body.icon,
+ },
+ });
+
+ return {
+ success: true,
+ message: "Program kreatif berhasil diupdate",
+ data: updated,
+ };
+ } catch (error: any) {
+ console.error("Error update program kreatif:", error);
+ return {
+ success: false,
+ message: "Gagal mengupdate program kreatif",
+ error: error.message,
+ };
+ }
+}
+