Compare commits

...

10 Commits

137 changed files with 5646 additions and 3484 deletions

View File

@@ -0,0 +1,57 @@
[
{
"id" : "cmdxyb9zi0010vniiaeyi55ui",
"name" : "Surat Keterangan Beda Biodata Diri",
"deskripsi" : "<p>Persyaratan Dokumen :</p><ul><li><p>Pengantar Kelian Banjar Dinas di Wilayah Masing - masing</p></li><li><p>Fotocopy KTP atau Kartu Keluarga</p></li><li><p>Fotocopy dokumen bersangkutan yang terdapat perbedaan biodata diri misal : Sertifikat Tanah/Ijazah/Polis Asuransi dan lainnya.</p></li></ul><p>Alur Pelayanan :</p>"
},
{
"id" : "cmdxycqz40014vniidftrixvf",
"name" : "Surat Keterangan Yatim Piatu",
"deskripsi" : "<p>Persyaratan Dokumen :</p><ul><li><p>Pengantar Kelian Banjar Dinas di Wilayah Masing - masing</p></li><li><p>Fotocopy KTP atau KIA atau Kartu Keluarga</p></li></ul><p>Alur Pelayanan :</p>"
},
{
"id" : "cmdwx3wph0003vnr74us2t7h7",
"name" : "Surat Keterangan Domisili Organisasi",
"deskripsi" : "<p>Persyaratan Dokumen:</p><ul><li><p>Pengantar Kelian Banjar Dinas di Wilayah Masing - masing</p></li><li><p>Fotocopy Surat Keterangan Terdaftar (SKT) organisasi atau Pengukuhan Kelompok</p></li><li><p>Jika Pengajuan baru pembuatan SKT maka melengkapi Susunan Pengurus lengkap dengan Kop Organisasi</p></li><li><p>Tanggal berdiri/Tahun berdiri/Sejak kapan berdirinya organisasi</p></li></ul><p>Alur Pelayanan:</p>"
},
{
"id" : "cmdxxv3i80004vniidg1mrucc",
"name" : "Surat Keterangan Penghasilan",
"deskripsi" : "<p>Persyaratan Dokumen :</p><ul><li><p>Pengantar Kelian Banjar Dinas di Wilayah Masing - masing</p></li><li><p>Fotocopy KTP orang tua atau Fotocopy Kartu keluarga</p></li><li><p>Membuat Surat Pernyataan Penghasilan bermaterai (disertai jumlah penghasilan)</p></li></ul><p>Alur Pelayanan :</p>"
},
{
"id" : "cmdxxwp070008vnii9jbdcto7",
"name" : "Surat Keterangan Tidak Mampu",
"deskripsi" : "<p>Persyaratan Dokumen :</p><ul><li><p>Pengantar Kelian Banjar Dinas di Wilayah Masing - masing</p></li><li><p>Fotocopy KTP/KIA atau Kartu Keluarga</p></li><li><p>Fotocopy Kartu Indonesia Pintar/Kartu Perlindungan Sosial/Terdaftar dalam DTKS</p></li><li><p>Jika tidak memiliki Kartu tersebut diatas diwajibkan membuat Surat Pernyataan Tidak Mampu</p></li></ul><p>Alur Pelayanan :</p>"
},
{
"id" : "cmdxxyfkl000cvnii1bxinnfi",
"name" : "Surat Keterangan Kelahiran",
"deskripsi" : "<p>Persyaratan Dokumen :</p><ul><li><p>Pengantar Kelian Banjar Dinas di Wilayah Masing - masing</p></li><li><p>Fotocopy Surat lahir dari dokter/bidan (jika ada)</p></li><li><p>Fotocopy Kartu Keluarga</p></li><li><p>Fotocopy KTP 2 orang saksi</p></li></ul><p>Alur Pelayanan :</p>"
},
{
"id" : "cmdxy23pl000gvniihsg38aq4",
"name" : "Surat Keterangan Usaha",
"deskripsi" : "<p>Persyaratan Dokumen :</p><ul><li><p>Pengantar Kelian Banjar Dinas di Wilayah Masing - masing</p></li><li><p>Fotocopy KTP atau Kartu Keluarga</p></li><li><p>Foto Lokasi dan Kegiatan Usaha di cetak dalam selembar kertas (diparaf dan stempel oleh Kelian Banjar Dinas)</p></li></ul><p>Alur Pelayanan :</p>"
},
{
"id" : "cmdxy4mgt000kvniib1nemjem",
"name" : "Surat Keterangan Kematian",
"deskripsi" : "<p>Persyaratan Dokumen :</p><ul><li><p>Pengantar Kelian Banjar Dinas di Wilayah Masing - masing</p></li><li><p>Fotocopy KTP atau Kartu Keluarga</p></li><li><p>Surat Kematian dari rumah sakit atau dokter (jika ada)</p></li><li><p>tanggal kematian</p></li></ul><p>Alur Pelayanan :</p>"
},
{
"id" : "cmdxy61a1000ovniif4ytb9hs",
"name" : "Surat Keterangan Tempat Usaha",
"deskripsi" : "<p>Persyaratan Dokumen :</p><ul><li><p>Pengantar Kelian Banjar Dinas di Wilayah Masing - masing</p></li><li><p>Fotocopy KTP atau Kartu Keluarga</p></li><li><p>Foto Lokasi&nbsp;dan Kegiatan Usaha di cetak dalam selembar kertas (diparaf dan stempel oleh Kelian Banjar Dinas)</p></li><li><p>Surat Perjanjian Sewa/Kontrak atau Kwintansi Pembayaran Sewa 3 bulan terakhir bagi yang mengontrak tempat usaha, apabila tempat usaha milik sendiri lampiri dengan dokumen kepemilikan tempat usaha (dapat berupa fotocopy sppt atau Fotocopy Sertipikat Hak Milik)</p></li></ul><p>Alur Pelayanan :</p>"
},
{
"id" : "cmdxy754q000svniiiz8oqyo0",
"name" : "Surat Keterangan Belum Kawin",
"deskripsi" : "<p>Persyaratan Dokumen :</p><ul><li><p>Pengantar Kelian Banjar Dinas di Wilayah Masing - masing</p></li><li><p>Fotocopy KTP atau Kartu Keluarga</p></li><li><p>Khusus bagi yang berstatus duda atau janda melampirkan fotocopy akta cerai atau dokumen pendukung lainnya</p></li></ul><p>Alur Pelayanan :</p>"
},
{
"id" : "cmdxy8pi2000wvnii48fc1sxd",
"name" : "Surat Keterangan Kelakuan Baik",
"deskripsi" : "<p>Persyaratan Dokumen :</p><ul><li><p>Pengantar Kelian Banjar Dinas di Wilayah Masing - masing</p></li><li><p>Fotocopy KTP atau Kartu Keluarga</p></li></ul><p>Alur Pelayanan :</p>"
}
]

View File

@@ -0,0 +1,20 @@
[
{
"id": "cmdy0dwx10000vnnb6nmt06rv",
"name": "Telunjuk Sakti Desa Akta Kelahiran (Petunjuk Pengajuan pada link berikut : Download",
"deskripsi": "Akta Kelahiran",
"link": "https://darmasaba.desa.id/storage/files/PERSYARATAN%20DAN%20ALUR%20PENGAJUAN%20AKTA%20KELAHIRAN_(dengan%20contoh%20Formulir).pdf"
},
{
"id": "cmdy0ttpz0001vnnbrvr9jb3z",
"name": "Telunjuk Sakti Desa Akta Perkawinan (Petunjuk Pengajuan pada link berikut : Download",
"deskripsi": "Akta Perkawinan",
"link": "https://darmasaba.desa.id/storage/files/PERSYARATAN%20DAN%20ALUR%20PENGAJUAN%20AKTA%20PERKAWINAN_(dengan%20contoh%20Formulir).pdf"
},
{
"id": "cmdy0vjic0002vnnbcp0e9lgq",
"name": "Telunjuk Sakti Desa Akta Kematian (Petunjuk Pengajuan pada link berikut : Download",
"deskripsi": "Akta Kematian",
"link": "https://darmasaba.desa.id/storage/files/PERSYARATAN%20DAN%20ALUR%20PENGAJUAN%20AKTA%20KEMATIAN_(dengan%20contoh%20Formulir).pdf"
}
]

View File

@@ -0,0 +1,74 @@
[
{
"id": "cmdyamai40004vnw3sdjbvn48",
"name": "TPS3R Pudak Mesari",
"deskripsi": "TPS 3R Pudak Mesari Darmasaba layak mendapat penghargaan demikian apresiasi dari Delterra Sosial Indonesia nie Semeton Darmasaba!, Hal tersebut dikarenakan walaupun baru berdiri namun TPS 3R kebanggaan Desa Darmasaba tersebut sudah berjalan dengan sangat baik.",
"content": "<p>TPS3R Pudak Mesari adalah Tempat Pengolahan Sampah dengan konsep Reduce, Reuse, dan Recycle (TPS3R) yang berlokasi di Desa Darmasaba, Kecamatan Abiansemal, Kabupaten Badung, Bali. Fasilitas ini berperan penting dalam pengelolaan sampah berbasis masyarakat, dengan tujuan mengurangi volume sampah yang masuk ke Tempat Pembuangan Akhir (TPA) dan meningkatkan kesadaran warga tentang pentingnya pengelolaan sampah yang berkelanjutan.</p><p>Potensi Desa melalui TPS3R Pudak Mesari:</p><ol><li><p><strong>Peningkatan Kesehatan Lingkungan:</strong></p><p>Dengan pengelolaan sampah yang efektif, desa dapat menjaga kebersihan lingkungan, mengurangi risiko penyakit, dan menciptakan suasana yang lebih nyaman bagi warga.</p></li><li><p><strong>Pemberdayaan Ekonomi Masyarakat:</strong></p><p>TPS3R membuka peluang usaha bagi warga melalui pemilahan dan pengolahan sampah, seperti produksi kompos dari sampah organik dan kerajinan tangan dari sampah anorganik yang dapat meningkatkan pendapatan masyarakat.</p></li><li><p><strong>Edukasi dan Kesadaran Lingkungan:</strong></p><p>Fasilitas ini dapat menjadi pusat edukasi bagi masyarakat tentang pentingnya pengelolaan sampah, mendorong partisipasi aktif dalam menjaga kelestarian lingkungan.</p></li><li><p><strong>Pengembangan Pariwisata Berkelanjutan:</strong></p><p>Dengan lingkungan yang bersih dan asri, Desa Darmasaba memiliki potensi untuk menarik wisatawan yang tertarik pada ekowisata dan budaya lokal, sehingga meningkatkan perekonomian desa.</p></li></ol>"
},
{
"id": "cmdyb7h440003vngjapbc84f7",
"name": "Bumdes Pudak Mesari",
"deskripsi": "Bumdes Pudak Mesari sangat membantu warga desa Darmasaba dalam mengelola dan membangun sebuah desa yang lebih baik lagi",
"content": "<p>Badan Usaha Milik Desa (BUMDes) Pudak Mesari adalah lembaga ekonomi desa yang berperan penting dalam pengembangan potensi dan kesejahteraan masyarakat Desa Darmasaba, Kecamatan Abiansemal, Kabupaten Badung, Bali. BUMDes ini berfungsi sebagai motor penggerak perekonomian desa melalui berbagai unit usaha yang dikelola secara profesional.</p><p>Potensi dan Peran BUMDes Pudak Mesari:</p><ol><li><p><strong>Pengembangan Usaha Mikro dan Kecil:</strong></p><p>BUMDes Pudak Mesari menyediakan layanan bagi pelaku usaha mikro dan kecil di desa, seperti penyediaan konsumsi dan snack kotak untuk berbagai acara.</p></li><li><p><strong>Pengelolaan Sampah Berbasis Masyarakat:</strong></p><p>Melalui kolaborasi dengan komunitas pemuda peduli lingkungan, BUMDes Pudak Mesari aktif dalam pengelolaan sampah berbasis masyarakat.</p></li><li><p><strong>Peningkatan Kapasitas dan Transparansi:</strong></p><p>Untuk memastikan pengelolaan yang akuntabel, BUMDes Pudak Mesari rutin mengadakan rapat koordinasi dan pendampingan penyusunan laporan pertanggungjawaban.</p></li><li><p><strong>Kolaborasi Internasional:</strong></p><p>Desa Darmasaba, melalui BUMDes Pudak Mesari, menerima kunjungan dari tim Osaki Jepang untuk memperkuat pengelolaan sampah dan lingkungan.</p></li></ol><p>Dengan berbagai inisiatif tersebut, BUMDes Pudak Mesari menunjukkan perannya sebagai pilar utama dalam pengembangan ekonomi dan kesejahteraan masyarakat Desa Darmasaba, sekaligus menjaga kelestarian lingkungan melalui program-program inovatif dan kolaboratif.</p>"
},
{
"id": "cmdybb53i0007vngjet38spn8",
"name": "Taman Beji Cengana",
"deskripsi": "Tirta Klebutan di Pura Taman Beji Cengana di Desa Adat Darmasaba, Badung, selain dipercaya nunas Taksu serta pembersihan diri. Tersemat juga asal usul cerita ditemukannya Tirta Klebutan yang tepat berada di pinggir Tukad Cengana tersebut.",
"content": "<p>Taman Beji Cengana, terletak di Desa Darmasaba, Kecamatan Abiansemal, Kabupaten Badung, Bali, adalah situs suci yang memiliki nilai spiritual dan sejarah yang tinggi. Tempat ini dikenal sebagai lokasi untuk ritual pembersihan diri (melukat) dan peribadatan oleh umat Hindu Bali. Keberadaan mata air suci (Tirta Klebutan) di Taman Beji Cengana dipercaya memberikan berkah dan penyucian bagi mereka yang datang untuk berdoa dan melakukan ritual.</p><p>Potensi Desa melalui Taman Beji Cengana:</p><ol><li><p><strong>Pengembangan Pariwisata Spiritual:</strong></p><p>Taman Beji Cengana memiliki potensi besar sebagai destinasi wisata spiritual. Wisatawan yang mencari pengalaman spiritual dan ketenangan batin dapat tertarik untuk mengunjungi tempat ini, mengikuti ritual melukat, dan merasakan suasana sakral yang ditawarkan.</p></li><li><p><strong>Pelestarian Budaya dan Tradisi:</strong></p><p>Dengan mempromosikan Taman Beji Cengana sebagai pusat kegiatan budaya dan ritual tradisional, desa dapat memastikan bahwa warisan budaya dan tradisi lokal tetap lestari.</p></li><li><p><strong>Pendidikan dan Penelitian:</strong></p><p>Taman Beji Cengana dapat dijadikan sebagai pusat pendidikan dan penelitian bagi akademisi, peneliti, dan pelajar yang tertarik mempelajari budaya, agama, dan sejarah Bali.</p></li><li><p><strong>Pengembangan Ekonomi Kreatif:</strong></p><p>Dengan meningkatnya jumlah pengunjung ke Taman Beji Cengana, peluang bagi pengembangan ekonomi kreatif juga terbuka lebar. Masyarakat lokal dapat mengembangkan produk kerajinan tangan, kuliner khas, dan suvenir yang mencerminkan budaya dan tradisi desa.</p></li><li><p><strong>Konservasi Lingkungan:</strong></p><p>Sebagai situs suci dengan mata air alami, Taman Beji Cengana memiliki peran penting dalam konservasi lingkungan. Upaya menjaga kebersihan dan kelestarian mata air serta lingkungan sekitarnya dapat menjadi contoh praktik konservasi yang baik.</p></li></ol><p>Dengan memanfaatkan potensi yang dimiliki Taman Beji Cengana, Desa Darmasaba dapat mengembangkan sektor pariwisata, budaya, pendidikan, ekonomi, dan lingkungan secara berkelanjutan, yang pada gilirannya akan meningkatkan kesejahteraan masyarakat dan pelestarian warisan budaya.</p>"
},
{
"id": "cmdybckcz000avngjfzpy60uk",
"name": "Gumuh Sari Water Park",
"deskripsi": "Gumuh Sari Rekreasi atau waterpark, tempat wisata yang asyik dan seru untuk kamu sekeluarga! Tempat liburan di Bali memang seakan nggak ada habisnya. Selalu ada aja destinasi wisata seru yang bisa jadi wishlist. Ada banyak banget tempat wisata yang kamu kunjungi di Bali, mulai dari wisata alam, wisata modern, sampai wisata air.",
"content": "<p>Gumuh Sari Waterpark, terletak di Jl. Tegal Gumuh No. 9, Desa Darmasaba, Kecamatan Abiansemal, Kabupaten Badung, Bali, adalah destinasi rekreasi yang menawarkan berbagai fasilitas untuk pengunjung dari segala usia. Taman rekreasi ini tidak hanya menyediakan wahana air yang menyenangkan, tetapi juga fasilitas olahraga dan kuliner, menjadikannya tempat ideal untuk rekreasi keluarga dan komunitas.</p><p>Potensi Desa melalui Gumuh Sari Waterpark:</p><ol><li><p><strong>Pengembangan Pariwisata Lokal:</strong></p><p>Dengan adanya destinasi seperti Gumuh Sari Waterpark, Desa Darmasaba dapat menarik lebih banyak wisatawan lokal maupun mancanegara. Kehadiran pengunjung ini berpotensi meningkatkan pendapatan desa dan membuka peluang usaha baru bagi masyarakat setempat.</p></li><li><p><strong>Peningkatan Ekonomi Masyarakat:</strong></p><p>Fasilitas seperti restoran dan pusat olahraga di dalam kompleks waterpark memberikan peluang bagi warga lokal untuk terlibat dalam sektor jasa dan perdagangan. Hal ini dapat menciptakan lapangan pekerjaan dan mendukung pertumbuhan ekonomi desa.</p></li><li><p><strong>Pengembangan Fasilitas Olahraga dan Kesehatan:</strong></p><p>Dengan adanya pusat futsal dan gym, Gumuh Sari Waterpark mendorong masyarakat untuk berpartisipasi dalam kegiatan olahraga, yang dapat meningkatkan kesehatan dan kesejahteraan warga.</p></li><li><p><strong>Pemberdayaan Komunitas Melalui Event dan Acara:</strong></p><p>Waterpark ini sering menjadi tuan rumah berbagai acara komunitas, seperti pesta busa dan bola, yang dapat mempererat hubungan antarwarga dan menciptakan lingkungan yang harmonis.</p></li><li><p><strong>Peningkatan Infrastruktur dan Aksesibilitas:</strong></p><p>Dengan meningkatnya jumlah pengunjung, infrastruktur desa seperti jalan, transportasi, dan layanan umum lainnya akan berkembang untuk memenuhi kebutuhan tersebut, yang pada gilirannya meningkatkan kualitas hidup masyarakat setempat.</p></li></ol><p>Melalui pengelolaan dan pengembangan yang tepat, Gumuh Sari Waterpark dapat menjadi motor penggerak bagi kemajuan Desa Darmasaba, meningkatkan kesejahteraan masyarakat, dan menjadikan desa ini sebagai destinasi wisata yang dikenal luas.</p>"
},
{
"id": "cmdyjuij40002vns5qyyjmzf4",
"name": "Pertanian",
"deskripsi": "Desa Darmasaba, yang terletak di Kecamatan Abiansemal, Kabupaten Badung, Bali, memiliki potensi pertanian yang besar sebagai bagian dari warisan agraris yang telah diwariskan secara turun-temurun. Dengan kondisi tanah yang subur serta sistem irigasi tradisional subak, pertanian di Darmasaba memainkan peran penting dalam ekonomi dan keberlanjutan lingkungan desa.",
"content": "<p>Desa Darmasaba, yang terletak di Kecamatan Abiansemal, Kabupaten Badung, Bali, memiliki potensi pertanian yang besar sebagai bagian dari warisan agraris yang telah diwariskan secara turun-temurun. Dengan kondisi tanah yang subur serta sistem irigasi tradisional subak, pertanian di Darmasaba memainkan peran penting dalam ekonomi dan keberlanjutan lingkungan desa.</p><p>Potensi Desa melalui Pertanian:</p><ol><li><p><strong>Potensi dan Komoditas Unggulan</strong></p><p>Pertanian di Desa Darmasaba mengandalkan berbagai jenis tanaman yang memiliki nilai ekonomi tinggi, di antaranya:</p><p>- Padi : Sebagai salah satu desa yang masih mempertahankan sistem subak, Darmasaba menjadi bagian dari lumbung pangan di Bali.</p><p>- Sayur-mayur : Beberapa jenis sayuran seperti kangkung, bayam, cabai, dan tomat banyak dibudidayakan oleh petani lokal.</p><p>- Buah-buahan tropis : Termasuk pisang, mangga, dan kelapa, yang menjadi sumber pendapatan tambahan bagi petani.</p><p>- Tanaman obat dan rempah : Seperti jahe, kunyit, dan lengkuas, yang memiliki permintaan tinggi baik untuk kebutuhan rumah tangga maupun industri herbal.</p></li><li><p><strong>Sistem Irigasi Tradisional Subak:</strong></p><p>Sebagai bagian dari warisan budaya Bali, sistem irigasi subak masih diterapkan di Darmasaba. Sistem ini memungkinkan distribusi air yang adil di antara lahan pertanian dan membantu menjaga keberlanjutan produksi pangan desa.</p></li><li><p><strong>Pengembangan Pertanian Organik:</strong></p><p>Dengan meningkatnya kesadaran akan pentingnya produk sehat dan ramah lingkungan, beberapa petani di Darmasaba mulai beralih ke metode pertanian organik. Hal ini membuka peluang bagi desa untuk mengembangkan produk-produk pertanian yang memiliki nilai jual lebih tinggi.</p></li><li><p><strong>Agrowisata sebagai Sumber Pendapatan Baru:</strong></p><p>Potensi pertanian Darmasaba juga dapat dikembangkan menjadi agrowisata, di mana wisatawan dapat merasakan pengalaman langsung dalam bertani, mengikuti workshop bercocok tanam, serta menikmati hasil pertanian segar. Hal ini dapat menarik wisatawan lokal maupun mancanegara, meningkatkan perekonomian desa.</p></li><li><p><strong>Pemberdayaan Petani dan UMKM Berbasis Pertanian:</strong></p><p>Dengan adanya BUMDes Pudak Mesari dan dukungan dari pemerintah setempat, petani di Darmasaba dapat diberikan pelatihan dan akses pasar yang lebih luas. Produk pertanian dapat diolah menjadi berbagai produk turunan seperti keripik pisang, sambal khas desa, hingga minuman herbal yang dapat dipasarkan ke luar daerah.</p></li></ol><p>Dengan berbagai potensi yang dimiliki, pertanian di Desa Darmasaba dapat terus berkembang melalui inovasi dan pemanfaatan teknologi pertanian modern. Dukungan dari masyarakat, pemerintah, dan lembaga terkait sangat penting untuk menjaga keberlanjutan sektor pertanian dan meningkatkan kesejahteraan petani di desa ini.</p>"
},
{
"id": "cmdykerrq0002vn6fy4sv7uvm",
"name": "Kawasan Kuliner",
"deskripsi": "Desa Darmasaba, yang terletak di Kecamatan Abiansemal, Kabupaten Badung, Bali, memiliki potensi besar dalam sektor kuliner. Sebagai desa yang strategis dan terus berkembang, Darmasaba mulai dikenal sebagai destinasi kuliner yang menawarkan beragam makanan khas Bali hingga makanan modern yang menarik minat wisatawan dan masyarakat lokal.",
"content": "<p>Desa Darmasaba, yang terletak di Kecamatan Abiansemal, Kabupaten Badung, Bali, memiliki potensi besar dalam sektor kuliner. Sebagai desa yang strategis dan terus berkembang, Darmasaba mulai dikenal sebagai destinasi kuliner yang menawarkan beragam makanan khas Bali hingga makanan modern yang menarik minat wisatawan dan masyarakat lokal.</p><p>Potensi Desa melalui Kawasan Kuliner:</p><ol><li><p><strong>Ragam Kuliner Khas Bali</strong></p><p>Darmasaba memiliki banyak warung dan rumah makan yang menyajikan hidangan khas Bali yang otentik, seperti:</p><p>- Babi Guling : Salah satu kuliner favorit di Bali yang banyak ditemukan di Darmasaba.</p><p>- Ayam Betutu : Hidangan ayam berbumbu khas yang dimasak dengan teknik khas Bali.</p><p>- Lawar : Campuran daging dan sayuran berbumbu khas Bali.</p><p>- Sate Lilit : Sate khas Bali yang terbuat dari daging cincang yang dibalut pada batang serai.</p></li><li><p><strong>Wisata Kuliner Modern &amp; Cafe Kekinian:</strong></p><p>Selain makanan tradisional, Darmasaba juga mulai berkembang dengan hadirnya cafe dan resto kekinian yang menyajikan menu modern seperti kopi spesial, burger, pizza, dan aneka dessert yang digemari anak muda. Keberadaan tempat-tempat ini menjadikan Darmasaba sebagai pilihan destinasi kuliner bagi wisatawan maupun warga sekitar.</p></li><li><p><strong>Pasar Kuliner Malam:</strong></p><p>Salah satu daya tarik Darmasaba adalah pusat kuliner malam yang menghadirkan aneka makanan kaki lima seperti nasi jinggo, tipat cantok, bakso, dan berbagai jajanan khas Bali. Suasana yang ramai dan harga yang terjangkau membuat pasar kuliner ini menjadi tempat favorit bagi masyarakat lokal.</p></li><li><p><strong>Potensi Ekonomi &amp; UMKM Kuliner:</strong></p><p>Dengan berkembangnya sektor kuliner, banyak pelaku UMKM di Darmasaba mulai merintis usaha makanan, baik dalam bentuk warung makan, katering, hingga produksi makanan ringan seperti keripik, sambal, dan minuman tradisional. Potensi ini dapat terus dikembangkan dengan dukungan pemerintah desa dan promosi melalui media sosial.</p></li><li><p><strong>Kawasan Kuliner Berbasis Pariwisata:</strong></p><p>Untuk menarik lebih banyak pengunjung, Darmasaba berpotensi mengembangkan kawasan kuliner berbasis wisata yang menggabungkan pengalaman makan dengan konsep alam terbuka, pertunjukan seni, dan edukasi kuliner khas Bali. Hal ini dapat menjadi daya tarik tambahan bagi wisatawan yang ingin merasakan pengalaman kuliner yang lebih autentik.</p></li></ol><p>Dengan kekayaan kuliner yang dimiliki, Desa Darmasaba berpotensi menjadi kawasan kuliner unggulan di Kabupaten Badung. Dukungan dari masyarakat, pemerintah desa, serta promosi yang lebih luas dapat menjadikan Darmasaba sebagai destinasi kuliner yang semakin dikenal dan berkembang.</p>"
},
{
"id": "cmdykhfhf0005vn6fnz25kify",
"name": "IKM Berbasis Pengolahan Pangan",
"deskripsi": "Desa Darmasaba, yang terletak di Kecamatan Abiansemal, Kabupaten Badung, memiliki potensi besar dalam Industri Kecil dan Menengah (IKM) berbasis pengolahan pangan. Dengan sumber daya alam yang melimpah dan warisan kuliner khas Bali, Darmasaba dapat mengembangkan sektor ini untuk meningkatkan kesejahteraan masyarakat dan menciptakan lapangan kerja baru.",
"content": "<p>Desa Darmasaba, yang terletak di Kecamatan Abiansemal, Kabupaten Badung, memiliki potensi besar dalam Industri Kecil dan Menengah (IKM) berbasis pengolahan pangan. Dengan sumber daya alam yang melimpah dan warisan kuliner khas Bali, Darmasaba dapat mengembangkan sektor ini untuk meningkatkan kesejahteraan masyarakat dan menciptakan lapangan kerja baru.</p><p>Potensi dan Peran IKM Berbasis Pengolahan Pangan:</p><ol><li><p><strong>Produk Unggulan Pengolahan Pangan</strong></p><p>Beberapa produk olahan pangan yang potensial dikembangkan di Darmasaba meliputi:</p><p>- Keripik dan Snack Tradisional : Seperti keripik pisang, keripik singkong, dan rengginang.</p><p>- Sambal Khas Bali : Seperti sambal matah dan sambal embe yang banyak diminati pasar lokal dan nasional.</p><p>- Minuman Herbal dan Jamu : Berbasis rempah seperti kunyit asam, beras kencur, dan wedang jahe.</p><p>- Olahan Makanan Berbasis Kelapa : Seperti virgin coconut oil (VCO), serundeng, dan gula aren.</p><p>- Kue Tradisional Bali : Seperti jaje laklak, jaje uli, dan klepon yang dapat dikemas secara modern.</p></li><li><p><strong>Peluang Ekonomi dan Pemberdayaan UMKM:</strong></p><p>IKM berbasis pengolahan pangan dapat membuka peluang bagi masyarakat, terutama ibu rumah tangga dan pemuda desa, untuk berwirausaha. Dengan dukungan modal dan pelatihan dari pemerintah desa atau BUMDes Pudak Mesari, usaha kecil ini dapat berkembang menjadi industri yang lebih besar.</p></li><li><p><strong>Digitalisasi dan Pemasaran Online:</strong></p><p>Darmasaba dapat mengembangkan kawasan sentra IKM sebagai pusat produksi, pelatihan, dan pemasaran produk olahan pangan. Dengan adanya fasilitas ini, para pelaku usaha dapat lebih mudah berkolaborasi, meningkatkan kualitas produk, serta mendapatkan akses ke permodalan dan distribusi yang lebih luas.</p></li><li><p><strong>Pengembangan Kawasan Sentra IKM:</strong></p><p>Dengan berkembangnya sektor kuliner, banyak pelaku UMKM di Darmasaba mulai merintis usaha makanan, baik dalam bentuk warung makan, katering, hingga produksi makanan ringan seperti keripik, sambal, dan minuman tradisional. Potensi ini dapat terus dikembangkan dengan dukungan pemerintah desa dan promosi melalui media sosial.</p></li><li><p><strong>Sinergi dengan Pariwisata dan Agrowisata:</strong></p><p>Dengan berkembangnya sektor wisata di Darmasaba, produk olahan pangan dapat dijadikan suvenir khas desa. Pengunjung dapat membeli oleh-oleh seperti sambal kemasan, jajanan khas, atau minuman herbal sebagai bagian dari pengalaman wisata mereka.</p></li></ol><p>IKM berbasis pengolahan pangan memiliki potensi besar untuk menjadi sektor unggulan di Desa Darmasaba. Dengan inovasi, dukungan teknologi, serta pemasaran yang baik, produk-produk lokal dapat bersaing di pasar yang lebih luas, meningkatkan kesejahteraan masyarakat, dan menjadikan Darmasaba sebagai pusat industri pangan kreatif di Kabupaten Badung.</p>"
},
{
"id": "cmdykjgmv0008vn6fwg0rr2nh",
"name": "Genteng",
"deskripsi": "Desa Darmasaba, yang terletak di Kecamatan Abiansemal, Kabupaten Badung, memiliki potensi besar dalam industri genteng yang dikelola oleh Usaha Mikro, Kecil, dan Menengah (UMKM). Sebagai desa yang masih mempertahankan nilai-nilai tradisional dalam pembangunan, industri genteng di Darmasaba berperan penting dalam penyediaan bahan bangunan berkualitas bagi masyarakat lokal maupun luar daerah.",
"content": "<p>Desa Darmasaba, yang terletak di Kecamatan Abiansemal, Kabupaten Badung, memiliki potensi besar dalam industri genteng yang dikelola oleh Usaha Mikro, Kecil, dan Menengah (UMKM). Sebagai desa yang masih mempertahankan nilai-nilai tradisional dalam pembangunan, industri genteng di Darmasaba berperan penting dalam penyediaan bahan bangunan berkualitas bagi masyarakat lokal maupun luar daerah.</p><p>Potensi dan Peran UMKM Genteng:</p><ol><li><p><strong>Genteng Tradisional Berkualitas Tinggi</strong></p><p>UMKM di Darmasaba memproduksi genteng dari bahan baku pilihan seperti tanah liat berkualitas, yang menghasilkan genteng dengan daya tahan tinggi, kuat, dan cocok untuk iklim tropis. Beberapa jenis genteng yang dihasilkan meliputi:</p><p>- Genteng Tanah Liat : Kuat, tahan lama, dan memiliki estetika khas tradisional.</p><p>- Genteng Beton : Cocok untuk bangunan modern dengan ketahanan lebih tinggi.</p><p>- Genteng Keramik : Memberikan tampilan elegan dan daya serap air yang lebih rendah.</p></li><li><p><strong>Peluang Ekonomi dan Pemberdayaan Masyarakat:</strong></p><p>Industri genteng di Darmasaba memberikan peluang kerja bagi masyarakat setempat, terutama dalam bidang produksi, distribusi, hingga pemasaran. UMKM genteng juga mendukung keberlanjutan ekonomi desa dengan meningkatkan pendapatan warga serta mengurangi angka pengangguran.</p></li><li><p><strong>Inovasi dan Pengembangan Teknologi</strong></p><p>Beberapa pengrajin genteng di Darmasaba telah mulai mengadopsi teknologi modern dalam proses produksi, seperti:</p><p>- Penggunaan cetakan dan oven pembakaran efisien untuk meningkatkan kualitas dan kapasitas produksi.</p><p>- Teknik pelapisan anti bocor dan anti lumut untuk membuat genteng lebih tahan lama.</p><p>- Desain genteng inovatif yang lebih ringan dan mudah dipasang.</p></li><li><p><strong>Pemasaran dan Ekspansi Pasar</strong></p><p>Dengan meningkatnya pembangunan perumahan dan proyek konstruksi di Bali, permintaan akan genteng berkualitas terus bertambah. UMKM genteng Darmasaba dapat memperluas pasarnya dengan:</p><p>- Menjalin kerja sama dengan kontraktor dan pengembang properti.</p><p>- Mempromosikan produk melalui media sosial dan marketplace online.</p><p>- Menyediakan layanan custom sesuai kebutuhan pelanggan.</p></li><li><p><strong>Keberlanjutan dan Ramah Lingkungan:</strong></p><p>Industri genteng di Darmasaba berpotensi dikembangkan secara lebih ramah lingkungan dengan menerapkan metode produksi yang mengurangi limbah dan emisi. Pemanfaatan energi alternatif serta daur ulang bahan limbah dapat membantu menciptakan industri yang lebih berkelanjutan.</p></li></ol><p>UMKM genteng di Desa Darmasaba memiliki potensi besar untuk terus berkembang sebagai sektor industri unggulan. Dengan inovasi, pemasaran yang lebih luas, serta dukungan dari pemerintah dan masyarakat, industri ini dapat meningkatkan kesejahteraan warga dan memperkuat perekonomian desa.</p>"
},
{
"id": "cmdyklax3000bvn6fdu53f3xq",
"name": "Peternakan Ikan Lele",
"deskripsi": "Desa Darmasaba, yang terletak di Kecamatan Abiansemal, Kabupaten Badung, memiliki potensi besar dalam sektor peternakan lele. Dengan kondisi lingkungan yang mendukung serta meningkatnya permintaan ikan lele di pasaran, budidaya ikan lele dapat menjadi salah satu sektor ekonomi unggulan yang mampu meningkatkan kesejahteraan masyarakat desa.",
"content": "<p>Desa Darmasaba, yang terletak di Kecamatan Abiansemal, Kabupaten Badung, memiliki potensi besar dalam sektor peternakan lele. Dengan kondisi lingkungan yang mendukung serta meningkatnya permintaan ikan lele di pasaran, budidaya ikan lele dapat menjadi salah satu sektor ekonomi unggulan yang mampu meningkatkan kesejahteraan masyarakat desa.</p><p>Potensi dan Peran Peternakan Ikan Lele:</p><ol><li><p><strong>Kondisi Lingkungan yang Mendukung</strong></p><p>Darmasaba memiliki sumber air yang cukup serta iklim yang cocok untuk budidaya ikan lele. Kolam-kolam budidaya dapat dibuat dengan berbagai sistem, seperti:</p><p>- Kolam Terpal : Mudah dibuat dan lebih efisien dalam perawatan.</p><p>- Kolam Beton : Lebih tahan lama dan cocok untuk produksi skala besar.</p><p>- Sistem Bioflok : Teknologi modern yang dapat meningkatkan kepadatan ikan dan mengurangi limbah.</p></li><li><p><strong>Permintaan Pasar yang Tinggi:</strong></p><p>Lele merupakan salah satu jenis ikan yang memiliki permintaan tinggi di Bali, baik untuk konsumsi rumah tangga, warung makan, hingga restoran. Produk olahan seperti lele goreng, pecel lele, dan abon lele semakin diminati, membuka peluang besar bagi peternak lele di Darmasaba.</p></li><li><p><strong>Inovasi dan Pengembangan Teknologi</strong></p><p>Beberapa pengrajin genteng di Darmasaba telah mulai mengadopsi teknologi modern dalam proses produksi, seperti:</p><p>- Penggunaan cetakan dan oven pembakaran efisien untuk meningkatkan kualitas dan kapasitas produksi.</p><p>- Teknik pelapisan anti bocor dan anti lumut untuk membuat genteng lebih tahan lama.</p><p>- Desain genteng inovatif yang lebih ringan dan mudah dipasang.</p></li><li><p><strong>Pemasaran dan Ekspansi Pasar</strong></p><p>Dengan meningkatnya pembangunan perumahan dan proyek konstruksi di Bali, permintaan akan genteng berkualitas terus bertambah. UMKM genteng Darmasaba dapat memperluas pasarnya dengan:</p><p>- Menjalin kerja sama dengan kontraktor dan pengembang properti.</p><p>- Mempromosikan produk melalui media sosial dan marketplace online.</p><p>- Menyediakan layanan custom sesuai kebutuhan pelanggan.</p></li><li><p><strong>Keberlanjutan dan Ramah Lingkungan:</strong></p><p>Industri genteng di Darmasaba berpotensi dikembangkan secara lebih ramah lingkungan dengan menerapkan metode produksi yang mengurangi limbah dan emisi. Pemanfaatan energi alternatif serta daur ulang bahan limbah dapat membantu menciptakan industri yang lebih berkelanjutan.</p></li></ol><p>UMKM genteng di Desa Darmasaba memiliki potensi besar untuk terus berkembang sebagai sektor industri unggulan. Dengan inovasi, pemasaran yang lebih luas, serta dukungan dari pemerintah dan masyarakat, industri ini dapat meningkatkan kesejahteraan warga dan memperkuat perekonomian desa.</p>"
},
{
"id": "cmdykpkwf000gvn6ftas2cjje",
"name": "Jogging Track Tegeh Aban, Karang Gadon dan Munduk Uma Desa",
"deskripsi": "Desa Darmasaba, yang terletak di Kecamatan Abiansemal, Kabupaten Badung, memiliki potensi wisata olahraga dan rekreasi melalui Jogging Track Tegeh Aban, Karang Gadon, dan Munduk Uma Desa. Jalur jogging ini tidak hanya menjadi fasilitas olahraga bagi warga, tetapi juga berpotensi dikembangkan sebagai destinasi wisata sehat berbasis alam yang menarik bagi wisatawan lokal maupun luar daerah.",
"content": "<p>Desa Darmasaba, yang terletak di Kecamatan Abiansemal, Kabupaten Badung, memiliki potensi wisata olahraga dan rekreasi melalui Jogging Track Tegeh Aban, Karang Gadon, dan Munduk Uma Desa. Jalur jogging ini tidak hanya menjadi fasilitas olahraga bagi warga, tetapi juga berpotensi dikembangkan sebagai destinasi wisata sehat berbasis alam yang menarik bagi wisatawan lokal maupun luar daerah.</p><p>Potensi dan Peran Jogging Track Tegeh Aban, Karang Gadon dan Munduk Uma Desa:</p><ol><li><p><strong>Keindahan Alam dan Udara Segar:</strong></p><p>Jogging track yang membentang di Tegeh Aban, Karang Gadon, dan Munduk Uma Desa menawarkan pemandangan alam yang asri dengan udara segar khas pedesaan. Jalur ini melewati area persawahan hijau, perkebunan, serta hutan kecil yang memberikan pengalaman jogging yang lebih menyenangkan dan menenangkan.</p></li><li><p><strong>Fasilitas Olahraga dan Rekreasi</strong></p><p>Selain untuk jogging, jalur ini juga cocok digunakan untuk:</p><p>- Bersepeda santai : Jalur yang nyaman untuk pecinta sepeda.</p><p>- Trekking ringan : Cocok bagi wisatawan yang ingin menikmati suasana pedesaan.</p><p>- Meditasi dan Yoga : Area yang tenang dan alami, ideal untuk relaksasi.</p></li><li><p><strong>Destinasi Wisata Sehat dan Edukasi:</strong></p><p>Jogging track ini berpotensi dikembangkan sebagai wisata sehat berbasis alam, di mana pengunjung bisa menikmati udara segar sambil berolahraga. Selain itu, jalur ini dapat dijadikan sebagai rute edukasi lingkungan, mengenalkan keanekaragaman hayati, pertanian, serta kehidupan masyarakat desa.</p></li><li><p><strong>Potensi Ekonomi bagi Masyarakat</strong></p><p>Dengan meningkatnya jumlah pengunjung, masyarakat sekitar dapat memanfaatkan peluang usaha seperti:</p><p>- Warung sehat dan kuliner lokal : Menyediakan makanan dan minuman sehat bagi para pengunjung.</p><p>- Jasa penyewaan sepeda : Menarik bagi wisatawan yang ingin berkeliling lebih jauh.</p><p>- Pemandu wisata lokal : Memberikan pengalaman lebih bagi wisatawan yang ingin mengenal sejarah dan budaya desa.</p></li><li><p><strong>Pengembangan Berkelanjutan:</strong></p><p>Agar semakin menarik, jogging track ini bisa dilengkapi dengan fasilitas tambahan seperti tempat istirahat, spot foto alami, papan informasi tentang flora dan fauna, serta area taman bunga untuk mempercantik jalur jogging.</p></li></ol><p>Jogging Track Tegeh Aban, Karang Gadon, dan Munduk Uma Desa memiliki potensi besar sebagai destinasi wisata sehat dan olahraga berbasis alam. Dengan pengelolaan yang baik serta dukungan dari pemerintah desa dan masyarakat, jalur ini bisa menjadi ikon baru Desa Darmasaba yang menarik bagi wisatawan serta meningkatkan perekonomian warga setempat.</p>"
},
{
"id": "cmdykr76v000jvn6fqngibbmq",
"name": "Dam Tanah Putih",
"deskripsi": "Desa Darmasaba, yang terletak di Kecamatan Abiansemal, Kabupaten Badung, memiliki potensi wisata olahraga dan rekreasi melalui Jogging Track Tegeh Aban, Karang Gadon, dan Munduk Uma Desa. Jalur jogging ini tidak hanya menjadi fasilitas olahraga bagi warga, tetapi juga berpotensi dikembangkan sebagai destinasi wisata sehat berbasis alam yang menarik bagi wisatawan lokal maupun luar daerah.",
"content": "<p>Desa Darmasaba, yang terletak di Kecamatan Abiansemal, Kabupaten Badung, memiliki Dam Tanah Putih sebagai salah satu potensi desa yang bernilai strategis. Selain berfungsi sebagai infrastruktur pengairan, dam ini juga memiliki potensi untuk dikembangkan sebagai destinasi wisata alam, edukasi, dan rekreasi bagi masyarakat lokal maupun wisatawan.</p><p>Potensi dan Peran Dam Tanah Putih:</p><ol><li><p><strong>Fungsi Utama Sebagai Sumber Pengairan</strong></p><p>Dam Tanah Putih memiliki peran penting dalam sistem irigasi yang menopang sektor pertanian di Darmasaba. Air dari dam ini digunakan untuk:</p><p>- Mengairi sawah dan ladang : Menjamin ketersediaan air bagi petani sepanjang tahun.</p><p>- Menjaga keseimbangan ekosistem : Menjadi habitat bagi ikan air tawar dan berbagai biota air.</p><p>- Menampung air hujan : Membantu mengurangi risiko banjir dan kekeringan.</p></li><li><p><strong>Potensi Wisata Alam dan Rekreasi</strong></p><p>Dengan pemandangan alam yang asri dan suasana yang sejuk, Dam Tanah Putih memiliki potensi besar untuk dikembangkan sebagai tempat wisata alam. Beberapa kegiatan yang bisa dikembangkan di area ini antara lain:</p><p>- Trekking dan jogging di sekitar dam : Menikmati udara segar dan pemandangan indah.</p><p>- Berkemah dan piknik : Cocok untuk keluarga dan komunitas yang ingin menikmati alam.</p><p>- Wisata air : Seperti pemancingan atau wisata perahu kecil yang dapat menarik wisatawan.</p><p>- Spot fotografi alam : Keindahan dam dan sekitarnya menjadi latar yang menarik bagi para fotografer.</p></li><li><p><strong>Potensi Ekonomi dan UMKM Lokal</strong></p><p>Dengan pengembangan dam sebagai destinasi wisata, masyarakat sekitar dapat memanfaatkan peluang usaha seperti:</p><p>- Warung makan dan jajanan tradisional : Menyediakan makanan khas Bali bagi wisatawan.</p><p>- Jasa penyewaan alat rekreasi : Seperti pancing atau perahu kecil.</p><p>- Produk kerajinan tangan dan suvenir : Oleh-oleh khas Darmasaba yang menarik bagi pengunjung.</p></li><li><p><strong>Pengembangan Konservasi dan Edukasi Lingkungan</strong></p><p>Dam Tanah Putih juga bisa menjadi tempat edukasi lingkungan dengan konsep konservasi, di mana pengunjung bisa belajar tentang:</p><p>- Pengelolaan sumber daya air yang berkelanjutan.</p><p>- Keanekaragaman hayati di sekitar dam.</p><p>- Pentingnya ekosistem perairan bagi pertanian dan kehidupan masyarakat.</p></li></ol><p>Dengan berbagai fungsi dan keindahannya, Dam Tanah Putih memiliki potensi besar untuk dikembangkan sebagai destinasi wisata alam, rekreasi, serta edukasi lingkungan. Dengan pengelolaan yang baik dan dukungan dari masyarakat serta pemerintah desa, dam ini dapat menjadi aset penting bagi Darmasaba, baik dari sisi ekonomi maupun kelestarian lingkungan.</p>"
},
{
"id": "cmdyku9qh000mvn6ft76322sv",
"name": "UMKM",
"deskripsi": "Desa Darmasaba, yang terletak di Kecamatan Abiansemal, Kabupaten Badung, memiliki potensi besar dalam sektor Usaha Mikro, Kecil, dan Menengah (UMKM). Keberadaan UMKM di desa ini tidak hanya menjadi motor penggerak ekonomi lokal, tetapi juga mendukung pelestarian budaya dan kearifan lokal melalui berbagai produk unggulan.",
"content": "<p>Desa Darmasaba, yang terletak di Kecamatan Abiansemal, Kabupaten Badung, memiliki potensi besar dalam sektor Usaha Mikro, Kecil, dan Menengah (UMKM). Keberadaan UMKM di desa ini tidak hanya menjadi motor penggerak ekonomi lokal, tetapi juga mendukung pelestarian budaya dan kearifan lokal melalui berbagai produk unggulan.</p><p>Potensi dan Peran UMKM:</p><ol><li><p><strong>Kerajinan Tangan dan Produk Lokal</strong></p><p>Darmasaba memiliki banyak pengrajin yang menghasilkan produk unik dengan nilai seni tinggi, seperti:</p><p>- Genteng dan bahan bangunan tradisional : Genteng khas Darmasaba yang berkualitas tinggi.</p><p>- Kerajinan anyaman dan ukiran : Produk berbasis rotan dan kayu yang banyak diminati pasar lokal dan internasional.</p><p>- Pakaian adat dan kain tradisional : Mendukung pelestarian budaya Bali.</p></li><li><p><strong>Industri Kuliner Khas Darmasaba</strong></p><p>Kuliner khas desa ini memiliki potensi besar untuk dikembangkan sebagai bisnis UMKM, seperti:</p><p>- Babi Guling : Salah satu kuliner favorit yang banyak diminati wisatawan.</p><p>- Jajanan tradisional Bali : Seperti laklak, jaja uli, dan klepon yang masih dibuat dengan cara tradisional.</p><p>- Olahan ikan lele : Seperti abon lele, lele asap, dan pecel lele yang memiliki pasar luas.</p></li><li><p><strong>UMKM Berbasis Pengolahan Pangan</strong></p><p>Beberapa UMKM di Darmasaba mengolah hasil pertanian dan peternakan menjadi produk bernilai tambah, seperti:</p><p>- Keripik singkong dan pisang : Camilan sehat berbasis bahan lokal.</p><p>- Olahan kelapa : Seperti minyak kelapa murni dan gula aren.</p><p>- Produk herbal dan jamu : Menggunakan bahan-bahan alami dari tanaman lokal.</p></li><li><p><strong>Dukungan dan Pengembangan UMKM</strong></p><p>Agar UMKM di Darmasaba semakin berkembang, perlu adanya:</p><p>- Pelatihan dan pendampingan usaha : Untuk meningkatkan kualitas produk dan manajemen usaha.</p><p>- Pemasaran digital : Menggunakan media sosial dan e-commerce untuk menjangkau pasar lebih luas.</p><p>- Kerja sama dengan BUMDes Pudak Mesari : Untuk membantu akses modal dan pengelolaan bisnis yang lebih profesional.</p></li></ol><p>UMKM di Desa Darmasaba memiliki potensi besar dalam berbagai sektor, mulai dari kerajinan tangan, kuliner, hingga wisata berbasis masyarakat. Dengan inovasi, pemasaran yang lebih luas, dan dukungan dari pemerintah desa serta masyarakat, UMKM Darmasaba dapat berkembang pesat dan menjadi tulang punggung perekonomian desa.</p>"
}
]

