diff --git a/bun.lockb b/bun.lockb index 0618a3a7..775d4131 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/foldergambar/desa/name.png b/foldergambar/desa/name.png new file mode 100644 index 00000000..8f31f48e Binary files /dev/null and b/foldergambar/desa/name.png differ diff --git a/foldergambar/desa/ppid/profile-ppid/name.png b/foldergambar/desa/ppid/profile-ppid/name.png new file mode 100644 index 00000000..97ffac9e Binary files /dev/null and b/foldergambar/desa/ppid/profile-ppid/name.png differ diff --git a/package.json b/package.json index 3f5f3a58..c9a52ec1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "desa-darmasaba", - "version": "0.1.1", + "version": "0.1.2", "private": true, "scripts": { "dev": "next dev --turbopack", @@ -16,6 +16,7 @@ "@cubejs-client/core": "^0.31.0", "@elysiajs/cors": "^1.2.0", "@elysiajs/eden": "^1.2.0", + "@elysiajs/static": "^1.3.0", "@elysiajs/stream": "^1.1.0", "@elysiajs/swagger": "^1.2.0", "@mantine/carousel": "^7.16.2", @@ -47,14 +48,16 @@ "elysia": "^1.2.12", "embla-carousel-autoplay": "^8.5.2", "embla-carousel-react": "^7.1.0", + "form-data": "^4.0.2", "framer-motion": "^12.4.1", "get-port": "^7.1.0", "jotai": "^2.12.3", "lodash": "^4.17.21", "motion": "^12.4.1", - "nanoid": "^5.1.0", + "nanoid": "^5.1.5", "next": "15.1.6", "next-view-transitions": "^0.3.4", + "node-fetch": "^3.3.2", "p-limit": "^6.2.0", "prisma": "^6.3.1", "react": "^19.0.0", @@ -64,6 +67,7 @@ "readdirp": "^4.1.1", "recharts": "^2.15.3", "swr": "^2.3.2", + "uuid": "^11.1.0", "valtio": "^2.1.3", "zod": "^3.24.3" }, diff --git a/prisma/data/desa/profile/profil_perbekel.json b/prisma/data/desa/profile/profil_perbekel.json new file mode 100644 index 00000000..3f846693 --- /dev/null +++ b/prisma/data/desa/profile/profil_perbekel.json @@ -0,0 +1,9 @@ +[ + { + "id": "1", + "biodata": "

I.B Surya Prabhawa Manuaba, S.H., M.H., adalah Perbekel Darmasaba periode 2021-2027, seorang advokat, pendiri Mantra Legal Consultants & Advocates, serta aktif di bidang musik dan akademis. Dia menempuh pendidikan hukum di Universitas Udayana dan Universitas Mahasaraswati Denpasar serta memiliki pengalaman luas di berbagai organisasi dan kepemimpinan.

", + "pengalaman": "", + "pengalamanOrganisasi": "", + "programUnggulan": "

Pemberdayaan Ekonomi dan UMKM

" + } +] \ No newline at end of file diff --git a/prisma/data/desa/profile/profile_desa.json b/prisma/data/desa/profile/profile_desa.json new file mode 100644 index 00000000..29ed4d8a --- /dev/null +++ b/prisma/data/desa/profile/profile_desa.json @@ -0,0 +1,11 @@ +[ + { + "id": "1", + "sejarah" : "

Asal – usul nama Darmasaba tertuang dalam lontar Usada Bali. Seperti di tulis dalam monografi Desa Darmasaba tahun 1980 silam, nama Darmasaba berkaitan dengan keturunan Danghyang Nirarta diceritakan, Sang kawi-wiku asal Daha (Jawa Timur) itu memiliki cucu bernama Ida Pedanda Sakti Manuaba yang tigggal di Desa Kendran Tegalalang Gianyar. Merasa tidak disenangi sang ayah, Ida Pedanda Sakti Manuaba pergi mengembara bersama dua orang pengiringnya. Pengembaraan sang pendeta sampai di pura Sarin Buana di Jimbaran. Saat mengadakan semedi di tempat ini sang pendeta melihat sinar api. Yang sangat jauh di utara. Timbul keinginan Ida Pedanda Manuaba untuk mengunjungi tempat itu. Sampailah sang Pedanda di pura Batan Bila Peguyangan. Disini Ida Pedanda Manuaba singgah menghadap Ida Pedanda Budha yang tinggal disana. Selanjutnya, kedua pendeta bersama-sama menuju arah utara dan singgah di Taman Cang Ana, sebuah taman milik Arya Lanang Blusung. Di tempat ini kedua pendeta bersama-sama melaksanakan semedi dan menetap untuk sementara waktu.

", + "visi" : "

Mewujudkan Desa Darmasaba yang sejahtera, unggul, religius, berbudaya, dan aman dengan berlandaskan Tri Hita Karana

", + "misi" : "", + "lambang" : "", + "maskot" : "

Pudak adalah bunga dari tanaman sejenis pandan (Pandanaceae). Bentuk bunga ini tersusun dalam beberapa lapisan, terbungkus oleh kelopak warna putih (semacam daun lonjong) yang ujungnya meruncing.

Bunga Pudak berwarna kuning dan akan terlihat jika kelopak atau pelepahnya telah mekar. Kekhasan dari bunga pudak, yaitu mempunyai aroma wangi yang semerbak nan lembut (tidak menyengat), dan dapat menebar keharuman sepanjang pagi atau pun sore hari. Tanaman ini dapat tumbuh di sepanjang pantai, aliran sungai, di atas batu-batu karang, dan juga di tanah ladang.

Dalam Kamus Jawa Kuna- Indonesia kata “Pudak” berarti bunga pandan atau Pandanus Moschatus (Mardiwarsito: 1981: 442). Selain itu bunga pudak juga dapat disebut ketaka atau ketaki (Mardiwarsito, 1981: 276). Sedangkan kata “Sategal” berasal dari kata dasar “Tegal” yang berarti ladang (Mardiwarsito, 1981: 593). Jadi Pudak Sategal dapat diartikan sebagai satu ladang luas yang dipenuhi bunga pudak dan menabar keharuman.

Pada sebuah kesempatan, Ida Pedanda Putu Pemaron menjelaskan mengenai makna dari istilah Pudak Sategal dengan sebuah analogi bahwa, sekuntum bunga pudak memiliki aroma wangi atau keharuman yang sangat kuat, apalagi jika satu ladang penuh bunga pudak, maka dapat dipastikan aroma keharumannya akan membumbung menyebar ke segala penjuru (Wawancara, 18 Mei 2019 di Geria Putra Mandara Kenderan, Tegallalang). “Pudak” ialah sebuah bunga yang memiliki aroma wangi atau keharuman yang semerbak, lembut, dan khas.

Garapan Tari Maskot Desa Darmasaba Sekar Pudak diwujudkan ke dalam bentuk tari kreasi yang ditarikan secara berkelompok dengan jumlah lima orang penari perempuan (putri).

Pemilihan penari perempuan dimaksudkan untuk mempresentasikan keindahan, keluwesan, dan keharuman dari bunga pudak. Sedangkan penetapan jumlah penari lima orang didasarkan atas pertimbangan kebutuhan koreografi agar dapat membentuk desain-desain komposisi lantai yang menarik dan dinamis, baik ketika ditarikan di area panggung yang luas atau pun area panggung yang kecil. Penyajian tari maskot ini dirancang dengan durasi waktu 9 menit.

", + "profilPerbekelId" : "1" + } +] \ No newline at end of file diff --git a/prisma/data/katagory-berita.json b/prisma/data/kategori-berita.json similarity index 100% rename from prisma/data/katagory-berita.json rename to prisma/data/kategori-berita.json diff --git a/prisma/data/list-caraMemperolehInformasi.json b/prisma/data/list-caraMemperolehInformasi.json new file mode 100644 index 00000000..c3f7fdad --- /dev/null +++ b/prisma/data/list-caraMemperolehInformasi.json @@ -0,0 +1,5 @@ +[ + {"name": "Melihat/Membaca/Mendengarkan/Mencatat"}, + {"name": "Mendapatkan Salinan Informasi (Hardcopy)"}, + {"name": "Mendapatkan Salinan Informasi (Softcopy)"} +] \ No newline at end of file diff --git a/prisma/data/list-caraMemperolehSalinanInformasi.json b/prisma/data/list-caraMemperolehSalinanInformasi.json new file mode 100644 index 00000000..6587f648 --- /dev/null +++ b/prisma/data/list-caraMemperolehSalinanInformasi.json @@ -0,0 +1,5 @@ +[ + { "name": "Mengambil Langsung" }, + { "name": "Dikirim Via Post" }, + { "name": "Dikirim Via Email" } +] diff --git a/prisma/data/list-jenisInfromasi.json b/prisma/data/list-jenisInfromasi.json new file mode 100644 index 00000000..393523e8 --- /dev/null +++ b/prisma/data/list-jenisInfromasi.json @@ -0,0 +1,6 @@ +[ + { "name": "Keuangan Desa" }, + { "name": "Pembangunan Desa" }, + { "name": "Data Demografi" }, + { "name": "Lainnya" } +] diff --git a/prisma/data/ppid/daftar-informasi-publik-desa-darmasaba/daftarInformasi.json b/prisma/data/ppid/daftar-informasi-publik-desa-darmasaba/daftarInformasi.json new file mode 100644 index 00000000..a3209314 --- /dev/null +++ b/prisma/data/ppid/daftar-informasi-publik-desa-darmasaba/daftarInformasi.json @@ -0,0 +1,8 @@ +[ + { + "id": "1", + "jenisInformasi": "Peraturan Desa", + "deskripsi": "Dokumen yang berisi kebijakan dan regulasi desa", + "tanggal": "15 Januari 2024" + } +] \ No newline at end of file diff --git a/prisma/data/ppid/dasar-hukum-ppid/dasarhukumPPID.json b/prisma/data/ppid/dasar-hukum-ppid/dasarhukumPPID.json new file mode 100644 index 00000000..a81befaf --- /dev/null +++ b/prisma/data/ppid/dasar-hukum-ppid/dasarhukumPPID.json @@ -0,0 +1,7 @@ +[ + { + "id": "1", + "judul": "DASAR HUKUM PEMBENTUKAN PPID DESA DARMASABA", + "content" : "" + } +] \ No newline at end of file diff --git a/prisma/data/ppid/profile-ppid/profilePPid.json b/prisma/data/ppid/profile-ppid/profilePPid.json new file mode 100644 index 00000000..3727382c --- /dev/null +++ b/prisma/data/ppid/profile-ppid/profilePPid.json @@ -0,0 +1,11 @@ +[ + { + "id": "1", + "name": "I.B Surya Prabhawa Manuaba, S.H., M.H.", + "biodata": "

I.B Surya Prabhawa Manuaba, S.H., M.H., adalah Perbekel Darmasaba periode 2021-2027, seorang advokat, pendiri Mantra Legal Consultants & Advocates, serta aktif di bidang musik dan akademis. Dia menempuh pendidikan hukum di Universitas Udayana dan Universitas Mahasaraswati Denpasar, serta memiliki pengalaman luas di berbagai organisasi dan kepemimpinan.

", + "riwayat": "", + "pengalaman": "", + "unggulan": "

Pemberdayaan Ekonomi dan UMKM

", + "imageUrl": "/uploads/seeded-images/profile-ppid/perbekel.png" + } +] diff --git a/prisma/data/ppid/visi-misi-ppid/visimisiPPID.json b/prisma/data/ppid/visi-misi-ppid/visimisiPPID.json new file mode 100644 index 00000000..a0eb4ab3 --- /dev/null +++ b/prisma/data/ppid/visi-misi-ppid/visimisiPPID.json @@ -0,0 +1,8 @@ +[ + { + "id": "1", + "misi": "
  1. Meningkatkan pengelolaan dan pelayanan informasi yang berkualitas, benar dan bertanggung jawab.
  2. Membangun dan mengembangkan sistem penyediaan dan layanan informasi.
  3. Meningkatkan dan mengembangkan kompetensi dan kualitas SDM dalam bidang pelayanan informasi.
  4. Mewujudkan keterbukaan informasi Pemerintah Desa Punggul dengan proses yang cepat, tepat, mudah dan sederhana.
", + "visi": "Memberikan pelayanan informasi yanng transparan dan akuntabel untuk memenuhi hak pemohon informasi sesuai dengan ketentuan peraturan perundang-undangan yang berlaku." + + } +] \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 4bce5939..4c0d39cb 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -47,23 +47,213 @@ model AppMenuChild { appMenuId String? } +//========================================= MENU PPID ========================================= // +// ========================================= VISI MISI PPID ========================================= // +model VisiMisiPPID { + id String @id @default(cuid()) + visi String @db.Text + misi String @db.Text + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime @default(now()) + isActive Boolean @default(true) +} + +// ========================================= DASAR HUKUM PPID ========================================= // +model DasarHukumPPID { + id String @id @default(cuid()) + judul String @db.Text + content String @db.Text + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime @default(now()) + isActive Boolean @default(true) +} + +// ========================================= PROFILE PPID ========================================= // +model ProfilePPID { + id String @id @default(cuid()) + name String @db.Text + biodata String @db.Text + riwayat String @db.Text + pengalaman String @db.Text + unggulan String @db.Text + imageUrl String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime @default(now()) + isActive Boolean @default(true) +} + +// ========================================= DAFTAR INFORMASI PUBLIK ========================================= // +model DaftarInformasiPublik { + id String @id @default(cuid()) + nomor Int @default(autoincrement()) + jenisInformasi String + deskripsi String + tanggal String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime @default(now()) + isActive Boolean @default(true) +} + +//=========================================PERMOHONAN INFORMASI PUBLIK========================= // +model PermohonanInformasiPublik { + id String @id @default(cuid()) + nomor Int @default(autoincrement()) + name String + nik String + notelp String + alamat String + email String + jenisInformasiDiminta JenisInformasiDiminta? @relation(fields: [jenisInformasiDimintaId], references: [id]) + jenisInformasiDimintaId String? + caraMemperolehInformasi CaraMemperolehInformasi? @relation(fields: [caraMemperolehInformasiId], references: [id]) + caraMemperolehInformasiId String? + caraMemperolehSalinanInformasi CaraMemperolehSalinanInformasi? @relation(fields: [caraMemperolehSalinanInformasiId], references: [id]) + caraMemperolehSalinanInformasiId String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime @default(now()) + isActive Boolean @default(true) +} + +model JenisInformasiDiminta { + id String @id @default(cuid()) + name String @unique + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime @default(now()) + isActive Boolean @default(true) + PermohonanInformasiPublik PermohonanInformasiPublik[] +} + +model CaraMemperolehInformasi { + id String @id @default(cuid()) + name String @unique + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime @default(now()) + isActive Boolean @default(true) + PermohonanInformasiPublik PermohonanInformasiPublik[] +} + +model CaraMemperolehSalinanInformasi { + id String @id @default(cuid()) + name String @unique + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime @default(now()) + isActive Boolean @default(true) + PermohonanInformasiPublik PermohonanInformasiPublik[] +} + +//=========================================PERMOHONAN INFORMASI KEBERATAN PUBLIK========================= // +model FormulirPermohonanKeberatan { + id String @id @default(cuid()) + name String + email String + notelp String + alasan String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime @default(now()) + isActive Boolean @default(true) +} + +// ========================================= IKM ========================================= // +model IndeksKepuasanMasyarakat { + id Int @id @default(autoincrement()) + label String + kepuasan String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime @default(now()) + isActive Boolean @default(true) +} + +model GrafikBerdasarkanJenisKelamin { + id String @id @default(cuid()) + perempuan String + laki String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime @default(now()) + isActive Boolean @default(true) +} + +model GrafikBerdasarkanResponden { + id String @id @default(cuid()) + sangatbaik String + baik String + kurangbaik String + tidakbaik String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime @default(now()) + isActive Boolean @default(true) +} + +model GrafikBerdasarkanUmur { + id String @id @default(cuid()) + remaja String + dewasa String + orangtua String + lansia String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime @default(now()) + isActive Boolean @default(true) +} + // ========================================= MENU DESA ========================================= // +// ========================================= PROFILE DESA ========================================= // +model ProfileDesa { + id String @id @default(cuid()) + sejarah String @db.Text + visi String @db.Text + misi String @db.Text + lambang String @db.Text + maskot String @db.Text + ProfilPerbekel ProfilPerbekel? @relation(fields: [profilPerbekelId], references: [id]) + profilPerbekelId String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime @default(now()) + isActive Boolean @default(true) +} + +model ProfilPerbekel { + id String @id @default(cuid()) + biodata String @db.Text + pengalaman String @db.Text + pengalamanOrganisasi String @db.Text + programUnggulan String @db.Text + ProfileDesa ProfileDesa[] + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime @default(now()) + isActive Boolean @default(true) +} + // ========================================= BERITA ========================================= // model Berita { id String @id @default(cuid()) judul String deskripsi String - image String + image FileStorage @relation(fields: [imageId], references: [id]) + imageId String content String @db.Text createdAt DateTime @default(now()) updatedAt DateTime @updatedAt deletedAt DateTime @default(now()) isActive Boolean @default(true) - KatagoryBerita KatagoryBerita? @relation(fields: [katagoryBeritaId], references: [id]) - katagoryBeritaId String? + kategoriBerita KategoriBerita? @relation(fields: [kategoriBeritaId], references: [id]) + kategoriBeritaId String? } -model KatagoryBerita { +model KategoriBerita { id String @id @default(cuid()) name String @unique beritas Berita[] @@ -323,3 +513,89 @@ model GrafikKepuasan { deletedAt DateTime @default(now()) isActive Boolean @default(true) } + +// ========================================= ARTIKEL KESEHATAN ========================================= // +model ArtikelKesehatan { + id Int @id @default(autoincrement()) + title String + content String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime @default(now()) + isActive Boolean @default(true) +} + +model Introduction { + id Int @id @default(autoincrement()) + content String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime @default(now()) + isActive Boolean @default(true) +} + +model Symptom { + id Int @id @default(autoincrement()) + title String + content String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime @default(now()) + isActive Boolean @default(true) +} + +model Prevention { + id Int @id @default(autoincrement()) + title String + content String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime @default(now()) + isActive Boolean @default(true) +} + +model FirstAid { + id Int @id @default(autoincrement()) + title String + content String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime @default(now()) + isActive Boolean @default(true) +} + +model MythVsFact { + id Int @id @default(autoincrement()) + title String + mitos String + fakta String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime @default(now()) + isActive Boolean @default(true) +} + +model DoctorSign { + id Int @id @default(autoincrement()) + content String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime @default(now()) + isActive Boolean @default(true) +} + +// === BARU + +model FileStorage { + id String @id @default(cuid()) + name String @unique + realName String + path String + mimeType String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime? + isActive Boolean @default(true) + link String + Berita Berita[] +} diff --git a/prisma/seed-images/perbekel.png b/prisma/seed-images/perbekel.png new file mode 100644 index 00000000..ed1cbd10 Binary files /dev/null and b/prisma/seed-images/perbekel.png differ diff --git a/prisma/seed.ts b/prisma/seed.ts index 5f296d13..6cd35bf2 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -1,82 +1,277 @@ -import layanan from './data/list-layanan.json' -import potensi from './data/list-potensi.json' -import katagoryBerita from './data/katagory-berita.json' -import categoryPengumuman from './data/category-pengumuman.json' -import prisma from '@/lib/prisma'; +import prisma from "@/lib/prisma"; +import categoryPengumuman from "./data/category-pengumuman.json"; +import kategoriBerita from "./data/kategori-berita.json"; +import caraMemperolehInformasi from "./data/list-caraMemperolehInformasi.json"; +import caraMemperolehSalinanInformasi from "./data/list-caraMemperolehSalinanInformasi.json"; +import jenisInformasiDiminta from "./data/list-jenisInfromasi.json"; +import layanan from "./data/list-layanan.json"; +import potensi from "./data/list-potensi.json"; +import visiMisiPPID from "./data/ppid/visi-misi-ppid/visimisiPPID.json"; +import dasarHukumPPID from "./data/ppid/dasar-hukum-ppid/dasarhukumPPID.json"; +import profileDesa from "./data/desa/profile/profile_desa.json"; +import profilePerbekel from "./data/desa/profile/profil_perbekel.json"; +import profilePPID from "./data/ppid/profile-ppid/profilePPid.json"; +import path from "path"; +import fs from "fs"; +import { mkdir, writeFile } from "fs/promises"; +import { v4 as uuid } from "uuid"; + (async () => { - for (const l of layanan) { - await prisma.layanan.upsert({ - where: { - name: l.name - }, - update: { - name: l.name - }, - create: { - name: l.name - } - }) + for (const l of layanan) { + await prisma.layanan.upsert({ + where: { + name: l.name, + }, + update: { + name: l.name, + }, + create: { + name: l.name, + }, + }); + } + + console.log("layanan success ..."); + + for (const p of potensi) { + await prisma.potensi.upsert({ + where: { + name: p.name, + }, + update: { + name: p.name, + }, + create: { + name: p.name, + }, + }); + } + + console.log("potensi success ..."); + + for (const k of kategoriBerita) { + await prisma.kategoriBerita.upsert({ + where: { + name: k.name, + }, + update: { + name: k.name, + }, + create: { + name: k.name, + }, + }); + } + + console.log("kategori berita success ..."); + + for (const c of categoryPengumuman) { + await prisma.categoryPengumuman.upsert({ + where: { + name: c.name, + }, + update: { + name: c.name, + }, + create: { + name: c.name, + }, + }); + } + + console.log("category pengumuman success ..."); + + for (const j of jenisInformasiDiminta) { + await prisma.jenisInformasiDiminta.upsert({ + where: { + name: j.name, + }, + update: { + name: j.name, + }, + create: { + name: j.name, + }, + }); + } + console.log("jenis informasi diminta success ..."); + + for (const c of caraMemperolehInformasi) { + await prisma.caraMemperolehInformasi.upsert({ + where: { + name: c.name, + }, + update: { + name: c.name, + }, + create: { + name: c.name, + }, + }); + } + console.log("cara memperoleh informasi success ..."); + + for (const c of caraMemperolehSalinanInformasi) { + await prisma.caraMemperolehSalinanInformasi.upsert({ + where: { + name: c.name, + }, + update: { + name: c.name, + }, + create: { + name: c.name, + }, + }); + } + console.log("cara memperoleh salinan informasi success ..."); + + const seedProfilePPID = async () => { + const targetDir = path.resolve("public", "uploads", "seeded-images", "profile-ppid") + + // Buat folder hanya jika belum ada + if (!fs.existsSync(targetDir)) { + await mkdir(targetDir, { recursive: true }) } - - console.log("layanan success ...") - - for (const p of potensi) { - await prisma.potensi.upsert({ - where: { - name: p.name - }, - update: { - name: p.name - }, - create: { - name: p.name - } - }) + + for (const c of profilePPID) { + let finalImageUrl = c.imageUrl + + if (c.imageUrl.startsWith("/uploads/seeded-images/")) { + const filename = path.basename(c.imageUrl) + const seedImagePath = path.resolve("prisma", "seed-images", filename) + + const targetFilename = `${uuid()}_${filename}` + const targetPath = path.join(targetDir, targetFilename) + + const buffer = fs.readFileSync(seedImagePath) + await writeFile(targetPath, buffer) + + finalImageUrl = `/uploads/seeded-images/profile-ppid/${targetFilename}` + } + + await prisma.profilePPID.upsert({ + where: { id: c.id }, + update: { + name: c.name, + biodata: c.biodata, + riwayat: c.riwayat, + pengalaman: c.pengalaman, + unggulan: c.unggulan, + imageUrl: finalImageUrl, + }, + create: { + id: c.id, + name: c.name, + biodata: c.biodata, + riwayat: c.riwayat, + pengalaman: c.pengalaman, + unggulan: c.unggulan, + imageUrl: finalImageUrl, + }, + }) } + + console.log("✅ profilePPID seeded from JSON with image copying") + } + + await seedProfilePPID() - console.log("potensi success ...") + for (const v of visiMisiPPID) { + await prisma.visiMisiPPID.upsert({ + where: { + id: v.id, + }, + update: { + misi: v.misi, + visi: v.visi, + }, + create: { + id: v.id, + misi: v.misi, + visi: v.visi, + }, + }); + } + console.log("visi misi PPID success ..."); - for (const k of katagoryBerita) { - await prisma.katagoryBerita.upsert({ - where: { - name: k.name - }, - update: { - name: k.name - }, - create: { - name: k.name - } - }) - } + for (const v of dasarHukumPPID) { + await prisma.dasarHukumPPID.upsert({ + where: { + id: v.id, + }, + update: { + judul: v.judul, + content: v.content, + }, + create: { + id: v.id, + judul: v.judul, + content: v.content, + }, + }); + } + console.log("dasar hukum PPID success ..."); - console.log("katagory berita success ...") + for (const v of profileDesa) { + await prisma.profileDesa.upsert({ + where: { + id: v.id, + }, + update: { + sejarah: v.sejarah, + visi: v.visi, + misi: v.misi, + lambang: v.lambang, + maskot: v.maskot, + profilPerbekelId: v.profilPerbekelId, + }, + create: { + id: v.id, + sejarah: v.sejarah, + visi: v.visi, + misi: v.misi, + lambang: v.lambang, + maskot: v.maskot, + profilPerbekelId: v.profilPerbekelId, + }, + }); + } + console.log("profile desa success ..."); - for (const c of categoryPengumuman) { - await prisma.categoryPengumuman.upsert({ - where: { - name: c.name - }, - update: { - name: c.name - }, - create: { - name: c.name - } - }) - } + for (const v of profilePerbekel) { + await prisma.profilPerbekel.upsert({ + where: { + id: v.id, + }, + update: { + biodata: v.biodata, + pengalaman: v.pengalaman, + pengalamanOrganisasi: v.pengalamanOrganisasi, + programUnggulan: v.programUnggulan, + }, + create: { + id: v.id, + biodata: v.biodata, + pengalaman: v.pengalaman, + pengalamanOrganisasi: v.pengalamanOrganisasi, + programUnggulan: v.programUnggulan, + }, + }); + } + console.log("profile perbekel success ..."); +})() + .then(() => prisma.$disconnect()) + .catch((e) => { + console.error(e); + prisma.$disconnect(); + }); - console.log("category pengumuman success ...") -})().then(() => prisma.$disconnect()).catch((e) => { - console.error(e) - prisma.$disconnect() +process.on("exit", () => { + prisma.$disconnect(); }); -process.on('exit', () => { - prisma.$disconnect() -}) - -process.on('SIGINT', () => { - prisma.$disconnect() - process.exit(0) -}) \ No newline at end of file +process.on("SIGINT", () => { + prisma.$disconnect(); + process.exit(0); +}); diff --git a/public/assets/images/ppid/profile-ppid/1585618b-aec2-4b6c-a8a6-08e9c6eefa79_perbekel.png b/public/assets/images/ppid/profile-ppid/1585618b-aec2-4b6c-a8a6-08e9c6eefa79_perbekel.png new file mode 100644 index 00000000..ed1cbd10 Binary files /dev/null and b/public/assets/images/ppid/profile-ppid/1585618b-aec2-4b6c-a8a6-08e9c6eefa79_perbekel.png differ diff --git a/public/assets/images/ppid/profile-ppid/1_1747623028993_berita-pemerintahan.jpg b/public/assets/images/ppid/profile-ppid/1_1747623028993_berita-pemerintahan.jpg new file mode 100644 index 00000000..6b0e5be3 Binary files /dev/null and b/public/assets/images/ppid/profile-ppid/1_1747623028993_berita-pemerintahan.jpg differ diff --git a/public/assets/images/ppid/profile-ppid/1_1747820179116_berita-pemerintahan.jpg b/public/assets/images/ppid/profile-ppid/1_1747820179116_berita-pemerintahan.jpg new file mode 100644 index 00000000..6b0e5be3 Binary files /dev/null and b/public/assets/images/ppid/profile-ppid/1_1747820179116_berita-pemerintahan.jpg differ diff --git a/public/assets/images/ppid/profile-ppid/1_1747835389645_capybara.png b/public/assets/images/ppid/profile-ppid/1_1747835389645_capybara.png new file mode 100644 index 00000000..16a01d4c Binary files /dev/null and b/public/assets/images/ppid/profile-ppid/1_1747835389645_capybara.png differ diff --git a/public/assets/images/ppid/profile-ppid/1_1747836129969_bgDesktop.jpg b/public/assets/images/ppid/profile-ppid/1_1747836129969_bgDesktop.jpg new file mode 100644 index 00000000..9a7f9d24 Binary files /dev/null and b/public/assets/images/ppid/profile-ppid/1_1747836129969_bgDesktop.jpg differ diff --git a/public/assets/images/ppid/profile-ppid/1_1747836263333_capybara.png b/public/assets/images/ppid/profile-ppid/1_1747836263333_capybara.png new file mode 100644 index 00000000..16a01d4c Binary files /dev/null and b/public/assets/images/ppid/profile-ppid/1_1747836263333_capybara.png differ diff --git a/public/assets/images/ppid/profile-ppid/1_1747836558027_capybara.png b/public/assets/images/ppid/profile-ppid/1_1747836558027_capybara.png new file mode 100644 index 00000000..16a01d4c Binary files /dev/null and b/public/assets/images/ppid/profile-ppid/1_1747836558027_capybara.png differ diff --git a/public/assets/images/ppid/profile-ppid/1_1747836664305_capybara.png b/public/assets/images/ppid/profile-ppid/1_1747836664305_capybara.png new file mode 100644 index 00000000..16a01d4c Binary files /dev/null and b/public/assets/images/ppid/profile-ppid/1_1747836664305_capybara.png differ diff --git a/public/assets/images/ppid/profile-ppid/1_1747894445049_capybara.png b/public/assets/images/ppid/profile-ppid/1_1747894445049_capybara.png new file mode 100644 index 00000000..16a01d4c Binary files /dev/null and b/public/assets/images/ppid/profile-ppid/1_1747894445049_capybara.png differ diff --git a/public/assets/images/ppid/profile-ppid/1_1747971309851_perbekel.png b/public/assets/images/ppid/profile-ppid/1_1747971309851_perbekel.png new file mode 100644 index 00000000..ed1cbd10 Binary files /dev/null and b/public/assets/images/ppid/profile-ppid/1_1747971309851_perbekel.png differ diff --git a/public/assets/images/ppid/profile-ppid/4bd7c413-c88b-487d-8572-9b61df6c1251_perbekel.png b/public/assets/images/ppid/profile-ppid/4bd7c413-c88b-487d-8572-9b61df6c1251_perbekel.png new file mode 100644 index 00000000..ed1cbd10 Binary files /dev/null and b/public/assets/images/ppid/profile-ppid/4bd7c413-c88b-487d-8572-9b61df6c1251_perbekel.png differ diff --git a/public/assets/images/ppid/profile-ppid/77004be4-0ec0-4ed2-a0a1-4e4ea6a9f4ad_perbekel.png b/public/assets/images/ppid/profile-ppid/77004be4-0ec0-4ed2-a0a1-4e4ea6a9f4ad_perbekel.png new file mode 100644 index 00000000..ed1cbd10 Binary files /dev/null and b/public/assets/images/ppid/profile-ppid/77004be4-0ec0-4ed2-a0a1-4e4ea6a9f4ad_perbekel.png differ diff --git a/public/assets/images/ppid/profile-ppid/7ede98a1-a154-480c-a0f6-5dca983fdef3_perbekel.png b/public/assets/images/ppid/profile-ppid/7ede98a1-a154-480c-a0f6-5dca983fdef3_perbekel.png new file mode 100644 index 00000000..ed1cbd10 Binary files /dev/null and b/public/assets/images/ppid/profile-ppid/7ede98a1-a154-480c-a0f6-5dca983fdef3_perbekel.png differ diff --git a/public/uploads/profile-ppid/1_1747885424609_budaya-1.jpg b/public/uploads/profile-ppid/1_1747885424609_budaya-1.jpg new file mode 100644 index 00000000..9cd84d04 Binary files /dev/null and b/public/uploads/profile-ppid/1_1747885424609_budaya-1.jpg differ diff --git a/public/uploads/seeded-images/ppid/profile-ppid/1_1747836703445_capybara.png b/public/uploads/seeded-images/ppid/profile-ppid/1_1747836703445_capybara.png new file mode 100644 index 00000000..16a01d4c Binary files /dev/null and b/public/uploads/seeded-images/ppid/profile-ppid/1_1747836703445_capybara.png differ diff --git a/public/uploads/seeded-images/ppid/profile-ppid/1_1747836821689_capybara.png b/public/uploads/seeded-images/ppid/profile-ppid/1_1747836821689_capybara.png new file mode 100644 index 00000000..16a01d4c Binary files /dev/null and b/public/uploads/seeded-images/ppid/profile-ppid/1_1747836821689_capybara.png differ diff --git a/public/uploads/seeded-images/ppid/profile-ppid/1_1747839042145_capybara.png b/public/uploads/seeded-images/ppid/profile-ppid/1_1747839042145_capybara.png new file mode 100644 index 00000000..16a01d4c Binary files /dev/null and b/public/uploads/seeded-images/ppid/profile-ppid/1_1747839042145_capybara.png differ diff --git a/public/uploads/seeded-images/profile-ppid/021b212f-097f-4c2a-a5e5-273d7458c8da_perbekel.png b/public/uploads/seeded-images/profile-ppid/021b212f-097f-4c2a-a5e5-273d7458c8da_perbekel.png new file mode 100644 index 00000000..ed1cbd10 Binary files /dev/null and b/public/uploads/seeded-images/profile-ppid/021b212f-097f-4c2a-a5e5-273d7458c8da_perbekel.png differ diff --git a/public/uploads/seeded-images/profile-ppid/20202f69-857c-463a-b524-c407e50401c8_perbekel.png b/public/uploads/seeded-images/profile-ppid/20202f69-857c-463a-b524-c407e50401c8_perbekel.png new file mode 100644 index 00000000..ed1cbd10 Binary files /dev/null and b/public/uploads/seeded-images/profile-ppid/20202f69-857c-463a-b524-c407e50401c8_perbekel.png differ diff --git a/public/uploads/seeded-images/profile-ppid/47ef008b-bf7f-467f-8c8e-bf9259a08656_perbekel.png b/public/uploads/seeded-images/profile-ppid/47ef008b-bf7f-467f-8c8e-bf9259a08656_perbekel.png new file mode 100644 index 00000000..ed1cbd10 Binary files /dev/null and b/public/uploads/seeded-images/profile-ppid/47ef008b-bf7f-467f-8c8e-bf9259a08656_perbekel.png differ diff --git a/public/uploads/seeded-images/profile-ppid/54ad4911-4752-413b-8242-b8fbd0309df4_perbekel.png b/public/uploads/seeded-images/profile-ppid/54ad4911-4752-413b-8242-b8fbd0309df4_perbekel.png new file mode 100644 index 00000000..ed1cbd10 Binary files /dev/null and b/public/uploads/seeded-images/profile-ppid/54ad4911-4752-413b-8242-b8fbd0309df4_perbekel.png differ diff --git a/public/uploads/seeded-images/profile-ppid/64ed4abc-9a5a-40d8-afd4-042c408ec092_perbekel.png b/public/uploads/seeded-images/profile-ppid/64ed4abc-9a5a-40d8-afd4-042c408ec092_perbekel.png new file mode 100644 index 00000000..ed1cbd10 Binary files /dev/null and b/public/uploads/seeded-images/profile-ppid/64ed4abc-9a5a-40d8-afd4-042c408ec092_perbekel.png differ diff --git a/public/uploads/seeded-images/profile-ppid/664e4fbc-af1e-4e5f-aecd-4fdbf728c74d_perbekel.png b/public/uploads/seeded-images/profile-ppid/664e4fbc-af1e-4e5f-aecd-4fdbf728c74d_perbekel.png new file mode 100644 index 00000000..ed1cbd10 Binary files /dev/null and b/public/uploads/seeded-images/profile-ppid/664e4fbc-af1e-4e5f-aecd-4fdbf728c74d_perbekel.png differ diff --git a/public/uploads/seeded-images/profile-ppid/8e492234-e95e-4672-9041-d5c4f611d7fb_perbekel.png b/public/uploads/seeded-images/profile-ppid/8e492234-e95e-4672-9041-d5c4f611d7fb_perbekel.png new file mode 100644 index 00000000..ed1cbd10 Binary files /dev/null and b/public/uploads/seeded-images/profile-ppid/8e492234-e95e-4672-9041-d5c4f611d7fb_perbekel.png differ diff --git a/public/uploads/seeded-images/profile-ppid/90db5197-b0c1-441a-8585-bc33758cc3a9_perbekel.png b/public/uploads/seeded-images/profile-ppid/90db5197-b0c1-441a-8585-bc33758cc3a9_perbekel.png new file mode 100644 index 00000000..ed1cbd10 Binary files /dev/null and b/public/uploads/seeded-images/profile-ppid/90db5197-b0c1-441a-8585-bc33758cc3a9_perbekel.png differ diff --git a/public/uploads/seeded-images/profile-ppid/a146af5a-e48d-4c19-8350-2383a3e3be22_perbekel.png b/public/uploads/seeded-images/profile-ppid/a146af5a-e48d-4c19-8350-2383a3e3be22_perbekel.png new file mode 100644 index 00000000..ed1cbd10 Binary files /dev/null and b/public/uploads/seeded-images/profile-ppid/a146af5a-e48d-4c19-8350-2383a3e3be22_perbekel.png differ diff --git a/public/uploads/seeded-images/profile-ppid/a85625e1-cbd4-4869-b809-6f27bc4979b2_perbekel.png b/public/uploads/seeded-images/profile-ppid/a85625e1-cbd4-4869-b809-6f27bc4979b2_perbekel.png new file mode 100644 index 00000000..ed1cbd10 Binary files /dev/null and b/public/uploads/seeded-images/profile-ppid/a85625e1-cbd4-4869-b809-6f27bc4979b2_perbekel.png differ diff --git a/public/uploads/seeded-images/profile-ppid/bbfed5d8-a1f1-4087-a542-d7f7c9935bd8_perbekel.png b/public/uploads/seeded-images/profile-ppid/bbfed5d8-a1f1-4087-a542-d7f7c9935bd8_perbekel.png new file mode 100644 index 00000000..ed1cbd10 Binary files /dev/null and b/public/uploads/seeded-images/profile-ppid/bbfed5d8-a1f1-4087-a542-d7f7c9935bd8_perbekel.png differ diff --git a/public/uploads/seeded-images/profile-ppid/cdd3cd52-c9c4-4c96-a29d-728a774b5955_perbekel.png b/public/uploads/seeded-images/profile-ppid/cdd3cd52-c9c4-4c96-a29d-728a774b5955_perbekel.png new file mode 100644 index 00000000..ed1cbd10 Binary files /dev/null and b/public/uploads/seeded-images/profile-ppid/cdd3cd52-c9c4-4c96-a29d-728a774b5955_perbekel.png differ diff --git a/public/uploads/seeded-images/profile-ppid/e658ba55-fa8a-4f0d-bcbd-78de12b2ce33_perbekel.png b/public/uploads/seeded-images/profile-ppid/e658ba55-fa8a-4f0d-bcbd-78de12b2ce33_perbekel.png new file mode 100644 index 00000000..ed1cbd10 Binary files /dev/null and b/public/uploads/seeded-images/profile-ppid/e658ba55-fa8a-4f0d-bcbd-78de12b2ce33_perbekel.png differ diff --git a/public/uploads/seeded-images/profile-ppid/perbekel.png b/public/uploads/seeded-images/profile-ppid/perbekel.png new file mode 100644 index 00000000..ed1cbd10 Binary files /dev/null and b/public/uploads/seeded-images/profile-ppid/perbekel.png differ diff --git a/src/app/admin/(dashboard)/_com/modalKonfirmasiHapus.tsx b/src/app/admin/(dashboard)/_com/modalKonfirmasiHapus.tsx new file mode 100644 index 00000000..d8805319 --- /dev/null +++ b/src/app/admin/(dashboard)/_com/modalKonfirmasiHapus.tsx @@ -0,0 +1,35 @@ +// components/modal/ModalKonfirmasiHapus.tsx +import { Modal, Text, Button, Flex } from "@mantine/core" + +interface ModalKonfirmasiHapusProps { + opened: boolean + loading?: boolean + onClose: () => void + onConfirm: () => void + text: string +} + +export function ModalKonfirmasiHapus({ + opened, + loading = false, + onClose, + onConfirm, + text, +}: ModalKonfirmasiHapusProps) { + return ( + + {text} + + + + + + ) +} diff --git a/src/app/admin/(dashboard)/_state/desa/berita.ts b/src/app/admin/(dashboard)/_state/desa/berita.ts index 817c5127..64245619 100644 --- a/src/app/admin/(dashboard)/_state/desa/berita.ts +++ b/src/app/admin/(dashboard)/_state/desa/berita.ts @@ -1,23 +1,34 @@ -/* 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"; +// 1. Schema validasi dengan Zod const templateForm = z.object({ judul: z.string().min(3, "Judul minimal 3 karakter"), deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"), - image: z.string().url().min(3, "Image minimal 3 karakter"), content: z.string().min(3, "Content minimal 3 karakter"), - katagoryBeritaId: z.string().nonempty(), + kategoriBeritaId: z.string().nonempty(), + imageId: z.string().nonempty(), }); +// 2. Default value form berita (hindari uncontrolled input) +const defaultForm = { + judul: "", + deskripsi: "", + imageId: "", + content: "", + kategoriBeritaId: "", +}; + +// 3. Kategori proxy const category = proxy({ findMany: { data: null as | null - | Prisma.KatagoryBeritaGetPayload<{ omit: { isActive: true } }>[], + | Prisma.KategoriBeritaGetPayload<{ omit: { isActive: true } }>[], async load() { const res = await ApiFetch.api.desa.berita.category["find-many"].get(); if (res.status === 200) { @@ -27,23 +38,12 @@ const category = proxy({ }, }); -type BeritaForm = Prisma.BeritaGetPayload<{ - select: { - judul: true; - deskripsi: true; - image: true; - content: true; - katagoryBeritaId: true; - }; -}>; - +// 4. Berita proxy const berita = proxy({ create: { - form: {} as BeritaForm, + form: { ...defaultForm }, // ✅ ini kunci fix-nya loading: false, async create() { - berita.create.form.image = - "https://www.shutterstock.com/image-vector/lower-news-live-streaming-breaking-600nw-2535984111.jpg"; const cek = templateForm.safeParse(berita.create.form); if (!cek.success) { const err = `[${cek.error.issues @@ -51,6 +51,7 @@ const berita = proxy({ .join("\n")}] required`; return toast.error(err); } + try { berita.create.loading = true; const res = await ApiFetch.api.desa.berita["create"].post( @@ -58,7 +59,7 @@ const berita = proxy({ ); if (res.status === 200) { berita.findMany.load(); - return toast.success("succes create"); + return toast.success("success create"); } return toast.error("failed create"); @@ -68,20 +69,62 @@ const berita = proxy({ berita.create.loading = false; } }, + resetForm() { + berita.create.form = { ...defaultForm }; + }, }, + findMany: { data: null as - | Prisma.BeritaGetPayload<{ omit: { isActive: true } }>[] + | Prisma.BeritaGetPayload<{ + include: { + image: true; + kategoriBerita: true; + }; + }>[] | null, async load() { const res = await ApiFetch.api.desa.berita["find-many"].get(); if (res.status === 200) { - berita.findMany.data = (res.data?.data as any) ?? []; + berita.findMany.data = (res.data?.data ) ?? []; } }, }, + delete: { + loading: false, + async byId(id: string) { + if (!id) return toast.warn("ID tidak valid"); + + try { + berita.delete.loading = true; + + const response = await fetch(`/api/desa/berita/delete/${id}`, { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + }, + }); + + const result = await response.json(); + + if (response.ok && result?.success) { + toast.success(result.message || "Berita berhasil dihapus"); + await berita.findMany.load(); // refresh list + } else { + toast.error(result?.message || "Gagal menghapus berita"); + } + } catch (error) { + console.error("Gagal delete:", error); + toast.error("Terjadi kesalahan saat menghapus berita"); + } finally { + berita.delete.loading = false; + } + }, + }, + }); +// 5. State global const stateDashboardBerita = proxy({ category, berita, diff --git a/src/app/admin/(dashboard)/_state/desa/profile.ts b/src/app/admin/(dashboard)/_state/desa/profile.ts new file mode 100644 index 00000000..744b426d --- /dev/null +++ b/src/app/admin/(dashboard)/_state/desa/profile.ts @@ -0,0 +1,239 @@ +import ApiFetch from "@/lib/api-fetch"; +import { Prisma } from "@prisma/client"; +import { toast } from "react-toastify"; +import { proxy } from "valtio"; +import { z } from "zod"; + +/* Sejarah */ +const templateFormSejarahForm = z.object({ + sejarah: z.string().min(3, "Sejarah minimal 3 karakter"), +}) + +type SejarahForm = Prisma.ProfileDesaGetPayload<{ + select: { + id: true; + sejarah: true; + } +}> + +const Sejarah = proxy({ + findById: { + data: null as SejarahForm | null, + loading: false, + initialize() { + Sejarah.findById.data = { + id: "", + sejarah: "", + } as SejarahForm; + }, + async load(id: string) { + try { + Sejarah.findById.loading = true; + const res = await ApiFetch.api.desa.profile["find-by-id"].get({ + query: { id }, + }); + if (res.status === 200) { + Sejarah.findById.data = { + id: id, + sejarah: res.data?.data?.sejarah ?? "" + }; + } else { + toast.error("Gagal mengambil data sejarah"); + } + } catch (error) { + console.error((error as Error).message); + toast.error("Terjadi kesalahan saat mengambil data sejarah"); + } finally { + Sejarah.findById.loading = false; + } + } + }, + update: { + loading: false, + async save(data: SejarahForm) { + const cek = templateFormSejarahForm.safeParse(data); + if (!cek.success) { + const errors = cek.error.issues + .map((issue) => `${issue.path.join(".")}: ${issue.message}`) + .join(", "); + toast.error(`Form tidak valid: ${errors}`); + return; + } + try { + Sejarah.update.loading = true; + const res = await ApiFetch.api.desa.profile.sejarah["update"].post(data); + if (res.status === 200) { + toast.success("Berhasil update sejarah"); + await Sejarah.findById.load(data.id); + } else { + toast.error("Gagal update sejarah"); + } + } catch (error) { + console.error((error as Error).message); + toast.error("Terjadi kesalahan saat update sejarah"); + } finally { + Sejarah.update.loading = false; + } + } + } +}) + +/* Visi Misi Desa */ +const templateFormVisiForm = z.object({ + visi: z.string().min(3, "Visi minimal 3 karakter"), + misi: z.string().min(3, "Misi minimal 3 karakter") +}) + +type VisiMisiDesaForm = Prisma.ProfileDesaGetPayload<{ + select: { + id: true; + visi: true; + misi: true; + } +}> + +const VisiMisiDesa = proxy({ + findById: { + data: null as VisiMisiDesaForm | null, + loading: false, + initialize() { + VisiMisiDesa.findById.data = { + id: "", + visi: "", + misi: "" + } as VisiMisiDesaForm; + }, + async load(id: string) { + try { + VisiMisiDesa.findById.loading = true; + const res = await ApiFetch.api.desa.profile["find-by-id"].get({ + query: { id }, + }); + if (res.status === 200) { + VisiMisiDesa.findById.data = { + id: id, + visi: res.data?.data?.visi ?? "", + misi: res.data?.data?.misi ?? "" + }; + } else { + toast.error("Gagal mengambil data visi misi"); + } + } catch (error) { + console.error((error as Error).message); + toast.error("Terjadi kesalahan saat mengambil data visi misi"); + } finally { + VisiMisiDesa.findById.loading = false; + } + } + }, + update: { + loading: false, + async save(data: VisiMisiDesaForm) { + const cek = templateFormVisiForm.safeParse(data); + if (!cek.success) { + const errors = cek.error.issues + .map((issue) => `${issue.path.join(".")}: ${issue.message}`) + .join(", "); + toast.error(`Form tidak valid: ${errors}`); + return; + } + try { + VisiMisiDesa.update.loading = true; + const res = await ApiFetch.api.desa.profile.visimisiDesa["update"].post(data); + if (res.status === 200) { + toast.success("Berhasil update visi misi"); + await VisiMisiDesa.findById.load(data.id); + } else { + toast.error("Gagal update visi"); + } + } catch (error) { + console.error((error as Error).message); + toast.error("Terjadi kesalahan saat update visi misi"); + } finally { + VisiMisiDesa.update.loading = false; + } + } + } +}) +/* Lambang Desa */ +const templateFormLambangDesaForm = z.object({ + lambang: z.string().min(3, "Lambang minimal 3 karakter"), +}) + +type LambangDesaForm = Prisma.ProfileDesaGetPayload<{ + select: { + id: true; + lambang: true; + } +}> + +const LambangDesa = proxy({ + findById: { + data: null as LambangDesaForm | null, + loading: false, + initialize() { + LambangDesa.findById.data = { + id: "", + lambang: "", + } as LambangDesaForm; + }, + async load(id: string) { + try { + LambangDesa.findById.loading = true; + const res = await ApiFetch.api.desa.profile["find-by-id"].get({ + query: { id }, + }); + if (res.status === 200) { + LambangDesa.findById.data = { + id: id, + lambang: res.data?.data?.lambang ?? "" + }; + } else { + toast.error("Gagal mengambil data lambang desa"); + } + } catch (error) { + console.error((error as Error).message); + toast.error("Terjadi kesalahan saat mengambil data lambang desa"); + } finally { + LambangDesa.findById.loading = false; + } + } + }, + update: { + loading: false, + async save(data: LambangDesaForm) { + const cek = templateFormLambangDesaForm.safeParse(data); + if (!cek.success) { + const errors = cek.error.issues + .map((issue) => `${issue.path.join(".")}: ${issue.message}`) + .join(", "); + toast.error(`Form tidak valid: ${errors}`); + return; + } + try { + LambangDesa.update.loading = true; + const res = await ApiFetch.api.desa.profile.lambangDesa["update"].post(data); + if (res.status === 200) { + toast.success("Berhasil update lambang desa"); + await LambangDesa.findById.load(data.id); + } else { + toast.error("Gagal update lambang desa"); + } + } catch (error) { + console.error((error as Error).message); + toast.error("Terjadi kesalahan saat update lambang desa"); + } finally { + LambangDesa.update.loading = false; + } + } + } +}); + +const stateProfileDesa = { + Sejarah, + VisiMisiDesa, + LambangDesa, +}; + + +export default stateProfileDesa; diff --git a/src/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/artikelKesehatan.ts b/src/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/artikelKesehatan.ts new file mode 100644 index 00000000..e750a7c9 --- /dev/null +++ b/src/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/artikelKesehatan.ts @@ -0,0 +1,339 @@ +import ApiFetch from "@/lib/api-fetch"; +import { Prisma } from "@prisma/client"; +import { toast } from "react-toastify"; +import { proxy } from "valtio"; +import { z } from "zod"; + +/* Introduction */ +const templateIntroduction = z.object({ + content: z.string().min(3, "Content minimal 3 karakter"), +}) + +type Introduction = Prisma.IntroductionGetPayload<{ + select: { + content: true; + }; +}>; + +const introduction = proxy({ + create: { + form: {} as Introduction, + loading: false, + async create() { + const cek = templateIntroduction.safeParse(introduction.create.form); + if (!cek.success) { + const err = `[${cek.error.issues + .map((v) => `${v.path.join(".")}`) + .join("\n")}] required`; + return toast.error(err); + } + try { + introduction.create.loading = true; + const res = await ApiFetch.api.kesehatan.introduction["create"].post(introduction.create.form); + if (res.status === 200) { + introduction.findMany.load(); + return toast.success("success create"); + } + return toast.error("failed create"); + } catch (error) { + console.log((error as Error).message); + } finally { + introduction.create.loading = false; + } + }, + }, + findMany: { + data: null as + | Prisma.IntroductionGetPayload<{ omit: { isActive: true } }>[] + | null, + async load() { + const res = await ApiFetch.api.kesehatan.introduction["find-many"].get(); + if (res.status === 200) { + introduction.findMany.data = res.data?.data ?? []; + } + } + } +}); +/* ======================================================================= */ + +/* symptom */ +const templateSymptom = z.object({ + title: z.string().min(3, "Title minimal 3 karakter"), + content: z.string().min(3, "Content minimal 3 karakter"), +}) + +type Symptom = Prisma.SymptomGetPayload<{ + select: { + title: true; + content: true; + }; +}>; + +const symptom = proxy({ + create: { + form: {} as Symptom, + loading: false, + async create() { + const cek = templateSymptom.safeParse(symptom.create.form); + if (!cek.success) { + const err = `[${cek.error.issues + .map((v) => `${v.path.join(".")}`) + .join("\n")}] required`; + return toast.error(err); + } + try { + symptom.create.loading = true; + const res = await ApiFetch.api.kesehatan.symptom["create"].post(symptom.create.form); + if (res.status === 200) { + symptom.findMany.load(); + return toast.success("success create"); + } + return toast.error("failed create"); + } catch (error) { + console.log((error as Error).message); + } finally { + symptom.create.loading = false; + } + }, + }, + findMany: { + data: null as + | Prisma.SymptomGetPayload<{ omit: { isActive: true } }>[] + | null, + async load() { + const res = await ApiFetch.api.kesehatan.symptom["find-many"].get(); + if (res.status === 200) { + symptom.findMany.data = res.data?.data ?? []; + } + }, + }, +}); +/* ======================================================================= */ + +/* Prevention */ +const templatePrevention = z.object({ + title: z.string().min(3, "Title minimal 3 karakter"), + content: z.string().min(3, "Content minimal 3 karakter"), +}) + +type Prevention = Prisma.PreventionGetPayload<{ + select: { + title: true; + content: true; + }; +}>; + +const prevention = proxy({ + create: { + form: {} as Prevention, + loading: false, + async create() { + const cek = templatePrevention.safeParse(prevention.create.form); + if (!cek.success) { + const err = `[${cek.error.issues + .map((v) => `${v.path.join(".")}`) + .join("\n")}] required`; + return toast.error(err); + } + try { + prevention.create.loading = true; + const res = await ApiFetch.api.kesehatan.prevention["create"].post(prevention.create.form); + if (res.status === 200) { + prevention.findMany.load(); + return toast.success("success create"); + } + return toast.error("failed create"); + } catch (error) { + console.log((error as Error).message); + } finally { + prevention.create.loading = false; + } + }, + }, + findMany: { + data: null as + | Prisma.PreventionGetPayload<{ omit: { isActive: true } }>[] + | null, + async load() { + const res = await ApiFetch.api.kesehatan.prevention["find-many"].get(); + if (res.status === 200) { + prevention.findMany.data = res.data?.data ?? []; + } + }, + }, +}); +/* ======================================================================= */ + +/* First Aid */ +const templateFirstAid = z.object({ + title: z.string().min(3, "Title minimal 3 karakter"), + content: z.string().min(3, "Content minimal 3 karakter"), +}) + +type FirstAid = Prisma.FirstAidGetPayload<{ + select: { + title: true; + content: true; + }; +}>; + +const firstAid = proxy({ + create: { + form: {} as FirstAid, + loading: false, + async create() { + const cek = templateFirstAid.safeParse(firstAid.create.form); + if (!cek.success) { + const err = `[${cek.error.issues + .map((v) => `${v.path.join(".")}`) + .join("\n")}] required`; + return toast.error(err); + } + try { + firstAid.create.loading = true; + const res = await ApiFetch.api.kesehatan.firstaid["create"].post(firstAid.create.form); + if (res.status === 200) { + firstAid.findMany.load(); + return toast.success("success create"); + } + return toast.error("failed create"); + } catch (error) { + console.log((error as Error).message); + } finally { + firstAid.create.loading = false; + } + }, + }, + findMany: { + data: null as + | Prisma.FirstAidGetPayload<{ omit: { isActive: true } }>[] + | null, + async load() { + const res = await ApiFetch.api.kesehatan.firstaid["find-many"].get(); + if (res.status === 200) { + firstAid.findMany.data = res.data?.data ?? []; + } + }, + }, +}) +/* ======================================================================= */ + +/* Myth vs Fact */ +const templateMythFact = z.object({ + title: z.string().min(3, "Title minimal 3 karakter"), + mitos: z.string().min(3, "Mitos minimal 3 karakter"), + fakta: z.string().min(3, "Fakta minimal 3 karakter"), +}) + +type MythFact = Prisma.MythVsFactGetPayload<{ + select: { + title: true; + mitos: true; + fakta: true; + }; +}>; + +const mythFact = proxy({ + create: { + form: {} as MythFact, + loading: false, + async create() { + const cek = templateMythFact.safeParse(mythFact.create.form); + if (!cek.success) { + const err = `[${cek.error.issues + .map((v) => `${v.path.join(".")}`) + .join("\n")}] required`; + return toast.error(err); + } + try { + mythFact.create.loading = true; + const res = await ApiFetch.api.kesehatan.mythvsfact["create"].post(mythFact.create.form); + if (res.status === 200) { + mythFact.findMany.load(); + return toast.success("success create"); + } + return toast.error("failed create"); + } catch (error) { + console.log((error as Error).message); + } finally { + mythFact.create.loading = false; + } + }, + }, + findMany: { + data: null as + | Prisma.MythVsFactGetPayload<{ omit: { isActive: true } }>[] + | null, + async load() { + const res = await ApiFetch.api.kesehatan.mythvsfact["find-many"].get(); + if (res.status === 200) { + mythFact.findMany.data = res.data?.data ?? []; + } + }, + }, +}) +/* ======================================================================= */ + +/* Doctor Sign */ +const templateDoctorSign = z.object({ + content: z.string().min(3, "Content minimal 3 karakter"), +}) + +type DoctorSign = Prisma.DoctorSignGetPayload<{ + select: { + content: true + } +}> + +const doctorSign = proxy({ + create: { + form: {} as DoctorSign, + loading: false, + async create() { + const cek = templateDoctorSign.safeParse(doctorSign.create.form); + if (!cek.success) { + const err = `[${cek.error.issues + .map((v) => `${v.path.join(".")}`) + .join("\n")}] required`; + return toast.error(err); + } + try { + doctorSign.create.loading = true; + const res = await ApiFetch.api.kesehatan.doctor_sign["create"].post(doctorSign.create.form); + if (res.status === 200) { + doctorSign.findMany.load(); + return toast.success("success create"); + } + return toast.error("failed create"); + } catch (error) { + console.log((error as Error).message); + } finally { + doctorSign.create.loading = false; + } + }, + }, + findMany: { + data: null as + | Prisma.DoctorSignGetPayload<{ omit: { isActive: true } }>[] + | null, + async load() { + const res = await ApiFetch.api.kesehatan.doctor_sign["find-many"].get(); + if (res.status === 200) { + doctorSign.findMany.data = res.data?.data ?? []; + } + }, + }, +}) + +/* ======================================================================= */ + +const stateArtikelKesehatan = proxy({ + introduction, + symptom, + prevention, + firstAid, + mythFact, + doctorSign +}) + +export default stateArtikelKesehatan \ No newline at end of file diff --git a/src/app/admin/(dashboard)/_state/ppid/daftar_informasi_publik/daftarInformasiPublik.ts b/src/app/admin/(dashboard)/_state/ppid/daftar_informasi_publik/daftarInformasiPublik.ts new file mode 100644 index 00000000..ff0b2792 --- /dev/null +++ b/src/app/admin/(dashboard)/_state/ppid/daftar_informasi_publik/daftarInformasiPublik.ts @@ -0,0 +1,65 @@ +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 templateDaftarInformasi = z.object({ + jenisInformasi: z.string().min(3, "Jenis Informasi minimal 3 karakter"), + deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"), + tanggal: z.string().min(3, "Tanggal minimal 3 karakter"), +}) + +type DaftarInformasi = Prisma.DaftarInformasiPublikGetPayload<{ + select: { + jenisInformasi: true; + deskripsi: true; + tanggal: true; + }; +}>; + +const daftarInformasi = proxy({ + create: { + form: {} as DaftarInformasi, + loading: false, + async create() { + const cek = templateDaftarInformasi.safeParse(daftarInformasi.create.form); + if (!cek.success) { + const err = `[${cek.error.issues + .map((v) => `${v.path.join(".")}`) + .join("\n")}] required`; + return toast.error(err); + } + try { + daftarInformasi.create.loading = true; + const res = await ApiFetch.api.ppid.daftarinformasipublik["create"].post(daftarInformasi.create.form); + if (res.status === 200) { + daftarInformasi.findMany.load(); + return toast.success("success create"); + } + return toast.error("failed create"); + } catch (error) { + console.log((error as Error).message); + } finally { + daftarInformasi.create.loading = false; + } + }, + }, + findMany: { + data: null as + | Prisma.DaftarInformasiPublikGetPayload<{ omit: { isActive: true } }>[] + | null, + async load() { + const res = await ApiFetch.api.ppid.daftarinformasipublik["find-many"].get(); + if (res.status === 200) { + daftarInformasi.findMany.data = res.data?.data ?? []; + } + } + } + }); + + const stateDaftarInformasiPublik = proxy({ + daftarInformasi + }) + + export default stateDaftarInformasiPublik; \ No newline at end of file diff --git a/src/app/admin/(dashboard)/_state/ppid/dasar_hukum/dasarHukum.ts b/src/app/admin/(dashboard)/_state/ppid/dasar_hukum/dasarHukum.ts new file mode 100644 index 00000000..f4223a98 --- /dev/null +++ b/src/app/admin/(dashboard)/_state/ppid/dasar_hukum/dasarHukum.ts @@ -0,0 +1,82 @@ +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({ + judul: z.string().min(3, "Judul minimal 3 karakter"), + content: z.string().min(3, "Content minimal 3 karakter"), +}); + +type DasarHukumForm = Prisma.DasarHukumPPIDGetPayload<{ + select: { + id: true; + judul: true; + content: true; + }; +}>; + +const stateDasarHukumPPID = proxy({ + findById: { + data: null as DasarHukumForm | null, + loading: false, + initialize() { + stateDasarHukumPPID.findById.data = { + id: '', + judul: '', + content: '', + } as DasarHukumForm; + }, + async load(id: string) { + try { + stateDasarHukumPPID.findById.loading = true; + const res = await ApiFetch.api.ppid.dasarhukumppid["find-by-id"].get({ + query: { id }, + }); + if (res.status === 200) { + stateDasarHukumPPID.findById.data = res.data?.data ?? null; + } else { + toast.error("Gagal mengambil data dasar hukum"); + } + } catch (error) { + console.error((error as Error).message); + toast.error("Terjadi kesalahan saat mengambil data dasar hukum"); + } finally { + stateDasarHukumPPID.findById.loading = false; + } + }, + }, + + update: { + loading: false, + async save(data: DasarHukumForm) { + const cek = templateForm.safeParse(data); + if (!cek.success) { + const errors = cek.error.issues + .map((issue) => `${issue.path.join(".")}: ${issue.message}`) + .join(", "); + toast.error(`Form tidak valid: ${errors}`); + return; + } + + try { + stateDasarHukumPPID.update.loading = true; + const res = await ApiFetch.api.ppid.dasarhukumppid["update"].post(data); + if (res.status === 200) { + toast.success("Data dasar hukum berhasil diubah"); + await stateDasarHukumPPID.findById.load(data.id); + } else { + toast.error("Gagal mengubah data dasar hukum"); + } + } catch (error) { + console.error((error as Error).message); + toast.error("Terjadi kesalahan saat mengubah data dasar hukum"); + } finally { + stateDasarHukumPPID.update.loading = false; + } + }, + }, +}); + +export default stateDasarHukumPPID; diff --git a/src/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanJenisKelamin.ts b/src/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanJenisKelamin.ts new file mode 100644 index 00000000..5e2c0192 --- /dev/null +++ b/src/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanJenisKelamin.ts @@ -0,0 +1,77 @@ +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 templateGrafikJenisKelamin = z.object({ + laki: z.string().min(2, "Data laki-laki harus diisi"), + perempuan: z.string().min(2, "Data perempuan harus diisi"), +}); + +type GrafikJenisKelamin = Prisma.GrafikBerdasarkanJenisKelaminGetPayload<{ + select: { + laki: true; + perempuan: true; + }; +}>; + +const defaultForm: GrafikJenisKelamin = { + laki: "", + perempuan: "", +}; + +const grafikBerdasarkanJenisKelamin = proxy({ + create: { + form: defaultForm, + loading: false, + async create() { + const cek = templateGrafikJenisKelamin.safeParse( + grafikBerdasarkanJenisKelamin.create.form + ); + if (!cek.success) { + const err = `[${cek.error.issues + .map((v) => `${v.path.join(".")}`) + .join("\n")}] required`; + return toast.error(err); + } + try { + grafikBerdasarkanJenisKelamin.create.loading = true; + const res = await ApiFetch.api.ppid.grafikberdasarkanjeniskelamin[ + "create" + ].post(grafikBerdasarkanJenisKelamin.create.form); + if (res.status === 200) { + grafikBerdasarkanJenisKelamin.create.form = defaultForm; + grafikBerdasarkanJenisKelamin.findMany.load(); + return toast.success("success create"); + } + return toast.error("failed create"); + } catch (error) { + console.log((error as Error).message); + } finally { + grafikBerdasarkanJenisKelamin.create.loading = false; + } + }, + }, + findMany: { + data: null as + | Prisma.GrafikBerdasarkanJenisKelaminGetPayload<{ + omit: { isActive: true }; + }>[] + | null, + loading: false, + async load() { + const res = await ApiFetch.api.ppid.grafikberdasarkanjeniskelamin[ + "find-many" + ].get(); + if (res.status === 200) { + grafikBerdasarkanJenisKelamin.findMany.data = res.data?.data ?? []; + } + }, + }, +}); + +const stateGrafikBerdasarkanJenisKelamin = proxy({ + grafikBerdasarkanJenisKelamin, +}); +export default stateGrafikBerdasarkanJenisKelamin; diff --git a/src/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanResponden.ts b/src/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanResponden.ts new file mode 100644 index 00000000..4fd2e69b --- /dev/null +++ b/src/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanResponden.ts @@ -0,0 +1,84 @@ +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 templateGrafikResponden = z.object({ + sangatbaik: z.string().min(1, "Data sangat baik harus diisi"), + baik: z.string().min(1, "Data baik harus diisi"), + kurangbaik: z.string().min(1, "Data kurang baik harus diisi"), + tidakbaik: z.string().min(1, "Data tidak baik harus diisi"), +}); + +type GrafikResponden = Prisma.GrafikBerdasarkanRespondenGetPayload<{ + select: { + sangatbaik: true; + baik: true; + kurangbaik: true; + tidakbaik: true; + }; +}>; + +const defaultForm: GrafikResponden = { + sangatbaik: "", + baik: "", + kurangbaik: "", + tidakbaik: "", +}; + +const grafikBerdasarkanResponden = proxy({ + create: { + form: defaultForm, + loading: false, + async create() { + const cek = templateGrafikResponden.safeParse( + grafikBerdasarkanResponden.create.form + ); + if (!cek.success) { + const err = `[${cek.error.issues + .map((v) => `${v.path.join(".")}`) + .join("\n")}] required`; + return toast.error(err); + } + try { + grafikBerdasarkanResponden.create.loading = true; + const res = await ApiFetch.api.ppid.grafikberdasarkanresponden[ + "create" + ].post(grafikBerdasarkanResponden.create.form); + if (res.status === 200) { + grafikBerdasarkanResponden.create.form = defaultForm; + grafikBerdasarkanResponden.findMany.load(); + return toast.success("success create"); + } + return toast.error("failed create"); + } catch (error) { + console.log((error as Error).message); + } finally { + grafikBerdasarkanResponden.create.loading = false; + } + }, + }, + findMany: { + data: null as + | Prisma.GrafikBerdasarkanRespondenGetPayload<{ + omit: { isActive: true }; + }>[] + | null, + loading: false, + async load() { + const res = await ApiFetch.api.ppid.grafikberdasarkanresponden[ + "find-many" + ].get(); + if (res.status === 200) { + grafikBerdasarkanResponden.findMany.data = res.data?.data ?? []; + } + }, + }, +}); + +const stateGrafikResponden = proxy({ + grafikBerdasarkanResponden, +}); + +export default stateGrafikResponden; \ No newline at end of file diff --git a/src/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanUmur.ts b/src/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanUmur.ts new file mode 100644 index 00000000..3d05f6cf --- /dev/null +++ b/src/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanUmur.ts @@ -0,0 +1,84 @@ +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 templateGrafikUmur = z.object({ + remaja: z.string().min(2, "Data remaja harus diisi"), + dewasa: z.string().min(2, "Data dewasa harus diisi"), + orangtua: z.string().min(2, "Data orangtua harus diisi"), + lansia: z.string().min(2, "Data lansia harus diisi"), +}); + +type GrafikUmur = Prisma.GrafikBerdasarkanUmurGetPayload<{ + select: { + remaja: true; + dewasa: true; + orangtua: true; + lansia: true; + }; +}>; + +const defaultForm: GrafikUmur = { + remaja: "", + dewasa: "", + orangtua: "", + lansia: "", +}; + +const grafikBerdasarkanUmur = proxy({ + create: { + form: defaultForm, + loading: false, + async create() { + const cek = templateGrafikUmur.safeParse( + grafikBerdasarkanUmur.create.form + ); + if (!cek.success) { + const err = `[${cek.error.issues + .map((v) => `${v.path.join(".")}`) + .join("\n")}] required`; + return toast.error(err); + } + try { + grafikBerdasarkanUmur.create.loading = true; + const res = await ApiFetch.api.ppid.grafikberdasarkanumur[ + "create" + ].post(grafikBerdasarkanUmur.create.form); + if (res.status === 200) { + grafikBerdasarkanUmur.create.form = defaultForm; + grafikBerdasarkanUmur.findMany.load(); + return toast.success("success create"); + } + return toast.error("failed create"); + } catch (error) { + console.log((error as Error).message); + } finally { + grafikBerdasarkanUmur.create.loading = false; + } + }, + }, + findMany: { + data: null as + | Prisma.GrafikBerdasarkanUmurGetPayload<{ + omit: { isActive: true }; + }>[] + | null, + loading: false, + async load() { + const res = await ApiFetch.api.ppid.grafikberdasarkanumur[ + "find-many" + ].get(); + if (res.status === 200) { + grafikBerdasarkanUmur.findMany.data = res.data?.data ?? []; + } + }, + }, +}) + +const stateGrafikBerdasarkanUmur = proxy({ + grafikBerdasarkanUmur, +}) + +export default stateGrafikBerdasarkanUmur; \ No newline at end of file diff --git a/src/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikHasilKepuasan.ts b/src/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikHasilKepuasan.ts new file mode 100644 index 00000000..b8badf0c --- /dev/null +++ b/src/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikHasilKepuasan.ts @@ -0,0 +1,76 @@ +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 templateGrafikHasilKepuasanMasyarakat = z.object({ + label: z.string().min(2, "Label harus diisi"), + kepuasan: z.string().min(2, "Kepuasan harus diisi"), +}); + +type GrafikHasilKepuasanMasyarakat = Prisma.IndeksKepuasanMasyarakatGetPayload<{ + select: { + label: true; + kepuasan: true; + }; +}>; + +const defaultForm: GrafikHasilKepuasanMasyarakat = { + label: "", + kepuasan: "", +}; + +const grafikHasilKepuasanMasyarakat = proxy({ + create: { + form: defaultForm, + loading: false, + async create() { + const cek = templateGrafikHasilKepuasanMasyarakat.safeParse( + grafikHasilKepuasanMasyarakat.create.form + ); + if (!cek.success) { + const err = `[${cek.error.issues + .map((v) => `${v.path.join(".")}`) + .join("\n")}] required`; + return toast.error(err); + } + try { + grafikHasilKepuasanMasyarakat.create.loading = true; + const res = await ApiFetch.api.ppid.grafikhasilkepuasamanmasyarakat["create"].post( + grafikHasilKepuasanMasyarakat.create.form + ); + if (res.status === 200) { + grafikHasilKepuasanMasyarakat.create.form = { + label: "", + kepuasan: "" + }; + grafikHasilKepuasanMasyarakat.findMany.load(); + return toast.success("success create"); + } + return toast.error("failed create"); + } catch (error) { + console.log((error as Error).message); + } finally { + grafikHasilKepuasanMasyarakat.create.loading = false; + } + }, + }, + findMany: { + data: null as + | Prisma.IndeksKepuasanMasyarakatGetPayload<{ omit: { isActive: true } }>[] + | null, + async load() { + const res = await ApiFetch.api.ppid.grafikhasilkepuasamanmasyarakat["find-many"].get(); + if (res.status === 200) { + grafikHasilKepuasanMasyarakat.findMany.data = res.data?.data ?? []; + } + } + } +}); + +const stateGrafikHasilKepuasanMasyarakat = proxy({ + grafikHasilKepuasanMasyarakat, +}); + +export default stateGrafikHasilKepuasanMasyarakat; diff --git a/src/app/admin/(dashboard)/_state/ppid/permohonan_informasi_publik/permohonanInformasiPublik.ts b/src/app/admin/(dashboard)/_state/ppid/permohonan_informasi_publik/permohonanInformasiPublik.ts new file mode 100644 index 00000000..363998cf --- /dev/null +++ b/src/app/admin/(dashboard)/_state/ppid/permohonan_informasi_publik/permohonanInformasiPublik.ts @@ -0,0 +1,125 @@ +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(3, "Nama minimal 3 karakter"), + nik: z.string().min(3, "NIK minimal 3 karakter"), + notelp: z.string().min(3, "Nomor Telepon minimal 3 karakter"), + alamat: z.string().min(3, "Alamat minimal 3 karakter"), + email: z.string().min(3, "Email minimal 3 karakter"), + jenisInformasiDimintaId: z.string().nonempty(), + caraMemperolehInformasiId: z.string().nonempty(), + caraMemperolehSalinanInformasiId: z.string().nonempty(), +}) + +const jenisInformasiDiminta = proxy({ + findMany: { + data: null as + | null + | Prisma.JenisInformasiDimintaGetPayload<{ omit: { isActive: true } }>[], + async load(){ + const res = await ApiFetch.api.ppid.permohonaninformasipublik.jenisInformasi["find-many"].get(); + if (res.status === 200) { + jenisInformasiDiminta.findMany.data = res.data?.data ?? []; + } + } + } +}) + +const caraMemperolehInformasi = proxy({ + findMany: { + data: null as + | null + | Prisma.CaraMemperolehInformasiGetPayload<{ omit: { isActive: true } }>[], + async load() { + const res = await ApiFetch.api.ppid.permohonaninformasipublik.memperolehInformasi["find-many"].get(); + if (res.status === 200) { + caraMemperolehInformasi.findMany.data = res.data?.data ?? []; + } + } + } +}) + +const caraMemperolehSalinanInformasi = proxy({ + findMany: { + data: null as + | null + | Prisma.CaraMemperolehSalinanInformasiGetPayload<{ omit: { isActive: true } }>[], + async load() { + const res = await ApiFetch.api.ppid.permohonaninformasipublik.salinanInformasi["find-many"].get(); + if (res.status === 200) { + caraMemperolehSalinanInformasi.findMany.data = res.data?.data ?? []; + } + } + } +}) +console.log(caraMemperolehSalinanInformasi) + +type PermohonanInformasiPublikForm = Prisma.PermohonanInformasiPublikGetPayload<{ + select: { + name: true; + nik: true; + notelp: true; + alamat: true; + email: true; + jenisInformasiDimintaId: true; + caraMemperolehInformasiId: true; + caraMemperolehSalinanInformasiId: true; + }; +}>; + +const statepermohonanInformasiPublik = proxy({ + create: { + form: {} as PermohonanInformasiPublikForm, + loading: false, + async create(){ + const cek = templateForm.safeParse(statepermohonanInformasiPublik.create.form); + if(!cek.success) { + const err = `[${cek.error.issues + .map((v) => `${v.path.join(".")}`) + .join("\n")}] required`; + return toast.error(err); + } + try { + statepermohonanInformasiPublik.create.loading = true; + const res = await ApiFetch.api.ppid.permohonaninformasipublik["create"].post(statepermohonanInformasiPublik.create.form); + if (res.status === 200) { + statepermohonanInformasiPublik.findMany.load(); + return toast.success("success create"); + } + return toast.error("failed create"); + } catch (error) { + console.log((error as Error).message); + } finally { + statepermohonanInformasiPublik.create.loading = false; + } + } + }, + findMany: { + data: null as + | Prisma.PermohonanInformasiPublikGetPayload<{ include: { + caraMemperolehSalinanInformasi: true, + jenisInformasiDiminta: true, + caraMemperolehInformasi: true, + } }>[] + | null, + async load() { + const res = await ApiFetch.api.ppid.permohonaninformasipublik["find-many"].get(); + if (res.status === 200) { + statepermohonanInformasiPublik.findMany.data = res.data?.data ?? []; + } + } + } +}) + +const statepermohonanInformasiPublikForm = proxy({ + statepermohonanInformasiPublik, + jenisInformasiDiminta, + caraMemperolehInformasi, + caraMemperolehSalinanInformasi, +}) + +export default statepermohonanInformasiPublikForm; diff --git a/src/app/admin/(dashboard)/_state/ppid/permohonan_keberatan_informasi_publik/permohonanKeberatanInformasi.ts b/src/app/admin/(dashboard)/_state/ppid/permohonan_keberatan_informasi_publik/permohonanKeberatanInformasi.ts new file mode 100644 index 00000000..0decf48a --- /dev/null +++ b/src/app/admin/(dashboard)/_state/ppid/permohonan_keberatan_informasi_publik/permohonanKeberatanInformasi.ts @@ -0,0 +1,64 @@ +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(3, "Nama minimal 3 karakter"), + email: z.string().min(3, "Email minimal 3 karakter"), + notelp: z.string().min(3, "Nomor Telepon minimal 3 karakter"), + alasan: z.string().min(3, "Alasan minimal 3 karakter"), +}) + +type PermohonanKeberatanInformasiForm = Prisma.FormulirPermohonanKeberatanGetPayload<{ + select: { + name: true; + email: true; + notelp: true; + alasan: true; + }; +}>; + +const permohonanKeberatanInformasi = proxy({ + create: { + form: {} as PermohonanKeberatanInformasiForm, + loading: false, + async create(){ + const cek = templateForm.safeParse(permohonanKeberatanInformasi.create.form); + if(!cek.success) { + const err = `[${cek.error.issues + .map((v) => `${v.path.join(".")}`) + .join("\n")}] required`; + return toast.error(err); + } + try { + permohonanKeberatanInformasi.create.loading = true; + const res = await ApiFetch.api.ppid.permohonankeberataninformasipublik["create"].post(permohonanKeberatanInformasi.create.form); + if (res.status === 200) { + permohonanKeberatanInformasi.findMany.load(); + return toast.success("success create"); + } + return toast.error("failed create"); + } catch (error) { + console.log((error as Error).message); + } finally { + permohonanKeberatanInformasi.create.loading = false; + } + }, + }, + findMany: { + data: null as + | Prisma.FormulirPermohonanKeberatanGetPayload<{omit: {isActive: true}}>[] + | null, + async load() { + const res = await ApiFetch.api.ppid.permohonankeberataninformasipublik["find-many"].get(); + if (res.status === 200) { + permohonanKeberatanInformasi.findMany.data = res.data?.data ?? []; + } + } + } +}); + +export default permohonanKeberatanInformasi; + diff --git a/src/app/admin/(dashboard)/_state/ppid/profile_ppid/profile_PPID.ts b/src/app/admin/(dashboard)/_state/ppid/profile_ppid/profile_PPID.ts new file mode 100644 index 00000000..167d8edd --- /dev/null +++ b/src/app/admin/(dashboard)/_state/ppid/profile_ppid/profile_PPID.ts @@ -0,0 +1,172 @@ +import ApiFetch from "@/lib/api-fetch"; +import { Prisma } from "@prisma/client"; +import { toast } from "react-toastify"; +import { proxy } from "valtio"; +import { z } from "zod"; + +/** + * Schema validasi form ProfilePPID menggunakan Zod. + */ +const templateForm = z.object({ + name: z.string().min(3, "Nama minimal 3 karakter"), + biodata: z.string().min(3, "Biodata minimal 3 karakter"), + riwayat: z.string().min(3, "Riwayat minimal 3 karakter"), + pengalaman: z.string().min(3, "Pengalaman minimal 3 karakter"), + unggulan: z.string().min(3, "Unggulan minimal 3 karakter"), +}); + +/** + * Tipe data ProfilePPID yang digunakan dalam form dan API, berdasarkan Prisma schema. + */ +type ProfilePPIDForm = Prisma.ProfilePPIDGetPayload<{ + select: { + id: true; + name: true; + biodata: true; + riwayat: true; + pengalaman: true; + unggulan: true; + imageUrl: true; + }; +}>; + +/** + * State utama ProfilePPID yang mencakup fitur: + * - Ambil data berdasarkan ID + * - Update data + * - Upload gambar + */ +const stateProfilePPID = proxy({ + /** + * Bagian untuk ambil data berdasarkan ID + */ + findById: { + data: null as ProfilePPIDForm | null, + loading: false, + + /** + * Inisialisasi data kosong ke dalam state. + */ + initialize() { + stateProfilePPID.findById.data = { + id: '', + name: '', + biodata: '', + riwayat: '', + pengalaman: '', + unggulan: '', + imageUrl: '' + } as ProfilePPIDForm; + }, + + /** + * Mengambil data profil berdasarkan ID. + * @param {string} id - ID dari profile yang ingin diambil. + */ + async load(id: string) { + try { + stateProfilePPID.findById.loading = true; + const res = await ApiFetch.api.ppid.profileppid["find-by-id"].get({ + query: { id }, + }); + + if (res.status === 200) { + stateProfilePPID.findById.data = res.data?.data ?? null; + } else { + toast.error("Gagal mengambil data profile"); + } + } catch (error) { + console.error((error as Error).message); + toast.error("Terjadi kesalahan saat mengambil data profile"); + } finally { + stateProfilePPID.findById.loading = false; + } + }, + }, + + /** + * Bagian untuk update data profile + */ + update: { + loading: false, + + /** + * Melakukan validasi dan menyimpan perubahan data profile ke server. + * @param {ProfilePPIDForm} data - Data profil yang akan disimpan. + */ + async save(data: ProfilePPIDForm) { + const cek = templateForm.safeParse(data); + + if (!cek.success) { + const errors = cek.error.issues + .map((issue) => `${issue.path.join(".")}: ${issue.message}`) + .join(", "); + toast.error(`Form tidak valid: ${errors}`); + return; + } + + try { + stateProfilePPID.update.loading = true; + const res = await ApiFetch.api.ppid.profileppid["update"].post(data); + + if (res.status === 200) { + toast.success("Berhasil update profile"); + await stateProfilePPID.findById.load(data.id); + } else { + toast.error("Gagal update profile"); + } + } catch (error) { + console.error((error as Error).message); + toast.error("Terjadi kesalahan saat update profile"); + } finally { + stateProfilePPID.update.loading = false; + } + }, + }, + + /** + * Bagian untuk upload gambar profil + */ + uploadImage: { + loading: false, + + /** + * Mengunggah gambar profil berdasarkan ID. + * @param {File} file - File gambar yang akan diunggah. + * @param {string} id - ID dari profil yang akan diperbarui gambarnya. + */ + async save(file: File, id: string) { + if (!file || !id) { + toast.error("File atau ID harus disertakan"); + return; + } + + try { + stateProfilePPID.uploadImage.loading = true; + + const form = new FormData(); + form.append("file", file); + form.append("id", id); + + const res = await ApiFetch.api.ppid.profileppid["edit-img"].post(form); + + if (res.status === 200) { + toast.success("Berhasil mengunggah gambar"); + await stateProfilePPID.findById.load(id); + } else { + toast.error("Gagal mengunggah gambar"); + } + } catch (error) { + console.error((error as Error).message); + toast.error("Terjadi kesalahan saat mengunggah gambar"); + } finally { + stateProfilePPID.uploadImage.loading = false; + } + }, + }, +}); + +/** + * Ekspor state utama ProfilePPID untuk digunakan di komponen lain. + */ +export default stateProfilePPID; diff --git a/src/app/admin/(dashboard)/_state/ppid/visi_misi_ppid/visimisiPPID.ts b/src/app/admin/(dashboard)/_state/ppid/visi_misi_ppid/visimisiPPID.ts new file mode 100644 index 00000000..86e90bc7 --- /dev/null +++ b/src/app/admin/(dashboard)/_state/ppid/visi_misi_ppid/visimisiPPID.ts @@ -0,0 +1,81 @@ +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({ + misi: z.string().min(3, "Misi minimal 3 karakter"), + visi: z.string().min(3, "Visi minimal 3 karakter"), +}); + +type VisiMisiPPIDForm = Prisma.VisiMisiPPIDGetPayload<{ + select: { + id: true; + misi: true; + visi: true; + }; +}>; + +const stateVisiMisiPPID = proxy({ + findById: { + data: null as VisiMisiPPIDForm | null, + loading: false, + initialize() { + stateVisiMisiPPID.findById.data = { + id: "", + misi: "", + visi: "", + } as VisiMisiPPIDForm; + }, + async load(id: string) { + try { + stateVisiMisiPPID.findById.loading = true; + const res = await ApiFetch.api.ppid.visimisippid["find-by-id"].get({ + query: { id }, + }); + if (res.status === 200) { + stateVisiMisiPPID.findById.data = res.data?.data ?? null; + } else { + toast.error("Gagal mengambil data visi misi"); + } + } catch (error) { + console.error((error as Error).message); + toast.error("Terjadi kesalahan saat mengambil data visi misi"); + } finally { + stateVisiMisiPPID.findById.loading = false; + } + }, + }, + update: { + loading: false, + async save(data: VisiMisiPPIDForm) { + const cek = templateForm.safeParse(data); + if (!cek.success) { + const errors = cek.error.issues + .map((issue) => `${issue.path.join(".")}: ${issue.message}`) + .join(", "); + toast.error(`Form tidak valid: ${errors}`); + return; + } + + try { + stateVisiMisiPPID.update.loading = true; + const res = await ApiFetch.api.ppid.visimisippid["update"].post(data); + if (res.status === 200) { + toast.success("Berhasil update visi misi"); + await stateVisiMisiPPID.findById.load(data.id); + } else { + toast.error("Gagal update visi misi"); + } + } catch (error) { + console.error((error as Error).message); + toast.error("Terjadi kesalahan saat update visi misi"); + } finally { + stateVisiMisiPPID.update.loading = false; + } + }, + }, +}); + +export default stateVisiMisiPPID; diff --git a/src/app/admin/(dashboard)/desa/_com/desaEditor.tsx b/src/app/admin/(dashboard)/desa/_com/desaEditor.tsx new file mode 100644 index 00000000..de60355a --- /dev/null +++ b/src/app/admin/(dashboard)/desa/_com/desaEditor.tsx @@ -0,0 +1,93 @@ +'use client' +import colors from '@/con/colors'; +import { Button, Stack } from '@mantine/core'; +import { Link, RichTextEditor } from '@mantine/tiptap'; +import Highlight from '@tiptap/extension-highlight'; +import SubScript from '@tiptap/extension-subscript'; +import Superscript from '@tiptap/extension-superscript'; +import TextAlign from '@tiptap/extension-text-align'; +import Underline from '@tiptap/extension-underline'; +import { useEditor } from '@tiptap/react'; +import StarterKit from '@tiptap/starter-kit'; + +const content = + '

Welcome to Mantine rich text editor

RichTextEditor component focuses on usability and is designed to be as simple as possible to bring a familiar editing experience to regular users. RichTextEditor is based on Tiptap.dev and supports all of its features:

'; + +export function DesaEditor({showSubmit = true} : { + showSubmit: boolean +}) { + const editor = useEditor({ + extensions: [ + StarterKit, + Underline, + Link, + Superscript, + SubScript, + Highlight, + TextAlign.configure({ types: ['heading', 'paragraph'] }), + ], + immediatelyRender: false, + content, + }); + + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {showSubmit && ( + + )} + + ); +} \ No newline at end of file diff --git a/src/app/admin/(dashboard)/desa/_com/desaEditorText.tsx b/src/app/admin/(dashboard)/desa/_com/desaEditorText.tsx new file mode 100644 index 00000000..3452d0a2 --- /dev/null +++ b/src/app/admin/(dashboard)/desa/_com/desaEditorText.tsx @@ -0,0 +1,95 @@ +'use client' +import { Button, Stack } from '@mantine/core'; +import { Link, RichTextEditor } from '@mantine/tiptap'; +import Highlight from '@tiptap/extension-highlight'; +import SubScript from '@tiptap/extension-subscript'; +import Superscript from '@tiptap/extension-superscript'; +import TextAlign from '@tiptap/extension-text-align'; +import Underline from '@tiptap/extension-underline'; +import { useEditor } from '@tiptap/react'; +import StarterKit from '@tiptap/starter-kit'; + + +function DesaEditorText({ onSubmit, onChange, showSubmit = true, initialContent = '', }: { + onSubmit?: (val: string) => void, + onChange: (val: string) => void, + showSubmit?: boolean, + initialContent?: string }) { + const editor = useEditor({ + extensions: [ + StarterKit, + Underline, + Link, + Superscript, + SubScript, + Highlight, + TextAlign.configure({ types: ['heading', 'paragraph'] }), + ], + immediatelyRender: false, + content: initialContent, + onUpdate : ({editor}) => { + onChange(editor.getHTML()) + } + }); + + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {showSubmit && ( + + )} + + ); +} + +export default DesaEditorText; diff --git a/src/app/admin/(dashboard)/desa/berita/_com/BeritaEditor.tsx b/src/app/admin/(dashboard)/desa/berita/_com/BeritaEditor.tsx index aef1e401..4efd681f 100644 --- a/src/app/admin/(dashboard)/desa/berita/_com/BeritaEditor.tsx +++ b/src/app/admin/(dashboard)/desa/berita/_com/BeritaEditor.tsx @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ 'use client' import { RichTextEditor, Link } from '@mantine/tiptap'; import { useEditor } from '@tiptap/react'; @@ -8,23 +9,40 @@ import TextAlign from '@tiptap/extension-text-align'; import Superscript from '@tiptap/extension-superscript'; import SubScript from '@tiptap/extension-subscript'; import { Button, Stack } from '@mantine/core'; +import { useEffect } from 'react'; -const content = - '

Welcome to Mantine rich text editor

RichTextEditor component focuses on usability and is designed to be as simple as possible to bring a familiar editing experience to regular users. RichTextEditor is based on Tiptap.dev and supports all of its features:

'; +// const content = +// '

Welcome to Mantine rich text editor

RichTextEditor component focuses on usability and is designed to be as simple as possible to bring a familiar editing experience to regular users. RichTextEditor is based on Tiptap.dev and supports all of its features:

'; + + export function BeritaEditor({ + onEditorReady, + showSubmit = true, + onSubmit, + }: { + onEditorReady?: (editor: any | null) => void; + onSubmit?: (val: string) => void; + showSubmit?: boolean; + }) { + const editor = useEditor({ + extensions: [ + StarterKit, + Underline, + Link, + Superscript, + SubScript, + Highlight, + TextAlign.configure({ types: ['heading', 'paragraph'] }), + ], + content: '', + immediatelyRender: false + }); + + useEffect(() => { + onEditorReady?.(editor); + }, [editor, onEditorReady] ); + + if (!editor) return null; -export function BeritaEditor({ onSubmit }: { onSubmit: (val: string) => void }) { - const editor = useEditor({ - extensions: [ - StarterKit, - Underline, - Link, - Superscript, - SubScript, - Highlight, - TextAlign.configure({ types: ['heading', 'paragraph'] }), - ], - content, - }); return ( @@ -76,10 +94,12 @@ export function BeritaEditor({ onSubmit }: { onSubmit: (val: string) => void }) - + {showSubmit && ( + + )} ); } \ No newline at end of file diff --git a/src/app/admin/(dashboard)/desa/berita/page.tsx b/src/app/admin/(dashboard)/desa/berita/page.tsx index 26c3df72..134cd4ad 100644 --- a/src/app/admin/(dashboard)/desa/berita/page.tsx +++ b/src/app/admin/(dashboard)/desa/berita/page.tsx @@ -1,88 +1,358 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ 'use client' -import { Center, Group, Select, SimpleGrid, Skeleton, Stack, Text, TextInput } from '@mantine/core'; +import colors from '@/con/colors'; +import ApiFetch from '@/lib/api-fetch'; +import { ActionIcon, Box, Button, Center, FileInput, Flex, Image, Paper, Select, SimpleGrid, Skeleton, Stack, Text, TextInput, Title } from '@mantine/core'; import { useShallowEffect } from '@mantine/hooks'; import { Prisma } from '@prisma/client'; -import { IconImageInPicture } from '@tabler/icons-react'; +import { IconEdit, IconImageInPicture, IconX } from '@tabler/icons-react'; +import { useRouter } from 'next/navigation'; +import { useState } from 'react'; +import { toast } from 'react-toastify'; import { useProxy } from 'valtio/utils'; +import { ModalKonfirmasiHapus } from '../../_com/modalKonfirmasiHapus'; import stateDashboardBerita from '../../_state/desa/berita'; import { BeritaEditor } from './_com/BeritaEditor'; function Page() { return ( - - - - - - + + Berita + + + ); } +function BeritaCreate() { + const beritaState = useProxy(stateDashboardBerita); + const [previewImage, setPreviewImage] = useState(null); + const [file, setFile] = useState(null); + const [editorInstance, setEditorInstance] = useState(null); + + const resetForm = () => { + // Reset state di valtio + beritaState.berita.create.form = { + judul: "", + deskripsi: "", + kategoriBeritaId: "", + imageId: "", + content: "", + }; + + // Reset state lokal + setPreviewImage(null); + setFile(null); + if (editorInstance) { + editorInstance.commands.setContent(""); // Kosongkan editor + } + }; + + const handleSubmit = async () => { + if (!file) { + return toast.warn("Pilih file gambar terlebih dahulu"); + } + if (!editorInstance) return toast.error("Editor belum siap"); + + const htmlContent = editorInstance.getHTML(); + if (!htmlContent || htmlContent === "

") return toast.warn("Konten tidak boleh kosong"); + + beritaState.berita.create.form.content = htmlContent; + + // Upload gambar dulu + 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"); + } + + // Simpan ID gambar ke form + beritaState.berita.create.form.imageId = uploaded.id; + + // Submit data berita + await beritaState.berita.create.create(); + + toast.success("Berita berhasil disimpan!"); + + // Reset form setelah submit + resetForm(); + }; + + return ( + + + + { + beritaState.berita.create.form.judul = val.target.value; + }} + label={Judul} + placeholder="masukkan judul" + /> + { + beritaState.berita.create.form.kategoriBeritaId = val.id; + }} + /> + { + beritaState.berita.create.form.deskripsi = val.target.value; + }} + label={Deskripsi} + placeholder="masukkan deskripsi" + /> + + Upload Gambar} + value={file} + onChange={async (e) => { + if (!e) return; + setFile(e); + const base64 = await e.arrayBuffer().then((buf) => + "data:image/png;base64," + Buffer.from(buf).toString("base64") + ); + setPreviewImage(base64); + }} + /> + {previewImage ? ( + + ) : ( +
+ +
+ )} + + Konten + setEditorInstance(ed)} + /> + + +
+
+
+ ); +} + + + + +// function BeritaList() { +// const beritaState = useProxy(stateDashboardBerita) +// useShallowEffect(() => { +// beritaState.berita.findMany.load() +// }, []) + + + +// const router = useRouter() + +// if (!beritaState.berita.findMany.data) return +// {Array.from({ length: 10 }).map((v, k) => )} +// +// return ( +// +// +// +// List Berita +// +// {beritaState.berita.findMany.data?.map((item) => ( +// +// +// +// beritaState.berita.delete.byId(item.id)} +// disabled={beritaState.berita.delete.loading} +// color={colors['blue-button']} variant='transparent'> +// +// +// { +// router.push("/desa/berita/edit"); +// }} color={colors['blue-button']} variant='transparent'> +// +// +// +// +// Kategori +// +// {item.kategoriBerita?.name} +// +// Judul +// +// {item.judul} +// +// Deskripsi +// +// {item.deskripsi} +// +// Gambar +// +// gambar +// +// +// ))} +// +// +// +// +// ) +// } + function BeritaList() { const beritaState = useProxy(stateDashboardBerita) + const [modalHapus, setModalHapus] = useState(false) + const [selectedId, setSelectedId] = useState(null) + useShallowEffect(() => { beritaState.berita.findMany.load() }, []) - if (!beritaState.berita.findMany.data) return - {Array.from({ length: 10 }).map((v, k) => )} - - return - News List - {beritaState.berita.findMany.data?.map((item) => ( - {item.judul} - ))} - -} + const router = useRouter() -function BeritaCreate() { - const beritaState = useProxy(stateDashboardBerita) - return - Create Some News - { - beritaState.berita.create.form.katagoryBeritaId = val.id - }} /> - { - beritaState.berita.create.form.judul = val.target.value - }} label={"Judul"} placeholder='masukkan judul' /> - { - beritaState.berita.create.form.deskripsi = val.target.value - }} label={"Deskripsi"} placeholder='masukkan deskripsi' /> -
- -
- { - - beritaState.berita.create.form.content = val - beritaState.berita.create.create() - }} /> -
-} - -function SelectCategory({ onChange }: { - onChange: (value: Prisma.KatagoryBeritaGetPayload<{ - select: { - name: true, - id: true + const handleHapus = () => { + if (selectedId) { + beritaState.berita.delete.byId(selectedId) + setModalHapus(false) + setSelectedId(null) } - }>) => void -}) { - const beritaState = useProxy(stateDashboardBerita) - useShallowEffect(() => { - beritaState.category.findMany.load() - }, []) + } - if (!beritaState.category.findMany.data) return - return - Kategori} + placeholder="Pilih kategori" + data={categoryState.findMany.data.map((item) => ({ + label: item.name, + value: item.id, + }))} + onChange={(val) => { + const selected = categoryState.findMany.data?.find((item) => item.id === val); + if (selected) { + onChange(selected); + } + }} + searchable + nothingFoundMessage="Tidak ditemukan" + /> + ); +} + + +// function SelectCategory({ onChange }: { +// onChange: (value: Prisma.KategoriBeritaGetPayload<{ +// select: { +// name: true, +// id: true +// } +// }>) => void +// }) { +// const beritaState = useProxy(stateDashboardBerita) +// useShallowEffect(() => { +// beritaState.category.findMany.load() +// }, []) + +// if (!beritaState.category.findMany.data) return +// return +// ({ +