From 8ad38fc9077e0b9be79830f9d2c73a350eae771d Mon Sep 17 00:00:00 2001 From: nico Date: Wed, 8 Oct 2025 14:02:11 +0800 Subject: [PATCH] - QC User & Admin Menu Lingkungan - Fix SubMenu : Edukasi Lingkungan & Konservasi Adat Bali dibagian User - Fix SUbMenu : Gotong Royong User ( Tabs kategori menyesuaikan dengan data kategori kegiatan ) --- .../{ => desa/berita}/kategori-berita.json | 1 - .../gotong-royong/kategori-gotong-royong.json | 6 + prisma/schema.prisma | 2 +- prisma/seed.ts | 27 +- .../inovasi/ajukan-ide-inovatif/page.tsx | 21 +- .../[id]/edit/page.tsx | 166 +++--- .../desa-digital-smart-village/[id]/page.tsx | 169 ++++-- .../create/page.tsx | 82 ++- .../info-teknologi-tepat-guna/[id]/page.tsx | 2 +- .../jenis-layanan/[id]/edit/page.tsx | 108 ++-- .../kegiatan-desa/[id]/edit/page.tsx | 14 +- .../gotong-royong/kegiatan-desa/[id]/page.tsx | 2 +- .../kegiatan-desa/create/page.tsx | 16 +- src/app/admin/page.tsx | 35 +- .../lingkungan/gotong-royong/findFirst.ts | 2 +- .../desa/berita/[kategori]/Content.tsx | 2 +- .../(pages)/desa/berita/[kategori]/page.tsx | 1 - .../page.tsx | 314 ++++++++++- .../inovasi/kolaborasi-inovasi/page.tsx | 2 +- .../lingkungan/edukasi-lingkungan/page.tsx | 141 +++-- .../gotong-royong/[kategori]/content.tsx | 6 +- .../gotong-royong/[kategori]/page.tsx | 1 - .../gotong-royong/_lib/layoutTabs.tsx | 513 ++++++++++++++---- .../lingkungan/gotong-royong/semua/page.tsx | 23 +- .../lingkungan/konservasi-adat-bali/page.tsx | 156 ++++-- .../pengelolaan-sampah-bank-sampah/page.tsx | 34 +- 26 files changed, 1356 insertions(+), 490 deletions(-) rename prisma/data/{ => desa/berita}/kategori-berita.json (87%) create mode 100644 prisma/data/lingkungan/gotong-royong/kategori-gotong-royong.json diff --git a/prisma/data/kategori-berita.json b/prisma/data/desa/berita/kategori-berita.json similarity index 87% rename from prisma/data/kategori-berita.json rename to prisma/data/desa/berita/kategori-berita.json index ee0a53b0..4d777965 100644 --- a/prisma/data/kategori-berita.json +++ b/prisma/data/desa/berita/kategori-berita.json @@ -1,5 +1,4 @@ [ - { "name": "Semua" }, { "name": "Pemerintahan" }, { "name": "Pembangunan" }, { "name": "Ekonomi" }, diff --git a/prisma/data/lingkungan/gotong-royong/kategori-gotong-royong.json b/prisma/data/lingkungan/gotong-royong/kategori-gotong-royong.json new file mode 100644 index 00000000..874e2e32 --- /dev/null +++ b/prisma/data/lingkungan/gotong-royong/kategori-gotong-royong.json @@ -0,0 +1,6 @@ +[ + { "nama": "Kebersihan" }, + { "nama": "Infrastruktur" }, + { "nama": "Sosial" }, + { "nama": "Lingkungan" } + ] \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 2710da66..0a998e2f 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -1820,7 +1820,7 @@ model KategoriKegiatan { isActive Boolean @default(true) KegiatanDesa KegiatanDesa[] } - + // ========================================= EDUKASI LINGKUNGAN ========================================= // model TujuanEdukasiLingkungan { id String @id @default(cuid()) diff --git a/prisma/seed.ts b/prisma/seed.ts index beeb99f8..f90b09ae 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -33,11 +33,12 @@ import detailDataPengangguran from "./data/ekonomi/jumlah-pengangguran/detail-da import kategoriProduk from "./data/ekonomi/pasar-desa/kategori-produk.json"; import pegawai from "./data/ekonomi/struktur-organisasi/pegawai-bumdes.json"; import posisiOrganisasi from "./data/ekonomi/struktur-organisasi/posisi-organisasi-bumdes.json"; -import kategoriBerita from "./data/kategori-berita.json"; +import kategoriBerita from "./data/desa/berita/kategori-berita.json"; import contohEdukasiLingkungan from "./data/lingkungan/edukasi-lingkungan/contoh-kegiatan-di-desa-darmasaba.json"; import materiEdukasiLingkungan from "./data/lingkungan/edukasi-lingkungan/materi-edukasi-yang-diberikan.json"; import tujuanEdukasiLingkungan from "./data/lingkungan/edukasi-lingkungan/tujuan-edukasi-lingkungan.json"; import bentukKonservasiBerdasarkanAdat from "./data/lingkungan/konservasi-adat-bali/bentuk-konservasi.json"; +import kategoriKegiatanData from "./data/lingkungan/gotong-royong/kategori-gotong-royong.json"; import filosofiTriHita from "./data/lingkungan/konservasi-adat-bali/filosofi-tri-hita.json"; import nilaiKonservasiAdat from "./data/lingkungan/konservasi-adat-bali/nilai-konservasi-adat.json"; import caraMemperolehInformasi from "./data/list-caraMemperolehInformasi.json"; @@ -885,6 +886,30 @@ import { safeSeedUnique } from "./safeseedUnique"; } console.log("📊 detailDataPengangguran success ..."); + // =========== KATEGORI GOTONG ROYONG =========== + // Add IDs to the kategoriKegiatan data + const kategoriKegiatan = kategoriKegiatanData.map((k, index) => ({ + ...k, + id: `kategori-${index + 1}` + })); + + for (const k of kategoriKegiatan) { + await prisma.kategoriKegiatan.upsert({ + where: { + id: k.id, + }, + update: { + nama: k.nama, + }, + create: { + id: k.id, + nama: k.nama, + }, + }); + } + + console.log("kategori kegiatan success ..."); + for (const e of tujuanEdukasiLingkungan) { await prisma.tujuanEdukasiLingkungan.upsert({ where: { diff --git a/src/app/admin/(dashboard)/inovasi/ajukan-ide-inovatif/page.tsx b/src/app/admin/(dashboard)/inovasi/ajukan-ide-inovatif/page.tsx index 51ba6814..24bdbd06 100644 --- a/src/app/admin/(dashboard)/inovasi/ajukan-ide-inovatif/page.tsx +++ b/src/app/admin/(dashboard)/inovasi/ajukan-ide-inovatif/page.tsx @@ -4,7 +4,6 @@ import { Box, Button, Center, - Group, Pagination, Paper, Skeleton, @@ -16,11 +15,10 @@ import { TableThead, TableTr, Text, - Title, - Tooltip, + Title } from '@mantine/core'; import { useShallowEffect } from '@mantine/hooks'; -import { IconDeviceImac, IconSearch, IconPlus } from '@tabler/icons-react'; +import { IconDeviceImac, IconSearch } from '@tabler/icons-react'; import { useRouter } from 'next/navigation'; import { useState } from 'react'; import { useProxy } from 'valtio/utils'; @@ -72,20 +70,7 @@ function ListAjukanIdeInovatif({ search }: { search: string }) { return ( - - Daftar Ide Inovatif - - - - - + Daftar Ide Inovatif 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 aabb73c3..05c93b59 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,10 +1,21 @@ -'use client' +'use client'; /* eslint-disable react-hooks/exhaustive-deps */ import EditEditor from '@/app/admin/(dashboard)/_com/editEditor'; import desaDigitalState from '@/app/admin/(dashboard)/_state/inovasi/desa-digital'; import colors from '@/con/colors'; import ApiFetch from '@/lib/api-fetch'; -import { Box, Button, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core'; +import { + Box, + Button, + Group, + Image, + Paper, + Stack, + Text, + TextInput, + Title, + Tooltip, +} from '@mantine/core'; import { Dropzone } from '@mantine/dropzone'; import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react'; import { useParams, useRouter } from 'next/navigation'; @@ -20,16 +31,14 @@ function EditDigitalSmartVillage() { const [previewImage, setPreviewImage] = useState(null); const [file, setFile] = useState(null); - // ✅ hanya lokal state untuk form const [formData, setFormData] = useState({ name: '', deskripsi: '', imageId: '', }); - // load data sekali saat mount useEffect(() => { - const loadPenghargaan = async () => { + const loadData = async () => { const id = params?.id as string; if (!id) return; @@ -42,69 +51,67 @@ function EditDigitalSmartVillage() { imageId: data.imageId || '', }); - if (data?.image?.link) { - setPreviewImage(data.image.link); - } + if (data?.image?.link) setPreviewImage(data.image.link); } } catch (error) { - console.error("Error loading desa digital smart village:", error); - toast.error("Gagal memuat data desa digital smart village"); + console.error('Error loading data:', error); + toast.error('Gagal memuat data desa digital smart village'); } }; - loadPenghargaan(); + loadData(); }, [params?.id]); const handleSubmit = async () => { try { - // ✅ update global state hanya saat submit - stateDesaDigital.edit.form = { - ...stateDesaDigital.edit.form, - ...formData, - }; + stateDesaDigital.edit.form = { ...stateDesaDigital.edit.form, ...formData }; 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"); - } + if (!uploaded?.id) return toast.error('Gagal upload gambar'); stateDesaDigital.edit.form.imageId = uploaded.id; } await stateDesaDigital.edit.update(); - toast.success("Desa digital smart village berhasil diperbarui!"); - router.push("/admin/inovasi/desa-digital-smart-village"); + toast.success('Desa digital smart village berhasil diperbarui!'); + router.push('/admin/inovasi/desa-digital-smart-village'); } catch (error) { - console.error("Error updating desa digital smart village:", error); - toast.error("Terjadi kesalahan saat memperbarui desa digital smart village"); + console.error('Error updating desa digital:', error); + toast.error('Terjadi kesalahan saat memperbarui data'); } }; return ( - - - - - - - - Edit Desa Digital Smart Village - - {/* ✅ controlled input */} - setFormData({ ...formData, name: e.target.value })} - label={Judul} - placeholder="masukkan judul" - /> + + {/* Header */} + + + + + + Edit Desa Digital Smart Village + + + {/* Form Card */} + + + {/* Dropzone Upload */} - Gambar + + Gambar Desa Digital + { const selectedFile = files[0]; @@ -113,43 +120,43 @@ function EditDigitalSmartVillage() { setPreviewImage(URL.createObjectURL(selectedFile)); } }} - onReject={() => toast.error('File tidak valid.')} + onReject={() => toast.error('File tidak valid, gunakan format gambar')} maxSize={5 * 1024 ** 2} accept={{ 'image/*': [] }} + radius="md" + p="xl" > - + - + - + - + - -
- - Drag gambar ke sini atau klik untuk pilih file + + + Seret gambar atau klik untuk memilih file - - Maksimal 5MB dan harus format gambar + + Maksimal 5MB, format gambar wajib -
+
{previewImage && ( - + @@ -157,18 +164,43 @@ function EditDigitalSmartVillage() { )} + {/* Input Judul */} + setFormData({ ...formData, name: e.target.value })} + required + /> + + {/* Editor Deskripsi */} - Deskripsi - {/* ✅ controlled editor */} + + Deskripsi + { - setFormData((prev) => ({ ...prev, deskripsi: htmlContent })); - }} + onChange={(htmlContent) => + setFormData((prev) => ({ ...prev, deskripsi: htmlContent })) + } /> - + {/* Tombol Simpan */} + + +
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 c6fbf1db..c82485e0 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 @@ -1,8 +1,18 @@ 'use client' import colors from '@/con/colors'; -import { Box, Button, Flex, Image, Paper, Skeleton, Stack, Text } from '@mantine/core'; +import { + Box, + Button, + Group, + Image, + Paper, + Skeleton, + Stack, + Text, + Tooltip, +} from '@mantine/core'; import { useShallowEffect } from '@mantine/hooks'; -import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react'; +import { IconArrowBack, IconEdit, IconTrash } from '@tabler/icons-react'; import { useParams, useRouter } from 'next/navigation'; import { useState } from 'react'; import { useProxy } from 'valtio/utils'; @@ -10,95 +20,136 @@ import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus'; import desaDigitalState from '../../../_state/inovasi/desa-digital'; function DetailDesaDigital() { - const stateDesaDigital = useProxy(desaDigitalState) + const stateDesaDigital = useProxy(desaDigitalState); const [modalHapus, setModalHapus] = useState(false); const [selectedId, setSelectedId] = useState(null); - const router = useRouter() - const params = useParams() + const router = useRouter(); + const params = useParams(); useShallowEffect(() => { - stateDesaDigital.findUnique.load(params?.id as string) - }, []) + stateDesaDigital.findUnique.load(params?.id as string); + }, []); const handleHapus = () => { if (selectedId) { - stateDesaDigital.delete.byId(selectedId) - setModalHapus(false) - setSelectedId(null) - router.push("/admin/inovasi/desa-digital-smart-village") + stateDesaDigital.delete.byId(selectedId); + setModalHapus(false); + setSelectedId(null); + router.push("/admin/inovasi/desa-digital-smart-village"); } - } + }; if (!stateDesaDigital.findUnique.data) { return ( - + - ) + ); } + const data = stateDesaDigital.findUnique.data; + return ( - - - - - - - Detail Desa Digital Smart Village - {stateDesaDigital.findUnique.data ? ( - - - - Judul - {stateDesaDigital.findUnique.data?.name} - - - Deskripsi - - - - Gambar - - - + + {/* Tombol Kembali */} + + + {/* Card Utama */} + + + + Detail Desa Digital Smart Village + + + {/* Sub Card Detail */} + + + + Judul + {data?.name || '-'} + + + + Deskripsi + + + + + Gambar + {data?.image?.link ? ( + + ) : ( + Tidak ada gambar + )} + + + {/* Tombol Aksi */} + + + + + - - - - ) : null} + + + + - {/* Modal Konfirmasi Hapus */} + {/* Modal Konfirmasi */} setModalHapus(false)} onConfirm={handleHapus} - text='Apakah anda yakin ingin menghapus desa digital smart village ini?' + text="Apakah Anda yakin ingin menghapus desa digital smart village ini?" /> ); 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 5694b96a..6a00b669 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 @@ -22,7 +22,7 @@ import CreateEditor from '../../../_com/createEditor'; import desaDigitalState from '../../../_state/inovasi/desa-digital'; import { Dropzone } from '@mantine/dropzone'; -function CreateDesaDigital() { +export default function CreateDesaDigital() { const stateDesaDigital = useProxy(desaDigitalState); const [previewImage, setPreviewImage] = useState(null); const [file, setFile] = useState(null); @@ -44,7 +44,6 @@ function CreateDesaDigital() { } try { - // Upload gambar dulu const uploadRes = await ApiFetch.api.fileStorage.create.post({ file, name: file.name, @@ -55,10 +54,8 @@ function CreateDesaDigital() { return toast.error('Gagal mengunggah gambar'); } - // Set imageId ke form stateDesaDigital.create.form.imageId = uploaded.id; - // Submit form const success = await stateDesaDigital.create.create(); if (success) { resetForm(); @@ -72,10 +69,16 @@ function CreateDesaDigital() { return ( - {/* Header */} - + {/* Header dengan tombol kembali */} + - @@ -84,28 +87,32 @@ function CreateDesaDigital() { - {/* Card */} + {/* Card Form */} - - {/* Nama */} + + {/* Input Nama */} (stateDesaDigital.create.form.name = e.target.value)} - required + radius="md" + withAsterisk /> {/* Deskripsi */} - + Deskripsi - - Gambar + + Gambar Desa Digital { @@ -134,6 +141,11 @@ function CreateDesaDigital() { accept={{ 'image/*': [] }} radius="md" p="xl" + style={{ + border: '2px dashed #cfd8dc', + backgroundColor: '#fafafa', + transition: 'background-color 0.2s ease, border-color 0.2s ease', + }} > @@ -153,15 +165,22 @@ function CreateDesaDigital() { {/* Preview */} {previewImage && ( - + @@ -170,7 +189,7 @@ function CreateDesaDigital() { {/* Tombol Submit */} - + - - - - - Edit Jenis Layanan + + {/* Header */} + + + + + + Edit Jenis Layanan + + + {/* Form Container */} + + + {/* Input: Nama Jenis Layanan */} setFormData((prev) => ({ ...prev, nama: e.target.value })) } - label={Nama Jenis Layanan} - placeholder="masukkan nama jenis layanan" + required /> + {/* Input: Deskripsi (Rich Text Editor) */} - Deskripsi + + Deskripsi + - setFormData((prev) => ({ ...prev, deskripsi: htmlContent })) + setFormData((prev) => ({ + ...prev, + deskripsi: htmlContent, + })) } /> - + {/* Tombol Submit */} + + + diff --git a/src/app/admin/(dashboard)/lingkungan/gotong-royong/kegiatan-desa/[id]/edit/page.tsx b/src/app/admin/(dashboard)/lingkungan/gotong-royong/kegiatan-desa/[id]/edit/page.tsx index 1bda5027..15b5c48a 100644 --- a/src/app/admin/(dashboard)/lingkungan/gotong-royong/kegiatan-desa/[id]/edit/page.tsx +++ b/src/app/admin/(dashboard)/lingkungan/gotong-royong/kegiatan-desa/[id]/edit/page.tsx @@ -150,13 +150,13 @@ export default function EditKegiatanDesa() { onChange={(e) => setFormData({ ...formData, judul: e.target.value })} required /> - Deskripsi Singkat Kegiatan Desa} - placeholder="masukkan deskripsi singkat kegiatan desa" - onChange={(e) => setFormData({ ...formData, deskripsiSingkat: e.target.value })} - required - /> + + Deskripsi Singkat Kegiatan Desa + setFormData(prev => ({ ...prev, deskripsiSingkat: htmlContent }))} + /> +