View File

@@ -0,0 +1,22 @@
[
{
"id": "cmdwq7qp60008vntw67s4j6sq",
"name": "Pembiayaan",
"jumlah": "295 M"
},
{
"id": "cmdwpsprc0003vntw9o4d33dr",
"name": "Pendapatan",
"jumlah": "495 M"
},
{
"id": "cmdwqe8xl000cvntwcuqpvdhp",
"name": "Belanja",
"jumlah": "395 M"
},
{
"id": "cmdwqq4b6000gvntwm07rinx4",
"name": "Pangan",
"jumlah": "285 M"
}
]

View File

@@ -0,0 +1,128 @@
[
{
"id": "cmds9h9ko000pvnberdjnx64b",
"name": "1.1 ADANYA PERDES/KEPUTUSAN KEPALA DESA/SOP TENTANG PERENCANAAN, PELAKSANAAN, PENATAUSAHAAN DAN PERTANGGUNG JAWABAN APBDES BESERTA IMPLEMENTASINYA",
"deskripsi": "<p>ADANYA PERDES/KEPUTUSAN KEPALA DESA/SOP TENTANG PERENCANAAN, PELAKSANAAN, PENATAUSAHAAN DAN PERTANGGUNG JAWABAN APBDES BESERTA IMPLEMENTASINYA</p>",
"kategoriId": "cmds9es2o000ivnbe1o0swrvh",
"fileId": ""
},
{
"id": "cmds9sjmz000svnbesv2133of",
"name": "1.2 ADANYA PERDES/KEPUTUSAN KEPALA DESA/SOP MENGENAI MEKANISME EVALUASI KINERJA PERANGKAT DESA",
"deskripsi": "<p>ADANYA PERDES/KEPUTUSAN KEPALA DESA/SOP MENGENAI MEKANISME EVALUASI KINERJA PERANGKAT DESA</p>",
"kategoriId": "cmds9es2o000ivnbe1o0swrvh",
"fileId": ""
},
{
"id": "cmds9tcpi000vvnbev3ebtlnt",
"name": "1.3 ADANYA PERDES/KEPUTUSAN KEPALA DESA/SOP TENTANG PENGENDALIAN GRATIFIKASI, SUAP DAN KONFLIK KEPENTINGAN",
"deskripsi": "<p>ADANYA PERDES/KEPUTUSAN KEPALA DESA/SOP TENTANG PENGENDALIAN GRATIFIKASI, SUAP DAN KONFLIK KEPENTINGAN</p>",
"kategoriId": "cmds9es2o000ivnbe1o0swrvh",
"fileId": ""
},
{
"id": "cmds9twvj000yvnbep0pq8dzf",
"name": "1.4 PERJANJIAN KERJA SAMA ANTARA PELAKSANA KEGIATAN ANGGARAN DENGAN PIHAK PENYEDIA, DAN TELAH MELALUI PROSES PENGADAAN BARANG/JASA DI DESA",
"deskripsi": "<p>PERJANJIAN KERJA SAMA ANTARA PELAKSANA KEGIATAN ANGGARAN DENGAN PIHAK PENYEDIA, DAN TELAH MELALUI PROSES PENGADAAN BARANG/JASA DI DESA</p>",
"kategoriId": "cmds9es2o000ivnbe1o0swrvh",
"fileId": ""
},
{
"id": "cmds9ugap0011vnbe118yv871",
"name": "1.5 ADANYA PERDES/KEPUTUSAN KEPALA DESA/SOP TENTANG PAKTA INTEGRITAS DAN SEJENISNYA",
"deskripsi": "<p>ADANYA PERDES/KEPUTUSAN KEPALA DESA/SOP TENTANG PAKTA INTEGRITAS DAN SEJENISNYA</p>",
"kategoriId": "cmds9es2o000ivnbe1o0swrvh",
"fileId": ""
},
{
"id": "cmdsa35310014vnbe6qy6l1rz",
"name": "2.1 ADANYA KEGIATAN PENGAWASAN DAN EVALUASI KINERJA PERANGKAT DESA",
"deskripsi": "<p>ADANYA KEGIATAN PENGAWASAN DAN EVALUASI KINERJA PERANGKAT DESA</p>",
"kategoriId": "cmds9f2ua000jvnbeksu1sfwm",
"fileId": ""
},
{
"id": "cmdsa46590017vnbepp3noso1",
"name": "2.2 ADANYA TINDAK LANJUT HASIL PEMBINAAN, PETUNJUK, ARAH, PENGAWASAN, DAN PEMERIKSAAN DARI PEMERINTAH PUSAT/DAERAH",
"deskripsi": "<p>ADANYA TINDAK LANJUT HASIL PEMBINAAN, PETUNJUK, ARAH, PENGAWASAN, DAN PEMERIKSAAN DARI PEMERINTAH PUSAT/DAERAH</p>",
"kategoriId": "cmds9f2ua000jvnbeksu1sfwm",
"fileId": ""
},
{
"id": "cmdsa5m7z001avnbe4cvfrcz0",
"name": "2.3 TIDAK ADANYA APARATUR DESA DALAM 3(TIGA) TAHUN TERAKHIR YANG TERJERAT TINDAKAN PIDANA KORUPSI",
"deskripsi": "<p>TIDAK ADANYA APARATUR DESA DALAM 3(TIGA) TAHUN TERAKHIR YANG TERJERAT TINDAKAN PIDANA KORUPSI</p>",
"kategoriId": "cmds9f2ua000jvnbeksu1sfwm",
"fileId": ""
},
{
"id": "cmdsa8q5q001dvnbemch8j24x",
"name": "3.1 ADANYA LAYANAN PENGADUAN BAGI MASYARAKAT",
"deskripsi": "<p>ADANYA LAYANAN PENGADUAN BAGI MASYARAKAT</p>",
"kategoriId": "cmds9fr73000kvnbe6w281dcl",
"fileId": ""
},
{
"id": "cmdsa9lbi001gvnbequn2ba7m",
"name": "3.2 ADANYA SURVEY KEPUASAN MASYARAKAT (SKM) TERHADAP LAYANAN PEMERINTAH DESA",
"deskripsi": "<p>ADANYA SURVEY KEPUASAN MASYARAKAT (SKM) TERHADAP LAYANAN PEMERINTAH DESA</p>",
"kategoriId": "cmds9fr73000kvnbe6w281dcl",
"fileId": ""
},
{
"id": "cmdsaa7aq001jvnbeizh04e67",
"name": "3.3 ADANYA KETERBUKAAN AKSES MASYARAKAT TERHADAP INFORMASI LAYANAN PEMERINTAH DESA (KESEHATAN, PENDIDIKAN, SOSIAL, LINGKUNGAN, TRANTIBUMLINMAS, PEKERJAAN UMUM) PEMBANGUNAN, KEPENDUDUKAN, KEUANGAN, DAN PELAYANAN LAINNYA",
"deskripsi": "<p>ADANYA KETERBUKAAN AKSES MASYARAKAT TERHADAP INFORMASI LAYANAN PEMERINTAH DESA (KESEHATAN, PENDIDIKAN, SOSIAL, LINGKUNGAN, TRANTIBUMLINMAS, PEKERJAAN UMUM) PEMBANGUNAN, KEPENDUDUKAN, KEUANGAN, DAN PELAYANAN LAINNYA</p>",
"kategoriId": "cmds9fr73000kvnbe6w281dcl",
"fileId": ""
},
{
"id": "cmdsaaw8d001mvnbek3tfefrk",
"name": "3.4 ADANYA MEDIA INFORMASI TENTANG APBDES DI BALAI DESA DAN/ATAU TEMPAT LAIN YANG MUDAH DIAKSES OLEH MASYARAKAT",
"deskripsi": "<p>ADANYA MEDIA INFORMASI TENTANG APBDES DI BALAI DESA DAN/ATAU TEMPAT LAIN YANG MUDAH DIAKSES OLEH MASYARAKAT</p>",
"kategoriId": "cmds9fr73000kvnbe6w281dcl",
"fileId": ""
},
{
"id": "cmdsabhif001pvnbepm06hry6",
"name": "3.5 ADANYA MAKLUMAT PELAYANAN",
"deskripsi": "<p>ADANYA MAKLUMAT PELAYANAN</p>",
"kategoriId": "cmds9fr73000kvnbe6w281dcl",
"fileId": ""
},
{
"id": "cmdsag40b001svnbe7krq9khc",
"name": "4.1 ADANYA PARTISIPASI DAN KETERLIBATAN MASYARAKAT DALAM PENYUSUNAN RKP DESA",
"deskripsi": "<p>ADANYA PARTISIPASI DAN KETERLIBATAN MASYARAKAT DALAM PENYUSUNAN RKP DESA</p>",
"kategoriId": "cmds9g5ow000lvnbel3rkkwrv",
"fileId": ""
},
{
"id": "cmdsagkaf001vvnbejo26w8sa",
"name": "4.2 ADANYA KESADARAN MASYARAKAT DALAM MENCEGAH TERJADINYA PRAKTIK GRATIFIKASI, SUAP DAN KONFLIK KEPENTINGAN",
"deskripsi": "<p>ADANYA KESADARAN MASYARAKAT DALAM MENCEGAH TERJADINYA PRAKTIK GRATIFIKASI, SUAP DAN KONFLIK KEPENTINGAN</p>",
"kategoriId": "cmds9g5ow000lvnbel3rkkwrv",
"fileId": ""
},
{
"id": "cmdsah4qe001yvnbeiy3mwrvb",
"name": "4.3 ADANYA KETERLIBATAN LEMBAGA KEMASYARAKATAN DALAM PELAKSANAAN PEMBANGUNAN DESA",
"deskripsi": "<p>ADANYA KETERLIBATAN LEMBAGA KEMASYARAKATAN DALAM PELAKSANAAN PEMBANGUNAN DESA</p>",
"kategoriId": "cmds9g5ow000lvnbel3rkkwrv",
"fileId": ""
},
{
"id": "cmdsak5vn0021vnbemg86aab4",
"name": "5.1 ADANYA BUDAYA LOKAL/HUKUM ADAT YANG MENDORONG UPAYA PENCEGAHAN TINDAK PIDANA KORUPSI",
"deskripsi": "<p>ADANYA BUDAYA LOKAL/HUKUM ADAT YANG MENDORONG UPAYA PENCEGAHAN TINDAK PIDANA KORUPSI</p>",
"kategoriId": "cmds9govb000mvnbesq8b4y99",
"fileId": ""
},
{
"id": "cmdsalc800024vnbezgulhgrb",
"name": "5.2 ADANYA TOKOH MASYARAKAT, TOKOH AGAMA, TOKOH ADAT, TOKOH PEMUDA, DAN KAUM PEREMPUAN YANG MENDORONG UPAYA PENCEGAHAN TINDAK PIDANA KORUPSI",
"deskripsi": "<p>ADANYA TOKOH MASYARAKAT, TOKOH AGAMA, TOKOH ADAT, TOKOH PEMUDA, DAN KAUM PEREMPUAN YANG MENDORONG UPAYA PENCEGAHAN TINDAK PIDANA KORUPSI</p>",
"kategoriId": "cmds9govb000mvnbesq8b4y99",
"fileId": ""
}
]

