Compare commits

...

9 Commits

125 changed files with 5157 additions and 3058 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,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,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 {

View File

@@ -1,4 +1,10 @@
import prisma from "@/lib/prisma";
import profilePejabatDesa from "./data/landing-page/profile/profile.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 +26,99 @@ 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";
(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 ...");
// =========== 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("media sosial success ...");
// =========== LAYANAN ===========
for (const l of layanan) {
await prisma.layanan.upsert({
where: {
@@ -57,6 +135,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 +252,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 +277,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 +302,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 +372,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 +541,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 +729,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 +748,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 +794,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 +812,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 +830,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 +848,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 +866,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 +884,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,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,90 @@ 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)
);
})
.sort((a, b) => a.posisi?.hierarki - b.posisi?.hierarki);
}, [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 +126,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

@@ -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

@@ -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>
)
}

View File

@@ -0,0 +1,65 @@
/* eslint-disable react-hooks/exhaustive-deps */
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
import colors from '@/con/colors';
import { ActionIcon, Box, Divider, Flex, Skeleton, Text } from '@mantine/core';
import { IconBrandFacebook, IconBrandInstagram, IconBrandTwitter, IconBrandWhatsapp } from '@tabler/icons-react';
import React, { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils';
function PelayananPendudukNonPermanent() {
const state = useProxy(stateLayananDesa)
const [loading, setLoading] = useState(false)
useEffect(() => {
const loadData = async () => {
try {
setLoading(true)
await state.pelayananPendudukNonPermanen.findById.load('1')
} catch (error) {
console.error('Error loading data:', error);
} finally {
setLoading(false)
}
}
loadData()
}, [])
const data = state.pelayananPendudukNonPermanen.findById.data
return (
<Box>
{loading ? (
<Skeleton h={500} />
) : (
<Box>
<Box py={15}>
<Text fz={{ base: "h4", md: "h3" }} fw={"bold"}>{data?.name}</Text>
</Box>
<Text pb={20} fz={{ base: "sm", md: 'h3' }} ta={"justify"} dangerouslySetInnerHTML={{__html: data?.deskripsi || ''}} />
<Divider color={colors["blue-button"]} />
<Flex justify={"space-between"} py={20}>
<Text fz={{ base: "sm", md: 'h3' }}>25 May 2021 . Darmasaba</Text>
<Box>
<Flex gap={"lg"}>
<ActionIcon variant='transparent'>
<IconBrandFacebook color={colors["blue-button"]} size={"30"} />
</ActionIcon>
<ActionIcon variant='transparent'>
<IconBrandInstagram color={colors["blue-button"]} size={"30"} />
</ActionIcon>
<ActionIcon variant='transparent'>
<IconBrandTwitter color={colors["blue-button"]} size={"30"} />
</ActionIcon>
<ActionIcon variant='transparent'>
<IconBrandWhatsapp color={colors["blue-button"]} size={"30"} />
</ActionIcon>
</Flex>
</Box>
</Flex>
<Divider color={colors["blue-button"]} pb={50} />
</Box>
)}
</Box>
);
}
export default PelayananPendudukNonPermanent;

View File

@@ -0,0 +1,106 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
import { Box, Button, Center, Group, Skeleton, Stepper, StepperCompleted, StepperStep, Text } from '@mantine/core';
import { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils';
function PelayananPerizinanBerusaha() {
const state = useProxy(stateLayananDesa)
const [loading, setLoading] = useState(false)
const [active, setActive] = useState(1);
const nextStep = () => setActive((current) => (current < 6 ? current + 1 : current));
const prevStep = () => setActive((current) => (current > 0 ? current - 1 : current));
useEffect(() => {
const loadData = async () => {
try {
setLoading(true);
await state.pelayananPerizinanBerusaha.findById.load('1')
} catch (error) {
console.error('Error loading data:', error);
} finally {
setLoading(false);
}
}
loadData()
}, [])
const data = state.pelayananPerizinanBerusaha.findById.data;
if (!data) {
return (
<Center>
<Text>Data tidak tersedia</Text>
</Center>
);
}
return (
<Box>
{loading ? (
<Center>
<Skeleton h={250} />
</Center>
) : (
<Box>
<Box py={15}>
<Text fz={{ base: "h4", md: 'h2' }} fw={"bold"}>Pelayanan Perizinan Berusaha Berbasis Risiko Melalui Sistem ONLINE SINGLE SUBMISSION (OSS)</Text>
</Box>
<Text
py={10}
ta={"justify"}
fz={{ base: "sm", md: 'h3' }}
dangerouslySetInnerHTML={{ __html: data.deskripsi || '' }}
/>
<Text py={10} fz={{ base: "sm", md: 'h3' }}>Proses pendaftaran NIB melalui OSS mencakup beberapa langkah umum, seperti:</Text>
<Box p={"xl"} w={{ base: "100%", md: "100%" }}>
<Stepper active={active} onStepClick={setActive} orientation="vertical"
styles={{
separator: {
marginLeft: 25
},
step: {
padding: '12px 0'
}
}}>
<StepperStep label="Langkah Pertama" description="Pendaftaran Akun">
Pendaftaran akun pada portal OSS
</StepperStep>
<StepperStep label="Langkah Kedua" description="Pengisian Data Perusahaan">
Mengisi informasi perusahaan, termasuk data pemegang saham, alamat perusahaan, dan lainnya
</StepperStep>
<StepperStep label="Langkah Ketiga" description="Pemilihan KBLI">
Memilih KBLI dengan jenis usaha yang akan didaftarkan
</StepperStep>
<StepperStep label="Langkah Keempat" description="Pengunggahan Dokumen">
Mengunggah dokumen-dokumen yang diperlukan, seperti akta pendirian perusahaan, surat izin usaha, dan dokumen lainnya sesuai dengan ketentuan yang berlaku
</StepperStep>
<StepperStep label="Langkah Kelima" description="Verifikasi dan Persetujuan">
Proses verifikasi dan persetujuan oleh instansi terkait
</StepperStep>
<StepperStep label="Langkah Keenam" description="Penerimaan NIB">
Jika proses sebelumnya berhasil, perusahaan akan menerima NIB sebagai identitas resmi usaha anda
</StepperStep>
<StepperCompleted>
Selesai, anda telah mengikuti proses pendaftaran NIB melalui OSS
</StepperCompleted>
</Stepper>
<Group justify="center" mt="xl">
<Button variant="default" onClick={prevStep}>Back</Button>
<Button onClick={nextStep}>Next step</Button>
</Group>
<Text py={35} ta={"justify"} fz={{ base: "sm", md: 'h3' }}>
Penting untuk diingat bahwa prosedur dan persyaratan dapat berubah
seiring waktu. Untuk informasi yang lebih akurat dan terkini, saya sarankan untuk mengunjungi situs
resmi OSS <a href="https://oss.go.id/" target="_blank" rel="noopener noreferrer">(https://oss.go.id/)</a> atau menghubungi instansi terkait di pemerintah Indonesia yang bertanggung jawab atas urusan perizinan usaha.
</Text>
</Box>
</Box>
)}
</Box>
);
}
export default PelayananPerizinanBerusaha;

View File

@@ -0,0 +1,97 @@
/* eslint-disable react-hooks/exhaustive-deps */
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
import colors from '@/con/colors';
import { BackgroundImage, Box, Button, Center, Group, SimpleGrid, Skeleton, Stack, Text } from '@mantine/core';
import { useRouter } from 'next/navigation';
import React, { useEffect, useMemo, useState } from 'react';
import { useProxy } from 'valtio/utils';
function PelayananSuratKeterangan({ search }: { search: string }) {
const [loading, setLoading] = useState(false);
const router = useRouter()
const state = useProxy(stateLayananDesa)
const filteredData = useMemo(() => {
if (!state.suratKeterangan.findMany.data) return [];
return state.suratKeterangan.findMany.data.filter(item => {
const keyword = search.toLowerCase();
return (
item.name?.toLowerCase().includes(keyword)
);
})
}, [state.suratKeterangan.findMany.data, search]);
useEffect(() => {
const loadData = async () => {
try {
setLoading(true);
await state.suratKeterangan.findMany.load()
} catch (error) {
console.error('Error loading data:', error);
} finally {
setLoading(false);
}
}
loadData()
}, [])
return (
<Box pb={10}>
<Text fz={{ base: "h4", md: 'h2' }} fw={"bold"}>Pelayanan Surat Keterangan</Text>
<SimpleGrid
py={20}
cols={{
base: 1,
sm: 3
}}
>
{loading ? (
<Center>
<Skeleton h={250} />
</Center>
) : (
filteredData.map((v, k) => {
return (
<BackgroundImage
key={k}
src={v.image?.link || ''}
h={250}
radius={16}
pos={"relative"}
>
<Box
style={{
borderRadius: 16,
zIndex: 0
}}
pos={"absolute"}
w={"100%"}
h={"100%"}
bg={colors.trans.dark[2]}
/>
<Stack justify='space-between' h={"100%"} gap={0} p={"lg"} pos={"relative"}>
<Box p={"lg"}>
<Text
c={"white"}
size={"1.5rem"}
style={{
textAlign: "center",
}}>{v.name}</Text>
</Box>
<Group justify="center">
<Button px={20} radius={"100"} size="md" bg={colors["blue-button"]}
onClick={() => router.push(`/darmasaba/desa/layanan/${v.id}`)}>
Detail
</Button>
</Group>
</Stack>
</BackgroundImage>
)
})
)}
</SimpleGrid>
</Box>
);
}
export default PelayananSuratKeterangan;

View File

@@ -0,0 +1,67 @@
/* eslint-disable react-hooks/exhaustive-deps */
import stateLayananDesa from '@/app/admin/(dashboard)/_state/desa/layananDesa';
import { Box, Flex, Skeleton, Text, Title } from '@mantine/core';
import { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils';
interface ServiceItem {
name: string;
deskripsi: string;
link: string;
}
function PelayananTelunjukSaktiDesa() {
const state = useProxy(stateLayananDesa)
const [loading, setLoading] = useState(false)
useEffect(() => {
const loadData = async () => {
try {
setLoading(true)
await state.pelayananTelunjukSaktiDesa.findMany.load()
} catch (error) {
console.error('Error loading data:', error);
} finally {
setLoading(false)
}
}
loadData()
}, [])
const data = (state.pelayananTelunjukSaktiDesa.findMany.data || []) as Array<{
name: string;
id: string;
deskripsi: string;
link: string;
items: ServiceItem[];
createdAt: Date;
updatedAt: Date;
deletedAt: Date | null;
}>
return (
<Box>
<Title fz="h2" py={10} mb="md">Terwujudnya Layanan umum bertajuk Sistim administrasi Kependudukan Terintegrasi di Desa berbasi Elektronik, Smart dan Aman. Layanan Telunjuk Sakti Desa meliputi :</Title>
{loading ? (
<Skeleton h={500} />
) : (
data.map((v, k) => {
return (
<Box key={k}>
<Box py={10}>
<Flex gap={"3"} align={"center"}>
<Text fz={{ base: "h4", md: "h3" }} fw={"bold"}>{v.name}
</Text>
<Text span fz={{ base: "h4", md: "h3" }}>
<a href={v.link} target="_blank" rel="noopener noreferrer" style={{ color: '#228be6' }}>{v.deskripsi}</a>
</Text>
</Flex>
</Box>
</Box>
)
})
)}
</Box>
);
}
export default PelayananTelunjukSaktiDesa;

View File

@@ -1,85 +1,19 @@
'use client'
import colors from "@/con/colors";
import { ActionIcon, BackgroundImage, Box, Button, Container, Divider, Flex, Group, List, ListItem, SimpleGrid, Stack, Stepper, StepperCompleted, StepperStep, Text, TextInput } from "@mantine/core";
import { IconBrandFacebook, IconBrandInstagram, IconBrandTwitter, IconBrandWhatsapp, IconSearch } from "@tabler/icons-react";
import { useRouter } from "next/navigation";
import { Box, Container, Stack, Text, TextInput } from "@mantine/core";
import { IconSearch } from "@tabler/icons-react";
import { useState } from "react";
import BackButton from "./_com/BackButto";
import PelayananPerizinanBerusaha from "./_com/pelayananPerizinanBerusaha";
import PelayananSuratKeterangan from "./_com/pelayananSuratKeterangan";
import PelayananTelunjukSaktiDesa from "./_com/pelayananTelunjukSaktiDesa";
import PelayananPendudukNonPermanent from "./_com/pelayananPendudukNonPermanent";
const data = [
{
id: 1,
images: "/api/img/test.png",
name: "Surat Keterangan Domisili Organisasi",
link: "/darmasaba/desa/layanan/surat-keterangan-domisili"
},
{
id: 2,
images: "/api/img/test-3.jpeg",
name: "Surat Keterangan Penghasilan",
link: "/darmasaba/desa/layanan/surat-keterangan-penghasilan"
},
{
id: 3,
images: "/api/img/domisili.jpeg",
name: "Surat Keterangan Tidak Mampu",
link: "/darmasaba/desa/layanan/surat-keterangan-tidak-mampu"
},
{
id: 4,
images: "/api/img/kelahiran.jpeg",
name: "Surat Keterangan Kelahiran",
link: "/darmasaba/desa/layanan/surat-keterangan-kelahiran"
},
{
id: 5,
images: "/api/img/keterangan-usaha.jpeg",
name: "Surat Keterangan Usaha",
link: "/darmasaba/desa/layanan/surat-keterangan-usaha"
},
{
id: 6,
images: "/api/img/kematian.jpeg",
name: "Surat Keterangan Kematian",
link: "/darmasaba/desa/layanan/surat-keterangan-kematian"
},
{
id: 7,
images: "/api/img/tempatusaha.jpeg",
name: "Surat Keterangan Tempat Usaha",
link: "/darmasaba/desa/layanan/surat-keterangan-tempat-usaha"
},
{
id: 8,
images: "/api/img/belumkawin.jpeg",
name: "Surat Keterangan Belum Kawin",
link: "/darmasaba/desa/layanan/surat-keterangan-belum-kawin"
},
{
id: 9,
images: "/api/img/berkelakuan-baik.jpeg",
name: "Surat Keterangan Kelakuan Baik",
link: "/darmasaba/desa/layanan/surat-keterangan-kelakuan-baik"
},
{
id: 10,
images: "/api/img/biodata.jpeg",
name: "Surat Keterangan Beda Biodata Diri",
link: "/darmasaba/desa/layanan/surat-keterangan-beda-biodata-diri"
},
{
id: 11,
images: "/api/img/yatim.jpeg",
name: "Surat Keterangan Yatim Piatu",
link: "/darmasaba/desa/layanan/surat-keterangan-yatim-piatu"
}
]
export default function Page() {
const router = useRouter()
const [active, setActive] = useState(1);
const nextStep = () => setActive((current) => (current < 6 ? current + 1 : current));
const prevStep = () => setActive((current) => (current > 0 ? current - 1 : current));
const [search, setSearch] = useState("")
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
<Box px={{ base: 'md', md: 100 }}>
@@ -100,149 +34,20 @@ export default function Page() {
w={{ base: "70%", md: "50%" }}
placeholder="Cari Layanan"
leftSection={<IconSearch size={20} />}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
</Stack>
</Container>
<Box px={{ base: "md", md: 100 }}>
{/* Bagian Pelayanan Surat Keterangan */}
<Box pb={10}>
<Text fz={{ base: "h4", md: 'h2' }} fw={"bold"}>Pelayanan Surat Keterangan</Text>
</Box>
<SimpleGrid
py={20}
cols={{
base: 1,
sm: 3
}}
>
{data.map((v, k) => {
return (
<BackgroundImage
key={k}
src={v.images}
h={250}
radius={16}
pos={"relative"}
>
<Box
style={{
borderRadius: 16,
zIndex: 0
}}
pos={"absolute"}
w={"100%"}
h={"100%"}
bg={colors.trans.dark[2]}
/>
<Stack justify='space-between' h={"100%"} gap={0} p={"lg"} pos={"relative"}>
<Box p={"lg"}>
<Text
c={"white"}
size={"1.5rem"}
style={{
textAlign: "center",
}}>{v.name}</Text>
</Box>
<Group justify="center">
<Button px={20} radius={"100"} size="md" bg={colors["blue-button"]}
onClick={() => router.push(v.link)}>
Detail
</Button>
</Group>
</Stack>
</BackgroundImage>
)
})}
</SimpleGrid>
<PelayananSuratKeterangan search={search} />
{/* Bagian Pelayanan Perizinan Berusaha */}
<Box py={15}>
<Text fz={{ base: "h4", md: 'h2' }} fw={"bold"}>Pelayanan Perizinan Berusaha Berbasis Risiko Melalui Sistem ONLINE SINGLE SUBMISSION (OSS)</Text>
</Box>
<Text py={10} ta={"justify"} fz={{ base: "sm", md: 'h3' }}>Penyelenggaraan Perizinan Berusaha Berbasis Risiko melalui Sistem Online Single Submission (OSS)
merupakan pelaksanaan Undang-Undang Nomor 11 Tahun 2020 Tentang Cipta Kerja. OSS Berbasis Risiko wajib digunakan oleh Pelaku Usaha,
Kementerian/Lembaga, Pemerintah Daerah, Administrator Kawasan Ekonomi Khusus (KEK), dan Badan Pengusahaan Kawasan Perdagangan Bebas
Pelabuhan Bebas (KPBPB).Berdasarkan Peraturan Pemerintah Nomor 5 Tahun 2021 terdapat 1.702 kegiatan usaha yang terdiri atas 1.349
Klasifikasi Baku Lapangan Usaha Indonesia (KBLI) yang sudah diimplementasikan dalam Sistem OSS Berbasis Risiko.</Text>
<Text py={10} fz={{ base: "sm", md: 'h3' }}>Proses pendaftaran NIB melalui OSS mencakup beberapa langkah umum, seperti:</Text>
<Box p={"xl"} w={{ base: "100%", md: "100%" }} >
<Stepper active={active} onStepClick={setActive} orientation="vertical"
styles={{
separator: {
marginLeft: 25
},
step: {
padding: '12px 0'
}
}}>
<StepperStep label="Langkah Pertama" description="Pendaftaran Akun">
Pendaftaran akun pada portal OSS
</StepperStep>
<StepperStep label="Langkah Kedua" description="Pengisian Data Perusahaan">
Mengisi informasi perusahaan, termasuk data pemegang saham, alamat perusahaan, dan lainnya
</StepperStep>
<StepperStep label="Langkah Ketiga" description="Pemilihan KBLI ">
Memilih KBLI dengan jenis usaha yang akan didaftarkan
</StepperStep>
<StepperStep label="Langkah Keempat" description="Pengunggahan Dokumen">
Mengunggah dokumen-dokumen yang diperlukan, seperti akta pendirian perusahaan, surat izin usaha, dan dokumen lainnya sesuai dengan ketentuan yang berlaku
</StepperStep>
<StepperStep label="Langkah Kelima" description="Verifikasi dan Persetujuan">
Proses verifikasi dan persetujuan oleh instansi terkait
</StepperStep>
<StepperStep label="Langkah Keenam" description="Penerimaan NIB">
Jika proses sebelumnya berhasil, perusahaan akan menerima NIB sebagai identitas resmi usaha anda
</StepperStep>
<StepperCompleted >
Selesai, anda telah mengikuti proses pendaftaran NIB melalui OSS
</StepperCompleted>
</Stepper>
<Group justify="center" mt="xl">
<Button variant="default" onClick={prevStep}>Back</Button>
<Button onClick={nextStep}>Next step</Button>
</Group>
<Text py={35} ta={"justify"} fz={{ base: "sm", md: 'h3' }}>Penting untuk diingat bahwa prosedur dan persyaratan dapat berubah
seiring waktu. Untuk informasi yang lebih akurat dan terkini, saya sarankan untuk mengunjungi situs
resmi OSS <a href={"https://oss.go.id/"}>(https://oss.go.id/)</a> atau menghubungi instansi terkait di pemerintah Indonesia yang bertanggung jawab atas urusan perizinan usaha.</Text>
</Box>
<PelayananPerizinanBerusaha/>
{/* Bagian Pelayanan Telunjuk Sakti Desa */}
<Box py={15}>
<Text fz={{base: "h4", md: "h3"}} fw={"bold"}>Pelayanan Telunjuk Sakti Desa</Text>
</Box>
<Text fz={{ base: "sm", md: 'h3' }} py={10}>Terwujudnya Layanan umum bertajuk Sistim administrasi Kependudukan Terintegrasi di Desa berbasi Elektronik, Smart dan Aman. layanan telunjuk sakti Desa meliputi :</Text>
<List type="ordered" pb={20}>
<ListItem fz={{ base: "sm", md: 'h3' }}>Telunjuk Sakti Desa Akta Kelahiran (Petunjuk Pengajuan pada link berikut : Download <a href="https://darmasaba.desa.id/storage/files/PERSYARATAN%20DAN%20ALUR%20PENGAJUAN%20AKTA%20KELAHIRAN_(dengan%20contoh%20Formulir).pdf">Akta Kelahiran</a>)</ListItem>
<ListItem fz={{ base: "sm", md: 'h3' }}>Telunjuk Sakti Desa Akta Perkawinan (Petunjuk Pengajuan pada link berikut : Download <a href="https://darmasaba.desa.id/storage/files/PERSYARATAN%20DAN%20ALUR%20PENGAJUAN%20AKTA%20PERKAWINAN_(dengan%20contoh%20Formulir).pdf">Akta Perkawinan</a>)</ListItem>
<ListItem fz={{ base: "sm", md: 'h3' }}>Telunjuk Sakti Desa Akta Kematian (Petunjuk Pengajuan pada link berikut : Download <a href="https://darmasaba.desa.id/storage/files/PERSYARATAN%20DAN%20ALUR%20PENGAJUAN%20AKTA%20KEMATIAN_(dengan%20contoh%20Formulir).pdf">Akata Kematian</a>)</ListItem>
</List>
{/* Bagian Pelayanan Telunjuk Sakti Desa */}
<Box py={15}>
<Text fz={{base: "h4", md: "h3"}} fw={"bold"}>Pelayanan Penduduk Non-Permanent</Text>
</Box>
<Text pb={20} fz={{ base: "sm", md: 'h3' }} ta={"justify"}>Surat Keterangan Penduduk Non-Permanent adalah dokumen yang dikeluarkan oleh pihak berwenang untuk memberikan keterangan bahwa seseorang atau kelompok orang memiliki status penduduk non-permanent di suatu wilayah. Dokumen ini biasanya digunakan untuk keperluan administratif atau legal, seperti mendapatkan akses ke layanan kesehatan, pendidikan, atau pelayanan publik lainnya.</Text>
<Divider color={colors["blue-button"]} />
<Flex justify={"space-between"} py={20}>
<Text fz={{ base: "sm", md: 'h3' }}>25 May 2021 . Darmasaba</Text>
<Box>
<Flex gap={"lg"}>
<ActionIcon variant='transparent'>
<IconBrandFacebook color={colors["blue-button"]} size={"30"} />
</ActionIcon>
<ActionIcon variant='transparent'>
<IconBrandInstagram color={colors["blue-button"]} size={"30"} />
</ActionIcon>
<ActionIcon variant='transparent'>
<IconBrandTwitter color={colors["blue-button"]} size={"30"} />
</ActionIcon>
<ActionIcon variant='transparent'>
<IconBrandWhatsapp color={colors["blue-button"]} size={"30"} />
</ActionIcon>
</Flex>
</Box>
</Flex>
<Divider color={colors["blue-button"]} pb={50} />
<PelayananTelunjukSaktiDesa/>
{/* Bagian Pelayanan Penduduk Non Permanent */}
<PelayananPendudukNonPermanent/>
</Box>
</Stack>
)

View File

@@ -0,0 +1,77 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import potensiDesaState from '@/app/admin/(dashboard)/_state/desa/potensi';
import colors from '@/con/colors';
import { Box, Center, Container, Image, Skeleton, Stack, Text } from '@mantine/core';
import { useParams } from 'next/navigation';
import { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils';
import BackButton from '../../layanan/_com/BackButto';
function Page() {
const params = useParams<{ id: string }>();
const id = Array.isArray(params.id) ? params.id[0] : params.id;
const state = useProxy(potensiDesaState.potensiDesa)
const [loading, setLoading] = useState(true)
useEffect(() => {
const loadData = async () => {
if (!id) return;
try {
setLoading(true);
await state.findUnique.load(id);
} catch (error) {
console.error('Error loading data:', error);
} finally {
setLoading(false);
}
}
loadData()
}, [id])
if (loading) {
return (
<Center>
<Skeleton height={500} />
</Center>
);
}
if (!state.findUnique.data) {
return (
<Center>
<Text>Data tidak ditemukan</Text>
</Center>
);
}
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"} px={{ base: "md", md: 0 }}>
<Box px={{ base: "md", md: 100 }}><BackButton /></Box>
<Container w={{ base: "100%", md: "50%" }} >
<Box pb={20}>
<Text ta={"center"} fz={"3.4rem"} c={colors["blue-button"]} fw={"bold"}>
{state.findUnique.data?.name}
</Text>
<Text
ta={"center"}
fw={"bold"}
fz={"1.5rem"}
>
Informasi dan Pelayanan Administrasi Digital
</Text>
</Box>
<Image src={state.findUnique.data?.image?.link || ''} alt='' w={"100%"} />
</Container>
<Box px={{ base: "md", md: 100 }}>
<Text py={20} fz={{ base: "sm", md: "lg" }} ta={"justify"}>
{state.findUnique.data?.deskripsi || ''}
</Text>
</Box>
</Stack>
);
}
export default Page;

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