diff --git a/prisma/schema.prisma b/prisma/schema.prisma index d0152d76..08308229 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -1446,7 +1446,7 @@ model JenisPengaduan { isActive Boolean @default(true) PengaduanMasyarakat PengaduanMasyarakat[] } - +// ========================================= LINGKUNGAN ========================================= // // ========================================= PENGELOLAAN SAMPAH ========================================= // model PengelolaanSampah { id String @id @default(cuid()) @@ -1472,3 +1472,15 @@ model KeteranganBankSampahTerdekat { isActive Boolean @default(true) } +// ========================================= PORGRAM PENGHIJAUAN ========================================= // +model ProgramPenghijauan { + id String @id @default(cuid()) + name String + judul 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) +} \ No newline at end of file diff --git a/src/app/admin/(dashboard)/_com/leafletMapEdit.tsx b/src/app/admin/(dashboard)/_com/leafletMapEdit.tsx index bc5580bd..e28c2e8e 100644 --- a/src/app/admin/(dashboard)/_com/leafletMapEdit.tsx +++ b/src/app/admin/(dashboard)/_com/leafletMapEdit.tsx @@ -6,16 +6,6 @@ import { useState, useEffect } from 'react'; import 'leaflet/dist/leaflet.css'; import L, { LeafletMouseEvent } from 'leaflet'; -delete (L.Icon.Default.prototype as any)._getIconUrl; -L.Icon.Default.mergeOptions({ - iconRetinaUrl: - 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-icon-2x.png', - iconUrl: - 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-icon.png', - shadowUrl: - 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-shadow.png', -}); - type Props = { initialPosition: { lat: number; lng: number }; onChange: (pos: { lat: number; lng: number }) => void; @@ -24,6 +14,21 @@ type Props = { export default function LeafletMapEdit({ initialPosition, onChange }: Props) { const [markerPos, setMarkerPos] = useState(initialPosition); + // ✅ Pastikan icon config cuma jalan di client + useEffect(() => { + if (typeof window !== 'undefined') { + delete (L.Icon.Default.prototype as any)._getIconUrl; + L.Icon.Default.mergeOptions({ + iconRetinaUrl: + 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-icon-2x.png', + iconUrl: + 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-icon.png', + shadowUrl: + 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-shadow.png', + }); + } + }, []); + useEffect(() => { setMarkerPos(initialPosition); }, [initialPosition]); diff --git a/src/app/admin/(dashboard)/inovasi/program-kreatif-desa/_lib/selectIcon.tsx b/src/app/admin/(dashboard)/_com/selectIcon.tsx similarity index 85% rename from src/app/admin/(dashboard)/inovasi/program-kreatif-desa/_lib/selectIcon.tsx rename to src/app/admin/(dashboard)/_com/selectIcon.tsx index d2fd42c1..33fb5abf 100644 --- a/src/app/admin/(dashboard)/inovasi/program-kreatif-desa/_lib/selectIcon.tsx +++ b/src/app/admin/(dashboard)/_com/selectIcon.tsx @@ -4,12 +4,16 @@ import { Box, rem, Select } from '@mantine/core'; import { IconChartLine, + IconChristmasTreeFilled, IconClipboardTextFilled, + IconHomeEco, IconLeaf, IconRecycle, IconScale, + IconShieldFilled, IconTent, IconTrashFilled, + IconTrendingUp, IconTrophy, IconTruckFilled, } from '@tabler/icons-react'; @@ -25,6 +29,10 @@ const iconMap = { scale: { label: 'Scale', icon: IconScale }, clipboard: { label: 'Clipboard', icon: IconClipboardTextFilled }, trash: { label: 'Trash', icon: IconTrashFilled }, + lingkunganSehat: {label: 'Lingkungan Sehat', icon: IconHomeEco}, + sumberOksigen: {label: 'Sumber Oksigen', icon: IconChristmasTreeFilled}, + ekonomiBerkelanjutan: {label: 'Ekonomi Berkelanjutan', icon: IconTrendingUp}, + mencegahBencana: {label: 'Mencegah Bencana', icon: IconShieldFilled}, }; diff --git a/src/app/admin/(dashboard)/inovasi/program-kreatif-desa/_lib/selectIconEdit.tsx b/src/app/admin/(dashboard)/_com/selectIconEdit.tsx similarity index 82% rename from src/app/admin/(dashboard)/inovasi/program-kreatif-desa/_lib/selectIconEdit.tsx rename to src/app/admin/(dashboard)/_com/selectIconEdit.tsx index 217b50e0..f9df1582 100644 --- a/src/app/admin/(dashboard)/inovasi/program-kreatif-desa/_lib/selectIconEdit.tsx +++ b/src/app/admin/(dashboard)/_com/selectIconEdit.tsx @@ -3,12 +3,16 @@ import { Box, rem, Select } from '@mantine/core'; import { IconChartLine, + IconChristmasTreeFilled, IconClipboardTextFilled, + IconHomeEco, IconLeaf, IconRecycle, IconScale, + IconShieldFilled, IconTent, IconTrashFilled, + IconTrendingUp, IconTrophy, IconTruckFilled, } from '@tabler/icons-react'; @@ -23,6 +27,10 @@ const iconMap = { scale: { label: 'Scale', icon: IconScale }, clipboard: { label: 'Clipboard', icon: IconClipboardTextFilled }, trash: { label: 'Trash', icon: IconTrashFilled }, + lingkunganSehat: {label: 'Lingkungan Sehat', icon: IconHomeEco}, + sumberOksigen: {label: 'Sumber Oksigen', icon: IconChristmasTreeFilled}, + ekonomiBerkelanjutan: {label: 'Ekonomi Berkelanjutan', icon: IconTrendingUp}, + mencegahBencana: {label: 'Mencegah Bencana', icon: IconShieldFilled}, }; type IconKey = keyof typeof iconMap; diff --git a/src/app/admin/(dashboard)/_state/lingkungan/program-penghijauan.ts b/src/app/admin/(dashboard)/_state/lingkungan/program-penghijauan.ts new file mode 100644 index 00000000..df0cd94e --- /dev/null +++ b/src/app/admin/(dashboard)/_state/lingkungan/program-penghijauan.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"), + judul: z.string().min(1, "Judul minimal 1 karakter"), + icon: z.string().min(1, "Icon minimal 1 karakter"), +}); + +const defaultForm = { + name: "", + deskripsi: "", + judul: "", + icon: "", +}; + +const programPenghijauanState = proxy({ + create: { + form: { ...defaultForm }, + loading: false, + async create() { + const cek = templateForm.safeParse(programPenghijauanState.create.form); + if (!cek.success) { + const err = `[${cek.error.issues + .map((v) => `${v.path.join(".")}`) + .join("\n")}] required`; + return toast.error(err); + } + + try { + programPenghijauanState.create.loading = true; + const res = await ApiFetch.api.lingkungan.programpenghijauan["create"].post( + programPenghijauanState.create.form + ); + if (res.status === 200) { + programPenghijauanState.findMany.load(); + return toast.success("success create"); + } + console.log(res); + return toast.error("failed create"); + } catch (error) { + console.log((error as Error).message); + } finally { + programPenghijauanState.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 + programPenghijauanState.findMany.loading = true; // Use the full path to access the property + programPenghijauanState.findMany.page = page; + try { + const res = await ApiFetch.api.lingkungan.programpenghijauan["find-many"].get({ + query: { page, limit }, + }); + + if (res.status === 200 && res.data?.success) { + programPenghijauanState.findMany.data = res.data.data || []; + programPenghijauanState.findMany.total = res.data.total || 0; + programPenghijauanState.findMany.totalPages = res.data.totalPages || 1; + } else { + console.error( + "Failed to load grafik berdasarkan program penghijauan:", + res.data?.message + ); + programPenghijauanState.findMany.data = []; + programPenghijauanState.findMany.total = 0; + programPenghijauanState.findMany.totalPages = 1; + } + } catch (error) { + console.error("Error loading grafik berdasarkan program penghijauan:", error); + programPenghijauanState.findMany.data = []; + programPenghijauanState.findMany.total = 0; + programPenghijauanState.findMany.totalPages = 1; + } finally { + programPenghijauanState.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/lingkungan/programpenghijauan/${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, + judul: data.judul, + icon: data.icon, + }; + return data; + } else { + throw new Error(result?.message || "Gagal mengambil data"); + } + } catch (error) { + console.error("Error loading program penghijauan:", 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/lingkungan/programpenghijauan/${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 programPenghijauanState.findMany.load(); + return result.data; + } catch (error) { + console.error("Error update data:", error); + toast.error("Gagal update data program penghijauan"); + } finally { + this.loading = false; + } + }, + }, + findUnique: { + data: null as Prisma.ProgramPenghijauanGetPayload<{ + omit: { isActive: true }; + }> | null, + async load(id: string) { + try { + const res = await fetch(`/api/lingkungan/programpenghijauan/${id}`); + if (res.ok) { + const data = await res.json(); + programPenghijauanState.findUnique.data = data.data ?? null; + } else { + console.error("Failed to fetch data", res.status, res.statusText); + programPenghijauanState.findUnique.data = null; + } + } catch (error) { + console.error("Error loading program penghijauan:", error); + programPenghijauanState.findUnique.data = null; + } + }, + }, + delete: { + loading: false, + async byId(id: string) { + if (!id) return toast.warn("ID tidak valid"); + + try { + programPenghijauanState.delete.loading = true; + + const response = await fetch(`/api/lingkungan/programpenghijauan/del/${id}`, { + method: "DELETE", + headers: { + "Content-Type": "application/json", + }, + }); + + const result = await response.json(); + + if (response.ok && result?.success) { + toast.success(result.message || "Program penghijauan berhasil dihapus"); + await programPenghijauanState.findMany.load(); // refresh list + } else { + toast.error(result?.message || "Gagal menghapus program penghijauan"); + } + } catch (error) { + console.error("Gagal delete:", error); + toast.error("Terjadi kesalahan saat menghapus program penghijauan"); + } finally { + programPenghijauanState.delete.loading = false; + } + }, + }, +}); + +export default programPenghijauanState; 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 index 72168726..cd1fc06e 100644 --- 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 @@ -9,7 +9,7 @@ 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'; +import SelectIconProgramEdit from '../../../../_com/selectIconEdit'; interface FormProgramKreatif { name: string; 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 index f5c15480..91a89f73 100644 --- a/src/app/admin/(dashboard)/inovasi/program-kreatif-desa/[id]/page.tsx +++ b/src/app/admin/(dashboard)/inovasi/program-kreatif-desa/[id]/page.tsx @@ -3,7 +3,7 @@ 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 { IconArrowBack, IconChartLine, IconChristmasTreeFilled, IconClipboard, IconEdit, IconHomeEco, IconLeaf, IconRecycle, IconScale, IconShieldFilled, IconTent, IconTrash, IconTrendingUp, IconTrophy, IconTruck, IconX } from '@tabler/icons-react'; import { useParams, useRouter } from 'next/navigation'; import React, { useState } from 'react'; import { useProxy } from 'valtio/utils'; @@ -25,6 +25,14 @@ function DetailProgramKreatifDesa() { wisata: IconTent, ekonomi: IconChartLine, sampah: IconRecycle, + truck: IconTruck, + scale: IconScale, + clipboard: IconClipboard, + trash: IconTrash, + lingkunganSehat: IconHomeEco, + sumberOksigen: IconChristmasTreeFilled, + ekonomiBerkelanjutan: IconTrendingUp, + mencegahBencana: IconShieldFilled, }; useShallowEffect(() => { 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 7194adb9..d7764af7 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 @@ -6,7 +6,7 @@ import { useRouter } from 'next/navigation'; import { useProxy } from 'valtio/utils'; import CreateEditor from '../../../_com/createEditor'; import programKreatifState from '../../../_state/inovasi/program-kreatif'; -import SelectIconProgram from '../_lib/selectIcon'; +import SelectIconProgram from '../../../_com/selectIcon'; function CreateProgramKreatifDesa() { diff --git a/src/app/admin/(dashboard)/lingkungan/pengelolaan-sampah-bank-sampah/list-pengelolaan-sampah-bank-sampah/[id]/page.tsx b/src/app/admin/(dashboard)/lingkungan/pengelolaan-sampah-bank-sampah/list-pengelolaan-sampah-bank-sampah/[id]/page.tsx index a39f4a62..53442675 100644 --- a/src/app/admin/(dashboard)/lingkungan/pengelolaan-sampah-bank-sampah/list-pengelolaan-sampah-bank-sampah/[id]/page.tsx +++ b/src/app/admin/(dashboard)/lingkungan/pengelolaan-sampah-bank-sampah/list-pengelolaan-sampah-bank-sampah/[id]/page.tsx @@ -1,7 +1,7 @@ /* eslint-disable react-hooks/exhaustive-deps */ 'use client' import pengelolaanSampahState from '@/app/admin/(dashboard)/_state/lingkungan/pengelolaan-sampah'; -import SelectIconProgramEdit from '@/app/admin/(dashboard)/inovasi/program-kreatif-desa/_lib/selectIconEdit'; +import SelectIconProgramEdit from '@/app/admin/(dashboard)/_com/selectIconEdit'; import colors from '@/con/colors'; import { Box, Button, Paper, Stack, Text, TextInput, Title } from '@mantine/core'; import { IconArrowBack } from '@tabler/icons-react'; diff --git a/src/app/admin/(dashboard)/lingkungan/pengelolaan-sampah-bank-sampah/list-pengelolaan-sampah-bank-sampah/create/page.tsx b/src/app/admin/(dashboard)/lingkungan/pengelolaan-sampah-bank-sampah/list-pengelolaan-sampah-bank-sampah/create/page.tsx index 99058152..4ab7f4db 100644 --- a/src/app/admin/(dashboard)/lingkungan/pengelolaan-sampah-bank-sampah/list-pengelolaan-sampah-bank-sampah/create/page.tsx +++ b/src/app/admin/(dashboard)/lingkungan/pengelolaan-sampah-bank-sampah/list-pengelolaan-sampah-bank-sampah/create/page.tsx @@ -1,6 +1,6 @@ 'use client' import pengelolaanSampahState from '@/app/admin/(dashboard)/_state/lingkungan/pengelolaan-sampah'; -import SelectIconProgram from '@/app/admin/(dashboard)/inovasi/program-kreatif-desa/_lib/selectIcon'; +import SelectIconProgram from '@/app/admin/(dashboard)/_com/selectIcon'; import colors from '@/con/colors'; import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core'; import { IconArrowBack } from '@tabler/icons-react'; diff --git a/src/app/admin/(dashboard)/lingkungan/program-penghijauan/[id]/edit/page.tsx b/src/app/admin/(dashboard)/lingkungan/program-penghijauan/[id]/edit/page.tsx new file mode 100644 index 00000000..7bd4ea37 --- /dev/null +++ b/src/app/admin/(dashboard)/lingkungan/program-penghijauan/[id]/edit/page.tsx @@ -0,0 +1,147 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +'use client' +import EditEditor from '@/app/admin/(dashboard)/_com/editEditor'; +import SelectIconProgramEdit from '@/app/admin/(dashboard)/_com/selectIconEdit'; +import programPenghijauanState from '@/app/admin/(dashboard)/_state/lingkungan/program-penghijauan'; +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'; + + +interface FormProgramPenghijauan { + name: string; + deskripsi: string; + judul: string; + icon: string; +} + +type IconKey = 'ekowisata' | 'kompetisi' | 'wisata' | 'ekonomi' | 'sampah' | 'truck' | 'scale' | 'clipboard' | 'trash' | 'lingkunganSehat' | 'sumberOksigen' | 'ekonomiBerkelanjutan' | 'mencegahBencana'; + + +function EditProgramPenghijauan() { + const stateProgramPenghijauan = useProxy(programPenghijauanState) + const params = useParams() + const router = useRouter(); + const [formData, setFormData] = useState({ + name: '', + deskripsi: '', + judul: '', + icon: '', + }) + + useEffect(() => { + const loadProgramPenghijauan = async () => { + const id = params?.id as string; + if (!id) return; + + try { + const data = await stateProgramPenghijauan.update.load(id); + if (data) { + // ⬇️ FIX PENTING: tambahkan ini + stateProgramPenghijauan.update.id = id; + + stateProgramPenghijauan.update.form = { + name: data.name, + judul: data.judul, + deskripsi: data.deskripsi, + icon: data.icon, + }; + + setFormData({ + name: data.name, + judul: data.judul, + deskripsi: data.deskripsi, + icon: data.icon, + }); + } + } catch (error) { + console.error("Error loading program penghijauan:", error); + toast.error("Gagal memuat data program penghijauan"); + } + } + + loadProgramPenghijauan(); + }, [params?.id]); + + + + const handleSubmit = async () => { + try { + stateProgramPenghijauan.update.form = { + ...stateProgramPenghijauan.update.form, + name: formData.name.trim(), + deskripsi: formData.deskripsi.trim(), + judul: formData.judul.trim(), + icon: formData.icon.trim(), + } + await stateProgramPenghijauan.update.submit(); + router.push("/admin/lingkungan/program-penghijauan"); + } catch (error) { + console.error("Error updating program penghijauan:", error); + toast.error("Gagal memuat data program penghijauan"); + } + } + return ( + + + + + + + + Edit Program Penghijauan + Nama Program Penghijauan} + placeholder="masukkan nama program penghijauan" + onChange={(val) => { + setFormData({ + ...formData, + name: val.target.value + }) + }} + /> + Judul Deskripsi Program Penghijauan} + placeholder="masukkan judul deskripsi program penghijauan" + onChange={(val) => { + setFormData({ + ...formData, + judul: val.target.value + }) + }} + /> + + Deskripsi + { + setFormData((prev) => ({ ...prev, deskripsi: htmlContent })); + stateProgramPenghijauan.update.form.deskripsi = htmlContent; + }} + /> + + + Ikon Program Penghijauan + { + setFormData((prev) => ({ ...prev, icon: value })); + stateProgramPenghijauan.update.form.icon = value; + }} /> + + + + + + ); +} + +export default EditProgramPenghijauan; diff --git a/src/app/admin/(dashboard)/lingkungan/program-penghijauan/[id]/page.tsx b/src/app/admin/(dashboard)/lingkungan/program-penghijauan/[id]/page.tsx new file mode 100644 index 00000000..567b8388 --- /dev/null +++ b/src/app/admin/(dashboard)/lingkungan/program-penghijauan/[id]/page.tsx @@ -0,0 +1,135 @@ +/* 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, IconChristmasTreeFilled, IconClipboard, IconEdit, IconHomeEco, IconLeaf, IconRecycle, IconScale, IconShieldFilled, IconTent, IconTrash, IconTrendingUp, IconTrophy, IconTruck, IconX } from '@tabler/icons-react'; +import { useParams, useRouter } from 'next/navigation'; +import React, { useState } from 'react'; +import { useProxy } from 'valtio/utils'; +import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus'; +import programPenghijauanState from '../../../_state/lingkungan/program-penghijauan'; + +// import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus'; + +function DetailProgramPenghijauan() { + const [modalHapus, setModalHapus] = useState(false) + const stateProgramPenghijauan = useProxy(programPenghijauanState) + const router = useRouter() + const params = useParams() + const [selectedId, setSelectedId] = useState(null) + + const iconMap: Record> = { + ekowisata: IconLeaf, + kompetisi: IconTrophy, + wisata: IconTent, + ekonomi: IconChartLine, + sampah: IconRecycle, + truck: IconTruck, + scale: IconScale, + clipboard: IconClipboard, + trash: IconTrash, + lingkunganSehat: IconHomeEco, + sumberOksigen: IconChristmasTreeFilled, + ekonomiBerkelanjutan: IconTrendingUp, + mencegahBencana: IconShieldFilled, + }; + + useShallowEffect(() => { + stateProgramPenghijauan.findUnique.load(params?.id as string) + }, [params?.id]) + + const handleHapus = () => { + if (selectedId) { + stateProgramPenghijauan.delete.byId(selectedId) + setModalHapus(false) + setSelectedId(null) + router.push("/admin/lingkungan/program-penghijauan") + } + } + + if (!stateProgramPenghijauan.findUnique.data) { + return ( + + + + ) + } + + return ( + + + + + + + Detail Program Penghijauan + + + + + Nama Program Penghijauan + {stateProgramPenghijauan.findUnique.data?.name} + + + Ikon Program Penghijauan + {iconMap[stateProgramPenghijauan.findUnique.data?.icon] && ( + + {React.createElement(iconMap[stateProgramPenghijauan.findUnique.data?.icon], { size: 24 })} + + )} + + + Judul Deskripsi Program Penghijauan + {stateProgramPenghijauan.findUnique.data?.judul} + + + Deskripsi Program Penghijauan + + + + + + + + + + + + + + {/* Modal Hapus */} + setModalHapus(false)} + onConfirm={handleHapus} + text="Apakah anda yakin ingin menghapus program penghijauan ini?" + /> + + ); +} + +export default DetailProgramPenghijauan; diff --git a/src/app/admin/(dashboard)/lingkungan/program-penghijauan/create/page.tsx b/src/app/admin/(dashboard)/lingkungan/program-penghijauan/create/page.tsx index 210924d8..760f4c73 100644 --- a/src/app/admin/(dashboard)/lingkungan/program-penghijauan/create/page.tsx +++ b/src/app/admin/(dashboard)/lingkungan/program-penghijauan/create/page.tsx @@ -1,53 +1,71 @@ '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 SelectIconProgram from '../../../_com/selectIcon'; +import programPenghijauanState from '../../../_state/lingkungan/program-penghijauan'; -function CreateProgramKreatifDesa() { +function CreateProgramPenghijauan() { + const stateCreate = useProxy(programPenghijauanState) const router = useRouter(); + + const resetForm = () => { + stateCreate.create.form = { + name: "", + deskripsi: "", + judul: "", + icon: "", + } + } + + const handleSubmit = async () => { + await stateCreate.create.create(); + resetForm(); + router.push("/admin/lingkungan/program-penghijauan") + } return ( - + - Create Program Penghijauan + Create Program Penghijauan + Nama Program Penghijauan} + placeholder="masukkan nama program penghijauan" + onChange={(val) => stateCreate.create.form.name = val.target.value} + /> - Masukkan Image - + Ikon Program Penghijauan + stateCreate.create.form.icon = value} /> Nama Program Penghijauan} - placeholder='Masukkan nama program penghijauan' - /> - Deskripsi Singkat Program Penghijauan} - placeholder='Masukkan deskripsi singkat program penghijauan' - /> - Jumlah} - placeholder='Masukkan jumlah' + onChange={(e) => stateCreate.create.form.judul = e.currentTarget.value} + label={Judul Deskripsi Program Penghijauan} + placeholder='Masukkan judul deskripsi program penghijauan' /> - Deskripsi Program Kreatif Desa - Deskripsi Program Penghijauan + stateCreate.create.form.deskripsi = htmlContent} /> - + - + - + ); } -export default CreateProgramKreatifDesa; +export default CreateProgramPenghijauan; diff --git a/src/app/admin/(dashboard)/lingkungan/program-penghijauan/detail/page.tsx b/src/app/admin/(dashboard)/lingkungan/program-penghijauan/detail/page.tsx deleted file mode 100644 index 0bfd8eb8..00000000 --- a/src/app/admin/(dashboard)/lingkungan/program-penghijauan/detail/page.tsx +++ /dev/null @@ -1,62 +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 DetailProgramPenghijauan() { - const router = useRouter(); - return ( - - - - - - - Detail Program Penghijauan - - - - - Nama Program Penghijauan - Test Judul - - - Gambar - gambar - - - Deskripsi - Test Deskripsi - - - - - - - - - - - - - {/* Modal Hapus - setModalHapus(false)} - onConfirm={handleHapus} - text="Apakah anda yakin ingin menghapus potensi ini?" - /> */} - - ); -} - -export default DetailProgramPenghijauan; diff --git a/src/app/admin/(dashboard)/lingkungan/program-penghijauan/edit/page.tsx b/src/app/admin/(dashboard)/lingkungan/program-penghijauan/edit/page.tsx deleted file mode 100644 index bf340820..00000000 --- a/src/app/admin/(dashboard)/lingkungan/program-penghijauan/edit/page.tsx +++ /dev/null @@ -1,53 +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 Penghijauan - - Masukkan Image - - - Nama Program Penghijauan} - placeholder='Masukkan nama program penghijauan' - /> - Deskripsi Singkat Program Penghijauan} - placeholder='Masukkan deskripsi singkat program penghijauan' - /> - Jumlah} - placeholder='Masukkan jumlah' - /> - - Deskripsi Program Kreatif Desa - - - - - - - - - ); -} - -export default EditProgramKreatifDesa; diff --git a/src/app/admin/(dashboard)/lingkungan/program-penghijauan/page.tsx b/src/app/admin/(dashboard)/lingkungan/program-penghijauan/page.tsx index a5c4278e..5b49f244 100644 --- a/src/app/admin/(dashboard)/lingkungan/program-penghijauan/page.tsx +++ b/src/app/admin/(dashboard)/lingkungan/program-penghijauan/page.tsx @@ -1,68 +1,163 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable react-hooks/exhaustive-deps */ 'use client' -import { Box, Button, Image, Paper, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core'; -import HeaderSearch from '../../_com/header'; -import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react'; import colors from '@/con/colors'; -import JudulList from '../../_com/judulList'; +import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core'; +import { + IconChartLine, IconChristmasTreeFilled, IconClipboardTextFilled, IconDeviceImac, IconHomeEco, IconLeaf, + IconRecycle, IconScale, IconSearch, IconShieldFilled, IconTent, + IconTrashFilled, + IconTrendingUp, + IconTrophy, + IconTruckFilled +} from '@tabler/icons-react'; import { useRouter } from 'next/navigation'; +import React, { useEffect, useState } from 'react'; +import { useProxy } from 'valtio/utils'; +import HeaderSearch from '../../_com/header'; +import JudulList from '../../_com/judulList'; +import programPenghijauanState from '../../_state/lingkungan/program-penghijauan'; + function ProgramPenghijauan() { + const [search, setSearch] = useState(""); return ( - - } - /> - - + + } + value={search} + onChange={(e) => setSearch(e.currentTarget.value)} + /> + + ); } -function ListManfaatPenghijauan() { +function ListProgramPenghijauan({ search }: { search: string }) { + const listState = useProxy(programPenghijauanState) + const { data, loading, page, totalPages, load } = listState.findMany const router = useRouter(); - return ( - - - - - - + + 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.judul.toLowerCase().includes(keyword) || + item.icon.toLowerCase().includes(keyword) + ); + }); + + const iconMap: Record> = { + ekowisata: IconLeaf, + kompetisi: IconTrophy, + wisata: IconTent, + ekonomi: IconChartLine, + sampah: IconRecycle, + truck: IconTruckFilled, + scale: IconScale, + clipboard: IconClipboardTextFilled, + trash: IconTrashFilled, + lingkunganSehat: IconHomeEco, + sumberOksigen: IconChristmasTreeFilled, + ekonomiBerkelanjutan: IconTrendingUp, + mencegahBencana: IconShieldFilled, + }; + + if (loading || !data) { + return ( + + + + ); + } + if (data.length === 0) { + return ( + + + + +
+ No Nama Program Penghijauan - Gambar - Deskripsi + Judul Deskripsi Program Penghijauan + Ikon Detail - - - - - Judul - - - - +
+ Tidak ada data program penghijauan yang tersedia +
+
+
+ ); + } + return ( + + + + + + + + No + Nama Program Penghijauan + Judul Deskripsi Program Penghijauan + Ikon + Detail + + + + {filteredData.map((item, index) => ( + + {index + 1} + {item.name} + + + {iconMap[item.icon] && ( + + {React.createElement(iconMap[item.icon], { size: 24 })} + + )} - Deskripsi - - - -
-
- + ))} + + +
+
+ { + load(newPage, 10); + window.scrollTo(0, 0); + }} + total={totalPages} + mt="md" + mb="md" + /> +
- ) + ); } - export default ProgramPenghijauan; diff --git a/src/app/api/[[...slugs]]/_lib/lingkungan/index.ts b/src/app/api/[[...slugs]]/_lib/lingkungan/index.ts index be2fca30..eee4da83 100644 --- a/src/app/api/[[...slugs]]/_lib/lingkungan/index.ts +++ b/src/app/api/[[...slugs]]/_lib/lingkungan/index.ts @@ -1,5 +1,6 @@ import Elysia from "elysia"; import PengelolaanSampah from "./pengelolaan-sampah"; +import ProgramPenghijauan from "./program-penghijauan"; const Lingkungan = new Elysia({ prefix: "/api/lingkungan", @@ -7,5 +8,6 @@ const Lingkungan = new Elysia({ }) .use(PengelolaanSampah) +.use(ProgramPenghijauan) export default Lingkungan; diff --git a/src/app/api/[[...slugs]]/_lib/lingkungan/program-penghijauan/create.ts b/src/app/api/[[...slugs]]/_lib/lingkungan/program-penghijauan/create.ts new file mode 100644 index 00000000..c334619a --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/lingkungan/program-penghijauan/create.ts @@ -0,0 +1,30 @@ +import prisma from "@/lib/prisma"; +import { Context } from "elysia"; + +type FormCreateProgramPenghijauan = { + name: string; + judul: string; + deskripsi: string; + icon: string; +} + +export default async function programPenghijauanCreate(context: Context){ + const body = context.body as FormCreateProgramPenghijauan; + + await prisma.programPenghijauan.create({ + data: { + name: body.name, + judul: body.judul, + 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/lingkungan/program-penghijauan/del.ts b/src/app/api/[[...slugs]]/_lib/lingkungan/program-penghijauan/del.ts new file mode 100644 index 00000000..aedf9b1b --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/lingkungan/program-penghijauan/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 programPenghijauanDelete(context: Context) { + const { id } = context.params as { id: string }; + + if (!id) { + return { + success: false, + message: "ID program penghijauan tidak ditemukan", + }; + } + + try { + const deleted = await prisma.programPenghijauan.delete({ + where: { id }, + }); + + return { + success: true, + message: "Program penghijauan berhasil dihapus", + data: deleted, + }; + } catch (error: any) { + console.error("Error delete program penghijauan:", error); + return { + success: false, + message: "Terjadi kesalahan saat menghapus program penghijauan", + error: error.message, + }; + } +} diff --git a/src/app/api/[[...slugs]]/_lib/lingkungan/program-penghijauan/findMany.ts b/src/app/api/[[...slugs]]/_lib/lingkungan/program-penghijauan/findMany.ts new file mode 100644 index 00000000..0aa06bb3 --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/lingkungan/program-penghijauan/findMany.ts @@ -0,0 +1,44 @@ +import prisma from "@/lib/prisma"; +import { Context } from "elysia"; + +// Di findMany.ts +export default async function programPenghijauanFindMany(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.programPenghijauan.findMany({ + where: { isActive: true }, + skip, + take: limit, + orderBy: { createdAt: 'desc' }, + }), + prisma.programPenghijauan.count({ + where: { isActive: true } + }) + ]); + + const totalPages = Math.ceil(total / limit); + + return { + success: true, + message: "Success fetch program penghijauan with pagination", + data, + page, + totalPages, + total, + }; + } catch (e) { + console.error("Find many paginated error:", e); + return { + success: false, + message: "Failed fetch program penghijauan with pagination", + data: [], + page: 1, + totalPages: 1, + total: 0, + }; + } +} \ No newline at end of file diff --git a/src/app/api/[[...slugs]]/_lib/lingkungan/program-penghijauan/findUnique.ts b/src/app/api/[[...slugs]]/_lib/lingkungan/program-penghijauan/findUnique.ts new file mode 100644 index 00000000..a91f5c2b --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/lingkungan/program-penghijauan/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 programPenghijauanFindUnique(context: Context) { + const { id } = context.params as { id: string }; + + if (!id) { + return { + success: false, + message: "ID program penghijauan diperlukan", + }; + } + + try { + const programPenghijauan = await prisma.programPenghijauan.findUnique({ + where: { id }, + }); + + if (!programPenghijauan) { + return { + success: false, + message: "Program penghijauan tidak ditemukan", + }; + } + + return { + success: true, + data: programPenghijauan, + }; + } catch (error: any) { + console.error("Error findUnique program penghijauan:", error); + return { + success: false, + message: "Gagal mengambil data program penghijauan", + error: error.message, + }; + } +} \ No newline at end of file diff --git a/src/app/api/[[...slugs]]/_lib/lingkungan/program-penghijauan/index.ts b/src/app/api/[[...slugs]]/_lib/lingkungan/program-penghijauan/index.ts new file mode 100644 index 00000000..598b9430 --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/lingkungan/program-penghijauan/index.ts @@ -0,0 +1,41 @@ +import Elysia, { t } from "elysia"; +import programPenghijauanFindMany from "./findMany"; +import programPenghijauanFindUnique from "./findUnique"; +import programPenghijauanCreate from "./create"; +import programPenghijauanUpdate from "./updt"; +import programPenghijauanDelete from "./del"; + +const ProgramPenghijauan = new Elysia({ + prefix: "/programpenghijauan", + tags: ["Lingkungan/Program Penghijauan"], +}) + .get("/find-many", programPenghijauanFindMany) + .get("/:id", async (context) => { + const response = await programPenghijauanFindUnique(context); + return response; + }) + .post("/create", programPenghijauanCreate, { + body: t.Object({ + name: t.String(), + judul: t.String(), + deskripsi: t.String(), + icon: t.String(), + }), + }) + .put( + "/:id", + async (context) => { + const response = await programPenghijauanUpdate(context); + return response; + }, + { + body: t.Object({ + name: t.String(), + judul: t.String(), + deskripsi: t.String(), + icon: t.String(), + }), + } + ) + .delete("/del/:id", programPenghijauanDelete); + export default ProgramPenghijauan; diff --git a/src/app/api/[[...slugs]]/_lib/lingkungan/program-penghijauan/updt.ts b/src/app/api/[[...slugs]]/_lib/lingkungan/program-penghijauan/updt.ts new file mode 100644 index 00000000..fa587795 --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/lingkungan/program-penghijauan/updt.ts @@ -0,0 +1,48 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import prisma from "@/lib/prisma"; +import { Context } from "elysia"; + +type FormUpdateProgramPenghijauan = { + id: string; + name?: string; + judul?: string; + deskripsi?: string; + icon?: string; +}; +export default async function programPenghijauanUpdate(context: Context) { + const body = context.body as FormUpdateProgramPenghijauan; + const id = context.params?.id; // ambil dari URL param + + if (!id) { + return { + success: false, + message: "ID program penghijauan wajib diisi", + }; + } + + try { + const updated = await prisma.programPenghijauan.update({ + where: { id }, + data: { + name: body.name, + judul: body.judul, + deskripsi: body.deskripsi, + icon: body.icon, + }, + }); + + return { + success: true, + message: "Program penghijauan berhasil diupdate", + data: updated, + }; + } catch (error: any) { + console.error("Error update program penghijauan:", error); + return { + success: false, + message: "Gagal mengupdate program penghijauan", + error: error.message, + }; + } +} +