View File

@@ -0,0 +1,22 @@
[
{
"id": "cmds9es2o000ivnbe1o0swrvh",
"name": "PENGUATAN TATA LAKSANA"
},
{
"id": "cmds9f2ua000jvnbeksu1sfwm",
"name": "PENGUATAN PENGAWASAN"
},
{
"id": "cmds9fr73000kvnbe6w281dcl",
"name": "PENGUATAN KUALITAS PELAYANAN PUBLIK"
},
{
"id": "cmds9g5ow000lvnbel3rkkwrv",
"name": "PENGUATAN PARTISIPASI MASYARAKAT"
},
{
"id": "cmds9govb000mvnbesq8b4y99",
"name": "KEARIFAN LOKAL"
}
]

View File

@@ -0,0 +1,26 @@
[
{
"id" : "cmdzdewrs0003vnp0yh1klh0m",
"name" : "Penghargaan Bhawana Sewaka Nugraha kepada Perbekel Darmasaba",
"juara" : "Penghargaan Bhawana Sewaka Nugraha kepada Perbekel Darmasaba",
"deskripsi" : "<p>Pada hari Jumat, 27 Desember 2024, sebuah momen membanggakan tercipta bagi Desa Darmasaba. Perbekel Darmasaba, Ida Bagus Surya Prabhawa Manuaba, S.H., M.H., NL.P., menerima Penghargaan Bhawana Sewaka Nugraha sebagai Penggiat Lingkungan Hidup.<br><br>Penghargaan bergengsi ini diserahkan langsung oleh Bapak Irjen. Pol. (Purn.) Drs. Sang Made Mahendra Jaya, M.H., selaku Pejabat Gubernur Provinsi Bali, dalam sebuah acara resmi yang berlangsung di Gedung Jaya Sabha, Denpasar.<br><br>Penghargaan ini merupakan pengakuan atas dedikasi, kerja keras, dan komitmen Perbekel Darmasaba dalam menjaga kelestarian lingkungan serta menginspirasi masyarakat untuk menciptakan desa yang lebih hijau, bersih, dan berkelanjutan.<br><br>Semoga prestasi ini tidak hanya menjadi kebanggaan bagi Desa Darmasaba, tetapi juga memotivasi kita semua untuk terus menjaga lingkungan demi masa depan yang lebih baik. Mari bersama-sama wujudkan Bali yang lestari dan berkelanjutan!</p>"
},
{
"id" : "cmdzdlwqe0006vnp0lsrp5ybn",
"name" : "DESA DARMASABA KEMBALI MERAIH PERINGKAT V DALAM AJANG MANGUPURA AWARD",
"juara" : "5",
"deskripsi" : "<p>Bangga, Darmasaba Berprestasi!</p><p><br>Pada hari Senin, 18 November 2024, Desa Darmasaba kembali mencetak prestasi gemilang dengan meraih peringkat V dalam ajang bergengsi Mangupura Award. Penganugerahan ini berlangsung dalam rangkaian acara HUT Kota Mangupura ke-15 yang dihadiri oleh Drs. I Ketut Suiasa, S.H., Plt Bupati Badung, serta para tokoh penting lainnya.</p><p><br>Penghargaan ini merupakan buah kerja keras seluruh elemen masyarakat Darmasaba yang telah berpartisipasi aktif dalam memajukan desa. Proses menuju penghargaan ini melalui tahapan yang menantang, yaitu:<br>- Presentasi Desa pada tanggal 19 Juli 2024<br>- Verifikasi Faktual Lapangan oleh Tim Juri Mangupura Award pada tanggal 5 September 2024</p><p><br>✨ Penghargaan ini bukan hanya sebuah pengakuan, tetapi juga menjadi motivasi bagi Desa Darmasaba untuk terus meningkatkan inovasi, pelayanan, pembangunan serta transformasi digital.<br>Mari bersama kita lanjutkan semangat kolaborasi dan inovasi untuk menjadikan Darmasaba sebagai desa yang semakin unggul dan inspiratif!</p><p><br>#DesaDarmasaba<br>#MangupuraAward2024<br>#BanggaDarmasaba<br>#InovasiDesa<br>#HUTKotaMangupura15<br>#KemajuanDesa<br>#PemdesDarmasaba<br>#PerbekelDarmasaba<br>#DarmasabaBisa<br>#DarmasabaJuara<br>&nbsp;<br>@surya_prabhawa&nbsp;<br>@kecamatanabiansemal&nbsp;<br>@dpmdbadungkab&nbsp;<br>@pemkabbadung<br>@prokompimbadung&nbsp;<br>@seputar_darmasaba</p>"
},
{
"id" : "cmdzdorcb000avnp0ldlsx73f",
"name" : "JEGEG DARMASABA MERAIH JUARA 2 DUTA INVESTASI BADUNG 2024",
"juara" : "2",
"deskripsi" : "<p>JUARA 2</p><p>DUTA INVESTASI KAB. BADUNG</p><p>TAHUN 2024</p><p>Selamat kepada Ni Made Amelia Prasetya Putri (Jegeg Darmasaba Th 2023) telah berhasil mengharumkan nama Desa Darmasaba dengan meraih Juara 2 Duta Invenstasi Kab. Badung dalam ajang Badung Investment Week 2024. Setelah bersaing dengan 40an peserta lainnya dan support penuh dari Pemdes Darmasaba, usaha memang tidak pernah menghianati hasil.</p>"
},
{
"id" : "cmdzdq8ns000dvnp0v917pevj",
"name" : "DARMASABA BORONG JUARA BADUNG INVESTMENT AWARD 2024",
"juara" : "DARMASABA BORONG JUARA BADUNG INVESTMENT AWARD 2024",
"deskripsi" : "<p>DARMASABA MEMBORONG JUARA</p><p>Darmasaba bisa, Darmasaba juara,</p><p>Pemdes Darmasaba tak henti-henti menorehkan prestasi nie Semeton Darmasaba!</p><p>Pemdes Darmasaba berpartisipasi aktif dalam kegiatan Badung Invesment Week Tahun 2024 yang diselenggarakan oleh Dinas Penanaman Modal dan Pelayanan Terpadu Satu Pintu Kab. Badung. Pemdes Darmasaba melalui talenta-talenta terbaiknya mampu meraih prestasi dalam ajang tersebut diantaranya:</p><p>1. Juara 2 Lomba Video Pendek Potensi dan Peluang Investasi di Desa Kabupaten Badung 2024.</p><p>2. Juara 2 Duta Invenstasi Kabupaten Badung 2024.</p><p>3. Juara Favorit Lomba Video Pendek Potensi dan Peluang Investasi di Desa Kabupaten Badung 2024.</p><p>Penyerahan penghargaan lomba-lomba tersebut diserahkan dalam acara Badung Invesment Award 2024 pada hari Jumat (18/10/2024) bertempat di Balai Budaya Giri Nata Mandala Puspem Kab. Badung. Penghargaan Juara Lomba diserahkan langsung oleh Plt. Bupati Badung Drs. I Ketut Suiasa, S.H. didampingi Kadis DPMPTSP Kab. Badung Dr. Ir. I Made Agus Aryawan ST., MT.</p>"
}
]

View File

@@ -0,0 +1,14 @@
[
{
"id": "cmdwrolsl0000vnd3e24q5440",
"name": "Olahraga dan Kepemudaan"
},
{
"id": "cmdwrot900001vnd30b5kj96g",
"name": "Hukum dan Kesadaran Masyarakat"
},
{
"id": "cmdwrp0pr0002vnd35w6nkjh0",
"name": "Tata Kelola dan Inovasi Desa"
}
]

View File

@@ -0,0 +1,20 @@
[
{
"id": "cmdwrrxkh0005vnd3p5rxkiev",
"name": "Tim Bola Voli Putri Dharma Temaja meraih juara 3 dalam Turnamen Bola Voli Mangupura Cup 2024 kategori Putri Se-Bali",
"deskripsi": "<p>Tim Bola Voli Putri Dharma Temaja meraih juara 3 dalam Turnamen Bola Voli Mangupura Cup 2024 kategori Putri Se-Bali</p>",
"kategoriId": "cmdwrolsl0000vnd3e24q5440"
},
{
"id": "cmdwrzs740008vnd329ysez5x",
"name": "Prestasi Juara 3 dalam Lomba Keluarga Sadar Hukum Kabupaten Badung Tahun 2024",
"deskripsi": "<p>Prestasi Juara 3 dalam Lomba Keluarga Sadar Hukum Kabupaten Badung Tahun 2024</p>",
"kategoriId": "cmdwrot900001vnd30b5kj96g"
},
{
"id": "cmdws0sgq000bvnd32o7m94im",
"name": "Peringkat 5 Dalam Ajang Bergengsi Mangupura Award",
"deskripsi": "<p>Peringkat 5 Dalam Ajang Bergengsi Mangupura Award</p>",
"kategoriId": "cmdwrp0pr0002vnd35w6nkjh0"
}
]

View File

@@ -1,8 +0,0 @@
[
{
"id": "edit",
"name": "I.B Surya Prabhawa Manuaba, S.H., M.H.",
"position": "Perbekel Darmasaba periode 2021-2027"
}
]

View File

@@ -0,0 +1,32 @@
[
{
"id": "cmds8w2q60002vnbe6i8qhkuo",
"name": "Telephone Desa Darmasaba",
"iconUrl": "081239580000"
},
{
"id": "cmds8z7u20005vnbegyyvnbk0",
"name": "Email Desa Darmasaba",
"iconUrl": "desadarmasaba@badungkab.go.id"
},
{
"id": "cmds9023u0008vnbe3oxmhwyf",
"name": "Desa Darmasaba",
"iconUrl": "https://www.youtube.com/channel/UCtPw9WOQO7d2HIKzKgel4Xg"
},
{
"id": "cmds90oul000bvnbe2bqkptoi",
"name": "Pemerintah Desa Darmasaba",
"iconUrl": "https://www.facebook.com/DarmasabaDesaku"
},
{
"id": "cmds91i4e000evnbe8gtf1gub",
"name": "ddarmasaba",
"iconUrl": "https://www.instagram.com/ddarmasaba/"
},
{
"id": "cmds92de5000hvnbemlu6sq5x",
"name": "desa.darmasaba",
"iconUrl": "https://www.tiktok.com/@desa.darmasaba?is_from_webapp=1&sender_device=pc"
}
]

View File

@@ -0,0 +1,7 @@
[
{
"id": "edit",
"name": "I.B Surya Prabhawa Manuaba, S.H., M.H.",
"position": "Perbekel Darmasaba periode 2021-2027"
}
]

View File

@@ -0,0 +1,50 @@
[
{
"id": "cmdr7039z0002vn5rttctt9hn",
"name": "Davest",
"description": "Darmasaba Village Festval",
"link": "https://darmasaba.desa.id/berita/55862-rakor-davest-2024"
},
{
"id": "cmdr755pf0005vn5rp8tyuubw",
"name": "Dmangan",
"description": "Darmasaba Aman Pangan",
"link": "https://darmasaba.desa.id/berita/61452-kader-d-mangan-berhasil-meraih-prestasi-dalam-ajang-lomba-banjar-bali-quis-bbq-tahun-2024"
},
{
"id": "cmdr76nqk0008vn5rdddvcxnr",
"name": "Bicara Darmasaba",
"description": "Bicara Darmasaba",
"link": "https://darmasaba.desa.id/berita/42506-bicara-darmasaba"
},
{
"id": "cmdr77vbw000bvn5rvpmoq31s",
"name": "Bares",
"description": "Darmasaba Recycling Stock/Exchange",
"link": "http://darmasaba.desa.id/berita/56722-bares"
},
{
"id": "cmdr7bxtp000evn5rmy85wihx",
"name": "Sajjana Dharma Raksaka",
"description": "Sajjana Dharma Raksaka",
"link": "https://ppid.badungkab.go.id/storage/dokumen/5RS9dldGkrgzMQq6bKdZsqsVRHI8gffWv4PGfb3r.pdf"
},
{
"id": "cmdr7dlnk000hvn5r9lur3z35",
"name": "PDKT",
"description": "Perangkat Desa Kuat Teknologi",
"link": "https://darmasaba.desa.id/berita/53752-p-d-k-t"
},
{
"id": "cmdr7ftob000mvn5rfhgdtg8v",
"name": "GM",
"description": "Galah Melah",
"link": "https://darmasaba.desa.id/berita/52880-galah-melah"
},
{
"id": "cmdr7glue000pvn5r6onzslju",
"name": "Inovasi Desa Darmasaba",
"description": "Inovasi Desa Darmasaba",
"link": "https://darmasaba.desa.id/produk-lokal-desa"
}
]

View File

@@ -0,0 +1,114 @@
[
{
"id": "cmdsjzdl30002vneknuvo4irv",
"name": "Desa Tanpa Kemiskinan",
"jumlah": "52.62",
"imageId": ""
},
{
"id": "cmdskargd0005vnek0mu2ofk9",
"name": "Desa Tanpa Kelaparan",
"jumlah": "35.75",
"imageId": ""
},
{
"id": "cmdskbvl0008vnek5dmieatb",
"name": "Desa Sehat Dan Sejahtera",
"jumlah": "77.37",
"imageId": ""
},
{
"id": "cmdskcx91000bvneko7tuaoqa",
"name": "Pendidikan Desa Berkualitas",
"jumlah": "34.11",
"imageId": ""
},
{
"id": "cmdskjare000evnek1hglu0x8",
"name": "Keterlibatan Perempuan Desa",
"jumlah": "45.70",
"imageId": ""
},
{
"id": "cmdskqcpc0002vnvnqjkqgm92",
"name": "Desa Layak Air Bersih Dan Sanitasi",
"jumlah": "48.54",
"imageId": ""
},
{
"id": "cmdsktl3x0005vnvne15seefw",
"name": "Desa Berenergi Bersih Dan Terbarukan",
"jumlah": "99.64",
"imageId": ""
},
{
"id": "cmdskuncw0008vnvcsdqoeog",
"name": "Pertumbuhan Ekonomi Desa Merata",
"jumlah": "40.92",
"imageId": ""
},
{
"id": "cmdskw83j000bvvn9szqrea6",
"name": "Infrastruktur Dan Inovasi Desa Sesuai Kebutuhan",
"jumlah": "35.37",
"imageId": ""
},
{
"id": "cmdskwrq7000envnvy0c5nbgf",
"name": "Desa Tanpa Kesenjangan",
"jumlah": "35.47",
"imageId": ""
},
{
"id": "cmdskxivx000hnvnvsx520gv1",
"name": "Kawasan Pemukiman Desa Aman Dan Nyaman",
"jumlah": "40.35",
"imageId": ""
},
{
"id": "cmdskzg4c000kvnnkiv61gkt",
"name": "Konsumsi Dan Produksi Desa Sadar Lingkungan",
"jumlah": "16.67",
"imageId": ""
},
{
"id": "cmdsl07lk000nvnnvnrepsdy5m",
"name": "Desa Tanggap Perubahan Iklim",
"jumlah": "0.00",
"imageId": ""
},
{
"id": "cmdsl10rq000qvnvnlch9c1yv",
"name": "Desa Peduli Lingkungan Laut",
"jumlah": "50.00",
"imageId": ""
},
{
"id": "cmdsl1mc2000tvnvn357n8usi",
"name": "Desa Peduli Lingkungan Darat",
"jumlah": "0.00",
"imageId": ""
},
{
"id": "cmdsl2bx3000wvnvntshi4gnj",
"name": "Desa Damai Berkeadilan",
"jumlah": "78.65",
"imageId": ""
},
{
"id": "cmdsl2yz3000zvnvnmf60ok7q",
"name": "Kemitraan Untuk Pembangunan Desa",
"jumlah": "20.00",
"imageId": ""
},
{
"id": "cmdsl492h0012vnvnmckm3n2x",
"name": "Kelembagaan Desa Dinamis Dan Budaya Desa Adaptif",
"jumlah": "47.22",
"imageId": ""
}
]

View File

@@ -50,52 +50,54 @@ model AppMenuChild {
// ========================================= FILE STORAGE ========================================= //
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
category String // "image" / "document" / "other"
Berita Berita[]
PotensiDesa PotensiDesa[]
Posyandu Posyandu[]
StrukturPPID StrukturPPID[]
GalleryFoto GalleryFoto[]
PelayananSuratKeterangan PelayananSuratKeterangan[]
Penghargaan Penghargaan[]
ProfileDesaImage ProfileDesaImage[]
ProfilePPID ProfilePPID[]
ProfilPerbekel ProfilPerbekel[]
Puskesmas Puskesmas[]
ProgramKesehatan ProgramKesehatan[]
PenangananDarurat PenangananDarurat[]
KontakDarurat KontakDarurat[]
InfoWabahPenyakit InfoWabahPenyakit[]
KeamananLingkungan KeamananLingkungan[]
MenuTipsKeamanan MenuTipsKeamanan[]
Pelapor Pelapor[]
PasarDesa PasarDesa[]
KontakDaruratKeamanan KontakDaruratKeamanan[]
KontakItem KontakItem[]
Pegawai Pegawai[]
DesaDigital DesaDigital[]
KolaborasiInovasi KolaborasiInovasi[]
InfoTekno InfoTekno[]
PengaduanMasyarakat PengaduanMasyarakat[]
KegiatanDesa KegiatanDesa[]
ProgramInovasi ProgramInovasi[]
PejabatDesa PejabatDesa[]
MediaSosial MediaSosial[]
DesaAntiKorupsi DesaAntiKorupsi[]
SDGSDesa SDGSDesa[]
APBDesImage APBDes[] @relation("APBDesImage")
APBDesFile APBDes[] @relation("APBDesFile")
PrestasiDesa PrestasiDesa[]
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
category String // "image" / "document" / "other"
Berita Berita[]
PotensiDesa PotensiDesa[]
Posyandu Posyandu[]
StrukturPPID StrukturPPID[]
GalleryFoto GalleryFoto[]
Pelapor Pelapor[]
Penghargaan Penghargaan[]
ProfileDesaImage ProfileDesaImage[]
ProfilePPID ProfilePPID[]
ProfilPerbekel ProfilPerbekel[]
Puskesmas Puskesmas[]
ProgramKesehatan ProgramKesehatan[]
PenangananDarurat PenangananDarurat[]
KontakDarurat KontakDarurat[]
InfoWabahPenyakit InfoWabahPenyakit[]
KeamananLingkungan KeamananLingkungan[]
MenuTipsKeamanan MenuTipsKeamanan[]
PelayananSuratKeteranganImage PelayananSuratKeterangan[] @relation("PelayananSuratKeteranganImage")
PelayananSuratKeteranganImage2 PelayananSuratKeterangan[] @relation("PelayananSuratKeteranganImage2")
PasarDesa PasarDesa[]
KontakDaruratKeamanan KontakDaruratKeamanan[]
KontakItem KontakItem[]
Pegawai Pegawai[]
DesaDigital DesaDigital[]
KolaborasiInovasi KolaborasiInovasi[]
InfoTekno InfoTekno[]
PengaduanMasyarakat PengaduanMasyarakat[]
KegiatanDesa KegiatanDesa[]
ProgramInovasi ProgramInovasi[]
PejabatDesa PejabatDesa[]
MediaSosial MediaSosial[]
DesaAntiKorupsi DesaAntiKorupsi[]
SDGSDesa SDGSDesa[]
APBDesImage APBDes[] @relation("APBDesImage")
APBDesFile APBDes[] @relation("APBDesFile")
PrestasiDesa PrestasiDesa[]
DataPerpustakaan DataPerpustakaan[]
@@ -130,15 +132,15 @@ model ProgramInovasi {
}
model MediaSosial {
id String @id @default(cuid())
id String @id @default(cuid())
name String
image FileStorage @relation(fields: [imageId], references: [id])
imageId String
iconUrl String? @db.VarChar(255)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
image FileStorage? @relation(fields: [imageId], references: [id])
imageId String?
iconUrl String? @db.VarChar(255)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
isActive Boolean @default(true)
isActive Boolean @default(true)
}
//========================================= PROFILE ========================================= //
@@ -148,8 +150,8 @@ model DesaAntiKorupsi {
deskripsi String @db.Text
kategori KategoriDesaAntiKorupsi @relation(fields: [kategoriId], references: [id])
kategoriId String
file FileStorage @relation(fields: [fileId], references: [id])
fileId String
file FileStorage? @relation(fields: [fileId], references: [id])
fileId String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
@@ -168,30 +170,30 @@ model KategoriDesaAntiKorupsi {
//========================================= SDGS Desa ========================================= //
model SDGSDesa {
id String @id @default(cuid())
name String @unique
id String @id @default(cuid())
name String @unique
jumlah String
image FileStorage @relation(fields: [imageId], references: [id])
imageId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
image FileStorage? @relation(fields: [imageId], references: [id])
imageId String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
//========================================= APBDes ========================================= //
model APBDes {
id String @id @default(cuid())
name String @unique
id String @id @default(cuid())
name String @unique
jumlah String
image FileStorage @relation("APBDesImage", fields: [imageId], references: [id])
imageId String
file FileStorage @relation("APBDesFile", fields: [fileId], references: [id])
fileId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
image FileStorage? @relation("APBDesImage", fields: [imageId], references: [id])
imageId String?
file FileStorage? @relation("APBDesFile", fields: [fileId], references: [id])
fileId String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
//========================================= PRESTASI DESA ========================================= //
@@ -313,14 +315,14 @@ model StrukturPPID {
}
model PosisiOrganisasiPPID {
id String @id @default(cuid())
nama String @db.VarChar(100)
deskripsi String? @db.Text
id String @id @default(cuid())
nama String @db.VarChar(100)
deskripsi String? @db.Text
hierarki Int
pegawai PegawaiPPID[]
strukturOrganisasi StrukturPPID[] // Relasi balik
parentId String?
parent PosisiOrganisasiPPID? @relation("Parent", fields: [parentId], references: [id])
parentId String?
parent PosisiOrganisasiPPID? @relation("Parent", fields: [parentId], references: [id])
children PosisiOrganisasiPPID[] @relation("Parent")
}
@@ -609,17 +611,28 @@ model KategoriBerita {
// ========================================= POTENSI DESA ========================================= //
model PotensiDesa {
id String @id @default(cuid())
name String
deskripsi String
kategori 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)
id String @id @default(cuid())
name String
deskripsi String
kategori KategoriPotensi? @relation(fields: [kategoriId], references: [id])
kategoriId 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)
}
model KategoriPotensi {
id String @id @default(cuid())
nama String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
PotensiDesa PotensiDesa[]
}
// ========================================= PENGUMUMAN ========================================= //
@@ -672,15 +685,17 @@ model GalleryVideo {
// ========================================= LAYANAN DESA ========================================= //
model PelayananSuratKeterangan {
id String @id @default(cuid())
id String @id @default(cuid())
name String
deskripsi String @db.Text
image FileStorage @relation(fields: [imageId], references: [id])
imageId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
deskripsi String @db.Text
image FileStorage? @relation("PelayananSuratKeteranganImage", fields: [imageId], references: [id])
imageId String?
image2 FileStorage? @relation("PelayananSuratKeteranganImage2", fields: [image2Id], references: [id])
image2Id String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
model PelayananTelunjukSaktiDesa {
@@ -721,8 +736,8 @@ model Penghargaan {
name String
juara String
deskripsi String @db.Text
image FileStorage @relation(fields: [imageId], references: [id])
imageId String
image FileStorage? @relation(fields: [imageId], references: [id])
imageId String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())

View File

@@ -1,4 +1,11 @@
import prisma from "@/lib/prisma";
import profilePejabatDesa from "./data/landing-page/profile/profile.json";
import penghargaan from "./data/landing-page/penghargaan/penghargaan.json";
import programInovasi from "./data/landing-page/profile/programInovasi.json";
import mediaSosial from "./data/landing-page/profile/mediaSosial.json";
import sdgsDesa from "./data/landing-page/sdgs-desa/sdgs-desa.json";
import apbdes from "./data/landing-page/apbdes/apbdes.json";
import pelayananSuratKeterangan from "./data/desa/layanan/pelayananSuratKeterangan.json";
import categoryPengumuman from "./data/category-pengumuman.json";
import kategoriBerita from "./data/kategori-berita.json";
import caraMemperolehInformasi from "./data/list-caraMemperolehInformasi.json";
@@ -20,27 +27,137 @@ import kategoriProduk from "./data/ekonomi/pasar-desa/kategori-produk.json";
import hubunganOrganisasi from "./data/ekonomi/struktur-organisasi/hubungan-organisasi.json";
import posisiOrganisasi from "./data/ekonomi/struktur-organisasi/posisi-organisasi.json";
import pegawai from "./data/ekonomi/struktur-organisasi/pegawai.json";
import detailDataPengangguran from './data/ekonomi/jumlah-pengangguran/detail-data-pengangguran.json';
import tujuanEdukasiLingkungan from './data/lingkungan/edukasi-lingkungan/tujuan-edukasi-lingkungan.json';
import materiEdukasiLingkungan from './data/lingkungan/edukasi-lingkungan/materi-edukasi-yang-diberikan.json';
import contohEdukasiLingkungan from './data/lingkungan/edukasi-lingkungan/contoh-kegiatan-di-desa-darmasaba.json';
import nilaiKonservasiAdat from './data/lingkungan/konservasi-adat-bali/nilai-konservasi-adat.json';
import bentukKonservasiBerdasarkanAdat from './data/lingkungan/konservasi-adat-bali/bentuk-konservasi.json';
import filosofiTriHita from './data/lingkungan/konservasi-adat-bali/filosofi-tri-hita.json';
import profilePejabatDesa from './data/landing-page/profile.json';
import tujuanProgram from './data/pendidikan/program-pendidikan-anak/tujuan-program.json';
import tujuanProgram2 from './data/pendidikan/pendidikan-non-formal/tujuan-program2.json';
import programUnggulan from './data/pendidikan/program-pendidikan-anak/program-unggulan.json';
import tujuanBimbinganBelajarDesa from './data/pendidikan/bimbingan-belajar-desa/tujuan-bimbingan-belajar-desa.json';
import lokasiJadwalBimbinganBelajarDesa from './data/pendidikan/bimbingan-belajar-desa/lokasi-dan-jadwal.json';
import fasilitasBimbinganBelajarDesa from './data/pendidikan/bimbingan-belajar-desa/fasilitas-yang-disediakan.json';
import tempatKegiatan from './data/pendidikan/pendidikan-non-formal/tempat-kegiatan.json';
import jenisProgramYangDiselenggarakan from './data/pendidikan/pendidikan-non-formal/jenis-program-yang-diselenggarakan.json';
import detailDataPengangguran from "./data/ekonomi/jumlah-pengangguran/detail-data-pengangguran.json";
import tujuanEdukasiLingkungan from "./data/lingkungan/edukasi-lingkungan/tujuan-edukasi-lingkungan.json";
import materiEdukasiLingkungan from "./data/lingkungan/edukasi-lingkungan/materi-edukasi-yang-diberikan.json";
import contohEdukasiLingkungan from "./data/lingkungan/edukasi-lingkungan/contoh-kegiatan-di-desa-darmasaba.json";
import nilaiKonservasiAdat from "./data/lingkungan/konservasi-adat-bali/nilai-konservasi-adat.json";
import bentukKonservasiBerdasarkanAdat from "./data/lingkungan/konservasi-adat-bali/bentuk-konservasi.json";
import filosofiTriHita from "./data/lingkungan/konservasi-adat-bali/filosofi-tri-hita.json";
import tujuanProgram from "./data/pendidikan/program-pendidikan-anak/tujuan-program.json";
import tujuanProgram2 from "./data/pendidikan/pendidikan-non-formal/tujuan-program2.json";
import programUnggulan from "./data/pendidikan/program-pendidikan-anak/program-unggulan.json";
import tujuanBimbinganBelajarDesa from "./data/pendidikan/bimbingan-belajar-desa/tujuan-bimbingan-belajar-desa.json";
import lokasiJadwalBimbinganBelajarDesa from "./data/pendidikan/bimbingan-belajar-desa/lokasi-dan-jadwal.json";
import fasilitasBimbinganBelajarDesa from "./data/pendidikan/bimbingan-belajar-desa/fasilitas-yang-disediakan.json";
import tempatKegiatan from "./data/pendidikan/pendidikan-non-formal/tempat-kegiatan.json";
import jenisProgramYangDiselenggarakan from "./data/pendidikan/pendidikan-non-formal/jenis-program-yang-diselenggarakan.json";
import posisiOrganisasiPPID from "./data/ppid/struktur-ppid/posisi-organisasi-PPID.json";
import pegawaiPPID from "./data/ppid/struktur-ppid/pegawai-PPID.json";
import pelayananTelunjukSaktiDesa from "./data/desa/layanan/pelayananTelunjukSaktiDesa.json";
(async () => {
// =========== LANDING PAGE ===========
// =========== PROFILE ===========
for (const p of profilePejabatDesa) {
await prisma.pejabatDesa.upsert({
where: { id: p.id },
update: {
name: p.name,
position: p.position,
},
create: {
id: p.id,
name: p.name,
position: p.position,
},
});
}
console.log(
"✅ profilePejabatDesa seeded without imageId (editable later via UI)"
);
// =========== PROGRAM INOVASI ===========
for (const p of programInovasi) {
await prisma.programInovasi.upsert({
where: { id: p.id },
update: {
name: p.name,
description: p.description,
link: p.link,
},
create: {
id: p.id,
name: p.name,
description: p.description,
link: p.link,
},
});
}
console.log("program inovasi success ...");
// =========== MEDIA SOSIAL ===========
for (const p of mediaSosial) {
await prisma.mediaSosial.upsert({
where: { id: p.id },
update: {
name: p.name,
iconUrl: p.iconUrl,
},
create: {
id: p.id,
name: p.name,
iconUrl: p.iconUrl,
},
});
}
console.log("media sosial success ...");
// =========== PENGHARGAAN ===========
for (const p of penghargaan) {
await prisma.penghargaan.upsert({
where: { id: p.id },
update: {
name: p.name,
juara: p.juara,
deskripsi: p.deskripsi,
},
create: {
id: p.id,
name: p.name,
juara: p.juara,
deskripsi: p.deskripsi,
},
});
}
console.log("penghargaan success ...");
// =========== LAYANAN DESA ===========
for (const p of pelayananSuratKeterangan) {
await prisma.pelayananSuratKeterangan.upsert({
where: { id: p.id },
update: {
name: p.name,
deskripsi: p.deskripsi,
},
create: {
id: p.id,
name: p.name,
deskripsi: p.deskripsi,
},
});
}
console.log("pelayanan surat keterangan success ...");
for (const p of pelayananTelunjukSaktiDesa) {
await prisma.pelayananTelunjukSaktiDesa.upsert({
where: { id: p.id },
update: {
name: p.name,
deskripsi: p.deskripsi,
link: p.link,
},
create: {
id: p.id,
name: p.name,
deskripsi: p.deskripsi,
link: p.link,
},
});
}
console.log("pelayanan surat keterangan success ...");
// =========== LAYANAN ===========
for (const l of layanan) {
await prisma.layanan.upsert({
where: {
@@ -57,6 +174,46 @@ import pegawaiPPID from "./data/ppid/struktur-ppid/pegawai-PPID.json";
console.log("layanan success ...");
// =========== SDGSDesa ===========
for (const l of sdgsDesa) {
await prisma.sDGSDesa.upsert({
where: {
name: l.name,
jumlah: l.jumlah,
},
update: {
name: l.name,
jumlah: l.jumlah,
},
create: {
name: l.name,
jumlah: l.jumlah,
},
});
}
console.log("sdgs desa success ...");
// =========== APBDes ===========
for (const l of apbdes) {
await prisma.aPBDes.upsert({
where: {
name: l.name,
jumlah: l.jumlah,
},
update: {
name: l.name,
jumlah: l.jumlah,
},
create: {
name: l.name,
jumlah: l.jumlah,
},
});
}
console.log("sdgs desa success ...");
for (const l of sejarahDesa) {
await prisma.sejarahDesa.upsert({
where: {
@@ -134,7 +291,9 @@ import pegawaiPPID from "./data/ppid/struktur-ppid/pegawai-PPID.json";
},
});
}
console.log("✅ profilePerbekel seeded without imageId (editable later via UI)");
console.log(
"✅ profilePerbekel seeded without imageId (editable later via UI)"
);
for (const l of visiMisiDesa) {
await prisma.visiMisiDesa.upsert({
@@ -157,7 +316,7 @@ import pegawaiPPID from "./data/ppid/struktur-ppid/pegawai-PPID.json";
// Flatten the nested array structure for posisiOrganisasiPPID
const flattenedPosisiOrganisasiPPID = posisiOrganisasiPPID.flat();
for (const p of flattenedPosisiOrganisasiPPID) {
await prisma.posisiOrganisasiPPID.upsert({
where: {
@@ -182,7 +341,7 @@ import pegawaiPPID from "./data/ppid/struktur-ppid/pegawai-PPID.json";
// Flatten the nested array structure for pegawaiPPID
const flattenedPegawaiPPID = pegawaiPPID.flat();
for (const p of flattenedPegawaiPPID) {
await prisma.pegawaiPPID.upsert({
where: {
@@ -252,7 +411,6 @@ import pegawaiPPID from "./data/ppid/struktur-ppid/pegawai-PPID.json";
}
console.log("pelayanan penduduk non permanen success ...");
for (const p of potensi) {
await prisma.potensi.upsert({
where: {
@@ -422,7 +580,6 @@ import pegawaiPPID from "./data/ppid/struktur-ppid/pegawai-PPID.json";
}
console.log("kategori produk success ...");
for (const p of posisiOrganisasi) {
await prisma.posisiOrganisasi.upsert({
where: {
@@ -611,7 +768,6 @@ import pegawaiPPID from "./data/ppid/struktur-ppid/pegawai-PPID.json";
console.log("bentuk konservasi berdasarkan adat success ...");
for (const n of nilaiKonservasiAdat) {
await prisma.nilaiKonservasiAdat.upsert({
where: {
@@ -631,22 +787,6 @@ import pegawaiPPID from "./data/ppid/struktur-ppid/pegawai-PPID.json";
console.log("nilai konservasi adat success ...");
for (const p of profilePejabatDesa) {
await prisma.pejabatDesa.upsert({
where: { id: p.id },
update: {
name: p.name,
position: p.position,
},
create: {
id: p.id,
name: p.name,
position: p.position,
},
});
}
console.log("✅ profilePejabatDesa seeded without imageId (editable later via UI)");
for (const t of tujuanProgram) {
await prisma.tujuanProgram.upsert({
where: { id: t.id },
@@ -693,7 +833,9 @@ import pegawaiPPID from "./data/ppid/struktur-ppid/pegawai-PPID.json";
},
});
}
console.log("✅ tujuan bimbingan belajar desa seeded (editable later via UI)");
console.log(
"✅ tujuan bimbingan belajar desa seeded (editable later via UI)"
);
for (const t of lokasiJadwalBimbinganBelajarDesa) {
await prisma.lokasiJadwalBimbinganBelajarDesa.upsert({
@@ -709,7 +851,9 @@ import pegawaiPPID from "./data/ppid/struktur-ppid/pegawai-PPID.json";
},
});
}
console.log("✅ lokasi jadwal bimbingan belajar desa seeded (editable later via UI)");
console.log(
"✅ lokasi jadwal bimbingan belajar desa seeded (editable later via UI)"
);
for (const t of fasilitasBimbinganBelajarDesa) {
await prisma.fasilitasBimbinganBelajarDesa.upsert({
@@ -725,7 +869,9 @@ import pegawaiPPID from "./data/ppid/struktur-ppid/pegawai-PPID.json";
},
});
}
console.log("✅ fasilitas bimbingan belajar desa seeded (editable later via UI)");
console.log(
"✅ fasilitas bimbingan belajar desa seeded (editable later via UI)"
);
for (const t of tujuanProgram2) {
await prisma.tujuanPendidikanNonFormal.upsert({
@@ -741,7 +887,9 @@ import pegawaiPPID from "./data/ppid/struktur-ppid/pegawai-PPID.json";
},
});
}
console.log("✅ fasilitas bimbingan belajar desa seeded (editable later via UI)");
console.log(
"✅ fasilitas bimbingan belajar desa seeded (editable later via UI)"
);
for (const t of tempatKegiatan) {
await prisma.tempatKegiatan.upsert({
@@ -757,7 +905,9 @@ import pegawaiPPID from "./data/ppid/struktur-ppid/pegawai-PPID.json";
},
});
}
console.log("✅ fasilitas bimbingan belajar desa seeded (editable later via UI)");
console.log(
"✅ fasilitas bimbingan belajar desa seeded (editable later via UI)"
);
for (const t of jenisProgramYangDiselenggarakan) {
await prisma.jenisProgramYangDiselenggarakan.upsert({
@@ -773,8 +923,9 @@ import pegawaiPPID from "./data/ppid/struktur-ppid/pegawai-PPID.json";
},
});
}
console.log("✅ fasilitas bimbingan belajar desa seeded (editable later via UI)");
console.log(
"✅ fasilitas bimbingan belajar desa seeded (editable later via UI)"
);
})()
.then(() => prisma.$disconnect())
.catch((e) => {

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
@@ -8,12 +9,14 @@ const templateSuratKeteranganForm = z.object({
name: z.string().min(3, "Nama minimal 3 karakter"),
deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"),
imageId: z.string().nonempty(),
image2Id: z.string().nonempty(),
});
const suratKeteranganForm = {
name: "",
deskripsi: "",
imageId: "",
image2Id: "",
};
const telunjukSaktiDesaForm = {
@@ -105,15 +108,38 @@ const suratKeterangan = proxy({
},
},
findMany: {
data: [] as Prisma.PelayananSuratKeteranganGetPayload<{
include: { image: true };
}>[],
async load() {
const res = await ApiFetch.api.desa.layanan.pelayanansuratketerangan[
"find-many"
].get();
if (res.status === 200) {
suratKeterangan.findMany.data = res.data?.data ?? [];
data: null as any[] | null,
page: 1,
totalPages: 1,
total: 0,
loading: false,
load: async (page = 1, limit = 10) => { // Change to arrow function
suratKeterangan.findMany.loading = true; // Use the full path to access the property
suratKeterangan.findMany.page = page;
try {
const res = await ApiFetch.api.desa.layanan.pelayanansuratketerangan[
"find-many"
].get({
query: { page, limit },
});
if (res.status === 200 && res.data?.success) {
suratKeterangan.findMany.data = res.data.data || [];
suratKeterangan.findMany.total = res.data.total || 0;
suratKeterangan.findMany.totalPages = res.data.totalPages || 1;
} else {
console.error("Failed to load surat keterangan:", res.data?.message);
suratKeterangan.findMany.data = [];
suratKeterangan.findMany.total = 0;
suratKeterangan.findMany.totalPages = 1;
}
} catch (error) {
console.error("Error loading surat keterangan:", error);
suratKeterangan.findMany.data = [];
suratKeterangan.findMany.total = 0;
suratKeterangan.findMany.totalPages = 1;
} finally {
suratKeterangan.findMany.loading = false;
}
},
},
@@ -121,6 +147,7 @@ const suratKeterangan = proxy({
data: null as Prisma.PelayananSuratKeteranganGetPayload<{
include: {
image: true;
image2: true;
};
}> | null,
async load(id: string) {
@@ -202,6 +229,7 @@ const suratKeterangan = proxy({
name: data.name,
deskripsi: data.deskripsi,
imageId: data.imageId || "",
image2Id: data.image2Id || "",
};
return data;
} else {
@@ -238,6 +266,7 @@ const suratKeterangan = proxy({
name: this.form.name,
deskripsi: this.form.deskripsi,
imageId: this.form.imageId,
image2Id: this.form.image2Id,
}),
}
);
@@ -307,15 +336,38 @@ const pelayananTelunjukSaktiDesa = proxy({
},
},
findMany: {
data: [] as Prisma.PelayananTelunjukSaktiDesaGetPayload<{
omit: { isActive: true };
}>[],
async load() {
const res = await ApiFetch.api.desa.layanan.pelayanantelunjuksaktidesa[
"find-many"
].get();
if (res.status === 200) {
pelayananTelunjukSaktiDesa.findMany.data = res.data?.data ?? [];
data: null as any[] | null,
page: 1,
totalPages: 1,
total: 0,
loading: false,
load: async (page = 1, limit = 10) => { // Change to arrow function
pelayananTelunjukSaktiDesa.findMany.loading = true; // Use the full path to access the property
pelayananTelunjukSaktiDesa.findMany.page = page;
try {
const res = await ApiFetch.api.desa.layanan.pelayanantelunjuksaktidesa[
"find-many"
].get({
query: { page, limit },
});
if (res.status === 200 && res.data?.success) {
pelayananTelunjukSaktiDesa.findMany.data = res.data.data || [];
pelayananTelunjukSaktiDesa.findMany.total = res.data.total || 0;
pelayananTelunjukSaktiDesa.findMany.totalPages = res.data.totalPages || 1;
} else {
console.error("Failed to load telunjuk sakti desa:", res.data?.message);
pelayananTelunjukSaktiDesa.findMany.data = [];
pelayananTelunjukSaktiDesa.findMany.total = 0;
pelayananTelunjukSaktiDesa.findMany.totalPages = 1;
}
} catch (error) {
console.error("Error loading telunjuk sakti desa:", error);
pelayananTelunjukSaktiDesa.findMany.data = [];
pelayananTelunjukSaktiDesa.findMany.total = 0;
pelayananTelunjukSaktiDesa.findMany.totalPages = 1;
} finally {
pelayananTelunjukSaktiDesa.findMany.loading = false;
}
},
},

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
@@ -5,10 +6,10 @@ import { proxy } from "valtio";
import { z } from "zod";
const templateForm = z.object({
name: z.string().min(1).max(50),
juara: z.string().min(1).max(50),
name: z.string().min(1).max(5000),
juara: z.string().min(1).max(5000),
deskripsi: z.string().min(1).max(5000),
imageId: z.string().min(1).max(50),
imageId: z.string().min(1).max(5000),
});
const defaultForm = {
@@ -50,17 +51,38 @@ const penghargaanState = proxy({
},
},
findMany: {
data: null as
| Prisma.PenghargaanGetPayload<{
include: {
image: true;
};
}>[]
| null,
async load() {
const res = await ApiFetch.api.desa.penghargaan["find-many"].get();
if (res.status === 200) {
penghargaanState.findMany.data = res.data?.data ?? [];
data: null as any[] | null,
page: 1,
totalPages: 1,
total: 0,
loading: false,
load: async (page = 1, limit = 10) => { // Change to arrow function
penghargaanState.findMany.loading = true; // Use the full path to access the property
penghargaanState.findMany.page = page;
try {
const res = await ApiFetch.api.desa.penghargaan[
"find-many"
].get({
query: { page, limit },
});
if (res.status === 200 && res.data?.success) {
penghargaanState.findMany.data = res.data.data || [];
penghargaanState.findMany.total = res.data.total || 0;
penghargaanState.findMany.totalPages = res.data.totalPages || 1;
} else {
console.error("Failed to load penghargaan:", res.data?.message);
penghargaanState.findMany.data = [];
penghargaanState.findMany.total = 0;
penghargaanState.findMany.totalPages = 1;
}
} catch (error) {
console.error("Error loading penghargaan:", error);
penghargaanState.findMany.data = [];
penghargaanState.findMany.total = 0;
penghargaanState.findMany.totalPages = 1;
} finally {
penghargaanState.findMany.loading = false;
}
},
},

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
@@ -5,219 +6,470 @@ import { proxy } from "valtio";
import { z } from "zod";
const templateForm = z.object({
name: z.string().min(1).max(50),
deskripsi: z.string().min(1).max(5000),
kategori: z.string().min(1).max(50),
imageId: z.string().min(1).max(50),
content: z.string().min(1).max(5000),
})
name: z.string().min(1).max(5000),
deskripsi: z.string().min(1).max(5000),
kategoriId: z.string().min(1).max(50),
imageId: z.string().min(1).max(50),
content: z.string().min(1).max(5000),
});
const defaultForm = {
name: "",
deskripsi: "",
kategori: "",
imageId: "",
content: "",
}
name: "",
deskripsi: "",
kategoriId: "",
imageId: "",
content: "",
};
const potensiDesa = proxy({
create: {
form: { ...defaultForm },
loading: false,
async create() {
const cek = templateForm.safeParse(potensiDesa.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
potensiDesa.create.loading = true;
const res = await ApiFetch.api.desa.potensi["create"].post(
potensiDesa.create.form
);
if (res.status === 200) {
potensiDesa.findMany.load();
return toast.success("Potensi berhasil disimpan!");
}
return toast.error("Gagal menyimpan potensi");
} catch (error) {
console.log((error as Error).message);
} finally {
potensiDesa.create.loading = false;
}
},
},
findMany: {
data: null as any[] | null,
page: 1,
totalPages: 1,
total: 0,
loading: false,
load: async (page = 1, limit = 10) => { // Change to arrow function
potensiDesa.findMany.loading = true; // Use the full path to access the property
potensiDesa.findMany.page = page;
try {
const res = await ApiFetch.api.desa.potensi[
"find-many"
].get({
query: { page, limit },
});
if (res.status === 200 && res.data?.success) {
potensiDesa.findMany.data = res.data.data || [];
potensiDesa.findMany.total = res.data.total || 0;
potensiDesa.findMany.totalPages = res.data.totalPages || 1;
} else {
console.error("Failed to load potensi desa:", res.data?.message);
potensiDesa.findMany.data = [];
potensiDesa.findMany.total = 0;
potensiDesa.findMany.totalPages = 1;
}
} catch (error) {
console.error("Error loading potensi desa:", error);
potensiDesa.findMany.data = [];
potensiDesa.findMany.total = 0;
potensiDesa.findMany.totalPages = 1;
} finally {
potensiDesa.findMany.loading = false;
}
},
},
findUnique: {
data: null as Prisma.PotensiDesaGetPayload<{
include: {
image: true;
kategori: true;
};
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/desa/potensi/${id}`);
if (res.ok) {
const data = await res.json();
potensiDesa.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch potensi:", res.statusText);
potensiDesa.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching potensi:", error);
potensiDesa.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
potensiDesa.delete.loading = true;
const response = await fetch(`/api/desa/potensi/del/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Potensi berhasil dihapus");
await potensiDesa.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus potensi");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus potensi");
} finally {
potensiDesa.delete.loading = false;
}
},
},
edit: {
id: "",
form: { ...defaultForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(`/api/desa/potensi/${id}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
if (result?.success) {
const data = result.data;
this.id = data.id;
this.form = {
name: data.name,
deskripsi: data.deskripsi,
kategoriId: data.kategoriId,
imageId: data.imageId || "",
content: data.content,
};
return data; // Return the loaded data
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error loading potensi:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async update() {
const cek = templateForm.safeParse(potensiDesa.edit.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return false;
}
try {
potensiDesa.edit.loading = true;
const response = await fetch(`/api/desa/potensi/${this.id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: this.form.name,
deskripsi: this.form.deskripsi,
kategoriId: this.form.kategoriId,
imageId: this.form.imageId,
content: this.form.content,
}),
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(
errorData.message || `HTTP error! status: ${response.status}`
);
}
const result = await response.json();
if (result.success) {
toast.success("Berhasil update potensi");
await potensiDesa.findMany.load(); // refresh list
return true;
} else {
throw new Error(result.message || "Gagal update potensi");
}
} catch (error) {
console.error("Error updating potensi:", error);
toast.error(
error instanceof Error
? error.message
: "Terjadi kesalahan saat update potensi"
);
return false;
} finally {
potensiDesa.edit.loading = false;
}
},
reset() {
potensiDesa.edit.id = "";
potensiDesa.edit.form = { ...defaultForm };
},
},
});
const templateKategoriPotensi = z.object({
nama: z.string().min(1, "Nama harus diisi"),
});
const defaultKategoriPotensi = {
nama: "",
};
const kategoriPotensi = proxy({
create: {
form: { ...defaultKategoriPotensi },
loading: false,
async create() {
const cek = templateKategoriPotensi.safeParse(
kategoriPotensi.create.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
kategoriPotensi.create.loading = true;
const res = await ApiFetch.api.desa.kategoripotensi["create"].post(
kategoriPotensi.create.form
);
if (res.status === 200) {
kategoriPotensi.findMany.load();
return toast.success("Data Kategori Potensi Berhasil Dibuat");
}
console.log(res);
return toast.error("failed create");
} catch (error) {
console.log(error);
return toast.error("failed create");
} finally {
kategoriPotensi.create.loading = false;
}
},
},
findMany: {
data: [] as Prisma.KategoriPotensiGetPayload<{
omit: {
isActive: true;
};
}>[],
loading: false,
async load() {
const res = await ApiFetch.api.desa.kategoripotensi["findMany"].get();
if (res.status === 200) {
kategoriPotensi.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.KategoriPotensiGetPayload<{
omit: {
isActive: true;
};
}> | null,
loading: false,
async load(id: string) {
try {
const res = await fetch(`/api/desa/kategoripotensi/${id}`);
if (res.ok) {
const data = await res.json();
kategoriPotensi.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
kategoriPotensi.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching data:", error);
kategoriPotensi.findUnique.data = null;
}
},
},
delete: {
loading: false,
async delete(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
kategoriPotensi.delete.loading = true;
const response = await fetch(`/api/desa/kategoripotensi/del/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
const result = await response.json();
if (response.ok && result?.success) {
toast.success(
result.message || "Data Kategori Potensi berhasil dihapus"
);
await kategoriPotensi.findMany.load(); // refresh list
} else {
toast.error(
result?.message || "Gagal menghapus Data Kategori Potensi"
);
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus Data Kategori Potensi");
} finally {
kategoriPotensi.delete.loading = false;
}
},
},
update: {
id: "",
form: { ...defaultKategoriPotensi },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(`/api/desa/kategoripotensi/${id}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
if (result?.success) {
const data = result.data;
this.id = data.id;
this.form = {
nama: data.nama,
};
return data; // Return the loaded data
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error loading kategori potensi:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async update() {
const cek = templateKategoriPotensi.safeParse(
kategoriPotensi.update.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return false;
}
try {
kategoriPotensi.update.loading = true;
const response = await fetch(`/api/desa/kategoripotensi/${this.id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
nama: this.form.nama,
}),
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(
errorData.message || `HTTP error! status: ${response.status}`
);
}
const result = await response.json();
if (result.success) {
toast.success("Berhasil update data kategori potensi");
await kategoriPotensi.findMany.load(); // refresh list
return true;
} else {
throw new Error(
result.message || "Gagal update data kategori potensi"
);
}
} catch (error) {
console.error("Error updating data kategori potensi:", error);
toast.error(
error instanceof Error
? error.message
: "Terjadi kesalahan saat update data kategori potensi"
);
return false;
} finally {
kategoriPotensi.update.loading = false;
}
},
reset() {
kategoriPotensi.update.id = "";
kategoriPotensi.update.form = { ...defaultKategoriPotensi };
},
},
});
const potensiDesaState = proxy({
create: {
form: { ...defaultForm },
loading: false,
async create() {
const cek = templateForm.safeParse(potensiDesaState.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
potensiDesaState.create.loading = true;
const res = await ApiFetch.api.desa.potensi["create"].post(potensiDesaState.create.form);
if (res.status === 200) {
potensiDesaState.findMany.load();
return toast.success("Potensi berhasil disimpan!");
}
return toast.error("Gagal menyimpan potensi");
} catch (error) {
console.log((error as Error).message);
} finally {
potensiDesaState.create.loading = false;
}
}
},
findMany: {
data: null as
| Prisma.PotensiDesaGetPayload<{
include: {
image: true;
}
}>[]
| null,
async load() {
const res = await ApiFetch.api.desa.potensi["find-many"].get();
if (res.status === 200) {
potensiDesaState.findMany.data = res.data?.data ?? [];
}
}
},
findUnique: {
data: null as
| Prisma.PotensiDesaGetPayload<{
include: {
image: true;
}
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/desa/potensi/${id}`);
if (res.ok) {
const data = await res.json();
potensiDesaState.findUnique.data = data.data ?? null;
} else {
console.error('Failed to fetch potensi:', res.statusText);
potensiDesaState.findUnique.data = null;
}
} catch (error) {
console.error('Error fetching potensi:', error);
potensiDesaState.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
potensiDesaState.delete.loading = true;
const response = await fetch(`/api/desa/potensi/del/${id}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
},
});
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Potensi berhasil dihapus");
await potensiDesaState.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus potensi");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus potensi");
} finally {
potensiDesaState.delete.loading = false;
}
},
},
edit: {
id: "",
form: { ...defaultForm },
loading: false,
potensiDesa,
kategoriPotensi,
});
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(`/api/desa/potensi/${id}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
if (result?.success) {
const data = result.data;
this.id = data.id;
this.form = {
name: data.name,
deskripsi: data.deskripsi,
kategori: data.kategori,
imageId: data.imageId || "",
content: data.content,
};
return data; // Return the loaded data
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error loading potensi:", error);
toast.error(error instanceof Error ? error.message : "Gagal memuat data");
return null;
}
},
async update() {
const cek = templateForm.safeParse(potensiDesaState.edit.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return false;
}
try {
potensiDesaState.edit.loading = true;
const response = await fetch(`/api/desa/potensi/${this.id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: this.form.name,
deskripsi: this.form.deskripsi,
kategori: this.form.kategori,
imageId: this.form.imageId,
content: this.form.content,
}),
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.message || `HTTP error! status: ${response.status}`);
}
const result = await response.json();
if (result.success) {
toast.success("Berhasil update potensi");
await potensiDesaState.findMany.load(); // refresh list
return true;
} else {
throw new Error(result.message || "Gagal update potensi");
}
} catch (error) {
console.error("Error updating potensi:", error);
toast.error(error instanceof Error ? error.message : "Terjadi kesalahan saat update potensi");
return false;
} finally {
potensiDesaState.edit.loading = false;
}
},
reset() {
potensiDesaState.edit.id = "";
potensiDesaState.edit.form = { ...defaultForm };
}
}
})
export default potensiDesaState
export default potensiDesaState;

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
@@ -54,20 +55,38 @@ const desaAntikorupsi = proxy({
},
},
findMany: {
data: null as Array<
Prisma.DesaAntiKorupsiGetPayload<{
include: {
file: true;
kategori: true;
};
}>
> | null,
async load() {
const res = await ApiFetch.api.landingpage.desaantikorupsi[
"find-many"
].get();
if (res.status === 200) {
desaAntikorupsi.findMany.data = res.data?.data ?? [];
data: null as any[] | null,
page: 1,
totalPages: 1,
total: 0,
loading: false,
load: async (page = 1, limit = 10) => { // Change to arrow function
desaAntikorupsi.findMany.loading = true; // Use the full path to access the property
desaAntikorupsi.findMany.page = page;
try {
const res = await ApiFetch.api.landingpage.desaantikorupsi[
"findMany"
].get({
query: { page, limit },
});
if (res.status === 200 && res.data?.success) {
desaAntikorupsi.findMany.data = res.data.data || [];
desaAntikorupsi.findMany.total = res.data.total || 0;
desaAntikorupsi.findMany.totalPages = res.data.totalPages || 1;
} else {
console.error("Failed to load media sosial:", res.data?.message);
desaAntikorupsi.findMany.data = [];
desaAntikorupsi.findMany.total = 0;
desaAntikorupsi.findMany.totalPages = 1;
}
} catch (error) {
console.error("Error loading media sosial:", error);
desaAntikorupsi.findMany.data = [];
desaAntikorupsi.findMany.total = 0;
desaAntikorupsi.findMany.totalPages = 1;
} finally {
desaAntikorupsi.findMany.loading = false;
}
},
},
@@ -281,14 +300,38 @@ const kategoriDesaAntiKorupsi = proxy({
},
},
findMany: {
data: null as Array<{
id: string;
name: string;
}> | null,
async load() {
const res = await ApiFetch.api.landingpage.kategoridak["find-many"].get();
if (res.status === 200) {
kategoriDesaAntiKorupsi.findMany.data = res.data?.data ?? [];
data: null as any[] | null,
page: 1,
totalPages: 1,
total: 0,
loading: false,
load: async (page = 1, limit = 10) => { // Change to arrow function
kategoriDesaAntiKorupsi.findMany.loading = true; // Use the full path to access the property
kategoriDesaAntiKorupsi.findMany.page = page;
try {
const res = await ApiFetch.api.landingpage.kategoridak[
"findMany"
].get({
query: { page, limit },
});
if (res.status === 200 && res.data?.success) {
kategoriDesaAntiKorupsi.findMany.data = res.data.data || [];
kategoriDesaAntiKorupsi.findMany.total = res.data.total || 0;
kategoriDesaAntiKorupsi.findMany.totalPages = res.data.totalPages || 1;
} else {
console.error("Failed to load media sosial:", res.data?.message);
kategoriDesaAntiKorupsi.findMany.data = [];
kategoriDesaAntiKorupsi.findMany.total = 0;
kategoriDesaAntiKorupsi.findMany.totalPages = 1;
}
} catch (error) {
console.error("Error loading media sosial:", error);
kategoriDesaAntiKorupsi.findMany.data = [];
kategoriDesaAntiKorupsi.findMany.total = 0;
kategoriDesaAntiKorupsi.findMany.totalPages = 1;
} finally {
kategoriDesaAntiKorupsi.findMany.loading = false;
}
},
},

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
@@ -5,10 +6,10 @@ import { proxy } from "valtio";
import { z } from "zod";
const templateProgramInovasi = z.object({
name: z.string().min(3, "Nama minimal 3 karakter"),
description: z.string().min(3, "Deskripsi minimal 3 karakter"),
name: z.string().min(1, "Nama minimal 1 karakter"),
description: z.string().min(1, "Deskripsi minimal 1 karakter"),
imageId: z.string().min(1, "Gambar wajib dipilih"),
link: z.string().min(3, "Link minimal 3 karakter"),
link: z.string().min(1, "Link minimal 1 karakter"),
});
type ProgramInovasiForm = Prisma.ProgramInovasiGetPayload<{
@@ -59,15 +60,38 @@ const programInovasi = proxy({
},
},
findMany: {
data: null as
| Prisma.ProgramInovasiGetPayload<{ include: { image: true } }>[]
| null,
async load() {
const res = await ApiFetch.api.landingpage.programinovasi[
"find-many"
].get();
if (res.status === 200) {
programInovasi.findMany.data = res.data?.data ?? [];
data: null as any[] | null,
page: 1,
totalPages: 1,
total: 0,
loading: false,
load: async (page = 1, limit = 10) => { // Change to arrow function
programInovasi.findMany.loading = true; // Use the full path to access the property
programInovasi.findMany.page = page;
try {
const res = await ApiFetch.api.landingpage.programinovasi[
"findMany"
].get({
query: { page, limit },
});
if (res.status === 200 && res.data?.success) {
programInovasi.findMany.data = res.data.data || [];
programInovasi.findMany.total = res.data.total || 0;
programInovasi.findMany.totalPages = res.data.totalPages || 1;
} else {
console.error("Failed to load pegawai:", res.data?.message);
programInovasi.findMany.data = [];
programInovasi.findMany.total = 0;
programInovasi.findMany.totalPages = 1;
}
} catch (error) {
console.error("Error loading pegawai:", error);
programInovasi.findMany.data = [];
programInovasi.findMany.total = 0;
programInovasi.findMany.totalPages = 1;
} finally {
programInovasi.findMany.loading = false;
}
},
},
@@ -453,13 +477,38 @@ const mediaSosial = proxy({
},
},
findMany: {
data: null as
| Prisma.MediaSosialGetPayload<{ include: { image: true } }>[]
| null,
async load() {
const res = await ApiFetch.api.landingpage.mediasosial["find-many"].get();
if (res.status === 200) {
mediaSosial.findMany.data = res.data?.data ?? [];
data: null as any[] | null,
page: 1,
totalPages: 1,
total: 0,
loading: false,
load: async (page = 1, limit = 10) => { // Change to arrow function
mediaSosial.findMany.loading = true; // Use the full path to access the property
mediaSosial.findMany.page = page;
try {
const res = await ApiFetch.api.landingpage.mediasosial[
"findMany"
].get({
query: { page, limit },
});
if (res.status === 200 && res.data?.success) {
mediaSosial.findMany.data = res.data.data || [];
mediaSosial.findMany.total = res.data.total || 0;
mediaSosial.findMany.totalPages = res.data.totalPages || 1;
} else {
console.error("Failed to load media sosial:", res.data?.message);
mediaSosial.findMany.data = [];
mediaSosial.findMany.total = 0;
mediaSosial.findMany.totalPages = 1;
}
} catch (error) {
console.error("Error loading media sosial:", error);
mediaSosial.findMany.data = [];
mediaSosial.findMany.total = 0;
mediaSosial.findMany.totalPages = 1;
} finally {
mediaSosial.findMany.loading = false;
}
},
},

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
@@ -52,19 +53,38 @@ const sdgsDesa = proxy({
},
},
findMany: {
data: null as Array<
Prisma.SDGSDesaGetPayload<{
include: {
image: true;
};
}>
> | null,
async load() {
const res = await ApiFetch.api.landingpage.sdgsdesa[
"find-many"
].get();
if (res.status === 200) {
sdgsDesa.findMany.data = res.data?.data ?? [];
data: null as any[] | null,
page: 1,
totalPages: 1,
total: 0,
loading: false,
load: async (page = 1, limit = 10) => { // Change to arrow function
sdgsDesa.findMany.loading = true; // Use the full path to access the property
sdgsDesa.findMany.page = page;
try {
const res = await ApiFetch.api.landingpage.sdgsdesa[
"findMany"
].get({
query: { page, limit },
});
if (res.status === 200 && res.data?.success) {
sdgsDesa.findMany.data = res.data.data || [];
sdgsDesa.findMany.total = res.data.total || 0;
sdgsDesa.findMany.totalPages = res.data.totalPages || 1;
} else {
console.error("Failed to load media sosial:", res.data?.message);
sdgsDesa.findMany.data = [];
sdgsDesa.findMany.total = 0;
sdgsDesa.findMany.totalPages = 1;
}
} catch (error) {
console.error("Error loading media sosial:", error);
sdgsDesa.findMany.data = [];
sdgsDesa.findMany.total = 0;
sdgsDesa.findMany.totalPages = 1;
} finally {
sdgsDesa.findMany.loading = false;
}
},
},

View File

@@ -4,8 +4,9 @@ import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
import colors from '@/con/colors';
import ApiFetch from '@/lib/api-fetch';
import { Box, Button, Center, FileInput, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
import { Box, Button, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
import { Dropzone } from '@mantine/dropzone';
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
import { useParams, useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { toast } from 'react-toastify';
@@ -16,11 +17,14 @@ function EditSuratKeterangan() {
const params = useParams()
const stateSurat = useProxy(stateLayananDesa.suratKeterangan)
const [previewImage, setPreviewImage] = useState<string | null>(null);
const [previewImage2, setPreviewImage2] = useState<string | null>(null);
const [file, setFile] = useState<File | null>(null);
const [file2, setFile2] = useState<File | null>(null);
const [formData, setFormData] = useState({
name: stateSurat.edit.form.name,
deskripsi: stateSurat.edit.form.deskripsi,
imageId: stateSurat.edit.form.imageId,
image2Id: stateSurat.edit.form.image2Id,
})
useEffect(() => {
@@ -31,12 +35,22 @@ function EditSuratKeterangan() {
const data = await stateSurat.edit.load(id);
if (data) {
setFormData({
name: data.name,
deskripsi: data.deskripsi,
imageId: data.imageId,
name: data.name || "",
deskripsi: data.deskripsi || "",
imageId: data.imageId || "",
image2Id: data.image2Id || "",
});
if (data?.image?.link) {
if (data.image?.link) {
setPreviewImage(data.image.link);
} else {
setPreviewImage(null);
}
if (data.image2?.link) {
setPreviewImage2(data.image2.link);
} else {
setPreviewImage2(null);
}
}
} catch (error) {
@@ -54,6 +68,7 @@ function EditSuratKeterangan() {
name: formData.name,
deskripsi: formData.deskripsi,
imageId: formData.imageId,
image2Id: formData.image2Id,
}
if (file) {
const res = await ApiFetch.api.fileStorage.create.post({ file, name: file.name });
@@ -66,6 +81,17 @@ function EditSuratKeterangan() {
stateSurat.edit.form.imageId = uploaded.id;
}
if (file2) {
const res = await ApiFetch.api.fileStorage.create.post({ file: file2, name: file2.name });
const uploaded = res.data?.data;
if (!uploaded?.id) {
return toast.error("Gagal upload gambar");
}
stateSurat.edit.form.image2Id = uploaded.id;
}
await stateSurat.edit.update()
toast.success("Surat berhasil diperbarui!")
router.push("/admin/desa/layanan/pelayanan_surat_keterangan")
@@ -103,25 +129,106 @@ function EditSuratKeterangan() {
}}
/>
</Box>
<FileInput
label={<Text fz={"sm"} fw={"bold"}>Upload Gambar Konten</Text>}
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 ? (
<Image alt="" src={previewImage} w={200} h={200} />
) : (
<Center w={200} h={200} bg={"gray"}>
<IconImageInPicture />
</Center>
)}
<Box>
<Text fz={"md"} fw={"bold"}>Gambar</Text>
<Box >
<Dropzone
onDrop={(files) => {
const file = files[0]; // Hanya ambil file pertama
if (file) {
setFile(file);
setPreviewImage(URL.createObjectURL(file)); // Buat preview
}
}}
maxSize={5 * 1024 ** 2} // 5MB
accept={{
'image/*': ['.jpeg', '.jpg', '.png', '.webp']
}}
>
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
<Dropzone.Accept>
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
</Dropzone.Accept>
<Dropzone.Reject>
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
</Dropzone.Reject>
<Dropzone.Idle>
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
</Dropzone.Idle>
<div>
<Text size="xl" inline>
Drag images here or click to select files
</Text>
<Text size="sm" c="dimmed" inline mt={7}>
Attach as many files as you like, each file should not exceed 5mb
</Text>
</div>
</Group>
</Dropzone>
{previewImage && (
<Image
src={previewImage}
alt="Preview"
width={280}
height={180}
fit="cover"
radius="sm"
mt="md"
/>
)}
</Box>
</Box>
<Box>
<Text fz={"md"} fw={"bold"}>Gambar</Text>
<Box >
<Dropzone
onDrop={(files) => {
const file = files[0]; // Hanya ambil file pertama
if (file) {
setFile2(file);
setPreviewImage2(URL.createObjectURL(file)); // Buat preview
}
}}
maxSize={5 * 1024 ** 2} // 5MB
accept={{
'image/*': ['.jpeg', '.jpg', '.png', '.webp']
}}
>
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
<Dropzone.Accept>
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
</Dropzone.Accept>
<Dropzone.Reject>
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
</Dropzone.Reject>
<Dropzone.Idle>
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
</Dropzone.Idle>
<div>
<Text size="xl" inline>
Drag images here or click to select files
</Text>
<Text size="sm" c="dimmed" inline mt={7}>
Attach as many files as you like, each file should not exceed 5mb
</Text>
</div>
</Group>
</Dropzone>
{previewImage2 && (
<Image
src={previewImage2}
alt="Preview"
width={280}
height={180}
fit="cover"
radius="sm"
mt="md"
/>
)}
</Box>
</Box>
<Button bg={colors['blue-button']} onClick={handleSubmit}>Simpan</Button>
</Stack>
</Paper>

View File

@@ -64,6 +64,10 @@ function DetailSuratKeterangan() {
<Text fw={"bold"} fz={"lg"}>Gambar</Text>
<Image w={{ base: 150, md: 150, lg: 150 }} src={suratKeteranganState.findUnique.data?.image?.link} alt="gambar" />
</Box>
<Box>
<Text fw={"bold"} fz={"lg"}>Gambar</Text>
<Image w={{ base: 150, md: 150, lg: 150 }} src={suratKeteranganState.findUnique.data?.image2?.link} alt="gambar" />
</Box>
<Flex gap={"xs"} mt={10}>
<Button
onClick={() => {

View File

@@ -3,8 +3,9 @@ import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
import colors from '@/con/colors';
import ApiFetch from '@/lib/api-fetch';
import { Box, Button, Center, FileInput, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
import { Box, Button, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
import { Dropzone } from '@mantine/dropzone';
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import { toast } from 'react-toastify';
@@ -12,8 +13,8 @@ import { useProxy } from 'valtio/utils';
function CreateSuratKeterangan() {
const stateSurat = useProxy(stateLayananDesa.suratKeterangan)
const [previewImage, setPreviewImage] = useState<string | null>(null);
const [file, setFile] = useState<File | null>(null);
const [previewImage2, setPreviewImage2] = useState<{ preview: string; file: File } | null>(null);
const [previewImage, setPreviewImage] = useState<{ preview: string; file: File } | null>(null);
const router = useRouter()
const resetForm = () => {
@@ -21,33 +22,57 @@ function CreateSuratKeterangan() {
name: "",
deskripsi: "",
imageId: "",
image2Id: ""
}
setPreviewImage(null)
setFile(null)
setPreviewImage2(null)
}
const handleSubmit = async () => {
if (!file) {
return toast.error("Silahkan pilih file gambar terlebih dahulu")
if (!previewImage) {
return toast.warn("Pilih file gambar utama terlebih dahulu");
}
const res = await ApiFetch.api.fileStorage.create.post({
file: file,
name: file.name
})
try {
// Upload gambar utama
const res1 = await ApiFetch.api.fileStorage.create.post({
file: previewImage.file,
name: `main_${previewImage.file.name}`,
});
const uploaded = res.data?.data
if (!uploaded?.id) {
return toast.error("Gagal upload gambar")
const uploadedImage1 = res1.data?.data;
if (!uploadedImage1?.id) {
return toast.error("Gagal upload gambar utama");
}
let uploadedImage2 = null;
// Upload gambar kedua jika ada
if (previewImage2) {
const res2 = await ApiFetch.api.fileStorage.create.post({
file: previewImage2.file,
name: `secondary_${previewImage2.file.name}`,
});
uploadedImage2 = res2.data?.data;
}
// Set form data
stateSurat.create.form.imageId = uploadedImage1.id;
if (uploadedImage2?.id) {
stateSurat.create.form.image2Id = uploadedImage2.id;
}
// Create the record
await stateSurat.create.create();
// Reset form dan redirect
resetForm();
toast.success("Data surat keterangan berhasil ditambahkan");
router.push("/admin/desa/layanan/pelayanan_surat_keterangan");
} catch (error) {
console.error("Error creating surat keterangan:", error);
toast.error("Terjadi kesalahan saat menambahkan surat keterangan");
}
stateSurat.create.form.imageId = uploaded.id
await stateSurat.create.create()
resetForm()
router.push("/admin/desa/layanan/pelayanan_surat_keterangan")
}
};
return (
<Box>
<Box mb={10}>
@@ -75,25 +100,105 @@ function CreateSuratKeterangan() {
}}
/>
</Box>
<FileInput
label={<Text fz={"sm"} fw={"bold"}>Upload Gambar Konten</Text>}
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 ? (
<Image alt="" src={previewImage} w={200} h={200} />
) : (
<Center w={200} h={200} bg={"gray"}>
<IconImageInPicture />
</Center>
)}
<Box>
<Text fz={"md"} fw={"bold"} mb="sm">Gambar Utama</Text>
<Dropzone
onDrop={(files) => {
const file = files[0];
if (file) {
setPreviewImage({
file,
preview: URL.createObjectURL(file)
});
}
}}
maxSize={5 * 1024 ** 2}
accept={{
'image/*': ['.jpeg', '.jpg', '.png', '.webp']
}}
>
<Group justify="center" gap="xl" mih={120} style={{ pointerEvents: 'none' }}>
<Dropzone.Accept>
<IconUpload size={32} color="var(--mantine-color-blue-6)" stroke={1.5} />
</Dropzone.Accept>
<Dropzone.Reject>
<IconX size={32} color="var(--mantine-color-red-6)" stroke={1.5} />
</Dropzone.Reject>
<Dropzone.Idle>
<IconPhoto size={32} color="var(--mantine-color-dimmed)" stroke={1.5} />
</Dropzone.Idle>
<div>
<Text size="md" inline>Seret gambar ke sini atau klik untuk memilih</Text>
<Text size="sm" c="dimmed" inline mt={7} display="block">
Ukuran maksimal 5MB (JPEG, JPG, PNG, WebP)
</Text>
</div>
</Group>
</Dropzone>
{previewImage && (
<Image
src={previewImage.preview}
alt="Preview Gambar Utama"
width={280}
height={180}
fit="cover"
radius="sm"
mt="md"
/>
)}
</Box>
<Box mt="lg">
<Text fz={"md"} fw={"bold"} mb="sm">Gambar Tambahan (Opsional)</Text>
<Dropzone
onDrop={(files) => {
const file = files[0];
if (file) {
setPreviewImage2({
file,
preview: URL.createObjectURL(file)
});
}
}}
maxSize={5 * 1024 ** 2}
accept={{
'image/*': ['.jpeg', '.jpg', '.png', '.webp']
}}
>
<Group justify="center" gap="xl" mih={120} style={{ pointerEvents: 'none' }}>
<Dropzone.Accept>
<IconUpload size={32} color="var(--mantine-color-blue-6)" stroke={1.5} />
</Dropzone.Accept>
<Dropzone.Reject>
<IconX size={32} color="var(--mantine-color-red-6)" stroke={1.5} />
</Dropzone.Reject>
<Dropzone.Idle>
<IconPhoto size={32} color="var(--mantine-color-dimmed)" stroke={1.5} />
</Dropzone.Idle>
<div>
<Text size="md" inline>Seret gambar ke sini atau klik untuk memilih</Text>
<Text size="sm" c="dimmed" inline mt={7} display="block">
Ukuran maksimal 5MB (JPEG, JPG, PNG, WebP)
</Text>
</div>
</Group>
</Dropzone>
{previewImage2 ? (
<Image
src={previewImage2.preview}
alt="Preview Gambar Tambahan"
width={280}
height={180}
fit="cover"
radius="sm"
mt="md"
/>
) : (
<Text size="sm" c="dimmed" mt="sm">
Kosongkan jika tidak ada gambar tambahan
</Text>
)}
</Box>
<Button bg={colors['blue-button']} onClick={handleSubmit}>Simpan</Button>
</Stack>
</Paper>

View File

@@ -1,21 +1,21 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import JudulListTab from '@/app/admin/(dashboard)/_com/judulListTab';
import colors from '@/con/colors';
import { Box, Button, Image, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
import { useProxy } from 'valtio/utils';
import stateLayananDesa from '../../../_state/desa/layananDesa';
import { useShallowEffect } from '@mantine/hooks';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import { useEffect, useMemo, useState } from 'react';
import { useProxy } from 'valtio/utils';
import HeaderSearch from '../../../_com/header';
import JudulList from '../../../_com/judulList';
import stateLayananDesa from '../../../_state/desa/layananDesa';
function SuratKeterangan() {
const [search, setSearch] = useState("");
return (
<Box>
<HeaderSearch
title='Posisi Organisasi'
title='Pelayanan Surat Keterangan'
placeholder='pencarian'
searchIcon={<IconSearch size={20} />}
value={search}
@@ -30,54 +30,89 @@ function ListSuratKeterangan({ search }: { search: string }) {
const suratKeteranganState = useProxy(stateLayananDesa.suratKeterangan)
const router = useRouter()
useShallowEffect(() => {
suratKeteranganState.findMany.load()
const {
data,
page,
totalPages,
loading,
load,
} = suratKeteranganState.findMany;
useEffect(() => {
load(page, 10)
}, [])
const filteredData = (suratKeteranganState.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.name.toLowerCase().includes(keyword) ||
item.deskripsi.toLowerCase().includes(keyword)
);
});
if (!suratKeteranganState.findMany.data) {
return (
<Stack py={10}>
<Skeleton h={500} />
</Stack>
)
}
const filteredData = useMemo(() => {
if (!data) return [];
return data.filter(item => {
const keyword = search.toLowerCase();
return (
item.name?.toLowerCase().includes(keyword) ||
item.deskripsi?.toLowerCase().includes(keyword)
);
})
}, [data, search]);
// Handle loading state
if (loading || !data) {
return (
<Stack py={10}>
<Skeleton height={300} />
</Stack>
);
}
if (data.length === 0) {
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<JudulList
title='List Surat Keterangan'
href='/admin/desa/layanan/pelayanan_surat_keterangan/create'
/>
<Box style={{ overflowX: "auto" }}>
<Table striped withTableBorder withRowBorders>
<TableThead>
<TableTr>
<TableTh>Nama</TableTh>
<TableTh>Deskripsi</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
</Table>
</Box>
</Paper>
</Box>
);
}
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<JudulListTab
<JudulList
title='List Surat Keterangan'
href='/admin/desa/layanan/pelayanan_surat_keterangan/create'
placeholder='pencarian'
searchIcon={<IconSearch size={16} />}
/>
<Table striped withTableBorder withRowBorders>
<TableThead>
<TableTr>
<TableTh>Nama</TableTh>
<TableTh>Deskripsi</TableTh>
<TableTh>Image</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>{item.name}</TableTd>
<TableTd>
<Text truncate="end" fz={"sm"} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
</TableTd>
<TableTd>
<Image w={100} src={item.image?.link} alt="gambar" />
<Box w={200}>
<Text truncate="end" fz={"sm"}>{item.name}</Text>
</Box>
</TableTd>
<TableTd>
<Box w={300}>
<Text truncate="end" lineClamp={1} fz={"sm"} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
</Box>
</TableTd>
<TableTd>
<Text>
<Button onClick={() => router.push(`/admin/desa/layanan/pelayanan_surat_keterangan/${item.id}`)}>
@@ -90,6 +125,18 @@ function ListSuratKeterangan({ search }: { search: string }) {
</TableTbody>
</Table>
</Paper>
<Center>
<Pagination
value={page}
onChange={(newPage) => {
load(newPage, 10);
window.scrollTo(0, 0);
}}
total={totalPages}
mt="md"
mb="md"
/>
</Center>
</Box>
);
}

View File

@@ -1,6 +1,5 @@
'use client'
/* eslint-disable react-hooks/exhaustive-deps */
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
import colors from '@/con/colors';
import { Box, Button, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
@@ -76,6 +75,14 @@ function EditPelayananTelunjukSakti() {
label={<Text fz={"sm"} fw={"bold"}>Nama Surat Keterangan</Text>}
placeholder="masukkan nama surat keterangan"
/>
<TextInput
value={formData.deskripsi}
onChange={(val) => {
setFormData({ ...formData, deskripsi: val.target.value });
}}
label={<Text fz={"sm"} fw={"bold"}>Tautan Link</Text>}
placeholder="masukkan tautan link"
/>
<TextInput
value={formData.link}
onChange={(val) => {
@@ -84,15 +91,6 @@ function EditPelayananTelunjukSakti() {
label={<Text fz={"sm"} fw={"bold"}>Link</Text>}
placeholder="masukkan link"
/>
<Box>
<Text fz={"sm"} fw={"bold"}>Konten</Text>
<EditEditor
value={formData.deskripsi}
onChange={(htmlContent) => {
setFormData({ ...formData, deskripsi: htmlContent });
}}
/>
</Box>
<Button bg={colors['blue-button']} onClick={handleSubmit}>Simpan</Button>
</Stack>
</Paper>

View File

@@ -58,11 +58,24 @@ function DetailPelayananTelunjukSakti() {
</Box>
<Box>
<Text fw={"bold"} fz={"lg"}>Link</Text>
<Text fz={"lg"}>{telunjukSaktiState.findUnique.data?.link}</Text>
<Text
component="a"
href={telunjukSaktiState.findUnique.data?.link}
target="_blank"
rel="noopener noreferrer"
style={{
display: 'block',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap'
}}
>
{telunjukSaktiState.findUnique.data?.link}
</Text>
</Box>
<Box>
<Text fw={"bold"} fz={"lg"}>Deskripsi</Text>
<Text fz={"lg"}dangerouslySetInnerHTML={{ __html: telunjukSaktiState.findUnique.data?.deskripsi }}></Text>
<Text fz={"lg"}>{telunjukSaktiState.findUnique.data?.deskripsi}</Text>
</Box>
<Flex gap={"xs"} mt={10}>
<Button

View File

@@ -1,5 +1,4 @@
'use client'
import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
import colors from '@/con/colors';
import { Box, Button, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
@@ -43,6 +42,14 @@ function CreatePelayananTelunjukDesa() {
label={<Text fz={"sm"} fw={"bold"}>Nama Pelayanan Telunjuk Sakti Desa</Text>}
placeholder="masukkan nama pelayanan telunjuk sakti desa"
/>
<TextInput
value={stateTelunjukDesa.create.form.deskripsi}
onChange={(val) => {
stateTelunjukDesa.create.form.deskripsi = val.target.value;
}}
label={<Text fz={"sm"} fw={"bold"}>Tautan Link</Text>}
placeholder="masukkan tautan link"
/>
<TextInput
value={stateTelunjukDesa.create.form.link}
onChange={(val) => {
@@ -51,15 +58,6 @@ function CreatePelayananTelunjukDesa() {
label={<Text fz={"sm"} fw={"bold"}>Link</Text>}
placeholder="masukkan link"
/>
<Box>
<Text fz={"sm"} fw={"bold"}>Konten</Text>
<CreateEditor
value={stateTelunjukDesa.create.form.deskripsi}
onChange={(htmlContent) => {
stateTelunjukDesa.create.form.deskripsi = htmlContent;
}}
/>
</Box>
<Button bg={colors['blue-button']} onClick={handleSubmit}>Simpan</Button>
</Stack>
</Paper>

View File

@@ -1,89 +1,153 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import JudulListTab from '@/app/admin/(dashboard)/_com/judulListTab';
import colors from '@/con/colors';
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
import { useProxy } from 'valtio/utils';
import stateLayananDesa from '../../../_state/desa/layananDesa';
import { useShallowEffect } from '@mantine/hooks';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import { useEffect, useMemo, useState } from 'react';
import { useProxy } from 'valtio/utils';
import HeaderSearch from '../../../_com/header';
import JudulList from '../../../_com/judulList';
import stateLayananDesa from '../../../_state/desa/layananDesa';
function PelayananTelunjukSakti() {
const [search, setSearch] = useState("");
return (
<Box>
<HeaderSearch
title='Posisi Organisasi'
placeholder='pencarian'
searchIcon={<IconSearch size={20} />}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<ListPelayananTelunjukSakti search={search} />
</Box>
);
const [search, setSearch] = useState("");
return (
<Box>
<HeaderSearch
title='Posisi Organisasi'
placeholder='pencarian'
searchIcon={<IconSearch size={20} />}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<ListPelayananTelunjukSakti search={search} />
</Box>
);
}
function ListPelayananTelunjukSakti({ search }: { search: string }) {
const telunjukSaktiState = useProxy(stateLayananDesa.pelayananTelunjukSaktiDesa)
const router = useRouter()
useShallowEffect(() => {
telunjukSaktiState.findMany.load()
const {
data,
page,
totalPages,
loading,
load,
} = telunjukSaktiState.findMany;
useEffect(() => {
load(page, 10)
}, [])
const filteredData = (telunjukSaktiState.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.name.toLowerCase().includes(keyword) ||
item.deskripsi.toLowerCase().includes(keyword)
);
});
const filteredData = useMemo(() => {
if (!data) return [];
return data.filter(item => {
const keyword = search.toLowerCase();
return (
item.name?.toLowerCase().includes(keyword) ||
item.link?.toLowerCase().includes(keyword) ||
item.deskripsi?.toLowerCase().includes(keyword)
);
})
.sort((a, b) => a.posisi?.hierarki - b.posisi?.hierarki);
}, [data, search]);
if (!telunjukSaktiState.findMany.data) {
if (loading || !data) {
return (
<Stack py={10}>
<Skeleton h={500} />
<Skeleton height={300} />
</Stack>
)
);
}
if (data.length === 0) {
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<JudulList
title='List Pelayanan Telunjuk Sakti Desa'
href='/admin/desa/layanan/pelayanan_telunjuk_sakti_desa/create'
/>
<Table striped withTableBorder withRowBorders>
<TableThead>
<TableTr>
<TableTh>Nama</TableTh>
<TableTh>Link</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
<TableTbody>
<TableTr>
<TableTd colSpan={3}>
<Text fz={"sm"} color="gray.5">
Tidak ada data
</Text>
</TableTd>
</TableTr>
</TableTbody>
</Table>
</Paper>
</Box>
);
}
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<JudulListTab
<JudulList
title='List Pelayanan Telunjuk Sakti Desa'
href='/admin/desa/layanan/pelayanan_telunjuk_sakti_desa/create'
placeholder='pencarian'
searchIcon={<IconSearch size={16} />}
/>
<Table striped withTableBorder withRowBorders>
<TableThead>
<TableTr>
<TableTh>Nama</TableTh>
<TableTh>Deskripsi</TableTh>
<TableTh>Link</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>{item.name}</TableTd>
<TableTd><Text truncate="end" fz={"sm"} dangerouslySetInnerHTML={{ __html: item.deskripsi }} /></TableTd>
<TableTd>
<Text>
<Button onClick={() => router.push(`/admin/desa/layanan/pelayanan_telunjuk_sakti_desa/${item.id}`)}>
<IconDeviceImac size={20} />
</Button>
</Text>
</TableTd>
</TableTr>
<TableTd>
<Box w={100}>
<Text truncate="end" lineClamp={1} fz={"sm"} dangerouslySetInnerHTML={{ __html: item.name }} />
</Box>
</TableTd>
<TableTd>
<Box w={100}>
<a href={item.link} target="_blank" rel="noopener noreferrer">
<Text dangerouslySetInnerHTML={{ __html: item.deskripsi }} truncate="end" fz={"sm"} />
</a>
</Box>
</TableTd>
<TableTd>
<Text>
<Button onClick={() => router.push(`/admin/desa/layanan/pelayanan_telunjuk_sakti_desa/${item.id}`)}>
<IconDeviceImac size={20} />
</Button>
</Text>
</TableTd>
</TableTr>
))}
</TableTbody>
</Table>
</Paper>
<Center>
<Pagination
value={page}
onChange={(newPage) => {
load(newPage, 10);
window.scrollTo(0, 0);
}}
total={totalPages}
mt="md"
mb="md"
/>
</Center>
</Box>
);
}

View File

@@ -50,6 +50,10 @@ function DetailPenghargaan() {
{statePenghargaan.findUnique.data ? (
<Paper key={statePenghargaan.findUnique.data.id} bg={colors['BG-trans']} p={'md'}>
<Stack gap={"xs"}>
<Box>
<Text fw={"bold"} fz={"lg"}>Gambar</Text>
<Image w={{ base: 400, md: 400, lg: 400 }} src={statePenghargaan.findUnique.data?.image?.link} alt="gambar" />
</Box>
<Box>
<Text fw={"bold"} fz={"lg"}>Judul</Text>
<Text fz={"lg"}>{statePenghargaan.findUnique.data?.name}</Text>
@@ -62,10 +66,6 @@ function DetailPenghargaan() {
<Text fw={"bold"} fz={"lg"}>Deskripsi</Text>
<Text fz={"lg"} dangerouslySetInnerHTML={{ __html: statePenghargaan.findUnique.data?.deskripsi }} />
</Box>
<Box>
<Text fw={"bold"} fz={"lg"}>Gambar</Text>
<Image w={{ base: 150, md: 150, lg: 150 }} src={statePenghargaan.findUnique.data?.image?.link} alt="gambar" />
</Box>
<Flex gap={"xs"} mt={10}>
<Button
onClick={() => {

View File

@@ -1,61 +1,103 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import penghargaanState from '@/app/admin/(dashboard)/_state/desa/penghargaan';
import colors from '@/con/colors';
import { Box, Button, Image, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { Box, Button, Center, Image, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useEffect, useMemo, useState } from 'react';
import { useProxy } from 'valtio/utils';
import JudulListTab from '../../_com/judulListTab';
import { useState } from 'react';
import HeaderSearch from '../../_com/header';
import JudulList from '../../_com/judulList';
function Penghargaan() {
const [search, setSearch] = useState("");
return (
<Box>
<HeaderSearch
title='Posisi Organisasi'
placeholder='pencarian'
searchIcon={<IconSearch size={20} />}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<ListPenghargaan search={search} />
</Box>
);
const [search, setSearch] = useState("");
return (
<Box>
<HeaderSearch
title='Penghargaan'
placeholder='pencarian'
searchIcon={<IconSearch size={20} />}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<ListPenghargaan search={search} />
</Box>
);
}
function ListPenghargaan({ search }: { search: string }) {
const state = useProxy(penghargaanState)
const router = useRouter()
useShallowEffect(() => {
state.findMany.load()
const {
data,
page,
totalPages,
loading,
load,
} = state.findMany;
useEffect(() => {
load(page, 10)
}, [])
const filteredData = (state.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.name.toLowerCase().includes(keyword) ||
item.deskripsi.toLowerCase().includes(keyword)
);
});
const filteredData = useMemo(() => {
if (!data) return [];
return data.filter(item => {
const keyword = search.toLowerCase();
return (
item.name?.toLowerCase().includes(keyword) ||
item.deskripsi?.toLowerCase().includes(keyword)
);
})
}, [data, search]);
if (!state.findMany.data) {
return(
// Handle loading state
if (loading || !data) {
return (
<Stack py={10}>
<Skeleton h={500} />
<Skeleton height={300} />
</Stack>
);
}
if (data.length === 0) {
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<JudulList
title='List Penghargaan'
href='/admin/desa/penghargaan/create'
/>
<Table striped withTableBorder withRowBorders>
<TableThead>
<TableTr>
<TableTh>Nama</TableTh>
<TableTh>Deskripsi</TableTh>
<TableTh>Image</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
<TableTbody>
<TableTr>
<TableTd colSpan={4}>
<Text ta="center">Tidak ada data</Text>
</TableTd>
</TableTr>
</TableTbody>
</Table>
</Paper>
</Box>
)
}
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<JudulListTab
<JudulList
title='List Penghargaan'
href='/admin/desa/penghargaan/create'
placeholder='pencarian'
searchIcon={<IconSearch size={16} />}
/>
<Table striped withTableBorder withRowBorders>
<TableThead>
@@ -69,25 +111,43 @@ function ListPenghargaan({ search }: { search: string }) {
<TableTbody>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>{item.name}</TableTd>
<TableTd>
<Text truncate="end" fz={"sm"} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
<Box w={100}>
<Text lineClamp={1} truncate="end" fz={"sm"}>{item.name}</Text>
</Box>
</TableTd>
<TableTd>
<Box w={100}>
<Text lineClamp={1} truncate="end" fz={"sm"} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
</Box>
</TableTd>
<TableTd>
<Image w={100} src={item.image?.link} alt="gambar" />
</TableTd>
</TableTd>
<TableTd>
<Text>
<Button onClick={() => router.push(`/admin/desa/penghargaan/${item.id}`)}>
<IconDeviceImac size={20} />
</Button>
</Text>
</TableTd>
</TableTr>
))}
<IconDeviceImac size={20} />
</Button>
</Text>
</TableTd>
</TableTr>
))}
</TableTbody>
</Table>
</Paper>
<Center>
<Pagination
value={page}
onChange={(newPage) => {
load(newPage, 10);
window.scrollTo(0, 0);
}}
total={totalPages}
mt="md"
mb="md"
/>
</Center>
</Box>
);
}

View File

@@ -0,0 +1,63 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import colors from '@/con/colors';
import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
import { usePathname, useRouter } from 'next/navigation';
import React, { useEffect, useState } from 'react';
function LayoutTabsPotensi({ children }: { children: React.ReactNode }) {
const router = useRouter()
const pathname = usePathname()
const tabs = [
{
label: "List Potensi",
value: "list_potensi",
href: "/admin/desa/potensi/list-potensi"
},
{
label: "Kategori Potensi",
value: "kategori_potensi",
href: "/admin/desa/potensi/kategori-potensi"
},
];
const curentTab = tabs.find(tab => tab.href === pathname)
const [activeTab, setActiveTab] = useState<string | null>(curentTab?.value || tabs[0].value);
const handleTabChange = (value: string | null) => {
const tab = tabs.find(t => t.value === value)
if (tab) {
router.push(tab.href)
}
setActiveTab(value)
}
useEffect(() => {
const match = tabs.find(tab => tab.href === pathname)
if (match) {
setActiveTab(match.value)
}
}, [pathname])
return (
<Stack>
<Title order={3}>Potensi</Title>
<Tabs color={colors['blue-button']} variant='pills' value={activeTab} onChange={handleTabChange}>
<TabsList p={"xs"} bg={"#BBC8E7FF"}>
{tabs.map((e, i) => (
<TabsTab key={i} value={e.value}>{e.label}</TabsTab>
))}
</TabsList>
{tabs.map((e, i) => (
<TabsPanel key={i} value={e.value}>
{/* Konten dummy, bisa diganti tergantung routing */}
<></>
</TabsPanel>
))}
</Tabs>
{children}
</Stack>
);
}
export default LayoutTabsPotensi;

View File

@@ -1,128 +0,0 @@
'use client';
import colors from '@/con/colors';
import ApiFetch from '@/lib/api-fetch';
import { Box, Button, Center, FileInput, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
import { useState } from 'react';
import { toast } from 'react-toastify';
import { useProxy } from 'valtio/utils';
import potensiDesaState from '../../../_state/desa/potensi';
import { useRouter } from 'next/navigation';
import CreateEditor from '../../../_com/createEditor';
function CreatePotensi() {
const potensiState = useProxy(potensiDesaState);
const [previewImage, setPreviewImage] = useState<string | null>(null);
const [file, setFile] = useState<File | null>(null);
const router = useRouter();
const handleSubmit = async () => {
if (!file) return toast.warn('Pilih file gambar terlebih dahulu');
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');
}
potensiState.create.form.imageId = uploaded.id;
await potensiState.create.create();
resetForm();
router.push('/admin/desa/potensi');
};
const resetForm = () => {
potensiState.create.form = {
name: '',
deskripsi: '',
kategori: '',
imageId: '',
content: '',
};
setPreviewImage(null);
setFile(null);
};
return (
<Box>
<Box mb={10}>
<Button variant="subtle" onClick={() => router.back()}>
<IconArrowBack color={colors['blue-button']} size={25} />
</Button>
</Box>
<Paper bg={colors['white-1']} p="md" w={{ base: '100%', md: '50%' }}>
<Stack gap="xs">
<Title order={3}>Create Potensi</Title>
<TextInput
value={potensiState.create.form.name}
onChange={(val) => (potensiState.create.form.name = val.target.value)}
label={<Text fz="sm" fw="bold">Judul</Text>}
placeholder="masukkan judul"
/>
<TextInput
value={potensiState.create.form.deskripsi}
onChange={(val) => (potensiState.create.form.deskripsi = val.target.value)}
label={<Text fz="sm" fw="bold">Deskripsi</Text>}
placeholder="masukkan deskripsi"
/>
<TextInput
value={potensiState.create.form.kategori}
onChange={(val) => (potensiState.create.form.kategori = val.target.value)}
label={<Text fz="sm" fw="bold">Kategori</Text>}
placeholder="masukkan kategori"
/>
<FileInput
label={<Text fz="sm" fw="bold">Upload Gambar</Text>}
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 ? (
<Image alt="" src={previewImage} w={200} h={200} />
) : (
<Center w={200} h={200} bg="gray">
<IconImageInPicture />
</Center>
)}
<Box>
<Text fz="sm" fw="bold">Konten</Text>
<CreateEditor
value={potensiState.create.form.content}
onChange={(htmlContent) => {
potensiState.create.form.content = htmlContent;
}}
/>
</Box>
<Button bg={colors['blue-button']} onClick={handleSubmit}>
Simpan Potensi
</Button>
</Stack>
</Paper>
</Box>
);
}
export default CreatePotensi;

View File

@@ -0,0 +1,80 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import potensiDesaState from '@/app/admin/(dashboard)/_state/desa/potensi';
import colors from '@/con/colors';
import { Box, Button, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
import { IconArrowBack } from '@tabler/icons-react';
import { useParams, useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { toast } from 'react-toastify';
import { useProxy } from 'valtio/utils';
function EditKategoriPotensi() {
const editState = useProxy(potensiDesaState.kategoriPotensi)
const router = useRouter();
const params = useParams();
const [formData, setFormData] = useState({
nama: editState.update.form.nama || '',
});
useEffect(() => {
const loadKategori = async () => {
const id = params?.id as string;
if (!id) return;
try {
const data = await editState.update.load(id); // akses langsung, bukan dari proxy
if (data) {
setFormData({
nama: data.nama || '',
});
}
} catch (error) {
console.error("Error loading kategori potensi:", error);
toast.error("Gagal memuat data kategori potensi");
}
};
loadKategori();
}, [params?.id]);
const handleSubmit = async () => {
try {
editState.update.form = {
...editState.update.form,
nama: formData.nama,
};
await editState.update.update();
toast.success('Kategori Potensi berhasil diperbarui!');
router.push('/admin/desa/potensi/kategori-potensi');
} catch (error) {
console.error('Error updating kategori potensi:', error);
toast.error('Terjadi kesalahan saat memperbarui kategori potensi');
}
};
return (
<Box>
<Box mb={10}>
<Button variant="subtle" onClick={() => router.back()}>
<IconArrowBack color={colors["blue-button"]} size={30} />
</Button>
</Box>
<Paper bg={"white"} p={"md"} w={{ base: "100%", md: "50%" }}>
<Stack gap={"xs"}>
<Title order={3}>Edit Kategori Potensi</Title>
<TextInput
value={formData.nama}
onChange={(e) => setFormData({ ...formData, nama: e.target.value })}
label={<Text fz={"sm"} fw={"bold"}>Nama Kategori Potensi</Text>}
placeholder="masukkan nama kategori potensi"
/>
<Button onClick={handleSubmit}>Simpan</Button>
</Stack>
</Paper>
</Box>
);
}
export default EditKategoriPotensi;

View File

@@ -0,0 +1,55 @@
'use client'
import potensiDesaState from '@/app/admin/(dashboard)/_state/desa/potensi';
import colors from '@/con/colors';
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
import { IconArrowBack } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useProxy } from 'valtio/utils';
function CreateKategoriPotensi() {
const createState = useProxy(potensiDesaState.kategoriPotensi)
const router = useRouter();
const resetForm = () => {
createState.create.form = {
nama: "",
};
};
const handleSubmit = async () => {
await createState.create.create();
resetForm();
router.push("/admin/desa/potensi/kategori-potensi")
};
return (
<Box>
<Box mb={10}>
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
<IconArrowBack color={colors['blue-button']} size={25} />
</Button>
</Box>
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
<Stack gap={"xs"}>
<Title order={4}>Create Kategori Potensi</Title>
<TextInput
label={<Text fw={"bold"} fz={"sm"}>Nama Kategori Potensi</Text>}
placeholder='Masukkan nama kategori Potensi'
value={createState.create.form.nama}
onChange={(val) => {
createState.create.form.nama = val.target.value;
}}
/>
<Group>
<Button onClick={handleSubmit} bg={colors['blue-button']}>Submit</Button>
</Group>
</Stack>
</Paper>
</Box>
);
}
export default CreateKategoriPotensi;

View File

@@ -0,0 +1,128 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import colors from '@/con/colors';
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { IconEdit, IconSearch, IconTrash } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils';
import HeaderSearch from '../../../_com/header';
import JudulList from '../../../_com/judulList';
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
import potensiDesaState from '../../../_state/desa/potensi';
function KategoriPotensi() {
const [search, setSearch] = useState('');
return (
<Box>
<HeaderSearch
title='Kategori Potensi'
placeholder='pencarian'
searchIcon={<IconSearch size={20} />}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<ListKategoriPotensi search={search} />
</Box>
);
}
function ListKategoriPotensi({ search }: { search: string }) {
const listDataState = useProxy(potensiDesaState.kategoriPotensi)
const router = useRouter();
const [modalHapus, setModalHapus] = useState(false)
const [selectedId, setSelectedId] = useState<string | null>(null)
useEffect(() => {
listDataState.findMany.load()
}, [])
const handleDelete = () => {
if (selectedId) {
listDataState.delete.delete(selectedId)
setModalHapus(false)
setSelectedId(null)
listDataState.findMany.load()
}
}
const filteredData = (listDataState.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.nama.toLowerCase().includes(keyword)
);
});
if (!listDataState.findMany.data) {
return (
<Stack py={10}>
<Skeleton h={500} />
</Stack>
)
}
return (
<Box py={10}>
<Paper bg={colors['white-1']} p="md">
<Stack>
<JudulList
title='List Kategori Potensi'
href='/admin/desa/potensi/kategori-potensi/create'
/>
<Box style={{ overflowX: 'auto' }}>
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
<TableThead>
<TableTr>
<TableTh>No</TableTh>
<TableTh>Nama</TableTh>
<TableTh>Edit</TableTh>
<TableTh>Hapus</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{filteredData.map((item, index) => (
<TableTr key={item.id}>
<TableTd>
<Box w={100}>
<Text truncate="end" fz={"sm"}>{index + 1}</Text>
</Box>
</TableTd>
<TableTd>{item.nama}</TableTd>
<TableTd>
<Button color='green' onClick={() => router.push(`/admin/desa/potensi/kategori-potensi/${item.id}`)}>
<IconEdit size={20} />
</Button>
</TableTd>
<TableTd>
<Button
color='red'
disabled={listDataState.delete.loading}
onClick={() => {
setSelectedId(item.id)
setModalHapus(true)
}}>
<IconTrash size={20} />
</Button>
</TableTd>
</TableTr>
))}
</TableTbody>
</Table>
</Box>
</Stack>
</Paper>
{/* Modal Konfirmasi Hapus */}
<ModalKonfirmasiHapus
opened={modalHapus}
onClose={() => setModalHapus(false)}
onConfirm={handleDelete}
text='Apakah anda yakin ingin menghapus kategori Potensi ini?'
/>
</Box>
)
}
export default KategoriPotensi;

View File

@@ -0,0 +1,14 @@
'use client'
import React from 'react';
import LayoutTabsPotensi from './_lib/layoutTabs';
function Layout({ children }: { children: React.ReactNode }) {
return (
<LayoutTabsPotensi>
{children}
</LayoutTabsPotensi>
)
}
export default Layout;

View File

@@ -1,21 +1,22 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import EditEditor from "@/app/admin/(dashboard)/_com/editEditor";
import potensiDesaState from "@/app/admin/(dashboard)/_state/desa/potensi";
import colors from "@/con/colors";
import ApiFetch from "@/lib/api-fetch";
import { Box, Button, Paper, Stack, Title, TextInput, FileInput, Center, Text, Image } from "@mantine/core";
import { IconArrowBack, IconImageInPicture } from "@tabler/icons-react";
import { useParams } from "next/navigation";
import { useRouter } from "next/navigation";
import { useState, useEffect } from "react";
import { Box, Button, Group, Image, Paper, Select, Stack, Text, TextInput, Title } from "@mantine/core";
import { Dropzone } from "@mantine/dropzone";
import { IconArrowBack, IconPhoto, IconUpload, IconX } from "@tabler/icons-react";
import { useParams, useRouter } from "next/navigation";
import { useEffect, useState } from "react";
import { toast } from "react-toastify";
import { useProxy } from "valtio/utils";
function EditPotensi() {
const potensiState = useProxy(potensiDesaState)
const potensiState = useProxy(potensiDesaState.potensiDesa)
const router = useRouter()
const params = useParams()
@@ -24,23 +25,24 @@ function EditPotensi() {
const [formData, setFormData] = useState({
name: '',
deskripsi: '',
kategori: '',
kategoriId: '',
content: '',
imageId: ''
});
useEffect(() => {
potensiDesaState.kategoriPotensi.findMany.load()
const loadPotensi = async () => {
const id = params?.id as string;
if (!id) return;
try {
const data = await potensiDesaState.edit.load(id); // ambil data dari API
const data = await potensiState.edit.load(id); // ambil data dari API
if (data) {
setFormData({
name: data.name || '',
deskripsi: data.deskripsi || '',
kategori: data.kategori || '',
kategoriId: data.kategoriId || '',
content: data.content || '',
imageId: data.imageId || '',
});
@@ -65,7 +67,7 @@ function EditPotensi() {
...potensiState.edit.form,
name: formData.name,
deskripsi: formData.deskripsi,
kategori: formData.kategori,
kategoriId: formData.kategoriId,
content: formData.content,
};
@@ -82,7 +84,7 @@ function EditPotensi() {
await potensiState.edit.update();
toast.success("Potensi berhasil diperbarui!");
router.push("/admin/desa/potensi");
router.push("/admin/desa/potensi/list-potensi");
} catch (error) {
console.error("Error updating potensi:", error);
toast.error("Terjadi kesalahan saat memperbarui potensi");
@@ -119,40 +121,82 @@ function EditPotensi() {
label={<Text fz={"sm"} fw={"bold"}>Deskripsi</Text>}
placeholder="masukkan deskripsi"
/>
<TextInput
value={formData.kategori}
onChange={(e) => {
const val = e.target.value;
setFormData((prev) => ({ ...prev, kategori: val }));
potensiState.edit.form.kategori = val;
}}
label={<Text fz={"sm"} fw={"bold"}>Kategori</Text>}
placeholder="masukkan kategori"
<Select
value={formData.kategoriId}
onChange={(val) => setFormData({ ...formData, kategoriId: val || "" })}
label={<Text fw={"bold"} fz={"sm"}>Kategori</Text>}
placeholder='Pilih kategori'
data={
potensiDesaState.kategoriPotensi.findMany.data?.map((v) => ({
value: v.id,
label: v.nama
})) || []
}
clearable
searchable
required
error={!formData.kategoriId ? "Pilih kategori" : undefined}
/>
<Box>
<Text fz={"md"} fw={"bold"}>Gambar</Text>
<Box>
<Dropzone
onDrop={(files) => {
const selectedFile = files[0]; // Ambil file pertama
if (selectedFile) {
setFile(selectedFile);
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
}
}}
onReject={() => toast.error('File tidak valid.')}
maxSize={5 * 1024 ** 2} // Maks 5MB
accept={{ 'image/*': [] }}
>
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
<Dropzone.Accept>
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
</Dropzone.Accept>
<Dropzone.Reject>
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
</Dropzone.Reject>
<Dropzone.Idle>
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
</Dropzone.Idle>
<FileInput
label={<Text fz={"sm"} fw={"bold"}>Upload Gambar</Text>}
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 ? (
<Image alt="" src={previewImage} w={200} h={200} />
) : (
<Center w={200} h={200} bg={"gray"}>
<IconImageInPicture />
</Center>
)}
<div>
<Text size="xl" inline>
Drag gambar ke sini atau klik untuk pilih file
</Text>
<Text size="sm" c="dimmed" inline mt={7}>
Maksimal 5MB dan harus format gambar
</Text>
</div>
</Group>
</Dropzone>
{/* Tampilkan preview kalau ada */}
{previewImage && (
<Box mt="sm">
<Image
src={previewImage}
alt="Preview"
style={{
maxWidth: '100%',
maxHeight: '200px',
objectFit: 'contain',
borderRadius: '8px',
border: '1px solid #ddd',
}}
/>
</Box>
)}
</Box>
</Box>
<Box>
<Text fz={"sm"} fw={"bold"}>Konten</Text>
<EditEditor
value={formData.content}
value={formData.content}
onChange={(htmlContent) => {
setFormData((prev) => ({ ...prev, content: htmlContent }));
potensiState.edit.form.content = htmlContent;

View File

@@ -5,18 +5,19 @@ import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import React from 'react';
import { useProxy } from 'valtio/utils';
import potensiDesaState from '../../../../_state/desa/potensi';
import { useShallowEffect } from '@mantine/hooks';
import { useParams } from 'next/navigation';
import { useState } from 'react';
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
import potensiDesaState from '@/app/admin/(dashboard)/_state/desa/potensi';
export default function DetailPotensi() {
const router = useRouter()
const params = useParams()
const [modalHapus, setModalHapus] = useState(false)
const [selectedId, setSelectedId] = useState<string | null>(null)
const potensiState = useProxy(potensiDesaState)
const potensiState = useProxy(potensiDesaState.potensiDesa)
useShallowEffect(() => {
potensiState.findUnique.load(params?.id as string)
@@ -60,7 +61,7 @@ export default function DetailPotensi() {
</Box>
<Box>
<Text fz={"lg"} fw={"bold"}>Kategori</Text>
<Text fz={"lg"}>{potensiState.findUnique.data.kategori}</Text>
<Text fz={"lg"}>{potensiState.findUnique.data.kategori?.nama}</Text>
</Box>
<Box>
<Text fz={"lg"} fw={"bold"}>Deskripsi</Text>
@@ -91,7 +92,7 @@ export default function DetailPotensi() {
<Button
onClick={() => {
if (potensiState.findUnique.data) {
router.push(`/admin/desa/potensi/edit/${potensiState.findUnique.data.id}`)
router.push(`/admin/desa/potensi/list-potensi/${potensiState.findUnique.data.id}/edit`)
}
}}
disabled={!potensiState.findUnique.data}

View File

@@ -0,0 +1,176 @@
'use client';
import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
import potensiDesaState from '@/app/admin/(dashboard)/_state/desa/potensi';
import colors from '@/con/colors';
import ApiFetch from '@/lib/api-fetch';
import { Box, Button, Group, Image, Paper, Select, Stack, Text, TextInput, Title } from '@mantine/core';
import { Dropzone } from '@mantine/dropzone';
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { toast } from 'react-toastify';
import { useProxy } from 'valtio/utils';
function CreatePotensi() {
const potensiState = useProxy(potensiDesaState.potensiDesa);
const [previewImage, setPreviewImage] = useState<string | null>(null);
const [file, setFile] = useState<File | null>(null);
const router = useRouter();
useEffect(() => {
potensiDesaState.kategoriPotensi.findMany.load()
}, [])
const handleSubmit = async () => {
if (!file) return toast.warn('Pilih file gambar terlebih dahulu');
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');
}
potensiState.create.form.imageId = uploaded.id;
await potensiState.create.create();
resetForm();
router.push('/admin/desa/potensi/list-potensi');
};
const resetForm = () => {
potensiState.create.form = {
name: '',
deskripsi: '',
kategoriId: '',
imageId: '',
content: '',
};
setPreviewImage(null);
setFile(null);
};
return (
<Box>
<Box mb={10}>
<Button variant="subtle" onClick={() => router.back()}>
<IconArrowBack color={colors['blue-button']} size={25} />
</Button>
</Box>
<Paper bg={colors['white-1']} p="md" w={{ base: '100%', md: '50%' }}>
<Stack gap="xs">
<Title order={3}>Create Potensi</Title>
<TextInput
value={potensiState.create.form.name}
onChange={(val) => (potensiState.create.form.name = val.target.value)}
label={<Text fz="sm" fw="bold">Judul</Text>}
placeholder="masukkan judul"
/>
<TextInput
value={potensiState.create.form.deskripsi}
onChange={(val) => (potensiState.create.form.deskripsi = val.target.value)}
label={<Text fz="sm" fw="bold">Deskripsi</Text>}
placeholder="masukkan deskripsi"
/>
<Select
label={<Text fw={"bold"} fz={"sm"}>Kategori</Text>}
placeholder='Pilih kategori'
value={potensiState.create.form.kategoriId || ""}
onChange={(val) => {
potensiState.create.form.kategoriId = val ?? "";
}}
data={potensiDesaState.kategoriPotensi.findMany.data?.map((item) => ({
value: item.id,
label: item.nama,
}))}
/>
<Box>
<Text fz={"md"} fw={"bold"}>Gambar</Text>
<Box>
<Dropzone
onDrop={(files) => {
const selectedFile = files[0]; // Ambil file pertama
if (selectedFile) {
setFile(selectedFile);
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
}
}}
onReject={() => toast.error('File tidak valid.')}
maxSize={5 * 1024 ** 2} // Maks 5MB
accept={{ 'image/*': [] }}
>
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
<Dropzone.Accept>
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
</Dropzone.Accept>
<Dropzone.Reject>
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
</Dropzone.Reject>
<Dropzone.Idle>
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
</Dropzone.Idle>
<div>
<Text size="xl" inline>
Drag gambar ke sini atau klik untuk pilih file
</Text>
<Text size="sm" c="dimmed" inline mt={7}>
Maksimal 5MB dan harus format gambar
</Text>
</div>
</Group>
</Dropzone>
{/* Tampilkan preview kalau ada */}
{previewImage && (
<Box mt="sm">
<Image
src={previewImage}
alt="Preview"
style={{
maxWidth: '100%',
maxHeight: '200px',
objectFit: 'contain',
borderRadius: '8px',
border: '1px solid #ddd',
}}
/>
</Box>
)}
</Box>
</Box>
<Box>
<Text fz="sm" fw="bold">Konten</Text>
<CreateEditor
value={potensiState.create.form.content}
onChange={(htmlContent) => {
potensiState.create.form.content = htmlContent;
}}
/>
</Box>
<Button bg={colors['blue-button']} onClick={handleSubmit}>
Simpan Potensi
</Button>
</Stack>
</Paper>
</Box>
);
}
export default CreatePotensi;

View File

@@ -0,0 +1,158 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import colors from '@/con/colors';
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils';
import HeaderSearch from '../../../_com/header';
import JudulList from '../../../_com/judulList';
import potensiDesaState from '../../../_state/desa/potensi';
function Potensi() {
const [search, setSearch] = useState("");
return (
<Box>
<HeaderSearch
title='Posisi Organisasi'
placeholder='pencarian'
searchIcon={<IconSearch size={20} />}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<ListPotensi search={search} />
</Box>
);
}
function ListPotensi({ search }: { search: string }) {
const potensiState = useProxy(potensiDesaState)
const router = useRouter()
const {
data,
page,
totalPages,
loading,
load,
} = potensiState.potensiDesa.findMany;
useEffect(() => {
potensiState.kategoriPotensi.findMany.load()
load(page, 10)
}, [])
const filteredData = (potensiState.potensiDesa.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.name.toLowerCase().includes(keyword) ||
item.kategori?.nama.toLowerCase().includes(keyword) ||
item.deskripsi.toLowerCase().includes(keyword)
);
});
// Handle loading state
if (loading || !data) {
return (
<Stack py={10}>
<Skeleton height={300} />
</Stack>
);
}
if (data.length === 0) {
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<Stack>
<JudulList
title='List Potensi'
href='/admin/desa/potensi/list-potensi/create'
/>
<Box style={{ overflowX: "auto" }}>
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
<TableThead>
<TableTr>
<TableTh>Judul</TableTh>
<TableTh>Kategori</TableTh>
<TableTh>Deskripsi</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
<TableTbody>
<TableTr>
<TableTd colSpan={4}>Tidak Ada Data</TableTd>
</TableTr>
</TableTbody>
</Table>
</Box>
</Stack>
</Paper>
</Box>
)
}
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<Stack>
<JudulList
title='List Potensi'
href='/admin/desa/potensi/list-potensi/create'
/>
<Box style={{ overflowX: "auto" }}>
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
<TableThead>
<TableTr>
<TableTh>Judul</TableTh>
<TableTh>Kategori</TableTh>
<TableTh>Deskripsi</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>
<Box w={100}>
<Text truncate="end" fz={"sm"}>{item.name}</Text>
</Box></TableTd>
<TableTd>{item.kategori?.nama}</TableTd>
<TableTd>
<Box w={300}>
<Text truncate="end" fz={"sm"} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
</Box>
</TableTd>
<TableTd>
<Button onClick={() => router.push(`/admin/desa/potensi/list-potensi/${item.id}`)}>
<IconDeviceImacCog size={25} />
</Button>
</TableTd>
</TableTr>
))}
</TableTbody>
</Table>
</Box>
</Stack>
</Paper>
<Center>
<Pagination
value={page}
onChange={(newPage) => {
load(newPage, 10);
window.scrollTo(0, 0);
}}
total={totalPages}
mt="md"
mb="md"
/>
</Center>
</Box>
)
}
export default Potensi;

View File

@@ -1,100 +0,0 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Image, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useProxy } from 'valtio/utils';
import HeaderSearch from '../../_com/header';
import JudulList from '../../_com/judulList';
import potensiDesaState from '../../_state/desa/potensi';
import { useState } from 'react';
function Potensi() {
const [search, setSearch] = useState("");
return (
<Box>
<HeaderSearch
title='Posisi Organisasi'
placeholder='pencarian'
searchIcon={<IconSearch size={20} />}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<ListPotensi search={search} />
</Box>
);
}
function ListPotensi({ search }: { search: string }) {
const potensiState = useProxy(potensiDesaState)
const router = useRouter()
useShallowEffect(() => {
potensiState.findMany.load()
}, [])
const filteredData = (potensiState.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.name.toLowerCase().includes(keyword) ||
item.kategori.toLowerCase().includes(keyword)
);
});
if (!potensiState.findMany.data) {
return (
<Stack py={10}>
<Skeleton h={500} />
</Stack>
)
}
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<Stack>
<JudulList
title='List Potensi'
href='/admin/desa/potensi/create'
/>
<Box style={{ overflowX: "auto" }}>
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
<TableThead>
<TableTr>
<TableTh>Judul</TableTh>
<TableTh>Kategori</TableTh>
<TableTh>Image</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>
<Box w={100}>
<Text truncate="end" fz={"sm"}>{item.name}</Text>
</Box></TableTd>
<TableTd>{item.kategori}</TableTd>
<TableTd>
<Image w={100} src={item.image?.link} alt="image" />
</TableTd>
<TableTd>
<Button onClick={() => router.push(`/admin/desa/potensi/detail/${item.id}`)}>
<IconDeviceImacCog size={25} />
</Button>
</TableTd>
</TableTr>
))}
</TableTbody>
</Table>
</Box>
</Stack>
</Paper>
</Box>
)
}
export default Potensi;

View File

@@ -195,7 +195,6 @@ function CreateAPBDes() {
placeholder='Masukkan judul'
/>
<TextInput
type='number'
value={stateAPBDes.create.form.jumlah}
onChange={(val) => {
stateAPBDes.create.form.jumlah = val.target.value;

View File

@@ -77,7 +77,7 @@ function ListAPBDes({ search }: { search: string }) {
</Box>
</TableTd>
<TableTd>
<Text truncate="end" fz={"sm"}>{item.jumlah}</Text>
<Text truncate="end" fz={"sm"}>Rp. {item.jumlah}</Text>
</TableTd>
<TableTd>
{item.file?.link ? (

View File

@@ -1,10 +1,10 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import colors from '@/con/colors';
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
import { IconEdit, IconSearch, IconX } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import { useEffect, useMemo, useState } from 'react';
import { useProxy } from 'valtio/utils';
import HeaderSearch from '../../../_com/header';
import JudulList from '../../../_com/judulList';
@@ -34,6 +34,14 @@ function ListKategoriKegiatan({ search }: { search: string }) {
const [selectedId, setSelectedId] = useState<string | null>(null)
const router = useRouter()
const {
data,
page,
totalPages,
loading,
load,
} = stateKategori.findMany;
const handleHapus = () => {
if (selectedId) {
stateKategori.delete.byId(selectedId)
@@ -42,23 +50,51 @@ function ListKategoriKegiatan({ search }: { search: string }) {
}
}
useShallowEffect(() => {
stateKategori.findMany.load()
}, [])
useEffect(() => {
load(page, 10)
}, [page])
const filteredData = (stateKategori.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.name.toLowerCase().includes(keyword)
);
});
const filteredData = useMemo(() => {
if (!data) return [];
return data.filter(item => {
const keyword = search.toLowerCase();
return (
item.name?.toLowerCase().includes(keyword)
);
})
}, [data, search]);
if (!stateKategori.findMany.data) {
// Handle loading state
if (loading || !data) {
return (
<Stack py={10}>
<Skeleton h={500} />
<Skeleton height={550} />
</Stack>
)
);
}
if (data.length === 0) {
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<JudulList
title='List Kategori Kegiatan'
href='/admin/landing-page/desa-anti-korupsi/kategori-desa-anti-korupsi/create'
/>
<Box style={{ overflowX: "auto" }}>
<Table striped withTableBorder withRowBorders>
<TableThead>
<TableTr>
<TableTh>Nama</TableTh>
<TableTh>Edit</TableTh>
<TableTh>Delete</TableTh>
</TableTr>
</TableThead>
</Table>
</Box>
</Paper>
</Box>
);
}
return (
@@ -68,38 +104,50 @@ function ListKategoriKegiatan({ search }: { search: string }) {
title='List Kategori Kegiatan'
href='/admin/landing-page/desa-anti-korupsi/kategori-desa-anti-korupsi/create'
/>
<Box style={{overflowY: "auto"}}>
<Table striped withTableBorder withRowBorders>
<TableThead>
<TableTr>
<TableTh>Nama Kategori</TableTh>
<TableTh>Edit</TableTh>
<TableTh>Delete</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>{item.name}</TableTd>
<TableTd>
<Button color="green" onClick={() => router.push(`/admin/landing-page/desa-anti-korupsi/kategori-desa-anti-korupsi/${item.id}`)}>
<IconEdit size={20} />
</Button>
</TableTd>
<TableTd>
<Button color="red" onClick={() => {
setSelectedId(item.id)
setModalHapus(true)
}}>
<IconX size={20} />
</Button>
</TableTd>
<Box style={{ overflowY: "auto" }}>
<Table striped withTableBorder withRowBorders>
<TableThead>
<TableTr>
<TableTh>Nama Kategori</TableTh>
<TableTh>Edit</TableTh>
<TableTh>Delete</TableTh>
</TableTr>
))}
</TableTbody>
</Table>
</TableThead>
<TableTbody>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>{item.name}</TableTd>
<TableTd>
<Button color="green" onClick={() => router.push(`/admin/landing-page/desa-anti-korupsi/kategori-desa-anti-korupsi/${item.id}`)}>
<IconEdit size={20} />
</Button>
</TableTd>
<TableTd>
<Button color="red" onClick={() => {
setSelectedId(item.id)
setModalHapus(true)
}}>
<IconX size={20} />
</Button>
</TableTd>
</TableTr>
))}
</TableTbody>
</Table>
</Box>
</Paper>
<Center>
<Pagination
value={page}
onChange={(newPage) => {
load(newPage, 10);
window.scrollTo(0, 0);
}}
total={totalPages}
mt="md"
mb="md"
/>
</Center>
{/* Modal Konfirmasi Hapus */}
<ModalKonfirmasiHapus
opened={modalHapus}

View File

@@ -1,10 +1,10 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import colors from '@/con/colors';
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { useEffect, useMemo, useState } from 'react';
import { useProxy } from 'valtio/utils';
import HeaderSearch from '../../../_com/header';
import JudulList from '../../../_com/judulList';
@@ -29,27 +29,65 @@ function DesaAntiKorupsi() {
function ListDesaAntiKorupsi({ search }: { search: string }) {
const listState = useProxy(korupsiState.desaAntikorupsi)
const router = useRouter();
const {
data,
page,
totalPages,
loading,
load,
} = listState.findMany;
useEffect(() => {
listState.findMany.load()
}, [])
load(page, 10);
}, [page]);
const filteredData = (listState.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.name.toLowerCase().includes(keyword) ||
item.deskripsi.toLowerCase().includes(keyword) ||
item.kategori?.name?.toLowerCase().includes(keyword)
);
});
const filteredData = useMemo(() => {
if (!data) return [];
return data.filter(item => {
const keyword = search.toLowerCase();
return (
item.name?.toLowerCase().includes(keyword) ||
item.deskripsi?.toLowerCase().includes(keyword) ||
item.kategori?.name?.toLowerCase().includes(keyword)
);
})
.sort((a, b) => b.createdAt - a.createdAt);
}, [data, search]);
if (!listState.findMany.data) {
// Handle loading state
if (loading || !data) {
return (
<Stack py={10}>
<Skeleton h={500} />
<Skeleton height={550} />
</Stack>
)
);
}
if (data.length === 0) {
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<JudulList
title='List Desa Anti Korupsi'
href='/admin/landing-page/desa-anti-korupsi/list-desa-anti-korupsi/create'
/>
<Box style={{ overflowX: "auto" }}>
<Table striped withTableBorder withRowBorders>
<TableThead>
<TableTr>
<TableTh>Nama</TableTh>
<TableTh>Deskripsi</TableTh>
<TableTh>Kategori</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
</Table>
</Box>
</Paper>
</Box>
);
}
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
@@ -77,7 +115,9 @@ function ListDesaAntiKorupsi({ search }: { search: string }) {
</Box>
</TableTd>
<TableTd>
<Text truncate="end" fz={"sm"} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
<Box w={200}>
<Text truncate="end" fz={"sm"} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
</Box>
</TableTd>
<TableTd>{item.kategori?.name}</TableTd>
<TableTd>
@@ -92,6 +132,18 @@ function ListDesaAntiKorupsi({ search }: { search: string }) {
</Box>
</Stack>
</Paper>
<Center>
<Pagination
value={page}
onChange={(newPage) => {
load(newPage, 10);
window.scrollTo(0, 0);
}}
total={totalPages}
mt="md"
mb="md"
/>
</Center>
</Box>
)
}

View File

@@ -77,8 +77,10 @@ function ListPrestasi({ search }: { search: string }) {
<Text truncate="end" fz={"sm"}>{item.name}</Text>
</Box>
</TableTd>
<TableTd>
<Text truncate="end" fz={"sm"} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
<TableTd >
<Box w={150}>
<Text truncate="end" fz={"sm"} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
</Box>
</TableTd>
<TableTd>{item.kategori?.name}</TableTd>
<TableTd>

View File

@@ -1,10 +1,10 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import colors from '@/con/colors';
import { Box, Button, Image, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { Box, Button, Center, Image, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import { useEffect, useMemo, useState } from 'react';
import { useProxy } from 'valtio/utils';
import HeaderSearch from '../../../_com/header';
import JudulList from '../../../_com/judulList';
@@ -30,24 +30,61 @@ function ListMediaSosial({ search }: { search: string }) {
const stateMediaSosial = useProxy(profileLandingPageState.mediaSosial)
const router = useRouter();
useShallowEffect(() => {
stateMediaSosial.findMany.load()
}, [])
const {
data,
page,
totalPages,
loading,
load,
} = stateMediaSosial.findMany;
const filteredData = (stateMediaSosial.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.name.toLowerCase().includes(keyword) ||
item.iconUrl?.toLowerCase().includes(keyword)
);
});
useEffect(() => {
load(page, 10)
}, [page])
if (!stateMediaSosial.findMany.data) {
const filteredData = useMemo(() => {
if (!data) return [];
return data.filter(item => {
const keyword = search.toLowerCase();
return (
item.name?.toLowerCase().includes(keyword) ||
item.iconUrl?.toLowerCase().includes(keyword)
);
})
}, [data, search]);
// Handle loading state
if (loading || !data) {
return (
<Stack py={10}>
<Skeleton h={500} />
<Skeleton height={550} />
</Stack>
)
);
}
if (data.length === 0) {
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<JudulList
title='List Media Sosial'
href='/admin/landing-page/profile/media-sosial/create'
/>
<Box style={{ overflowX: "auto" }}>
<Table striped withTableBorder withRowBorders>
<TableThead>
<TableTr>
<TableTh>Nama Media Sosial / Nama Kontak</TableTh>
<TableTh>Image</TableTh>
<TableTh>Icon URL / No Telephone</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
</Table>
</Box>
</Paper>
</Box>
);
}
return (
@@ -57,37 +94,55 @@ function ListMediaSosial({ search }: { search: string }) {
title='List Media Sosial'
href='/admin/landing-page/profile/media-sosial/create'
/>
<Box style={{overflowY: "auto"}}>
<Table striped withTableBorder withRowBorders>
<TableThead>
<TableTr>
<TableTh>Nama Media Sosial / Nama Kontak</TableTh>
<TableTh>Image</TableTh>
<TableTh>Icon URL / No Telephone</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>{item.name}</TableTd>
<TableTd>
<Box w={50} h={50}>
<Image src={item.image?.link} alt={item.name} />
</Box>
</TableTd>
<TableTd>{item.iconUrl}</TableTd>
<TableTd>
<Button onClick={() => router.push(`/admin/landing-page/profile/media-sosial/${item.id}`)}>
<IconDeviceImac size={20} />
</Button>
</TableTd>
<Box style={{ overflowY: "auto" }}>
<Table striped withTableBorder withRowBorders>
<TableThead>
<TableTr>
<TableTh>Nama Media Sosial / Nama Kontak</TableTh>
<TableTh>Image</TableTh>
<TableTh>Icon URL / No Telephone</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
))}
</TableTbody>
</Table>
</Box>
</TableThead>
<TableTbody>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>{item.name}</TableTd>
<TableTd>
<Box w={50} h={50}>
<Image src={item.image?.link} alt={item.name} />
</Box>
</TableTd>
<TableTd>
<Box w={250}>
<a style={{color: "black"}} href={item.iconUrl} target="_blank" rel="noopener noreferrer">
<Text truncate fz={'sm'}>{item.iconUrl}</Text>
</a>
</Box>
</TableTd>
<TableTd>
<Button onClick={() => router.push(`/admin/landing-page/profile/media-sosial/${item.id}`)}>
<IconDeviceImac size={20} />
</Button>
</TableTd>
</TableTr>
))}
</TableTbody>
</Table>
</Box>
</Paper>
<Center>
<Pagination
value={page}
onChange={(newPage) => {
load(newPage, 10);
window.scrollTo(0, 0);
}}
total={totalPages}
mt="md"
mb="md"
/>
</Center>
</Box>
);
}

View File

@@ -59,7 +59,13 @@ function DetailProgramInovasi() {
</Box>
<Box>
<Text fz={"lg"} fw={"bold"}>Link</Text>
<Text fz={"lg"}>{stateProgramInovasi.findUnique.data?.link}</Text>
<a
href={stateProgramInovasi.findUnique.data?.link || "#"}
target="_blank"
rel="noopener noreferrer"
>
{stateProgramInovasi.findUnique.data?.link || "Tidak ada link"}
</a>
</Box>
<Box>
<Text fz={"lg"} fw={"bold"}>Gambar</Text>

View File

@@ -1,10 +1,10 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import colors from '@/con/colors';
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import { useEffect, useMemo, useState } from 'react';
import { useProxy } from 'valtio/utils';
import HeaderSearch from '../../../_com/header';
import JudulList from '../../../_com/judulList';
@@ -30,27 +30,62 @@ function ListProgramInovasi({ search }: { search: string }) {
const stateProgramInovasi = useProxy(profileLandingPageState.programInovasi)
const router = useRouter();
useShallowEffect(() => {
stateProgramInovasi.findMany.load()
}, [])
const {
data,
page,
totalPages,
loading,
load,
} = stateProgramInovasi.findMany;
const filteredData = (stateProgramInovasi.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.name.toLowerCase().includes(keyword) ||
item.description?.toLowerCase().includes(keyword) ||
item.link?.toLowerCase().includes(keyword)
);
});
useEffect(() => {
load(page, 10);
}, [page]);
if (!stateProgramInovasi.findMany.data) {
const filteredData = useMemo(() => {
if (!data) return [];
return data.filter(item => {
const keyword = search.toLowerCase();
return (
item.name?.toLowerCase().includes(keyword) ||
item.description?.toLowerCase().includes(keyword) ||
item.link?.toLowerCase().includes(keyword)
);
})
}, [data, search]);
if (loading || !data) {
return (
<Stack py={10}>
<Skeleton h={500} />
<Skeleton height={550} />
</Stack>
)
);
}
if (data.length === 0) {
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<JudulList
title='List Program Inovasi'
href='/admin/landing-page/profile/program-inovasi/create'
/>
<Box style={{ overflowX: "auto" }}>
<Table striped withTableBorder withRowBorders>
<TableThead>
<TableTr>
<TableTh>Nama Program</TableTh>
<TableTh>Deskripsi</TableTh>
<TableTh>Link</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
</Table>
</Box>
</Paper>
</Box>
);
}
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
@@ -72,8 +107,14 @@ function ListProgramInovasi({ search }: { search: string }) {
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>{item.name}</TableTd>
<TableTd>{item.description}</TableTd>
<TableTd>{item.link}</TableTd>
<TableTd w={200}>{item.description}</TableTd>
<TableTd>
<Box w={250}>
<a style={{ color: "black" }} href={item.link} target="_blank" rel="noopener noreferrer">
<Text truncate fz={'sm'}>{item.link}</Text>
</a>
</Box>
</TableTd>
<TableTd>
<Button onClick={() => router.push(`/admin/landing-page/profile/program-inovasi/${item.id}`)}>
<IconDeviceImac size={20} />
@@ -85,6 +126,18 @@ function ListProgramInovasi({ search }: { search: string }) {
</Table>
</Box>
</Paper>
<Center>
<Pagination
value={page}
onChange={(newPage) => {
load(newPage, 10);
window.scrollTo(0, 0);
}}
total={totalPages}
mt="md"
mb="md"
/>
</Center>
</Box>
);
}

View File

@@ -163,7 +163,7 @@ function EditKolaborasiInovasi() {
<iframe
src={previewImage}
width="100%"
height="500px"
height="250px"
style={{ border: "1px solid #ccc", borderRadius: "8px" }}
/>
) : (

View File

@@ -110,7 +110,7 @@ function CreateSDGsDesa() {
<iframe
src={previewImage}
width="100%"
height="500px"
height="250px"
style={{ border: "1px solid #ccc", borderRadius: "8px" }}
/>
) : (

View File

@@ -1,10 +1,10 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import colors from '@/con/colors';
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { useEffect, useMemo, useState } from 'react';
import { useProxy } from 'valtio/utils';
import HeaderSearch from '../../_com/header';
import sdgsDesa from '../../_state/landing-page/sdgs-desa';
@@ -30,24 +30,61 @@ function SdgsDesa() {
function ListSdgsDesa({ search }: { search: string }) {
const listState = useProxy(sdgsDesa)
const router = useRouter();
const {
data,
page,
totalPages,
loading,
load,
} = listState.findMany;
useEffect(() => {
listState.findMany.load()
load(page, 10)
}, [])
const filteredData = (listState.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.name.toLowerCase().includes(keyword) ||
item.jumlah.toLowerCase().includes(keyword)
)
});
const filteredData = useMemo(() => {
if (!data) return [];
return data.filter(item => {
const keyword = search.toLowerCase();
return (
item.name?.toLowerCase().includes(keyword) ||
item.jumlah?.toLowerCase().includes(keyword)
);
})
}, [data, search]);
if (!listState.findMany.data) {
// Handle loading state
if (loading || !data) {
return (
<Stack py={10}>
<Skeleton h={500} />
<Skeleton height={550} />
</Stack>
)
);
}
if (data.length === 0) {
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<JudulList
title='List Desa Anti Korupsi'
href='/admin/landing-page/desa-anti-korupsi/list-desa-anti-korupsi/create'
/>
<Box style={{ overflowX: "auto" }}>
<Table striped withTableBorder withRowBorders>
<TableThead>
<TableTr>
<TableTh>Nama SDGs Desa</TableTh>
<TableTh>Jumlah SDGs Desa</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
</Table>
</Box>
</Paper>
</Box>
);
}
return (
@@ -90,6 +127,18 @@ function ListSdgsDesa({ search }: { search: string }) {
</Box>
</Stack>
</Paper>
<Center>
<Pagination
value={page}
onChange={(newPage) => {
load(newPage, 10);
window.scrollTo(0, 0);
}}
total={totalPages}
mt="md"
mb="md"
/>
</Center>
</Box>
)
}

View File

@@ -98,7 +98,7 @@ export const navBar = [
{
id: "Desa_2",
name: "Potensi",
path: "/admin/desa/potensi"
path: "/admin/desa/potensi/list-potensi"
},
{
id: "Desa_3",

View File

@@ -7,6 +7,7 @@ import GalleryFoto from "./gallery/foto";
import GalleryVideo from "./gallery/video";
import LayananDesa from "./layanan";
import Penghargaan from "./penghargaan";
import KategoriPotensi from "./potensi/kategori-potensi";
const Desa = new Elysia({ prefix: "/api/desa", tags: ["Desa"] })
@@ -18,5 +19,6 @@ const Desa = new Elysia({ prefix: "/api/desa", tags: ["Desa"] })
.use(GalleryVideo)
.use(LayananDesa)
.use(Penghargaan)
.use(KategoriPotensi)
export default Desa;

View File

@@ -7,6 +7,7 @@ type FormCreate = Prisma.PelayananSuratKeteranganGetPayload<{
name: true;
deskripsi: true;
imageId: true;
image2Id: true;
};
}>;
async function createPelayananSuratKeterangan(context: Context) {
@@ -17,6 +18,7 @@ async function createPelayananSuratKeterangan(context: Context) {
name: body.name,
deskripsi: body.deskripsi,
imageId: body.imageId,
image2Id: body.image2Id,
},
});
return {

View File

@@ -17,6 +17,7 @@ const pelayananSuratKeteranganDelete = async (context: Context) => {
where: { id },
include: {
image: true,
image2: true,
},
});
@@ -40,6 +41,18 @@ const pelayananSuratKeteranganDelete = async (context: Context) => {
}
}
if (pelayananSuratKeterangan.image2) {
try {
const filePath = path.join(pelayananSuratKeterangan.image2.path, pelayananSuratKeterangan.image2.name);
await fs.unlink(filePath);
await prisma.fileStorage.delete({
where: { id: pelayananSuratKeterangan.image2.id },
});
} catch (err) {
console.error("Gagal hapus gambar lama:", err);
}
}
const deleted = await prisma.pelayananSuratKeterangan.delete({
where: { id },
});

View File

@@ -1,24 +1,47 @@
import prisma from "@/lib/prisma";
import { Context } from "elysia";
export default async function pelayananSuratKeteranganFindMany() {
try {
const data = await prisma.pelayananSuratKeterangan.findMany({
where: { isActive: true },
include: {
image: true,
},
});
export default async function pelayananSuratKeteranganFindMany(context: Context) {
const page = Number(context.query.page) || 1;
const limit = Number(context.query.limit) || 10;
const skip = (page - 1) * limit;
return {
success: true,
message: "Success fetch pelayanan surat keterangan",
data,
};
} catch (e) {
console.error("Find many error:", e);
return {
success: false,
message: "Failed fetch pelayanan surat keterangan",
};
}
try {
const [data, total] = await Promise.all([
prisma.pelayananSuratKeterangan.findMany({
where: { isActive: true },
include: {
image: true,
image2: true,
},
skip,
take: limit,
orderBy: { createdAt: 'desc' },
}),
prisma.pelayananSuratKeterangan.count({
where: { isActive: true }
})
]);
const totalPages = Math.ceil(total / limit);
return {
success: true,
message: "Success fetch pelayanan surat keterangan with pagination",
data,
page,
totalPages,
total,
};
} catch (e) {
console.error("Find many paginated error:", e);
return {
success: false,
message: "Failed fetch pelayanan surat keterangan with pagination",
data: [],
page: 1,
totalPages: 1,
total: 0,
};
}
}

View File

@@ -24,6 +24,7 @@ export default async function pelayananSuratKeteranganFindUnique(request: Reques
where: { id },
include: {
image: true,
image2: true,
},
});

View File

@@ -18,6 +18,7 @@ const PelayananSuratKeterangan = new Elysia({ prefix: "/pelayanansuratketerangan
name: t.String(),
deskripsi: t.String(),
imageId: t.String(),
image2Id: t.String(),
}),
})
.delete("/del/:id", pelayananSuratKeteranganDelete)
@@ -30,6 +31,7 @@ const PelayananSuratKeterangan = new Elysia({ prefix: "/pelayanansuratketerangan
name: t.String(),
deskripsi: t.String(),
imageId: t.String(),
image2Id: t.String(),
}),
})
export default PelayananSuratKeterangan;

View File

@@ -9,6 +9,7 @@ type FormUpdate = Prisma.PelayananSuratKeteranganGetPayload<{
name: true;
deskripsi: true;
imageId: true;
image2Id: true;
};
}>;
export default async function updatePelayananSuratKeterangan(context: Context) {
@@ -16,7 +17,7 @@ export default async function updatePelayananSuratKeterangan(context: Context) {
const id = context.params?.id;
const body = (await context.body) as Omit<FormUpdate, "id">;
const { name, deskripsi, imageId } = body;
const { name, deskripsi, imageId, image2Id } = body;
if (!id) {
return new Response(JSON.stringify({
@@ -63,12 +64,28 @@ export default async function updatePelayananSuratKeterangan(context: Context) {
}
}
if (existing.image2Id && existing.image2Id !== image2Id) {
const oldImage = existing.image;
if (oldImage) {
try {
const filePath = path.join(oldImage.path, oldImage.name);
await fs.unlink(filePath);
await prisma.fileStorage.delete({
where: { id: oldImage.id },
});
} catch (err) {
console.error("Gagal hapus gambar lama:", err);
}
}
}
const updated = await prisma.pelayananSuratKeterangan.update({
where: { id },
data: {
name,
deskripsi,
imageId,
image2Id,
},
})

View File

@@ -1,21 +1,43 @@
import prisma from "@/lib/prisma";
import { Context } from "elysia";
export default async function pelayananTelunjukSaktiDesaFindMany() {
export default async function pelayananTelunjukSaktiDesaFindMany(context: Context) {
const page = Number(context.query.page) || 1;
const limit = Number(context.query.limit) || 10;
const skip = (page - 1) * limit;
try {
const data = await prisma.pelayananTelunjukSaktiDesa.findMany({
where: { isActive: true },
});
return {
success: true,
message: "Success fetch pelayanan telunjuk sakti desa",
data,
};
const [data, total] = await Promise.all([
prisma.pelayananTelunjukSaktiDesa.findMany({
where: { isActive: true },
skip,
take: limit,
orderBy: { createdAt: 'desc' },
}),
prisma.pelayananTelunjukSaktiDesa.count({
where: { isActive: true }
})
]);
const totalPages = Math.ceil(total / limit);
return {
success: true,
message: "Success fetch pelayanan telunjuk sakti desa with pagination",
data,
page,
totalPages,
total,
};
} catch (e) {
console.error("Find many error:", e);
return {
success: false,
message: "Failed fetch pelayanan telunjuk sakti desa",
};
console.error("Find many paginated error:", e);
return {
success: false,
message: "Failed fetch pelayanan telunjuk sakti desa with pagination",
data: [],
page: 1,
totalPages: 1,
total: 0,
};
}
}

View File

@@ -1,23 +1,46 @@
import prisma from "@/lib/prisma";
import { Context } from "elysia";
export default async function penghargaanFindMany(context: Context) {
const page = Number(context.query.page) || 1;
const limit = Number(context.query.limit) || 10;
const skip = (page - 1) * limit;
export default async function penghargaanFindMany() {
try {
const data = await prisma.penghargaan.findMany({
include: {
image: true,
},
});
const [data, total] = await Promise.all([
prisma.penghargaan.findMany({
where: { isActive: true },
include: {
image: true,
},
skip,
take: limit,
orderBy: { createdAt: 'desc' },
}),
prisma.penghargaan.count({
where: { isActive: true }
})
]);
const totalPages = Math.ceil(total / limit);
return {
success: true,
message: "Success fetch penghargaan",
message: "Success fetch penghargaan with pagination",
data,
page,
totalPages,
total,
};
} catch (error) {
console.error("Find many error:", error);
} catch (e) {
console.error("Find many paginated error:", e);
return {
success: false,
message: "Failed fetch penghargaan",
message: "Failed fetch penghargaan with pagination",
data: [],
page: 1,
totalPages: 1,
total: 0,
};
}
}
}

View File

@@ -6,7 +6,7 @@ type FormCreate = Prisma.PotensiDesaGetPayload<{
select: {
name: true;
deskripsi: true;
kategori: true;
kategoriId: true;
imageId: true;
content: true;
}
@@ -18,7 +18,7 @@ export default async function potensiDesaCreate(context: Context) {
data: {
name: body.name,
deskripsi: body.deskripsi,
kategori: body.kategori,
kategoriId: body.kategoriId,
imageId: body.imageId,
content: body.content,
},

View File

@@ -17,6 +17,7 @@ const potensiDesaDelete = async (context: Context) => {
where: { id },
include: {
image: true,
kategori: true
},
});

View File

@@ -1,26 +1,47 @@
import prisma from "@/lib/prisma";
import { Context } from "elysia";
async function potensiDesaFindMany() {
export default async function potensiDesaFindMany(context: Context) {
const page = Number(context.query.page) || 1;
const limit = Number(context.query.limit) || 10;
const skip = (page - 1) * limit;
try {
const data = await prisma.potensiDesa.findMany({
where: { isActive: true },
include: {
image: true,
},
});
return {
success: true,
message: "Success fetch potensi desa",
data,
};
const [data, total] = await Promise.all([
prisma.potensiDesa.findMany({
where: { isActive: true },
skip,
take: limit,
include: {
image: true,
kategori: true
},
orderBy: { createdAt: 'desc' },
}),
prisma.potensiDesa.count({
where: { isActive: true }
})
]);
const totalPages = Math.ceil(total / limit);
return {
success: true,
message: "Success fetch potensi desa with pagination",
data,
page,
totalPages,
total,
};
} catch (e) {
console.error("Find many error:", e);
return {
success: false,
message: "Failed fetch potensi desa",
};
console.error("Find many paginated error:", e);
return {
success: false,
message: "Failed fetch potensi desa with pagination",
data: [],
page: 1,
totalPages: 1,
total: 0,
};
}
}
export default potensiDesaFindMany
}

View File

@@ -25,6 +25,7 @@ export default async function findUnique(
where: { id },
include: {
image: true,
kategori: true
},
});

View File

@@ -14,7 +14,7 @@ const PotensiDesa = new Elysia({
body: t.Object({
name: t.String(),
deskripsi: t.String(),
kategori: t.String(),
kategoriId: t.String(),
imageId: t.String(),
content: t.String(),
}),
@@ -35,7 +35,7 @@ const PotensiDesa = new Elysia({
body: t.Object({
name: t.String(),
deskripsi: t.String(),
kategori: t.String(),
kategoriId: t.String(),
imageId: t.String(),
content: t.String(),
}),

View File

@@ -0,0 +1,27 @@
import prisma from "@/lib/prisma";
import { Context } from "elysia";
type FormCreate = {
nama: string;
}
export default async function kategoriPotensiCreate(context: Context) {
const body = (await context.body) as FormCreate;
try {
const result = await prisma.kategoriPotensi.create({
data: {
nama: body.nama
},
});
return {
success: true,
message: "Berhasil membuat kategori potensi",
data: result,
};
} catch (error) {
console.error("Error creating kategori potensi:", error);
throw new Error("Gagal membuat kategori potensi: " + (error as Error).message);
}
}

View File

@@ -0,0 +1,16 @@
import prisma from "@/lib/prisma";
import { Context } from "elysia";
export default async function kategoriPotensiDelete(context: Context) {
const id = context.params.id as string;
await prisma.kategoriPotensi.delete({
where: { id },
});
return {
status: 200,
success: true,
message: "Success delete kategori potensi",
};
}

View File

@@ -0,0 +1,11 @@
import prisma from "@/lib/prisma";
export default async function kategoriPotensiFindMany() {
const data = await prisma.kategoriPotensi.findMany();
return {
success: true,
message: "Success get all kategori potensi",
data,
};
}

View File

@@ -0,0 +1,46 @@
import prisma from "@/lib/prisma";
export default async function kategoriBukuFindUnique(request: Request) {
const url = new URL(request.url);
const pathSegments = url.pathname.split('/');
const id = pathSegments[pathSegments.length - 1];
if (!id) {
return {
success: false,
message: "ID is required",
}
}
try {
if (typeof id !== 'string') {
return {
success: false,
message: "ID is required",
}
}
const data = await prisma.kategoriPotensi.findUnique({
where: { id },
});
if (!data) {
return {
success: false,
message: "Data not found",
}
}
return {
success: true,
message: "Success get kategori potensi",
data,
}
} catch (error) {
console.error("Find by ID error:", error);
return {
success: false,
message: "Gagal mengambil data: " + (error instanceof Error ? error.message : 'Unknown error'),
}
}
}

View File

@@ -0,0 +1,33 @@
import Elysia, { t } from "elysia";
import kategoriPotensiCreate from "./create";
import kategoriPotensiDelete from "./del";
import kategoriPotensiFindMany from "./findMany";
import kategoriPotensiFindUnique from "./findUnique";
import kategoriPotensiUpdate from "./updt";
const KategoriPotensi = new Elysia({
prefix: "/kategoripotensi",
tags: ["Desa / Potensi"],
})
.post("/create", kategoriPotensiCreate, {
body: t.Object({
nama: t.String(),
}),
})
.get("/findMany", kategoriPotensiFindMany)
.get("/:id", async (context) => {
const response = await kategoriPotensiFindUnique(
new Request(context.request)
);
return response;
})
.put("/:id", kategoriPotensiUpdate, {
body: t.Object({
nama: t.String(),
}),
})
.delete("/del/:id", kategoriPotensiDelete);
export default KategoriPotensi;

View File

@@ -0,0 +1,28 @@
import prisma from "@/lib/prisma";
import { Context } from "elysia";
type FormUpdate = {
nama: string;
}
export default async function kategoriPotensiUpdate(context: Context) {
const body = (await context.body) as FormUpdate;
const id = context.params.id as string;
try {
const result = await prisma.kategoriPotensi.update({
where: { id },
data: {
nama: body.nama,
},
});
return {
success: true,
message: "Berhasil mengupdate kategori potensi",
data: result,
};
} catch (error) {
console.error("Error updating kategori potensi:", error);
throw new Error("Gagal mengupdate kategori potensi: " + (error as Error).message);
}
}

View File

@@ -9,7 +9,7 @@ type FormUpdate = Prisma.PotensiDesaGetPayload<{
id: true;
name: true;
deskripsi: true;
kategori: true;
kategoriId: true;
imageId: true;
content: true;
};
@@ -23,7 +23,7 @@ export default async function potensiDesaUpdate(context: Context) {
const {
name,
deskripsi,
kategori,
kategoriId,
imageId,
content,
} = body;
@@ -39,6 +39,7 @@ export default async function potensiDesaUpdate(context: Context) {
where: { id },
include: {
image: true,
kategori: true
}
});
@@ -69,7 +70,7 @@ export default async function potensiDesaUpdate(context: Context) {
data: {
name,
deskripsi,
kategori,
kategoriId,
imageId,
content,
},

View File

@@ -17,7 +17,7 @@ async function apbdesFindMany(context: Context) {
},
skip,
take: limit,
orderBy: { createdAt: "desc" }, // opsional, kalau mau urut berdasarkan waktu
orderBy: { name: "asc" }, // opsional, kalau mau urut berdasarkan waktu
}),
prisma.aPBDes.count({
where: { isActive: true },

View File

@@ -17,7 +17,7 @@ async function desaAntiKorupsiFindMany(context: Context) {
},
skip,
take: limit,
orderBy: { createdAt: 'desc' }, // opsional, kalau mau urut berdasarkan waktu
orderBy: { name: 'asc' }, // opsional, kalau mau urut berdasarkan waktu
}),
prisma.desaAntiKorupsi.count({
where: { isActive: true }

View File

@@ -11,7 +11,7 @@ const DesaAntiKorupsi = new Elysia({
})
// ✅ Find all
.get("/find-many", desaAntiKorupsiFindMany)
.get("/findMany", desaAntiKorupsiFindMany)
// ✅ Find by ID
.get("/:id", desaAntiKorupsiFindUnique)

View File

@@ -1,15 +1,40 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
// /api/berita/findManyPaginated.ts
import prisma from "@/lib/prisma";
import { Context } from "elysia";
export default async function kategoriDesaAntiKorupsiFindMany() {
const data = await prisma.kategoriDesaAntiKorupsi.findMany();
return {
success: true,
data: data.map((item: any) => {
return {
id: item.id,
name: item.name,
}
}),
async function kategoriDesaAntiKorupsiFindMany(context: Context) {
const page = Number(context.query.page) || 1;
const limit = Number(context.query.limit) || 10;
const skip = (page - 1) * limit;
try {
const [data, total] = await Promise.all([
prisma.kategoriDesaAntiKorupsi.findMany({
where: { isActive: true },
skip,
take: limit,
orderBy: { name: 'asc' }, // opsional, kalau mau urut berdasarkan waktu
}),
prisma.kategoriDesaAntiKorupsi.count({
where: { isActive: true }
})
]);
return {
success: true,
message: "Success fetch kategori desa anti korupsi with pagination",
data,
page,
totalPages: Math.ceil(total / limit),
total,
};
}
} catch (e) {
console.error("Find many paginated error:", e);
return {
success: false,
message: "Failed fetch kategori desa anti korupsi with pagination",
};
}
}
export default kategoriDesaAntiKorupsiFindMany;

View File

@@ -9,7 +9,7 @@ const KategoriDesaAntiKorupsi = new Elysia({
prefix: "/kategoridak",
tags: ["Landing Page/Desa Anti Korupsi"],
})
.get("/find-many", kategoriDesaAntiKorupsiFindMany)
.get("/findMany", kategoriDesaAntiKorupsiFindMany)
.get("/:id", async (context) => {
const response = await kategoriDesaAntiKorupsiFindUnique(context);
return response;

View File

@@ -16,7 +16,7 @@ async function mediaSosialFindMany(context: Context) {
},
skip,
take: limit,
orderBy: { createdAt: "desc" }, // opsional, kalau mau urut berdasarkan waktu
orderBy: { name: "asc" }, // opsional, kalau mau urut berdasarkan waktu
}),
prisma.mediaSosial.count({
where: { isActive: true },

View File

@@ -11,7 +11,7 @@ const MediaSosial = new Elysia({
})
// ✅ Find all
.get("/find-many", MediaSosialFindMany)
.get("/findMany", MediaSosialFindMany)
// ✅ Find by ID
.get("/:id", MediaSosialFindUnique)

View File

@@ -1,4 +1,4 @@
// /api/berita/findManyPaginated.ts
// // /api/berita/findManyPaginated.ts
import prisma from "@/lib/prisma";
import { Context } from "elysia";
@@ -16,7 +16,7 @@ async function programInovasiFindMany(context: Context) {
},
skip,
take: limit,
orderBy: { createdAt: "desc" }, // opsional, kalau mau urut berdasarkan waktu
orderBy: { name: "asc" }, // opsional, kalau mau urut berdasarkan waktu
}),
prisma.programInovasi.count({
where: { isActive: true },
@@ -41,3 +41,4 @@ async function programInovasiFindMany(context: Context) {
}
export default programInovasiFindMany;

View File

@@ -11,7 +11,7 @@ const ProgramInovasi = new Elysia({
})
// ✅ Find all
.get("/find-many", ProgramInovasiFindMany)
.get("/findMany", ProgramInovasiFindMany)
// ✅ Find by ID
.get("/:id", ProgramInovasiFindUnique)

View File

@@ -16,7 +16,7 @@ async function sdgsDesaFindMany(context: Context) {
},
skip,
take: limit,
orderBy: { createdAt: "desc" }, // opsional, kalau mau urut berdasarkan waktu
orderBy: { jumlah: "desc" }, // opsional, kalau mau urut berdasarkan waktu
}),
prisma.sDGSDesa.count({
where: { isActive: true },

View File

@@ -11,7 +11,7 @@ const SDGSDesa = new Elysia({
})
// ✅ Find all
.get("/find-many", sdgsDesaFindMany)
.get("/findMany", sdgsDesaFindMany)
// ✅ Find by ID
.get("/:id", sdgsDesaFindUnique)

View File

@@ -1,42 +0,0 @@
import colors from '@/con/colors';
import { Stack, Container, List, ListItem, Box, Text, Image, Button, Center, Group } from '@mantine/core';
import React from 'react';
import BackButton from '../../_com/BackButto';
function Page() {
return (
<Stack pos={"relative"} bg={colors.grey[1]} py={"xl"} gap={"22"}>
<Box px={{ base: "md", md: 100 }}><BackButton /></Box>
<Container w={{ base: "100%", md: "50%" }} >
<Text fz={{base: "2rem", md: "2.5rem", lg: "3rem", xl: "3.4rem"}} ta={"center"} fw={"bold"}>
Surat Keterangan Beda Biodata Diri
</Text>
</Container>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={0}>
<Text fz={{ base: "h4", md: "h2" }} pb={20}>
Persyaratan Dokumen:
</Text>
<List>
<ListItem fz={{ base: "sm", md: "h3" }}>Pengantar Kelian Banjar Dinas di Wilayah Masing - masing</ListItem>
<ListItem fz={{ base: "sm", md: "h3" }}>Fotocopy Ktp atau Kartu Keluarga</ListItem>
<ListItem fz={{ base: "sm", md: "h3" }}>Fotocopy dokumen bersangkutan yang terdapat perbedaan biodata diri misal : Sertifikat Tanah/Ijazah/Polis Asuransi dan lainnya.</ListItem>
</List>
<Box py={20}>
<Text fz={{ base: "h4", md: "h2" }} py={20}>
Alur Pelayanan:
</Text>
<Center>
<Image src="/api/img/flowchart-suket-beda-biodatadiri-removebg-preview.png" alt='' />
</Center>
</Box>
<Group justify='center'>
<Button radius={"lg"} fz={'h4'} bg={colors['blue-button']}>Ajukan Permohonan</Button>
</Group>
</Stack>
</Box>
</Stack>
);
}
export default Page;

View File

@@ -1,42 +0,0 @@
import colors from '@/con/colors';
import { Stack, Container, List, ListItem, Box, Text, Image, Button, Center, Group } from '@mantine/core';
import React from 'react';
import BackButton from '../../_com/BackButto';
function Page() {
return (
<Stack pos={"relative"} bg={colors.grey[1]} py={"xl"} gap={"22"}>
<Box px={{ base: "md", md: 100 }}><BackButton /></Box>
<Container w={{ base: "100%", md: "50%" }} >
<Text fz={{base: "2rem", md: "2.5rem", lg: "3rem", xl: "3.4rem"}} ta={"center"} fw={"bold"}>
Surat Keterangan Belum Kawin
</Text>
</Container>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={0}>
<Text fz={{ base: "h4", md: "h2" }} pb={20}>
Persyaratan Dokumen:
</Text>
<List>
<ListItem fz={{ base: "sm", md: "h3" }}>Pengantar Kelian Banjar Dinas di Wilayah Masing - masing</ListItem>
<ListItem fz={{ base: "sm", md: "h3" }}>Fotocopy Ktp atau Kartu Keluarga</ListItem>
<ListItem fz={{ base: "sm", md: "h3" }}>Khusus bagi yang berstatus duda atau janda melampirkan fotocopy akta cerai atau dokumen pendukung lainnya</ListItem>
</List>
<Box py={20}>
<Text fz={{ base: "h4", md: "h2" }} py={20}>
Alur Pelayanan:
</Text>
<Center>
<Image src="/api/img/flowchart-suket-beda-biodatadiri-removebg-preview.png" alt='' />
</Center>
</Box>
<Group justify='center'>
<Button radius={"lg"} fz={'h4'} bg={colors['blue-button']}>Ajukan Permohonan</Button>
</Group>
</Stack>
</Box>
</Stack>
);
}
export default Page;

View File

@@ -1,42 +0,0 @@
import colors from '@/con/colors';
import { Box, Button, Center, Container, Image, List, ListItem, Stack, Text } from '@mantine/core';
import BackButton from '../../_com/BackButto';
function Page() {
return (
<Stack pos={"relative"} bg={colors.grey[1]} py={"xl"} gap={"22"}>
<Box px={{ base: "md", md: 100 }}>
<BackButton />
</Box>
<Container w={{ base: "100%", md: "50%" }} >
<Text fz={{base: "2rem", md: "2.5rem", lg: "3rem", xl: "3.4rem"}} ta={"center"} fw={"bold"}>
Surat Keterangan Domisili Organisasi
</Text>
</Container>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={0} >
<Text fz={{ base: "h4", md: "h2" }} pb={20}>
Persyaratan Dokumen:
</Text>
<List>
<ListItem fz={{ base: "sm", md: "h3" }}>Pengantar Kelian Banjar Dinas di Wilayah Masing - masing</ListItem>
<ListItem fz={{ base: "sm", md: "h3" }}>Fotocopy Surat Keterangan Terdaftar (SKT) organisasi atau Pengukuhan Kelompok</ListItem>
<ListItem fz={{ base: "sm", md: "h3" }}>Jika Pengajuan baru pembuatan SKT maka melengkapi Susunan Pengurus lengkap dengan Kop Organisasi</ListItem>
<ListItem fz={{ base: "sm", md: "h3" }}>Tanggal berdiri/Tahun berdiri/Sejak kapan berdirinya organisasi</ListItem>
</List>
<Box py={20}>
<Text fz={{ base: "h4", md: "h2" }} py={20}>
Alur Pelayanan:
</Text>
<Center>
<Image src="/api/img/flowchart-suket-beda-biodatadiri-removebg-preview.png" alt='' />
</Center>
</Box>
<Button radius={"lg"} fz={'h4'} bg={colors['blue-button']}>Ajukan Permohonan</Button>
</Stack>
</Box>
</Stack>
);
}
export default Page;

View File

@@ -1,43 +0,0 @@
import colors from '@/con/colors';
import { Stack, Container, List, ListItem, Box, Text, Image, Button, Group, Center } from '@mantine/core';
import React from 'react';
import BackButton from '../../_com/BackButto';
function Page() {
return (
<Stack pos={"relative"} bg={colors.grey[1]} py={"xl"} gap={"22"}>
<Box px={{ base: "md", md: 100 }}><BackButton /></Box>
<Container w={{ base: "100%", md: "50%" }}>
<Text fz={{base: "2rem", md: "2.5rem", lg: "3rem", xl: "3.4rem"}} ta={"center"} fw={"bold"}>
Surat Keterangan Kelahiran
</Text>
</Container>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={0}>
<Text fz={{ base: "h4", md: "h2" }} pb={20}>
Persyaratan Dokumen:
</Text>
<List>
<ListItem fz={{ base: "sm", md: 'h3' }}>Pengantar Kelian Banjar Dinas di Wilayah Masing - masing</ListItem>
<ListItem fz={{ base: "sm", md: 'h3' }}>Fotocopy Surat lahir dari dokter/bidan (jika ada)</ListItem>
<ListItem fz={{ base: "sm", md: 'h3' }}>Fotocopy Kartu Keluarga</ListItem>
<ListItem fz={{ base: "sm", md: 'h3' }}>Fotocopy KTP 2 orang saksi</ListItem>
</List>
<Box py={20}>
<Text fz={{ base: "h4", md: "h2" }} py={20}>
Alur Pelayanan:
</Text>
<Center>
<Image src="/api/img/flowchart-suket-kelahiran-removebg-preview.png" alt='' />
</Center>
</Box>
<Group justify='center'>
<Button radius={"lg"} fz={'h4'} bg={colors['blue-button']}>Ajukan Permohonan</Button>
</Group>
</Stack>
</Box>
</Stack>
);
}
export default Page;

View File

@@ -1,41 +0,0 @@
import colors from '@/con/colors';
import { Stack, Container, List, ListItem, Box, Text, Image, Button, Center, Group } from '@mantine/core';
import React from 'react';
import BackButton from '../../_com/BackButto';
function Page() {
return (
<Stack pos={"relative"} bg={colors.grey[1]} py={"xl"} gap={"22"}>
<Box px={{ base: "md", md: 100 }}><BackButton /></Box>
<Container w={{ base: "100%", md: "50%" }} >
<Text fz={{base: "2rem", md: "2.5rem", lg: "3rem", xl: "3.4rem"}} ta={"center"} fw={"bold"}>
Surat Keterangan Kelakuan Baik (Pengantar SKCK)
</Text>
</Container>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={0}>
<Text fz={{ base: "h4", md: "h2" }} pb={20}>
Persyaratan Dokumen:
</Text>
<List>
<ListItem fz={{ base: "sm", md: 'h3' }}>Pengantar Kelian Banjar Dinas di Wilayah Masing - masing</ListItem>
<ListItem fz={{ base: "sm", md: 'h3' }}>Fotocopy Ktp atau Kartu Keluarga</ListItem>
</List>
<Box py={20}>
<Text fz={{ base: "h4", md: "h2" }} py={20}>
Alur Pelayanan:
</Text>
<Center>
<Image src="/api/img/flowchart-suket-beda-biodatadiri-removebg-preview.png" alt='' />
</Center>
</Box>
<Group justify='center'>
<Button radius={"lg"} fz={'h4'} bg={colors['blue-button']}>Ajukan Permohonan</Button>
</Group>
</Stack>
</Box>
</Stack>
);
}
export default Page;

View File

@@ -1,43 +0,0 @@
import colors from '@/con/colors';
import { Stack, Container, List, ListItem, Box, Text, Image, Button, Center, Group } from '@mantine/core';
import React from 'react';
import BackButton from '../../_com/BackButto';
function Page() {
return (
<Stack pos={"relative"} bg={colors.grey[1]} py={"xl"} gap={"22"}>
<Box px={{ base: "md", md: 100 }}><BackButton /></Box>
<Container w={{ base: "100%", md: "50%" }}>
<Text fz={{base: "2rem", md: "2.5rem", lg: "3rem", xl: "3.4rem"}} ta={"center"} fw={"bold"}>
Surat Keterangan Kematian
</Text>
</Container>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={0}>
<Text fz={{ base: "h4", md: "h2" }} pb={20}>
Persyaratan Dokumen:
</Text>
<List>
<ListItem fz={{ base: "sm", md: 'h3' }}>Pengantar Kelian Banjar Dinas di Wilayah Masing - masing</ListItem>
<ListItem fz={{ base: "sm", md: 'h3' }}>Fotocopy KTP atau Kartu keluarga</ListItem>
<ListItem fz={{ base: "sm", md: 'h3' }}>Surat Kematian dari rumah sakit atau dokter (jika ada)</ListItem>
<ListItem fz={{ base: "sm", md: 'h3' }}>Tanggal dan Waktu Kematian</ListItem>
</List>
<Box py={20}>
<Text fz={{ base: "h4", md: "h2" }} py={20}>
Alur Pelayanan:
</Text>
<Center>
<Image src="/api/img/flowchart-suket-beda-biodatadiri-removebg-preview.png" alt='' />
</Center>
</Box>
<Group justify='center'>
<Button radius={"lg"} fz={'h4'} bg={colors['blue-button']}>Ajukan Permohonan</Button>
</Group>
</Stack>
</Box>
</Stack>
);
}
export default Page;

View File

@@ -1,42 +0,0 @@
import colors from '@/con/colors';
import { Stack, Container, List, ListItem, Box, Text, Image, Button, Center, Group } from '@mantine/core';
import React from 'react';
import BackButton from '../../_com/BackButto';
function Page() {
return (
<Stack pos={"relative"} bg={colors.grey[1]} py={"xl"} gap={"22"}>
<Box px={{ base: "md", md: 100 }}><BackButton /></Box>
<Container w={{ base: "100%", md: "50%" }} >
<Text fz={{base: "2rem", md: "2.5rem", lg: "3rem", xl: "3.4rem"}} ta={"center"} fw={"bold"}>
Surat Keterangan Penghasilan
</Text>
</Container>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={0}>
<Text fz={{base:"h4", md: "h2"}} pb={20}>
Persyaratan Dokumen:
</Text>
<List>
<ListItem fz={{base:"sm", md: "h3"}}>Pengantar Kelian Banjar Dinas di Wilayah Masing - masing</ListItem>
<ListItem fz={{base:"sm", md: "h3"}}>Fotocopy KTP orang tua atau Fotocopy Kartu keluarga</ListItem>
<ListItem fz={{base:"sm", md: "h3"}}>Membuat Surat Pernyataan Penghasilan bermaterai (disertai jumlah penghasilan)</ListItem>
</List>
<Box py={20}>
<Text fz={{base:"h4", md: "h2"}} py={20}>
Alur Pelayanan:
</Text>
<Center>
<Image src="/api/img/flowchart-suket-beda-biodatadiri-removebg-preview.png" alt='' />
</Center>
</Box>
<Group justify='center'>
<Button radius={"lg"} fz={'h4'} bg={colors['blue-button']}>Ajukan Permohonan</Button>
</Group>
</Stack>
</Box>
</Stack>
);
}
export default Page;

View File

@@ -1,43 +0,0 @@
import colors from '@/con/colors';
import { Stack, Container, List, ListItem, Box, Text, Image, Button, Center, Group } from '@mantine/core';
import React from 'react';
import BackButton from '../../_com/BackButto';
function Page() {
return (
<Stack pos={"relative"} bg={colors.grey[1]} py={"xl"} gap={"22"}>
<Box px={{ base: "md", md: 100 }}><BackButton /></Box>
<Container w={{ base: "100%", md: "50%" }} >
<Text fz={{base: "2rem", md: "2.5rem", lg: "3rem", xl: "3.4rem"}} ta={"center"} fw={"bold"}>
Surat Keterangan Tempat Usaha
</Text>
</Container>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={0}>
<Text fz={{ base: "h4", md: 'h2' }} pb={20}>
Persyaratan Dokumen:
</Text>
<List>
<ListItem fz={{ base: "sm", md: "h3" }}>Pengantar Kelian Banjar Dinas di Wilayah Masing - masing</ListItem>
<ListItem fz={{ base: "sm", md: "h3" }}>Fotocopy KTP atau Kartu keluarga</ListItem>
<ListItem fz={{ base: "sm", md: "h3" }}>Foto Lokasi dan Kegiatan Usaha di cetak dalam selembar kertas (diparaf dan stempel oleh Kelian Banjar Dinas)</ListItem>
<ListItem fz={{ base: "sm", md: "h3" }}>Surat Perjanjian Sewa/Kontrak atau Kwintansi Pembayaran Sewa 3 bulan terakhir bagi yang mengontrak tempat usaha, apabila tempat usaha milik sendiri lampiri dengan dokumen kepemilikan tempat usaha (dapat berupa fotocopy sppt atau Fotocopy Sertipikat Hak Milik)</ListItem>
</List>
<Box py={20}>
<Text fz={{ base: "h4", md: 'h2' }} py={20}>
Alur Pelayanan:
</Text>
<Center>
<Image src="/api/img/flowchart-suket-tempat-usaha-removebg-preview.png" alt='' />
</Center>
</Box>
<Group justify='center'>
<Button radius={"lg"} fz={'h4'} bg={colors['blue-button']}>Ajukan Permohonan</Button>
</Group>
</Stack>
</Box>
</Stack>
);
}
export default Page;

View File

@@ -1,44 +0,0 @@
import colors from '@/con/colors';
import { Stack, Container, List, ListItem, Box, Text, Image, Button, Center, Group } from '@mantine/core';
import React from 'react';
import BackButton from '../../_com/BackButto';
function Page() {
return (
<Stack pos={"relative"} bg={colors.grey[1]} py={"xl"} gap={"22"}>
<Box px={{ base: "md", md: 100 }}><BackButton /></Box>
<Container w={{ base: "100%", md: "50%" }} >
<Text fz={{base: "2rem", md: "2.5rem", lg: "3rem", xl: "3.4rem"}} ta={"center"} fw={"bold"}>
Surat Keterangan Tidak Mampu
</Text>
</Container>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={0}>
<Text fz={{ base: "h4", md: "h2" }} py={20}>
Persyaratan Dokumen:
</Text>
<List>
<ListItem fz={{ base: "sm", md: "h3" }}>Pengantar Kelian Banjar Dinas di Wilayah Masing - masing</ListItem>
<ListItem fz={{ base: "sm", md: "h3" }}>Fotocopy KTP/KIA atau Kartu Keluarga</ListItem>
<ListItem fz={{ base: "sm", md: "h3" }}>Fotocopy Kartu Indonesia Pintar/Kartu Perlindungan Sosial/Terdaftar dalam DTKS</ListItem>
<ListItem fz={{ base: "sm", md: "h3" }}>Jika tidak memiliki Kartu tersebut diatas diwajibkan membuat Surat Pernyataan Tidak Mampu
</ListItem>
</List>
<Box py={20}>
<Text fz={{ base: "h4", md: "h2" }} py={20}>
Alur Pelayanan:
</Text>
<Center>
<Image src="/api/img/flowchart-suket-tidak-mampu-removebg-preview.png" alt='' />
</Center>
</Box>
<Group justify='center'>
<Button radius={"lg"} fz={'h4'} bg={colors['blue-button']}>Ajukan Permohonan</Button>
</Group>
</Stack>
</Box>
</Stack>
);
}
export default Page;

View File

@@ -1,42 +0,0 @@
import colors from '@/con/colors';
import { Stack, Container, List, ListItem, Box, Text, Image, Button, Center, Group } from '@mantine/core';
import React from 'react';
import BackButton from '../../_com/BackButto';
function Page() {
return (
<Stack pos={"relative"} bg={colors.grey[1]} py={"xl"} gap={"22"}>
<Box px={{ base: "md", md: 100 }}><BackButton /></Box>
<Container w={{ base: "100%", md: "50%" }} >
<Text fz={{base: "2rem", md: "2.5rem", lg: "3rem", xl: "3.4rem"}} ta={"center"} fw={"bold"}>
Surat Keterangan Usaha
</Text>
</Container>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={0}>
<Text fz={{ base: "h4", md: 'h2' }} pb={20}>
Persyaratan Dokumen:
</Text>
<List>
<ListItem fz={{ base: "sm", md: 'h3' }}>Pengantar Kelian Banjar Dinas di Wilayah Masing - masing</ListItem>
<ListItem fz={{ base: "sm", md: 'h3' }}>Fotocopy KTP atau Kartu keluarga</ListItem>
<ListItem fz={{ base: "sm", md: 'h3' }}>Foto Lokasi dan Kegiatan Usaha di cetak dalam selembar kertas (diparaf dan stempel oleh Kelian Banjar Dinas)</ListItem>
</List>
<Box py={20}>
<Text fz={{ base: "h4", md: 'h2' }} py={20}>
Alur Pelayanan:
</Text>
<Center>
<Image src="/api/img/flowchart-suket-beda-biodatadiri-removebg-preview.png" alt='' />
</Center>
</Box>
<Group justify='center'>
<Button radius={"lg"} fz={'h4'} bg={colors['blue-button']}>Ajukan Permohonan</Button>
</Group>
</Stack>
</Box>
</Stack >
);
}
export default Page;

View File

@@ -1,41 +0,0 @@
import colors from '@/con/colors';
import { Stack, Container, List, ListItem, Box, Text, Image, Button, Center, Group } from '@mantine/core';
import React from 'react';
import BackButton from '../../_com/BackButto';
function Page() {
return (
<Stack pos={"relative"} bg={colors.grey[1]} py={"xl"} gap={"22"}>
<Box px={{ base: "md", md: 100 }}><BackButton /></Box>
<Container w={{ base: "100%", md: "50%" }} >
<Text fz={{base: "2rem", md: "2.5rem", lg: "3rem", xl: "3.4rem"}} ta={"center"} fw={"bold"}>
Surat Keterangan Yatim Piatu
</Text>
</Container>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={0}>
<Text fz={{ base: "h4", md: 'h2' }} pb={20}>
Persyaratan Dokumen:
</Text>
<List>
<ListItem fz={{ base: "sm", md: 'h3' }}>Pengantar Kelian Banjar Dinas di Wilayah Masing - masing</ListItem>
<ListItem fz={{ base: "sm", md: 'h3' }}>Fotocopy Ktp, KIA atau Kartu Keluarga</ListItem>
</List>
<Box py={20}>
<Text fz={{ base: "h4", md: 'h2' }} py={20}>
Alur Pelayanan:
</Text>
<Center>
<Image src="/api/img/flowchart-suket-beda-biodatadiri-removebg-preview.png" alt='' />
</Center>
</Box>
<Group justify='center'>
<Button radius={"lg"} fz={'h4'} bg={colors['blue-button']}>Ajukan Permohonan</Button>
</Group>
</Stack>
</Box>
</Stack>
);
}
export default Page;

View File

@@ -0,0 +1,106 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import { useParams } from 'next/navigation';
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
import colors from '@/con/colors';
import { Box, Button, Center, Container, Group, Image, Skeleton, Stack, Text } from '@mantine/core';
import { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils';
import BackButton from '../_com/BackButto';
interface LayananData {
id: string;
name: string;
deskripsi: string;
imageId: string;
image2Id: string;
image?: {
id: string;
link: string;
};
image2?: {
id: string;
link: string;
};
}
function Page() {
const params = useParams<{ id: string }>();
const id = Array.isArray(params.id) ? params.id[0] : params.id;
const state = useProxy(stateLayananDesa);
const [loading, setLoading] = useState(true);
const [data, setData] = useState<LayananData | null>(null);
useEffect(() => {
const loadData = async () => {
if (!id) return;
try {
setLoading(true);
await state.suratKeterangan.findUnique.load(id);
const result = state.suratKeterangan.findUnique.data as unknown as LayananData;
setData(result);
} catch (error) {
console.error('Error loading data:', error);
} finally {
setLoading(false);
}
};
loadData();
}, [id]);
if (loading) {
return (
<Center>
<Skeleton height={500} />
</Center>
);
}
if (!data) {
return (
<Center>
<Text>Data tidak ditemukan</Text>
</Center>
);
}
return (
<Stack pos="relative" bg={colors.Bg} py="xl" gap="22">
<Box px={{ base: "md", md: 100 }}>
<BackButton />
</Box>
<Container w={{ base: "100%", md: "50%" }}>
<Text
fz={{ base: "2rem", md: "2.5rem", lg: "3rem", xl: "3.4rem" }}
ta="center"
fw="bold"
>
{data.name}
</Text>
</Container>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={0}>
<Text
dangerouslySetInnerHTML={{ __html: data.deskripsi }}
fz={{ base: "h4", md: "h2" }}
pb={20}
/>
{data.image2?.link && (
<Center>
<Image src={data.image2.link} alt={data.name} />
</Center>
)}
<Group justify='center' mt="md">
<Button radius="lg" fz="h4" bg={colors['blue-button']}>
Ajukan Permohonan
</Button>
</Group>
</Stack>
</Box>
</Stack>
);
}
export default Page;

View File

@@ -1,10 +0,0 @@
import { Stack } from "@mantine/core";
export default async function Page({ params }: { params: Promise<{ sub: string }> }) {
const { sub } = await params
return (
<Stack>
{sub}
</Stack>
)
}

Some files were not shown because too many files have changed in this diff Show More