Compare commits
20 Commits
nico/19-au
...
nico/29-au
| Author | SHA1 | Date | |
|---|---|---|---|
| 9f9a0fb451 | |||
| b6d6583e77 | |||
| a8fd715822 | |||
| f9530c32eb | |||
| f15ef5a275 | |||
| 3a726a3334 | |||
| b21e1f0c2e | |||
| f63249327d | |||
| bb8dab05ba | |||
| 3081e426bd | |||
| 8a275c2a32 | |||
| 8469ebd2e1 | |||
| 760ba4b6d2 | |||
| 20d4c90e60 | |||
| fafbb12a08 | |||
| 01aa0da5cc | |||
| b580978f8e | |||
| 1c01397c0d | |||
| 90a6605efd | |||
| c22d865283 |
@@ -3,126 +3,108 @@
|
|||||||
"id": "cmds9h9ko000pvnberdjnx64b",
|
"id": "cmds9h9ko000pvnberdjnx64b",
|
||||||
"name": "1.1 ADANYA PERDES/KEPUTUSAN KEPALA DESA/SOP TENTANG PERENCANAAN, PELAKSANAAN, PENATAUSAHAAN DAN PERTANGGUNG JAWABAN APBDES BESERTA IMPLEMENTASINYA",
|
"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>",
|
"deskripsi": "<p>ADANYA PERDES/KEPUTUSAN KEPALA DESA/SOP TENTANG PERENCANAAN, PELAKSANAAN, PENATAUSAHAAN DAN PERTANGGUNG JAWABAN APBDES BESERTA IMPLEMENTASINYA</p>",
|
||||||
"kategoriId": "cmds9es2o000ivnbe1o0swrvh",
|
"kategoriId": "cmds9es2o000ivnbe1o0swrvh"
|
||||||
"fileId": ""
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cmds9sjmz000svnbesv2133of",
|
"id": "cmds9sjmz000svnbesv2133of",
|
||||||
"name": "1.2 ADANYA PERDES/KEPUTUSAN KEPALA DESA/SOP MENGENAI MEKANISME EVALUASI KINERJA PERANGKAT DESA",
|
"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>",
|
"deskripsi": "<p>ADANYA PERDES/KEPUTUSAN KEPALA DESA/SOP MENGENAI MEKANISME EVALUASI KINERJA PERANGKAT DESA</p>",
|
||||||
"kategoriId": "cmds9es2o000ivnbe1o0swrvh",
|
"kategoriId": "cmds9es2o000ivnbe1o0swrvh"
|
||||||
"fileId": ""
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cmds9tcpi000vvnbev3ebtlnt",
|
"id": "cmds9tcpi000vvnbev3ebtlnt",
|
||||||
"name": "1.3 ADANYA PERDES/KEPUTUSAN KEPALA DESA/SOP TENTANG PENGENDALIAN GRATIFIKASI, SUAP DAN KONFLIK KEPENTINGAN",
|
"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>",
|
"deskripsi": "<p>ADANYA PERDES/KEPUTUSAN KEPALA DESA/SOP TENTANG PENGENDALIAN GRATIFIKASI, SUAP DAN KONFLIK KEPENTINGAN</p>",
|
||||||
"kategoriId": "cmds9es2o000ivnbe1o0swrvh",
|
"kategoriId": "cmds9es2o000ivnbe1o0swrvh"
|
||||||
"fileId": ""
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cmds9twvj000yvnbep0pq8dzf",
|
"id": "cmds9twvj000yvnbep0pq8dzf",
|
||||||
"name": "1.4 PERJANJIAN KERJA SAMA ANTARA PELAKSANA KEGIATAN ANGGARAN DENGAN PIHAK PENYEDIA, DAN TELAH MELALUI PROSES PENGADAAN BARANG/JASA DI DESA",
|
"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>",
|
"deskripsi": "<p>PERJANJIAN KERJA SAMA ANTARA PELAKSANA KEGIATAN ANGGARAN DENGAN PIHAK PENYEDIA, DAN TELAH MELALUI PROSES PENGADAAN BARANG/JASA DI DESA</p>",
|
||||||
"kategoriId": "cmds9es2o000ivnbe1o0swrvh",
|
"kategoriId": "cmds9es2o000ivnbe1o0swrvh"
|
||||||
"fileId": ""
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cmds9ugap0011vnbe118yv871",
|
"id": "cmds9ugap0011vnbe118yv871",
|
||||||
"name": "1.5 ADANYA PERDES/KEPUTUSAN KEPALA DESA/SOP TENTANG PAKTA INTEGRITAS DAN SEJENISNYA",
|
"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>",
|
"deskripsi": "<p>ADANYA PERDES/KEPUTUSAN KEPALA DESA/SOP TENTANG PAKTA INTEGRITAS DAN SEJENISNYA</p>",
|
||||||
"kategoriId": "cmds9es2o000ivnbe1o0swrvh",
|
"kategoriId": "cmds9es2o000ivnbe1o0swrvh"
|
||||||
"fileId": ""
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cmdsa35310014vnbe6qy6l1rz",
|
"id": "cmdsa35310014vnbe6qy6l1rz",
|
||||||
"name": "2.1 ADANYA KEGIATAN PENGAWASAN DAN EVALUASI KINERJA PERANGKAT DESA",
|
"name": "2.1 ADANYA KEGIATAN PENGAWASAN DAN EVALUASI KINERJA PERANGKAT DESA",
|
||||||
"deskripsi": "<p>ADANYA KEGIATAN PENGAWASAN DAN EVALUASI KINERJA PERANGKAT DESA</p>",
|
"deskripsi": "<p>ADANYA KEGIATAN PENGAWASAN DAN EVALUASI KINERJA PERANGKAT DESA</p>",
|
||||||
"kategoriId": "cmds9f2ua000jvnbeksu1sfwm",
|
"kategoriId": "cmds9f2ua000jvnbeksu1sfwm"
|
||||||
"fileId": ""
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cmdsa46590017vnbepp3noso1",
|
"id": "cmdsa46590017vnbepp3noso1",
|
||||||
"name": "2.2 ADANYA TINDAK LANJUT HASIL PEMBINAAN, PETUNJUK, ARAH, PENGAWASAN, DAN PEMERIKSAAN DARI PEMERINTAH PUSAT/DAERAH",
|
"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>",
|
"deskripsi": "<p>ADANYA TINDAK LANJUT HASIL PEMBINAAN, PETUNJUK, ARAH, PENGAWASAN, DAN PEMERIKSAAN DARI PEMERINTAH PUSAT/DAERAH</p>",
|
||||||
"kategoriId": "cmds9f2ua000jvnbeksu1sfwm",
|
"kategoriId": "cmds9f2ua000jvnbeksu1sfwm"
|
||||||
"fileId": ""
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cmdsa5m7z001avnbe4cvfrcz0",
|
"id": "cmdsa5m7z001avnbe4cvfrcz0",
|
||||||
"name": "2.3 TIDAK ADANYA APARATUR DESA DALAM 3(TIGA) TAHUN TERAKHIR YANG TERJERAT TINDAKAN PIDANA KORUPSI",
|
"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>",
|
"deskripsi": "<p>TIDAK ADANYA APARATUR DESA DALAM 3(TIGA) TAHUN TERAKHIR YANG TERJERAT TINDAKAN PIDANA KORUPSI</p>",
|
||||||
"kategoriId": "cmds9f2ua000jvnbeksu1sfwm",
|
"kategoriId": "cmds9f2ua000jvnbeksu1sfwm"
|
||||||
"fileId": ""
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cmdsa8q5q001dvnbemch8j24x",
|
"id": "cmdsa8q5q001dvnbemch8j24x",
|
||||||
"name": "3.1 ADANYA LAYANAN PENGADUAN BAGI MASYARAKAT",
|
"name": "3.1 ADANYA LAYANAN PENGADUAN BAGI MASYARAKAT",
|
||||||
"deskripsi": "<p>ADANYA LAYANAN PENGADUAN BAGI MASYARAKAT</p>",
|
"deskripsi": "<p>ADANYA LAYANAN PENGADUAN BAGI MASYARAKAT</p>",
|
||||||
"kategoriId": "cmds9fr73000kvnbe6w281dcl",
|
"kategoriId": "cmds9fr73000kvnbe6w281dcl"
|
||||||
"fileId": ""
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cmdsa9lbi001gvnbequn2ba7m",
|
"id": "cmdsa9lbi001gvnbequn2ba7m",
|
||||||
"name": "3.2 ADANYA SURVEY KEPUASAN MASYARAKAT (SKM) TERHADAP LAYANAN PEMERINTAH DESA",
|
"name": "3.2 ADANYA SURVEY KEPUASAN MASYARAKAT (SKM) TERHADAP LAYANAN PEMERINTAH DESA",
|
||||||
"deskripsi": "<p>ADANYA SURVEY KEPUASAN MASYARAKAT (SKM) TERHADAP LAYANAN PEMERINTAH DESA</p>",
|
"deskripsi": "<p>ADANYA SURVEY KEPUASAN MASYARAKAT (SKM) TERHADAP LAYANAN PEMERINTAH DESA</p>",
|
||||||
"kategoriId": "cmds9fr73000kvnbe6w281dcl",
|
"kategoriId": "cmds9fr73000kvnbe6w281dcl"
|
||||||
"fileId": ""
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cmdsaa7aq001jvnbeizh04e67",
|
"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",
|
"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>",
|
"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",
|
"kategoriId": "cmds9fr73000kvnbe6w281dcl"
|
||||||
"fileId": ""
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cmdsaaw8d001mvnbek3tfefrk",
|
"id": "cmdsaaw8d001mvnbek3tfefrk",
|
||||||
"name": "3.4 ADANYA MEDIA INFORMASI TENTANG APBDES DI BALAI DESA DAN/ATAU TEMPAT LAIN YANG MUDAH DIAKSES OLEH MASYARAKAT",
|
"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>",
|
"deskripsi": "<p>ADANYA MEDIA INFORMASI TENTANG APBDES DI BALAI DESA DAN/ATAU TEMPAT LAIN YANG MUDAH DIAKSES OLEH MASYARAKAT</p>",
|
||||||
"kategoriId": "cmds9fr73000kvnbe6w281dcl",
|
"kategoriId": "cmds9fr73000kvnbe6w281dcl"
|
||||||
"fileId": ""
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cmdsabhif001pvnbepm06hry6",
|
"id": "cmdsabhif001pvnbepm06hry6",
|
||||||
"name": "3.5 ADANYA MAKLUMAT PELAYANAN",
|
"name": "3.5 ADANYA MAKLUMAT PELAYANAN",
|
||||||
"deskripsi": "<p>ADANYA MAKLUMAT PELAYANAN</p>",
|
"deskripsi": "<p>ADANYA MAKLUMAT PELAYANAN</p>",
|
||||||
"kategoriId": "cmds9fr73000kvnbe6w281dcl",
|
"kategoriId": "cmds9fr73000kvnbe6w281dcl"
|
||||||
"fileId": ""
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cmdsag40b001svnbe7krq9khc",
|
"id": "cmdsag40b001svnbe7krq9khc",
|
||||||
"name": "4.1 ADANYA PARTISIPASI DAN KETERLIBATAN MASYARAKAT DALAM PENYUSUNAN RKP DESA",
|
"name": "4.1 ADANYA PARTISIPASI DAN KETERLIBATAN MASYARAKAT DALAM PENYUSUNAN RKP DESA",
|
||||||
"deskripsi": "<p>ADANYA PARTISIPASI DAN KETERLIBATAN MASYARAKAT DALAM PENYUSUNAN RKP DESA</p>",
|
"deskripsi": "<p>ADANYA PARTISIPASI DAN KETERLIBATAN MASYARAKAT DALAM PENYUSUNAN RKP DESA</p>",
|
||||||
"kategoriId": "cmds9g5ow000lvnbel3rkkwrv",
|
"kategoriId": "cmds9g5ow000lvnbel3rkkwrv"
|
||||||
"fileId": ""
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cmdsagkaf001vvnbejo26w8sa",
|
"id": "cmdsagkaf001vvnbejo26w8sa",
|
||||||
"name": "4.2 ADANYA KESADARAN MASYARAKAT DALAM MENCEGAH TERJADINYA PRAKTIK GRATIFIKASI, SUAP DAN KONFLIK KEPENTINGAN",
|
"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>",
|
"deskripsi": "<p>ADANYA KESADARAN MASYARAKAT DALAM MENCEGAH TERJADINYA PRAKTIK GRATIFIKASI, SUAP DAN KONFLIK KEPENTINGAN</p>",
|
||||||
"kategoriId": "cmds9g5ow000lvnbel3rkkwrv",
|
"kategoriId": "cmds9g5ow000lvnbel3rkkwrv"
|
||||||
"fileId": ""
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cmdsah4qe001yvnbeiy3mwrvb",
|
"id": "cmdsah4qe001yvnbeiy3mwrvb",
|
||||||
"name": "4.3 ADANYA KETERLIBATAN LEMBAGA KEMASYARAKATAN DALAM PELAKSANAAN PEMBANGUNAN DESA",
|
"name": "4.3 ADANYA KETERLIBATAN LEMBAGA KEMASYARAKATAN DALAM PELAKSANAAN PEMBANGUNAN DESA",
|
||||||
"deskripsi": "<p>ADANYA KETERLIBATAN LEMBAGA KEMASYARAKATAN DALAM PELAKSANAAN PEMBANGUNAN DESA</p>",
|
"deskripsi": "<p>ADANYA KETERLIBATAN LEMBAGA KEMASYARAKATAN DALAM PELAKSANAAN PEMBANGUNAN DESA</p>",
|
||||||
"kategoriId": "cmds9g5ow000lvnbel3rkkwrv",
|
"kategoriId": "cmds9g5ow000lvnbel3rkkwrv"
|
||||||
"fileId": ""
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cmdsak5vn0021vnbemg86aab4",
|
"id": "cmdsak5vn0021vnbemg86aab4",
|
||||||
"name": "5.1 ADANYA BUDAYA LOKAL/HUKUM ADAT YANG MENDORONG UPAYA PENCEGAHAN TINDAK PIDANA KORUPSI",
|
"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>",
|
"deskripsi": "<p>ADANYA BUDAYA LOKAL/HUKUM ADAT YANG MENDORONG UPAYA PENCEGAHAN TINDAK PIDANA KORUPSI</p>",
|
||||||
"kategoriId": "cmds9govb000mvnbesq8b4y99",
|
"kategoriId": "cmds9govb000mvnbesq8b4y99"
|
||||||
"fileId": ""
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cmdsalc800024vnbezgulhgrb",
|
"id": "cmdsalc800024vnbezgulhgrb",
|
||||||
"name": "5.2 ADANYA TOKOH MASYARAKAT, TOKOH AGAMA, TOKOH ADAT, TOKOH PEMUDA, DAN KAUM PEREMPUAN YANG MENDORONG UPAYA PENCEGAHAN TINDAK PIDANA KORUPSI",
|
"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>",
|
"deskripsi": "<p>ADANYA TOKOH MASYARAKAT, TOKOH AGAMA, TOKOH ADAT, TOKOH PEMUDA, DAN KAUM PEREMPUAN YANG MENDORONG UPAYA PENCEGAHAN TINDAK PIDANA KORUPSI</p>",
|
||||||
"kategoriId": "cmds9govb000mvnbesq8b4y99",
|
"kategoriId": "cmds9govb000mvnbesq8b4y99"
|
||||||
"fileId": ""
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -1,8 +1,14 @@
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
"id": "1",
|
"id": "cmeppcwzk0000vn5exmudcipd",
|
||||||
"jenisInformasi": "Peraturan Desa",
|
"jenisInformasi": "Potensi Desa",
|
||||||
"deskripsi": "Dokumen yang berisi kebijakan dan regulasi desa",
|
"deskripsi": "<p>“Potensi desa adalah segenap sumber daya alam dan sumber daya manusia yang dimiliki desa sebagai modal dasar yang perlu dikelola dan dikembangkan bagi kelangsungan dan perkembangan desa. Adapun potensi yang dimiliki Desa Darmasaba yaitu:</p><ol><li><p>TPS3R Pudak Mesari</p></li><li><p>Bumdes Pudak Mesari</p></li><li><p>Pertanian</p></li><li><p>Jogging Track Tegeh Aban, Karang Gadon dan Munduk Uma Desa</p></li><li><p>Taman Beji Cengana</p></li><li><p>Dam Tanah Putih</p></li><li><p>Gumuh Sari Water Park</p></li><li><p>UMKM</p></li><li><p>Kawasan Kuliner</p></li><li><p>IKM berbasis Pengolahan Pangan</p></li><li><p>Genteng</p></li><li><p>Peternakan Ikan Lele</p></li><li><p>Pemotongan Daging”</p></li></ol>",
|
||||||
"tanggal": "15 Januari 2024"
|
"tanggal": "2021-05-25"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cmeppieay0001vn5e8qe658ub",
|
||||||
|
"jenisInformasi": "Layanan Surat Keterangan Desa",
|
||||||
|
"deskripsi": "<p>“Desa Darmasaba menyediakan berbagai jenis layanan surat keterangan untuk kebutuhan administratif, antara lain:</p><ul><li><p>Surat Keterangan Domisili Organisasi</p></li><li><p>Surat Keterangan Penghasilan</p></li><li><p>Surat Keterangan Tidak Mampu</p></li><li><p>Surat Keterangan Kelahiran</p></li><li><p>Surat Keterangan Usaha</p></li><li><p>Surat Keterangan Tempat Usaha</p></li><li><p>Surat Keterangan Belum Kawin</p></li><li><p>Surat Keterangan Kelakuan Baik (Pengantar SKCK)</p></li><li><p>Surat Keterangan Kematian</p></li><li><p>Surat Keterangan Perbedaan Biodata Diri</p></li><li><p>Surat Keterangan Yatim/Piatu/Yatim Piatu<br>Untuk surat keterangan lainnya, masyarakat dapat berkonsultasi langsung ke kantor Perbekel Darmasaba.”<br><em>(Sumber: Laman Layanan Desa Darmasaba)</em></p></li></ul>",
|
||||||
|
"tanggal": "2025-02-21"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -85,7 +85,6 @@ model FileStorage {
|
|||||||
KontakItem KontakItem[]
|
KontakItem KontakItem[]
|
||||||
Pegawai Pegawai[]
|
Pegawai Pegawai[]
|
||||||
DesaDigital DesaDigital[]
|
DesaDigital DesaDigital[]
|
||||||
KolaborasiInovasi KolaborasiInovasi[]
|
|
||||||
InfoTekno InfoTekno[]
|
InfoTekno InfoTekno[]
|
||||||
PengaduanMasyarakat PengaduanMasyarakat[]
|
PengaduanMasyarakat PengaduanMasyarakat[]
|
||||||
KegiatanDesa KegiatanDesa[]
|
KegiatanDesa KegiatanDesa[]
|
||||||
@@ -100,6 +99,8 @@ model FileStorage {
|
|||||||
DataPerpustakaan DataPerpustakaan[]
|
DataPerpustakaan DataPerpustakaan[]
|
||||||
PegawaiPPID PegawaiPPID[]
|
PegawaiPPID PegawaiPPID[]
|
||||||
PerbekelDariMasaKeMasa PerbekelDariMasaKeMasa[]
|
PerbekelDariMasaKeMasa PerbekelDariMasaKeMasa[]
|
||||||
|
|
||||||
|
MitraKolaborasi MitraKolaborasi[]
|
||||||
}
|
}
|
||||||
|
|
||||||
//========================================= MENU LANDING PAGE ========================================= //
|
//========================================= MENU LANDING PAGE ========================================= //
|
||||||
@@ -201,8 +202,8 @@ model PrestasiDesa {
|
|||||||
deskripsi String @db.Text
|
deskripsi String @db.Text
|
||||||
kategori KategoriPrestasiDesa @relation(fields: [kategoriId], references: [id])
|
kategori KategoriPrestasiDesa @relation(fields: [kategoriId], references: [id])
|
||||||
kategoriId String
|
kategoriId String
|
||||||
image FileStorage @relation(fields: [imageId], references: [id])
|
image FileStorage? @relation(fields: [imageId], references: [id])
|
||||||
imageId String
|
imageId String?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
deletedAt DateTime @default(now())
|
deletedAt DateTime @default(now())
|
||||||
@@ -223,7 +224,7 @@ model KategoriPrestasiDesa {
|
|||||||
model Responden {
|
model Responden {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
name String @unique
|
name String @unique
|
||||||
tanggal String // misal: 2025-05-01
|
tanggal DateTime @db.Date // misal: 2025-05-01
|
||||||
jenisKelamin JenisKelaminResponden @relation(fields: [jenisKelaminId], references: [id])
|
jenisKelamin JenisKelaminResponden @relation(fields: [jenisKelaminId], references: [id])
|
||||||
jenisKelaminId String
|
jenisKelaminId String
|
||||||
rating PilihanRatingResponden @relation(fields: [ratingId], references: [id])
|
rating PilihanRatingResponden @relation(fields: [ratingId], references: [id])
|
||||||
@@ -292,6 +293,9 @@ model PosisiOrganisasiPPID {
|
|||||||
pegawai PegawaiPPID[]
|
pegawai PegawaiPPID[]
|
||||||
strukturOrganisasi StrukturPPID[] // Relasi balik
|
strukturOrganisasi StrukturPPID[] // Relasi balik
|
||||||
parentId String?
|
parentId String?
|
||||||
|
isActive Boolean @default(true)
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
parent PosisiOrganisasiPPID? @relation("Parent", fields: [parentId], references: [id])
|
parent PosisiOrganisasiPPID? @relation("Parent", fields: [parentId], references: [id])
|
||||||
children PosisiOrganisasiPPID[] @relation("Parent")
|
children PosisiOrganisasiPPID[] @relation("Parent")
|
||||||
}
|
}
|
||||||
@@ -1547,7 +1551,7 @@ model DataDemografiPekerjaan {
|
|||||||
model DetailDataPengangguran {
|
model DetailDataPengangguran {
|
||||||
id String @id @default(uuid()) @db.Uuid
|
id String @id @default(uuid()) @db.Uuid
|
||||||
month String @db.VarChar(20)
|
month String @db.VarChar(20)
|
||||||
year Int
|
year DateTime
|
||||||
totalUnemployment Int
|
totalUnemployment Int
|
||||||
educatedUnemployment Int
|
educatedUnemployment Int
|
||||||
uneducatedUnemployment Int
|
uneducatedUnemployment Int
|
||||||
@@ -1641,14 +1645,22 @@ model KolaborasiInovasi {
|
|||||||
slug String @db.Text //deskripsi singkat
|
slug String @db.Text //deskripsi singkat
|
||||||
deskripsi String @db.Text //deskripsi panjang
|
deskripsi String @db.Text //deskripsi panjang
|
||||||
kolaborator String
|
kolaborator String
|
||||||
image FileStorage @relation(fields: [imageId], references: [id])
|
|
||||||
imageId String
|
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
deletedAt DateTime @default(now())
|
deletedAt DateTime @default(now())
|
||||||
isActive Boolean @default(true)
|
isActive Boolean @default(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model MitraKolaborasi {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
name 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)
|
||||||
|
}
|
||||||
// ========================================= INFO TEKHNOLOGI TEPAT GUNA ========================================= //
|
// ========================================= INFO TEKHNOLOGI TEPAT GUNA ========================================= //
|
||||||
model InfoTekno {
|
model InfoTekno {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
|
|||||||
332
prisma/seed.ts
332
prisma/seed.ts
@@ -1,57 +1,62 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import profilePejabatDesa from "./data/landing-page/profile/profile.json";
|
import profilePejabatDesa from "./data/landing-page/profile/profile.json";
|
||||||
import penghargaan from "./data/landing-page/penghargaan/penghargaan.json";
|
|
||||||
import programInovasi from "./data/landing-page/profile/programInovasi.json";
|
import programInovasi from "./data/landing-page/profile/programInovasi.json";
|
||||||
import mediaSosial from "./data/landing-page/profile/mediaSosial.json";
|
import mediaSosial from "./data/landing-page/profile/mediaSosial.json";
|
||||||
|
import desaAntiKorupsi from "./data/landing-page/desa-anti-korupsi/desaantiKorpusi.json";
|
||||||
|
import kategoriDesaAntiKorupsi from "./data/landing-page/desa-anti-korupsi/kategoriDesaAntiKorupsi.json";
|
||||||
import sdgsDesa from "./data/landing-page/sdgs-desa/sdgs-desa.json";
|
import sdgsDesa from "./data/landing-page/sdgs-desa/sdgs-desa.json";
|
||||||
import apbdes from "./data/landing-page/apbdes/apbdes.json";
|
import apbdes from "./data/landing-page/apbdes/apbdes.json";
|
||||||
import pelayananSuratKeterangan from "./data/desa/layanan/pelayananSuratKeterangan.json";
|
import kategoriPrestasiDesa from "./data/landing-page/prestasi-desa/kategori-prestasi.json";
|
||||||
import categoryPengumuman from "./data/category-pengumuman.json";
|
import prestasiDesa from "./data/landing-page/prestasi-desa/prestasi-desa.json";
|
||||||
import kategoriBerita from "./data/kategori-berita.json";
|
import penghargaan from "./data/landing-page/penghargaan/penghargaan.json";
|
||||||
import caraMemperolehInformasi from "./data/list-caraMemperolehInformasi.json";
|
|
||||||
import caraMemperolehSalinanInformasi from "./data/list-caraMemperolehSalinanInformasi.json";
|
|
||||||
import jenisInformasiDiminta from "./data/list-jenisInfromasi.json";
|
|
||||||
import layanan from "./data/list-layanan.json";
|
|
||||||
import potensi from "./data/list-potensi.json";
|
|
||||||
import dasarHukumPPID from "./data/ppid/dasar-hukum-ppid/dasarhukumPPID.json";
|
|
||||||
import profilePPID from "./data/ppid/profile-ppid/profilePPid.json";
|
import profilePPID from "./data/ppid/profile-ppid/profilePPid.json";
|
||||||
|
import pegawaiPPID from "./data/ppid/struktur-ppid/pegawai-PPID.json";
|
||||||
|
import posisiOrganisasiPPID from "./data/ppid/struktur-ppid/posisi-organisasi-PPID.json";
|
||||||
import visiMisiPPID from "./data/ppid/visi-misi-ppid/visimisiPPID.json";
|
import visiMisiPPID from "./data/ppid/visi-misi-ppid/visimisiPPID.json";
|
||||||
|
import dasarHukumPPID from "./data/ppid/dasar-hukum-ppid/dasarhukumPPID.json";
|
||||||
import jenisKelamin from "./data/ppid/ikm/jenis-kelamin/jenis-kelamin.json";
|
import jenisKelamin from "./data/ppid/ikm/jenis-kelamin/jenis-kelamin.json";
|
||||||
|
import daftarInformasiPublik from "./data/ppid/daftar-informasi-publik-desa-darmasaba/daftarInformasi.json"
|
||||||
import pilihanRatingResponden from "./data/ppid/ikm/pilihan-rating-responden/rating-responden.json";
|
import pilihanRatingResponden from "./data/ppid/ikm/pilihan-rating-responden/rating-responden.json";
|
||||||
import umurResponden from "./data/ppid/ikm/umur-responden/umur-responden.json";
|
import umurResponden from "./data/ppid/ikm/umur-responden/umur-responden.json";
|
||||||
|
import categoryPengumuman from "./data/category-pengumuman.json";
|
||||||
import pelayananPerizinanBerusaha from "./data/desa/layanan/pelayananPerizinanBerusaha.json";
|
import pelayananPerizinanBerusaha from "./data/desa/layanan/pelayananPerizinanBerusaha.json";
|
||||||
|
import pelayananSuratKeterangan from "./data/desa/layanan/pelayananSuratKeterangan.json";
|
||||||
|
import pelayananTelunjukSaktiDesa from "./data/desa/layanan/pelayananTelunjukSaktiDesa.json";
|
||||||
import pelayananPendudukNonPermanen from "./data/desa/layanan/pelayanaPendudukNonPermanen.json";
|
import pelayananPendudukNonPermanen from "./data/desa/layanan/pelayanaPendudukNonPermanen.json";
|
||||||
import sejarahDesa from "./data/desa/profile/sejarah_desa.json";
|
|
||||||
import visiMisiDesa from "./data/desa/profile/visi_misi_desa.json";
|
|
||||||
import lambangDesa from "./data/desa/profile/lambang_desa.json";
|
import lambangDesa from "./data/desa/profile/lambang_desa.json";
|
||||||
import maskotDesa from "./data/desa/profile/maskot_desa.json";
|
import maskotDesa from "./data/desa/profile/maskot_desa.json";
|
||||||
import profilPerbekel from "./data/desa/profile/profil_perbekel.json";
|
import profilPerbekel from "./data/desa/profile/profil_perbekel.json";
|
||||||
|
import sejarahDesa from "./data/desa/profile/sejarah_desa.json";
|
||||||
|
import visiMisiDesa from "./data/desa/profile/visi_misi_desa.json";
|
||||||
|
import detailDataPengangguran from "./data/ekonomi/jumlah-pengangguran/detail-data-pengangguran.json";
|
||||||
import kategoriProduk from "./data/ekonomi/pasar-desa/kategori-produk.json";
|
import kategoriProduk from "./data/ekonomi/pasar-desa/kategori-produk.json";
|
||||||
import hubunganOrganisasi from "./data/ekonomi/struktur-organisasi/hubungan-organisasi.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 pegawai from "./data/ekonomi/struktur-organisasi/pegawai.json";
|
||||||
import detailDataPengangguran from "./data/ekonomi/jumlah-pengangguran/detail-data-pengangguran.json";
|
import posisiOrganisasi from "./data/ekonomi/struktur-organisasi/posisi-organisasi.json";
|
||||||
import tujuanEdukasiLingkungan from "./data/lingkungan/edukasi-lingkungan/tujuan-edukasi-lingkungan.json";
|
import kategoriBerita from "./data/kategori-berita.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 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 materiEdukasiLingkungan from "./data/lingkungan/edukasi-lingkungan/materi-edukasi-yang-diberikan.json";
|
||||||
|
import tujuanEdukasiLingkungan from "./data/lingkungan/edukasi-lingkungan/tujuan-edukasi-lingkungan.json";
|
||||||
import bentukKonservasiBerdasarkanAdat from "./data/lingkungan/konservasi-adat-bali/bentuk-konservasi.json";
|
import bentukKonservasiBerdasarkanAdat from "./data/lingkungan/konservasi-adat-bali/bentuk-konservasi.json";
|
||||||
import filosofiTriHita from "./data/lingkungan/konservasi-adat-bali/filosofi-tri-hita.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 nilaiKonservasiAdat from "./data/lingkungan/konservasi-adat-bali/nilai-konservasi-adat.json";
|
||||||
|
import caraMemperolehInformasi from "./data/list-caraMemperolehInformasi.json";
|
||||||
|
import caraMemperolehSalinanInformasi from "./data/list-caraMemperolehSalinanInformasi.json";
|
||||||
|
import jenisInformasiDiminta from "./data/list-jenisInfromasi.json";
|
||||||
|
import potensi from "./data/list-potensi.json";
|
||||||
|
import fasilitasBimbinganBelajarDesa from "./data/pendidikan/bimbingan-belajar-desa/fasilitas-yang-disediakan.json";
|
||||||
|
import lokasiJadwalBimbinganBelajarDesa from "./data/pendidikan/bimbingan-belajar-desa/lokasi-dan-jadwal.json";
|
||||||
|
import tujuanBimbinganBelajarDesa from "./data/pendidikan/bimbingan-belajar-desa/tujuan-bimbingan-belajar-desa.json";
|
||||||
|
import jenisProgramYangDiselenggarakan from "./data/pendidikan/pendidikan-non-formal/jenis-program-yang-diselenggarakan.json";
|
||||||
|
import tempatKegiatan from "./data/pendidikan/pendidikan-non-formal/tempat-kegiatan.json";
|
||||||
import tujuanProgram2 from "./data/pendidikan/pendidikan-non-formal/tujuan-program2.json";
|
import tujuanProgram2 from "./data/pendidikan/pendidikan-non-formal/tujuan-program2.json";
|
||||||
import programUnggulan from "./data/pendidikan/program-pendidikan-anak/program-unggulan.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 tujuanProgram from "./data/pendidikan/program-pendidikan-anak/tujuan-program.json";
|
||||||
import lokasiJadwalBimbinganBelajarDesa from "./data/pendidikan/bimbingan-belajar-desa/lokasi-dan-jadwal.json";
|
|
||||||
import fasilitasBimbinganBelajarDesa from "./data/pendidikan/bimbingan-belajar-desa/fasilitas-yang-disediakan.json";
|
|
||||||
import tempatKegiatan from "./data/pendidikan/pendidikan-non-formal/tempat-kegiatan.json";
|
|
||||||
import jenisProgramYangDiselenggarakan from "./data/pendidikan/pendidikan-non-formal/jenis-program-yang-diselenggarakan.json";
|
|
||||||
import posisiOrganisasiPPID from "./data/ppid/struktur-ppid/posisi-organisasi-PPID.json";
|
|
||||||
import pegawaiPPID from "./data/ppid/struktur-ppid/pegawai-PPID.json";
|
|
||||||
import pelayananTelunjukSaktiDesa from "./data/desa/layanan/pelayananTelunjukSaktiDesa.json";
|
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
// =========== LANDING PAGE ===========
|
// =========== LANDING PAGE ===========
|
||||||
// =========== PROFILE ===========
|
// =========== SUBMENU PROFILE ===========
|
||||||
|
// =========== PROFILE PEJABAT DESA ===========
|
||||||
for (const p of profilePejabatDesa) {
|
for (const p of profilePejabatDesa) {
|
||||||
await prisma.pejabatDesa.upsert({
|
await prisma.pejabatDesa.upsert({
|
||||||
where: { id: p.id },
|
where: { id: p.id },
|
||||||
@@ -106,6 +111,90 @@ import pelayananTelunjukSaktiDesa from "./data/desa/layanan/pelayananTelunjukSak
|
|||||||
}
|
}
|
||||||
console.log("media sosial success ...");
|
console.log("media sosial success ...");
|
||||||
|
|
||||||
|
// =========== SUBMENU DESA ANTI KORUPSI ===========
|
||||||
|
// =========== KATEGORI DESA ANTI KORUPSI ===========
|
||||||
|
for (const k of kategoriDesaAntiKorupsi) {
|
||||||
|
await prisma.kategoriDesaAntiKorupsi.upsert({
|
||||||
|
where: { id: k.id },
|
||||||
|
update: {
|
||||||
|
name: k.name,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
id: k.id,
|
||||||
|
name: k.name,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
console.log("kategori desa anti korupsi success ...");
|
||||||
|
|
||||||
|
// =========== DESA ANTI KORUPSI ===========
|
||||||
|
for (const p of desaAntiKorupsi) {
|
||||||
|
await prisma.desaAntiKorupsi.upsert({
|
||||||
|
where: { id: p.id },
|
||||||
|
update: {
|
||||||
|
name: p.name,
|
||||||
|
deskripsi: p.deskripsi,
|
||||||
|
kategoriId: p.kategoriId,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
id: p.id,
|
||||||
|
name: p.name,
|
||||||
|
deskripsi: p.deskripsi,
|
||||||
|
kategoriId: p.kategoriId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
console.log("desa anti korupsi success ...");
|
||||||
|
|
||||||
|
// =========== KATEGORI DESA ANTI KORUPSI ===========
|
||||||
|
for (const p of kategoriDesaAntiKorupsi) {
|
||||||
|
await prisma.kategoriDesaAntiKorupsi.upsert({
|
||||||
|
where: { id: p.id },
|
||||||
|
update: {
|
||||||
|
name: p.name,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
id: p.id,
|
||||||
|
name: p.name,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
console.log("desa anti korupsi success ...");
|
||||||
|
|
||||||
|
// =========== KATEGORI PRESTASI DESA===========
|
||||||
|
for (const c of kategoriPrestasiDesa) {
|
||||||
|
await prisma.kategoriPrestasiDesa.upsert({
|
||||||
|
where: { id: c.id },
|
||||||
|
update: {
|
||||||
|
name: c.name,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
id: c.id,
|
||||||
|
name: c.name,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
console.log("kategori prestasi desa success ...");
|
||||||
|
|
||||||
|
// =========== PRESTASI DESA===========
|
||||||
|
for (const p of prestasiDesa) {
|
||||||
|
await prisma.prestasiDesa.upsert({
|
||||||
|
where: { id: p.id },
|
||||||
|
update: {
|
||||||
|
name: p.name,
|
||||||
|
deskripsi: p.deskripsi,
|
||||||
|
kategoriId: p.kategoriId,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
id: p.id,
|
||||||
|
name: p.name,
|
||||||
|
deskripsi: p.deskripsi,
|
||||||
|
kategoriId: p.kategoriId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
console.log("prestasi desa success ...");
|
||||||
|
|
||||||
// =========== PENGHARGAAN ===========
|
// =========== PENGHARGAAN ===========
|
||||||
for (const p of penghargaan) {
|
for (const p of penghargaan) {
|
||||||
await prisma.penghargaan.upsert({
|
await prisma.penghargaan.upsert({
|
||||||
@@ -160,23 +249,6 @@ import pelayananTelunjukSaktiDesa from "./data/desa/layanan/pelayananTelunjukSak
|
|||||||
}
|
}
|
||||||
console.log("pelayanan surat keterangan success ...");
|
console.log("pelayanan surat keterangan success ...");
|
||||||
|
|
||||||
// =========== LAYANAN ===========
|
|
||||||
for (const l of layanan) {
|
|
||||||
await prisma.layanan.upsert({
|
|
||||||
where: {
|
|
||||||
name: l.name,
|
|
||||||
},
|
|
||||||
update: {
|
|
||||||
name: l.name,
|
|
||||||
},
|
|
||||||
create: {
|
|
||||||
name: l.name,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("layanan success ...");
|
|
||||||
|
|
||||||
// =========== SDGSDesa ===========
|
// =========== SDGSDesa ===========
|
||||||
for (const l of sdgsDesa) {
|
for (const l of sdgsDesa) {
|
||||||
await prisma.sDGSDesa.upsert({
|
await prisma.sDGSDesa.upsert({
|
||||||
@@ -217,6 +289,9 @@ import pelayananTelunjukSaktiDesa from "./data/desa/layanan/pelayananTelunjukSak
|
|||||||
|
|
||||||
console.log("sdgs desa success ...");
|
console.log("sdgs desa success ...");
|
||||||
|
|
||||||
|
// =========== MENU DESA ===========
|
||||||
|
// =========== SUBMENU PROFILE ===========
|
||||||
|
// =========== SEJARAH DESA ===========
|
||||||
for (const l of sejarahDesa) {
|
for (const l of sejarahDesa) {
|
||||||
await prisma.sejarahDesa.upsert({
|
await prisma.sejarahDesa.upsert({
|
||||||
where: {
|
where: {
|
||||||
@@ -236,6 +311,7 @@ import pelayananTelunjukSaktiDesa from "./data/desa/layanan/pelayananTelunjukSak
|
|||||||
|
|
||||||
console.log("sejarah desa success ...");
|
console.log("sejarah desa success ...");
|
||||||
|
|
||||||
|
// =========== MASKOT DESA ===========
|
||||||
for (const l of maskotDesa) {
|
for (const l of maskotDesa) {
|
||||||
await prisma.maskotDesa.upsert({
|
await prisma.maskotDesa.upsert({
|
||||||
where: {
|
where: {
|
||||||
@@ -255,6 +331,7 @@ import pelayananTelunjukSaktiDesa from "./data/desa/layanan/pelayananTelunjukSak
|
|||||||
|
|
||||||
console.log("maskot desa success ...");
|
console.log("maskot desa success ...");
|
||||||
|
|
||||||
|
// =========== LAMBANG DESA ===========
|
||||||
for (const l of lambangDesa) {
|
for (const l of lambangDesa) {
|
||||||
await prisma.lambangDesa.upsert({
|
await prisma.lambangDesa.upsert({
|
||||||
where: {
|
where: {
|
||||||
@@ -274,6 +351,7 @@ import pelayananTelunjukSaktiDesa from "./data/desa/layanan/pelayananTelunjukSak
|
|||||||
|
|
||||||
console.log("lambang desa success ...");
|
console.log("lambang desa success ...");
|
||||||
|
|
||||||
|
// =========== PROFIL PERBEKEL ===========
|
||||||
for (const c of profilPerbekel) {
|
for (const c of profilPerbekel) {
|
||||||
await prisma.profilPerbekel.upsert({
|
await prisma.profilPerbekel.upsert({
|
||||||
where: { id: c.id },
|
where: { id: c.id },
|
||||||
@@ -298,6 +376,7 @@ import pelayananTelunjukSaktiDesa from "./data/desa/layanan/pelayananTelunjukSak
|
|||||||
"✅ profilePerbekel seeded without imageId (editable later via UI)"
|
"✅ profilePerbekel seeded without imageId (editable later via UI)"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// =========== VISI MISI DESA ===========
|
||||||
for (const l of visiMisiDesa) {
|
for (const l of visiMisiDesa) {
|
||||||
await prisma.visiMisiDesa.upsert({
|
await prisma.visiMisiDesa.upsert({
|
||||||
where: {
|
where: {
|
||||||
@@ -317,6 +396,35 @@ import pelayananTelunjukSaktiDesa from "./data/desa/layanan/pelayananTelunjukSak
|
|||||||
|
|
||||||
console.log("visi misi desa success ...");
|
console.log("visi misi desa success ...");
|
||||||
|
|
||||||
|
// =========== MENU PPID ===========
|
||||||
|
// =========== SUBMENU PROFILE PPID ===========
|
||||||
|
for (const c of profilePPID) {
|
||||||
|
await prisma.profilePPID.upsert({
|
||||||
|
where: { id: c.id },
|
||||||
|
update: {
|
||||||
|
name: c.name,
|
||||||
|
biodata: c.biodata,
|
||||||
|
riwayat: c.riwayat,
|
||||||
|
pengalaman: c.pengalaman,
|
||||||
|
unggulan: c.unggulan,
|
||||||
|
// imageId tidak di-update
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
id: c.id,
|
||||||
|
name: c.name,
|
||||||
|
biodata: c.biodata,
|
||||||
|
riwayat: c.riwayat,
|
||||||
|
pengalaman: c.pengalaman,
|
||||||
|
unggulan: c.unggulan,
|
||||||
|
// imageId tidak di-create
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
console.log("✅ profilePPID seeded without imageId (editable later via UI)");
|
||||||
|
|
||||||
|
// =========== SUBMENU STRUKTUR PPID ===========
|
||||||
|
// =========== POSISI ORGANISASI PPID ===========
|
||||||
|
|
||||||
const flattenedPosisi = posisiOrganisasiPPID.flat();
|
const flattenedPosisi = posisiOrganisasiPPID.flat();
|
||||||
|
|
||||||
// ✅ Urutkan berdasarkan hierarki
|
// ✅ Urutkan berdasarkan hierarki
|
||||||
@@ -341,9 +449,9 @@ import pelayananTelunjukSaktiDesa from "./data/desa/layanan/pelayananTelunjukSak
|
|||||||
create: p,
|
create: p,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
console.log("✅ Posisi organisasi berhasil");
|
console.log("posisi organisasi berhasil");
|
||||||
|
|
||||||
// 2. Seed Pegawai
|
// =========== PEGAWAI PPID ===========
|
||||||
const flattenedPegawai = pegawaiPPID.flat();
|
const flattenedPegawai = pegawaiPPID.flat();
|
||||||
for (const p of flattenedPegawai) {
|
for (const p of flattenedPegawai) {
|
||||||
await prisma.pegawaiPPID.upsert({
|
await prisma.pegawaiPPID.upsert({
|
||||||
@@ -352,7 +460,70 @@ import pelayananTelunjukSaktiDesa from "./data/desa/layanan/pelayananTelunjukSak
|
|||||||
create: p,
|
create: p,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
console.log("✅ Pegawai berhasil");
|
console.log("pegawai berhasil");
|
||||||
|
|
||||||
|
// =========== SUBMENU VISI MISI PPID ===========
|
||||||
|
|
||||||
|
for (const v of visiMisiPPID) {
|
||||||
|
await prisma.visiMisiPPID.upsert({
|
||||||
|
where: {
|
||||||
|
id: v.id,
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
misi: v.misi,
|
||||||
|
visi: v.visi,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
id: v.id,
|
||||||
|
misi: v.misi,
|
||||||
|
visi: v.visi,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
console.log("visi misi PPID success ...");
|
||||||
|
|
||||||
|
// =========== SUBMENU DASAR HUKUM PPID ===========
|
||||||
|
for (const v of dasarHukumPPID) {
|
||||||
|
await prisma.dasarHukumPPID.upsert({
|
||||||
|
where: {
|
||||||
|
id: v.id,
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
judul: v.judul,
|
||||||
|
content: v.content,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
id: v.id,
|
||||||
|
judul: v.judul,
|
||||||
|
content: v.content,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
console.log("dasar hukum PPID success ...");
|
||||||
|
|
||||||
|
// =========== SUBMENU DAFTAR INFORMASI PUBLIK PPID ===========
|
||||||
|
for (const v of daftarInformasiPublik) {
|
||||||
|
// Convert string date to Date object
|
||||||
|
const tanggal = new Date(v.tanggal);
|
||||||
|
|
||||||
|
await prisma.daftarInformasiPublik.upsert({
|
||||||
|
where: {
|
||||||
|
id: v.id,
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
jenisInformasi: v.jenisInformasi,
|
||||||
|
deskripsi: v.deskripsi,
|
||||||
|
tanggal: tanggal,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
id: v.id,
|
||||||
|
jenisInformasi: v.jenisInformasi,
|
||||||
|
deskripsi: v.deskripsi,
|
||||||
|
tanggal: tanggal,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
console.log("daftar informasi publik PPID success ...");
|
||||||
|
|
||||||
for (const l of pelayananPerizinanBerusaha) {
|
for (const l of pelayananPerizinanBerusaha) {
|
||||||
await prisma.pelayananPerizinanBerusaha.upsert({
|
await prisma.pelayananPerizinanBerusaha.upsert({
|
||||||
@@ -486,48 +657,6 @@ import pelayananTelunjukSaktiDesa from "./data/desa/layanan/pelayananTelunjukSak
|
|||||||
}
|
}
|
||||||
console.log("cara memperoleh salinan informasi success ...");
|
console.log("cara memperoleh salinan informasi success ...");
|
||||||
|
|
||||||
for (const c of profilePPID) {
|
|
||||||
await prisma.profilePPID.upsert({
|
|
||||||
where: { id: c.id },
|
|
||||||
update: {
|
|
||||||
name: c.name,
|
|
||||||
biodata: c.biodata,
|
|
||||||
riwayat: c.riwayat,
|
|
||||||
pengalaman: c.pengalaman,
|
|
||||||
unggulan: c.unggulan,
|
|
||||||
// imageId tidak di-update
|
|
||||||
},
|
|
||||||
create: {
|
|
||||||
id: c.id,
|
|
||||||
name: c.name,
|
|
||||||
biodata: c.biodata,
|
|
||||||
riwayat: c.riwayat,
|
|
||||||
pengalaman: c.pengalaman,
|
|
||||||
unggulan: c.unggulan,
|
|
||||||
// imageId tidak di-create
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
console.log("✅ profilePPID seeded without imageId (editable later via UI)");
|
|
||||||
|
|
||||||
for (const v of visiMisiPPID) {
|
|
||||||
await prisma.visiMisiPPID.upsert({
|
|
||||||
where: {
|
|
||||||
id: v.id,
|
|
||||||
},
|
|
||||||
update: {
|
|
||||||
misi: v.misi,
|
|
||||||
visi: v.visi,
|
|
||||||
},
|
|
||||||
create: {
|
|
||||||
id: v.id,
|
|
||||||
misi: v.misi,
|
|
||||||
visi: v.visi,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
console.log("visi misi PPID success ...");
|
|
||||||
|
|
||||||
for (const j of jenisKelamin) {
|
for (const j of jenisKelamin) {
|
||||||
await prisma.jenisKelaminResponden.upsert({
|
await prisma.jenisKelaminResponden.upsert({
|
||||||
where: {
|
where: {
|
||||||
@@ -576,24 +705,6 @@ import pelayananTelunjukSaktiDesa from "./data/desa/layanan/pelayananTelunjukSak
|
|||||||
}
|
}
|
||||||
console.log("umur responden success ...");
|
console.log("umur responden success ...");
|
||||||
|
|
||||||
for (const v of dasarHukumPPID) {
|
|
||||||
await prisma.dasarHukumPPID.upsert({
|
|
||||||
where: {
|
|
||||||
id: v.id,
|
|
||||||
},
|
|
||||||
update: {
|
|
||||||
judul: v.judul,
|
|
||||||
content: v.content,
|
|
||||||
},
|
|
||||||
create: {
|
|
||||||
id: v.id,
|
|
||||||
judul: v.judul,
|
|
||||||
content: v.content,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
console.log("dasar hukum PPID success ...");
|
|
||||||
|
|
||||||
for (const k of kategoriProduk) {
|
for (const k of kategoriProduk) {
|
||||||
await prisma.kategoriProduk.upsert({
|
await prisma.kategoriProduk.upsert({
|
||||||
where: {
|
where: {
|
||||||
@@ -681,9 +792,12 @@ import pelayananTelunjukSaktiDesa from "./data/desa/layanan/pelayananTelunjukSak
|
|||||||
console.log("hubungan organisasi success ...");
|
console.log("hubungan organisasi success ...");
|
||||||
|
|
||||||
for (const d of detailDataPengangguran) {
|
for (const d of detailDataPengangguran) {
|
||||||
|
// Convert the year to a Date object (using January 1st of the year as the date)
|
||||||
|
const yearAsDate = new Date(d.year, 0, 1);
|
||||||
|
|
||||||
await prisma.detailDataPengangguran.upsert({
|
await prisma.detailDataPengangguran.upsert({
|
||||||
where: {
|
where: {
|
||||||
month_year: { month: d.month, year: d.year },
|
month_year: { month: d.month, year: yearAsDate },
|
||||||
},
|
},
|
||||||
update: {
|
update: {
|
||||||
totalUnemployment: d.totalUnemployment,
|
totalUnemployment: d.totalUnemployment,
|
||||||
@@ -693,7 +807,7 @@ import pelayananTelunjukSaktiDesa from "./data/desa/layanan/pelayananTelunjukSak
|
|||||||
},
|
},
|
||||||
create: {
|
create: {
|
||||||
month: d.month,
|
month: d.month,
|
||||||
year: d.year,
|
year: yearAsDate,
|
||||||
totalUnemployment: d.totalUnemployment,
|
totalUnemployment: d.totalUnemployment,
|
||||||
educatedUnemployment: d.educatedUnemployment,
|
educatedUnemployment: d.educatedUnemployment,
|
||||||
uneducatedUnemployment: d.uneducatedUnemployment,
|
uneducatedUnemployment: d.uneducatedUnemployment,
|
||||||
|
|||||||
62
src/app/admin/(dashboard)/_com/LeafletMultiMarkerMap.tsx
Normal file
62
src/app/admin/(dashboard)/_com/LeafletMultiMarkerMap.tsx
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import { MapContainer, TileLayer, Marker, Popup } from 'react-leaflet';
|
||||||
|
import { LatLngExpression } from 'leaflet';
|
||||||
|
import 'leaflet/dist/leaflet.css';
|
||||||
|
import L from 'leaflet';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
|
type MarkerData = {
|
||||||
|
position: [number, number];
|
||||||
|
popup: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
center: [number, number];
|
||||||
|
markers: MarkerData[];
|
||||||
|
zoom?: number;
|
||||||
|
scrollWheelZoom?: boolean;
|
||||||
|
className?: string;
|
||||||
|
style?: React.CSSProperties;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function LeafletMultiMarkerMap({
|
||||||
|
center,
|
||||||
|
markers,
|
||||||
|
zoom = 13,
|
||||||
|
scrollWheelZoom = true,
|
||||||
|
className = '',
|
||||||
|
style = { height: '100%', width: '100%', zIndex: 0 },
|
||||||
|
}: Props) {
|
||||||
|
// Fix for default marker icons in Next.js
|
||||||
|
useEffect(() => {
|
||||||
|
delete (L.Icon.Default.prototype as any)._getIconUrl;
|
||||||
|
L.Icon.Default.mergeOptions({
|
||||||
|
iconRetinaUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-icon-2x.png',
|
||||||
|
iconUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-icon.png',
|
||||||
|
shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-shadow.png',
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={className} style={style}>
|
||||||
|
<MapContainer
|
||||||
|
center={center as LatLngExpression}
|
||||||
|
zoom={zoom}
|
||||||
|
scrollWheelZoom={scrollWheelZoom}
|
||||||
|
style={{ height: '100%', width: '100%' }}
|
||||||
|
>
|
||||||
|
<TileLayer
|
||||||
|
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||||
|
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||||
|
/>
|
||||||
|
{markers.map((marker, index) => (
|
||||||
|
<Marker key={index} position={marker.position as LatLngExpression}>
|
||||||
|
<Popup>{marker.popup}</Popup>
|
||||||
|
</Marker>
|
||||||
|
))}
|
||||||
|
</MapContainer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import ApiFetch from "@/lib/api-fetch";
|
import ApiFetch from "@/lib/api-fetch";
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
@@ -61,13 +62,39 @@ const lowonganKerjaState = proxy({
|
|||||||
findMany: {
|
findMany: {
|
||||||
data: null as
|
data: null as
|
||||||
| Prisma.LowonganPekerjaanGetPayload<{
|
| Prisma.LowonganPekerjaanGetPayload<{
|
||||||
omit: { isActive: true };
|
omit: {
|
||||||
|
isActive: true;
|
||||||
|
};
|
||||||
}>[]
|
}>[]
|
||||||
| null,
|
| null,
|
||||||
async load() {
|
page: 1,
|
||||||
const res = await ApiFetch.api.ekonomi.lowongankerja["find-many"].get();
|
totalPages: 1,
|
||||||
if (res.status === 200) {
|
loading: false,
|
||||||
lowonganKerjaState.findMany.data = res.data?.data ?? [];
|
search: "",
|
||||||
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
|
lowonganKerjaState.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||||
|
lowonganKerjaState.findMany.page = page;
|
||||||
|
lowonganKerjaState.findMany.search = search;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
|
||||||
|
const res = await ApiFetch.api.ekonomi.lowongankerja["find-many"].get({ query });
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
lowonganKerjaState.findMany.data = res.data.data ?? [];
|
||||||
|
lowonganKerjaState.findMany.totalPages = res.data.totalPages ?? 1;
|
||||||
|
} else {
|
||||||
|
lowonganKerjaState.findMany.data = [];
|
||||||
|
lowonganKerjaState.findMany.totalPages = 1;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Gagal fetch lowongan kerja paginated:", err);
|
||||||
|
lowonganKerjaState.findMany.data = [];
|
||||||
|
lowonganKerjaState.findMany.totalPages = 1;
|
||||||
|
} finally {
|
||||||
|
lowonganKerjaState.findMany.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import ApiFetch from "@/lib/api-fetch";
|
import ApiFetch from "@/lib/api-fetch";
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
@@ -53,22 +54,47 @@ const pasarDesa = proxy({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
findMany: {
|
findMany: {
|
||||||
data: null as Array<
|
data: null as
|
||||||
Prisma.PasarDesaGetPayload<{
|
| Prisma.PasarDesaGetPayload<{
|
||||||
include: {
|
include: {
|
||||||
image: true;
|
image: true;
|
||||||
KategoriToPasar: {
|
KategoriToPasar: {
|
||||||
include: {
|
include: {
|
||||||
kategori: true;
|
kategori: true;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
}>[]
|
||||||
}>
|
| null,
|
||||||
> | null,
|
page: 1,
|
||||||
async load() {
|
totalPages: 1,
|
||||||
const res = await ApiFetch.api.ekonomi.pasardesa["find-many"].get();
|
loading: false,
|
||||||
if (res.status === 200) {
|
search: "",
|
||||||
pasarDesa.findMany.data = res.data?.data ?? [];
|
load: async (page = 1, limit = 10, search = "", categoryId?: string) => {
|
||||||
|
pasarDesa.findMany.loading = true;
|
||||||
|
pasarDesa.findMany.page = page;
|
||||||
|
pasarDesa.findMany.search = search;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
if (categoryId) query.categoryId = categoryId;
|
||||||
|
|
||||||
|
const res = await ApiFetch.api.ekonomi.pasardesa["find-many"].get({ query });
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
pasarDesa.findMany.data = res.data.data ?? [];
|
||||||
|
pasarDesa.findMany.totalPages = res.data.totalPages ?? 1;
|
||||||
|
} else {
|
||||||
|
pasarDesa.findMany.data = [];
|
||||||
|
pasarDesa.findMany.totalPages = 1;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Gagal fetch keamanan lingkungan paginated:", err);
|
||||||
|
pasarDesa.findMany.data = [];
|
||||||
|
pasarDesa.findMany.totalPages = 1;
|
||||||
|
} finally {
|
||||||
|
pasarDesa.findMany.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -272,14 +298,41 @@ const kategoriProduk = proxy({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
findMany: {
|
findMany: {
|
||||||
data: null as Array<{
|
data: null as
|
||||||
id: string;
|
| Prisma.KategoriProdukGetPayload<{
|
||||||
nama: string;
|
omit: {
|
||||||
}> | null,
|
isActive: true;
|
||||||
async load() {
|
};
|
||||||
const res = await ApiFetch.api.ekonomi.kategoriproduk["find-many"].get();
|
}>[]
|
||||||
if (res.status === 200) {
|
| null,
|
||||||
kategoriProduk.findMany.data = res.data?.data ?? [];
|
page: 1,
|
||||||
|
totalPages: 1,
|
||||||
|
loading: false,
|
||||||
|
search2: "",
|
||||||
|
load: async (page = 1, limit = 10, search2 = "") => {
|
||||||
|
kategoriProduk.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||||
|
kategoriProduk.findMany.page = page;
|
||||||
|
kategoriProduk.findMany.search2 = search2;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search2) query.search2 = search2;
|
||||||
|
|
||||||
|
const res = await ApiFetch.api.ekonomi.kategoriproduk["find-many"].get({ query });
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
kategoriProduk.findMany.data = res.data.data ?? [];
|
||||||
|
kategoriProduk.findMany.totalPages = res.data.totalPages ?? 1;
|
||||||
|
} else {
|
||||||
|
kategoriProduk.findMany.data = [];
|
||||||
|
kategoriProduk.findMany.totalPages = 1;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Gagal fetch kategori produk paginated:", err);
|
||||||
|
kategoriProduk.findMany.data = [];
|
||||||
|
kategoriProduk.findMany.totalPages = 1;
|
||||||
|
} finally {
|
||||||
|
kategoriProduk.findMany.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import ApiFetch from "@/lib/api-fetch";
|
import ApiFetch from "@/lib/api-fetch";
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
@@ -11,8 +12,7 @@ const templateForm = z.object({
|
|||||||
statistik: z.object({
|
statistik: z.object({
|
||||||
tahun: z.string().min(1, "Tahun minimal 1 karakter"),
|
tahun: z.string().min(1, "Tahun minimal 1 karakter"),
|
||||||
jumlah: z.string().min(1, "Jumlah minimal 1 karakter"),
|
jumlah: z.string().min(1, "Jumlah minimal 1 karakter"),
|
||||||
})
|
}),
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const defaultForm = {
|
const defaultForm = {
|
||||||
@@ -21,8 +21,8 @@ const defaultForm = {
|
|||||||
ikonUrl: "",
|
ikonUrl: "",
|
||||||
statistik: {
|
statistik: {
|
||||||
tahun: "",
|
tahun: "",
|
||||||
jumlah: ""
|
jumlah: "",
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const programKemiskinanState = proxy({
|
const programKemiskinanState = proxy({
|
||||||
@@ -64,12 +64,35 @@ const programKemiskinanState = proxy({
|
|||||||
};
|
};
|
||||||
}>[],
|
}>[],
|
||||||
loading: false,
|
loading: false,
|
||||||
async load() {
|
page: 1,
|
||||||
const res = await ApiFetch.api.ekonomi.programkemiskinan[
|
totalPages: 1,
|
||||||
"find-many"
|
search: "",
|
||||||
].get();
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
if (res.status === 200) {
|
programKemiskinanState.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||||
programKemiskinanState.findMany.data = res.data?.data ?? [];
|
programKemiskinanState.findMany.page = page;
|
||||||
|
programKemiskinanState.findMany.search = search;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
|
||||||
|
const res = await ApiFetch.api.ekonomi.programkemiskinan[
|
||||||
|
"find-many"
|
||||||
|
].get({ query });
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
programKemiskinanState.findMany.data = res.data.data ?? [];
|
||||||
|
programKemiskinanState.findMany.totalPages = res.data.totalPages ?? 1;
|
||||||
|
} else {
|
||||||
|
programKemiskinanState.findMany.data = [];
|
||||||
|
programKemiskinanState.findMany.totalPages = 1;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Gagal fetch program kemiskinan paginated:", err);
|
||||||
|
programKemiskinanState.findMany.data = [];
|
||||||
|
programKemiskinanState.findMany.totalPages = 1;
|
||||||
|
} finally {
|
||||||
|
programKemiskinanState.findMany.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import ApiFetch from "@/lib/api-fetch";
|
import ApiFetch from "@/lib/api-fetch";
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
@@ -55,10 +56,34 @@ const desaDigitalState = proxy({
|
|||||||
};
|
};
|
||||||
}>[]
|
}>[]
|
||||||
| null,
|
| null,
|
||||||
async load() {
|
page: 1,
|
||||||
const res = await ApiFetch.api.inovasi.desadigital["find-many"].get();
|
totalPages: 1,
|
||||||
if (res.status === 200) {
|
loading: false,
|
||||||
desaDigitalState.findMany.data = res.data?.data ?? [];
|
search: "",
|
||||||
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
|
desaDigitalState.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||||
|
desaDigitalState.findMany.page = page;
|
||||||
|
desaDigitalState.findMany.search = search;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
|
||||||
|
const res = await ApiFetch.api.inovasi.desadigital["find-many"].get({ query });
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
desaDigitalState.findMany.data = res.data.data ?? [];
|
||||||
|
desaDigitalState.findMany.totalPages = res.data.totalPages ?? 1;
|
||||||
|
} else {
|
||||||
|
desaDigitalState.findMany.data = [];
|
||||||
|
desaDigitalState.findMany.totalPages = 1;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Gagal fetch desa digital paginated:", err);
|
||||||
|
desaDigitalState.findMany.data = [];
|
||||||
|
desaDigitalState.findMany.totalPages = 1;
|
||||||
|
} finally {
|
||||||
|
desaDigitalState.findMany.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import ApiFetch from "@/lib/api-fetch";
|
import ApiFetch from "@/lib/api-fetch";
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
@@ -55,10 +56,34 @@ const infoTeknoState = proxy({
|
|||||||
};
|
};
|
||||||
}>[]
|
}>[]
|
||||||
| null,
|
| null,
|
||||||
async load() {
|
page: 1,
|
||||||
const res = await ApiFetch.api.inovasi.infotekno["find-many"].get();
|
totalPages: 1,
|
||||||
if (res.status === 200) {
|
loading: false,
|
||||||
infoTeknoState.findMany.data = res.data?.data ?? [];
|
search: "",
|
||||||
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
|
infoTeknoState.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||||
|
infoTeknoState.findMany.page = page;
|
||||||
|
infoTeknoState.findMany.search = search;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
|
||||||
|
const res = await ApiFetch.api.inovasi.infotekno["find-many"].get({ query });
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
infoTeknoState.findMany.data = res.data.data ?? [];
|
||||||
|
infoTeknoState.findMany.totalPages = res.data.totalPages ?? 1;
|
||||||
|
} else {
|
||||||
|
infoTeknoState.findMany.data = [];
|
||||||
|
infoTeknoState.findMany.totalPages = 1;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Gagal fetch info teknologi paginated:", err);
|
||||||
|
infoTeknoState.findMany.data = [];
|
||||||
|
infoTeknoState.findMany.totalPages = 1;
|
||||||
|
} finally {
|
||||||
|
infoTeknoState.findMany.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,12 +6,11 @@ import { proxy } from "valtio";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
const templateForm = z.object({
|
const templateForm = z.object({
|
||||||
name: z.string().min(1, "Nama minimal 1 karakter"),
|
name: z.string().min(1, "Nama kolaborasi inovasi harus diisi"),
|
||||||
tahun: z.number().min(4, "Tahun minimal 4 karakter"),
|
tahun: z.number().min(1900, "Tahun tidak valid").max(new Date().getFullYear() + 1, "Tahun tidak boleh lebih dari tahun depan"),
|
||||||
slug: z.string().min(1, "Deskripsi singkat minimal 1 karakter"),
|
slug: z.string().min(1, "Slug harus dihasilkan otomatis"),
|
||||||
deskripsi: z.string().min(1, "Deskripsi minimal 1 karakter"),
|
deskripsi: z.string().min(1, "Deskripsi harus diisi"),
|
||||||
kolaborator: z.string().min(1, "Kolaborator minimal 1 karakter"),
|
kolaborator: z.string().min(1, "Kolaborator harus diisi"),
|
||||||
imageId: z.string().min(1, "Image ID minimal 1 karakter"),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const defaultForm = {
|
const defaultForm = {
|
||||||
@@ -20,7 +19,6 @@ const defaultForm = {
|
|||||||
slug: "",
|
slug: "",
|
||||||
deskripsi: "",
|
deskripsi: "",
|
||||||
kolaborator: "",
|
kolaborator: "",
|
||||||
imageId: "",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const kolaborasiInovasiState = proxy({
|
const kolaborasiInovasiState = proxy({
|
||||||
@@ -28,27 +26,37 @@ const kolaborasiInovasiState = proxy({
|
|||||||
form: { ...defaultForm },
|
form: { ...defaultForm },
|
||||||
loading: false,
|
loading: false,
|
||||||
async create() {
|
async create() {
|
||||||
const cek = templateForm.safeParse(kolaborasiInovasiState.create.form);
|
|
||||||
if (!cek.success) {
|
|
||||||
const err = `[${cek.error.issues
|
|
||||||
.map((v) => `${v.path.join(".")}`)
|
|
||||||
.join("\n")}] required`;
|
|
||||||
return toast.error(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Validate form
|
||||||
|
const validation = templateForm.safeParse(kolaborasiInovasiState.create.form);
|
||||||
|
if (!validation.success) {
|
||||||
|
const errorMessages = validation.error.issues
|
||||||
|
.map(issue => `- ${issue.path.join('.')}: ${issue.message}`)
|
||||||
|
.join('\n');
|
||||||
|
return toast.error(`Validasi gagal:\n${errorMessages}`);
|
||||||
|
}
|
||||||
|
|
||||||
kolaborasiInovasiState.create.loading = true;
|
kolaborasiInovasiState.create.loading = true;
|
||||||
|
|
||||||
const res = await ApiFetch.api.inovasi.kolaborasiinovasi["create"].post(
|
const res = await ApiFetch.api.inovasi.kolaborasiinovasi["create"].post(
|
||||||
kolaborasiInovasiState.create.form
|
kolaborasiInovasiState.create.form
|
||||||
);
|
);
|
||||||
|
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
kolaborasiInovasiState.findMany.load();
|
await kolaborasiInovasiState.findMany.load();
|
||||||
return toast.success("success create");
|
return { success: true, data: res.data };
|
||||||
}
|
}
|
||||||
console.log(res);
|
|
||||||
return toast.error("failed create");
|
console.error('Create failed:', res);
|
||||||
|
toast.error(res.data?.message || "Gagal menyimpan data");
|
||||||
|
return { success: false, error: res.data };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log((error as Error).message);
|
console.error('Error in create:', error);
|
||||||
|
toast.error("Terjadi kesalahan saat menyimpan data");
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: error instanceof Error ? error.message : 'Unknown error'
|
||||||
|
};
|
||||||
} finally {
|
} finally {
|
||||||
kolaborasiInovasiState.create.loading = false;
|
kolaborasiInovasiState.create.loading = false;
|
||||||
}
|
}
|
||||||
@@ -60,13 +68,21 @@ const kolaborasiInovasiState = proxy({
|
|||||||
totalPages: 1,
|
totalPages: 1,
|
||||||
total: 0,
|
total: 0,
|
||||||
loading: false,
|
loading: false,
|
||||||
load: async (page = 1, limit = 10) => {
|
search: "",
|
||||||
// Change to arrow function
|
year: "",
|
||||||
kolaborasiInovasiState.findMany.loading = true; // Use the full path to access the property
|
load: async (page = 1, limit = 10, search = "", year?: string) => {
|
||||||
|
kolaborasiInovasiState.findMany.loading = true;
|
||||||
kolaborasiInovasiState.findMany.page = page;
|
kolaborasiInovasiState.findMany.page = page;
|
||||||
|
kolaborasiInovasiState.findMany.search = search;
|
||||||
|
kolaborasiInovasiState.findMany.year = year || "";
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
if (year) query.year = year;
|
||||||
|
|
||||||
const res = await ApiFetch.api.inovasi.kolaborasiinovasi["find-many"].get({
|
const res = await ApiFetch.api.inovasi.kolaborasiinovasi["find-many"].get({
|
||||||
query: { page, limit },
|
query,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res.status === 200 && res.data?.success) {
|
if (res.status === 200 && res.data?.success) {
|
||||||
@@ -124,7 +140,6 @@ const kolaborasiInovasiState = proxy({
|
|||||||
slug: data.slug,
|
slug: data.slug,
|
||||||
deskripsi: data.deskripsi,
|
deskripsi: data.deskripsi,
|
||||||
kolaborator: data.kolaborator,
|
kolaborator: data.kolaborator,
|
||||||
imageId: data.imageId,
|
|
||||||
};
|
};
|
||||||
return data;
|
return data;
|
||||||
} else {
|
} else {
|
||||||
@@ -179,7 +194,7 @@ const kolaborasiInovasiState = proxy({
|
|||||||
},
|
},
|
||||||
findUnique: {
|
findUnique: {
|
||||||
data: null as Prisma.KolaborasiInovasiGetPayload<{
|
data: null as Prisma.KolaborasiInovasiGetPayload<{
|
||||||
include: { image: true };
|
omit: { isActive: true };
|
||||||
}> | null,
|
}> | null,
|
||||||
async load(id: string) {
|
async load(id: string) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
229
src/app/admin/(dashboard)/_state/inovasi/mitra-kolaborasi.ts
Normal file
229
src/app/admin/(dashboard)/_state/inovasi/mitra-kolaborasi.ts
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
import ApiFetch from "@/lib/api-fetch";
|
||||||
|
import { Prisma } from "@prisma/client";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
import { proxy } from "valtio";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
const mitraKolaborasiForm = z.object({
|
||||||
|
name: z.string().min(1, { message: "Name is required" }),
|
||||||
|
imageId: z.string().nonempty(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const defaultForm = {
|
||||||
|
name: "",
|
||||||
|
imageId: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
const mitraKolaborasi = proxy({
|
||||||
|
create: {
|
||||||
|
form: { ...defaultForm },
|
||||||
|
loading: false,
|
||||||
|
async create() {
|
||||||
|
const cek = mitraKolaborasiForm.safeParse(mitraKolaborasi.create.form);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = `[${cek.error.issues
|
||||||
|
.map((v) => `${v.path.join(".")}`)
|
||||||
|
.join("\n")}] required`;
|
||||||
|
return toast.error(err);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
mitraKolaborasi.create.loading = true;
|
||||||
|
const res = await ApiFetch.api.inovasi.mitrakolaborasi["create"].post(
|
||||||
|
mitraKolaborasi.create.form
|
||||||
|
);
|
||||||
|
if (res.status === 200) {
|
||||||
|
mitraKolaborasi.findMany.load();
|
||||||
|
return toast.success("mitraKolaborasi berhasil disimpan!");
|
||||||
|
}
|
||||||
|
return toast.error("Gagal menyimpan mitraKolaborasi");
|
||||||
|
} catch (error) {
|
||||||
|
console.log((error as Error).message);
|
||||||
|
} finally {
|
||||||
|
mitraKolaborasi.create.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
resetForm() {
|
||||||
|
mitraKolaborasi.create.form = { ...defaultForm };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
findMany: {
|
||||||
|
data: null as
|
||||||
|
| Prisma.MitraKolaborasiGetPayload<{
|
||||||
|
include: {
|
||||||
|
image: true;
|
||||||
|
};
|
||||||
|
}>[]
|
||||||
|
| null,
|
||||||
|
page: 1,
|
||||||
|
totalPages: 1,
|
||||||
|
loading: false,
|
||||||
|
search: "",
|
||||||
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
|
mitraKolaborasi.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||||
|
mitraKolaborasi.findMany.page = page;
|
||||||
|
mitraKolaborasi.findMany.search = search;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
|
||||||
|
const res = await ApiFetch.api.inovasi.mitrakolaborasi["find-many"].get({ query });
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
mitraKolaborasi.findMany.data = res.data.data ?? [];
|
||||||
|
mitraKolaborasi.findMany.totalPages = res.data.totalPages ?? 1;
|
||||||
|
} else {
|
||||||
|
mitraKolaborasi.findMany.data = [];
|
||||||
|
mitraKolaborasi.findMany.totalPages = 1;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Gagal fetch mitraKolaborasi paginated:", err);
|
||||||
|
mitraKolaborasi.findMany.data = [];
|
||||||
|
mitraKolaborasi.findMany.totalPages = 1;
|
||||||
|
} finally {
|
||||||
|
mitraKolaborasi.findMany.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
findUnique: {
|
||||||
|
data: null as Prisma.MitraKolaborasiGetPayload<{
|
||||||
|
include: {
|
||||||
|
image: true;
|
||||||
|
};
|
||||||
|
}> | null,
|
||||||
|
async load(id: string) {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/inovasi/mitrakolaborasi/${id}`);
|
||||||
|
if (res.ok) {
|
||||||
|
const data = await res.json();
|
||||||
|
mitraKolaborasi.findUnique.data = data.data ?? null;
|
||||||
|
} else {
|
||||||
|
console.error("Failed to fetch mitraKolaborasi:", res.statusText);
|
||||||
|
mitraKolaborasi.findUnique.data = null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching mitraKolaborasi:", error);
|
||||||
|
mitraKolaborasi.findUnique.data = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
delete: {
|
||||||
|
loading: false,
|
||||||
|
async byId(id: string) {
|
||||||
|
if (!id) return toast.warn("ID tidak valid");
|
||||||
|
try {
|
||||||
|
mitraKolaborasi.delete.loading = true;
|
||||||
|
const response = await fetch(`/api/inovasi/mitrakolaborasi/del/${id}`, {
|
||||||
|
method: "DELETE",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const result = await response.json();
|
||||||
|
if (response.ok) {
|
||||||
|
toast.success(result.message || "mitraKolaborasi berhasil dihapus");
|
||||||
|
await mitraKolaborasi.findMany.load(); // refresh list
|
||||||
|
} else {
|
||||||
|
toast.error(result.message || "Gagal menghapus mitraKolaborasi");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Gagal delete:", error);
|
||||||
|
toast.error("Terjadi kesalahan saat menghapus mitraKolaborasi");
|
||||||
|
} finally {
|
||||||
|
mitraKolaborasi.delete.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
id: "",
|
||||||
|
form: { ...defaultForm },
|
||||||
|
loading: false,
|
||||||
|
async load(id: string) {
|
||||||
|
if (!id) {
|
||||||
|
toast.warn("ID tidak valid");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/inovasi/mitrakolaborasi/${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,
|
||||||
|
imageId: data.imageId,
|
||||||
|
};
|
||||||
|
return data;
|
||||||
|
} else {
|
||||||
|
throw new Error(result.message || "Gagal memuat data");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading mitraKolaborasi:", error);
|
||||||
|
toast.error(
|
||||||
|
error instanceof Error ? error.message : "Gagal memuat data"
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async update() {
|
||||||
|
const cek = mitraKolaborasiForm.safeParse(mitraKolaborasi.update.form);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = `[${cek.error.issues
|
||||||
|
.map((v) => `${v.path.join(".")}`)
|
||||||
|
.join("\n")}] required`;
|
||||||
|
toast.error(err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
mitraKolaborasi.update.loading = true;
|
||||||
|
const response = await fetch(`/api/inovasi/mitrakolaborasi/${this.id}`, {
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
name: this.form.name,
|
||||||
|
imageId: this.form.imageId,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
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(result.message || "mitraKolaborasi berhasil diupdate");
|
||||||
|
await mitraKolaborasi.findMany.load(); // refresh list
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
throw new Error(result.message || "Gagal mengupdate mitraKolaborasi");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error updating mitraKolaborasi:", error);
|
||||||
|
toast.error(
|
||||||
|
error instanceof Error ? error.message : "Gagal mengupdate mitraKolaborasi"
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
mitraKolaborasi.update.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
reset() {
|
||||||
|
mitraKolaborasi.update.id = "";
|
||||||
|
mitraKolaborasi.update.form = { ...defaultForm };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default mitraKolaborasi;
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import ApiFetch from "@/lib/api-fetch";
|
import ApiFetch from "@/lib/api-fetch";
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
@@ -53,15 +54,39 @@ const tipsKeamananState = proxy({
|
|||||||
findMany: {
|
findMany: {
|
||||||
data: null as
|
data: null as
|
||||||
| Prisma.MenuTipsKeamananGetPayload<{
|
| Prisma.MenuTipsKeamananGetPayload<{
|
||||||
include: { image: true };
|
include: {
|
||||||
|
image: true;
|
||||||
|
};
|
||||||
}>[]
|
}>[]
|
||||||
| null,
|
| null,
|
||||||
async load() {
|
page: 1,
|
||||||
const res = await ApiFetch.api.keamanan.menutipskeamanan[
|
totalPages: 1,
|
||||||
"find-many"
|
loading: false,
|
||||||
].get();
|
search: "",
|
||||||
if (res.status === 200) {
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
tipsKeamananState.findMany.data = res.data?.data ?? [];
|
tipsKeamananState.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||||
|
tipsKeamananState.findMany.page = page;
|
||||||
|
tipsKeamananState.findMany.search = search;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
|
||||||
|
const res = await ApiFetch.api.keamanan.menutipskeamanan["find-many"].get({ query });
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
tipsKeamananState.findMany.data = res.data.data ?? [];
|
||||||
|
tipsKeamananState.findMany.totalPages = res.data.totalPages ?? 1;
|
||||||
|
} else {
|
||||||
|
tipsKeamananState.findMany.data = [];
|
||||||
|
tipsKeamananState.findMany.totalPages = 1;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Gagal fetch menu tips keamanan paginated:", err);
|
||||||
|
tipsKeamananState.findMany.data = [];
|
||||||
|
tipsKeamananState.findMany.totalPages = 1;
|
||||||
|
} finally {
|
||||||
|
tipsKeamananState.findMany.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import ApiFetch from "@/lib/api-fetch";
|
import ApiFetch from "@/lib/api-fetch";
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
@@ -50,18 +51,50 @@ const apbdes = proxy({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
findMany: {
|
findMany: {
|
||||||
data: null as Array<
|
data: null as
|
||||||
Prisma.APBDesGetPayload<{
|
| Prisma.APBDesGetPayload<{
|
||||||
include: {
|
include: {
|
||||||
image: true;
|
image: true;
|
||||||
file: true;
|
file: true;
|
||||||
};
|
};
|
||||||
}>
|
}>[]
|
||||||
> | null,
|
| null,
|
||||||
async load() {
|
page: 1,
|
||||||
const res = await ApiFetch.api.landingpage.apbdes["find-many"].get();
|
totalPages: 1,
|
||||||
if (res.status === 200) {
|
total: 0,
|
||||||
apbdes.findMany.data = res.data?.data ?? [];
|
loading: false,
|
||||||
|
search: "",
|
||||||
|
load: async (page = 1, limit = 10, search = "") => { // Change to arrow function
|
||||||
|
apbdes.findMany.loading = true; // Use the full path to access the property
|
||||||
|
apbdes.findMany.page = page;
|
||||||
|
apbdes.findMany.search = search;
|
||||||
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
|
||||||
|
const res = await ApiFetch.api.landingpage.apbdes[
|
||||||
|
"findMany"
|
||||||
|
].get({
|
||||||
|
query
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
apbdes.findMany.data = res.data.data || [];
|
||||||
|
apbdes.findMany.total = res.data.total || 0;
|
||||||
|
apbdes.findMany.totalPages = res.data.totalPages || 1;
|
||||||
|
} else {
|
||||||
|
console.error("Failed to load pegawai:", res.data?.message);
|
||||||
|
apbdes.findMany.data = [];
|
||||||
|
apbdes.findMany.total = 0;
|
||||||
|
apbdes.findMany.totalPages = 1;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading pegawai:", error);
|
||||||
|
apbdes.findMany.data = [];
|
||||||
|
apbdes.findMany.total = 0;
|
||||||
|
apbdes.findMany.totalPages = 1;
|
||||||
|
} finally {
|
||||||
|
apbdes.findMany.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -60,16 +60,22 @@ const desaAntikorupsi = proxy({
|
|||||||
totalPages: 1,
|
totalPages: 1,
|
||||||
total: 0,
|
total: 0,
|
||||||
loading: false,
|
loading: false,
|
||||||
load: async (page = 1, limit = 10) => { // Change to arrow function
|
search: "",
|
||||||
desaAntikorupsi.findMany.loading = true; // Use the full path to access the property
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
|
// Change to arrow function
|
||||||
|
desaAntikorupsi.findMany.loading = true; // Use the full path to access the property
|
||||||
desaAntikorupsi.findMany.page = page;
|
desaAntikorupsi.findMany.page = page;
|
||||||
|
desaAntikorupsi.findMany.search = search;
|
||||||
try {
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
|
||||||
const res = await ApiFetch.api.landingpage.desaantikorupsi[
|
const res = await ApiFetch.api.landingpage.desaantikorupsi[
|
||||||
"findMany"
|
"findMany"
|
||||||
].get({
|
].get({
|
||||||
query: { page, limit },
|
query,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res.status === 200 && res.data?.success) {
|
if (res.status === 200 && res.data?.success) {
|
||||||
desaAntikorupsi.findMany.data = res.data.data || [];
|
desaAntikorupsi.findMany.data = res.data.data || [];
|
||||||
desaAntikorupsi.findMany.total = res.data.total || 0;
|
desaAntikorupsi.findMany.total = res.data.total || 0;
|
||||||
@@ -305,20 +311,25 @@ const kategoriDesaAntiKorupsi = proxy({
|
|||||||
totalPages: 1,
|
totalPages: 1,
|
||||||
total: 0,
|
total: 0,
|
||||||
loading: false,
|
loading: false,
|
||||||
load: async (page = 1, limit = 10) => { // Change to arrow function
|
search: "",
|
||||||
kategoriDesaAntiKorupsi.findMany.loading = true; // Use the full path to access the property
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
|
// Change to arrow function
|
||||||
|
kategoriDesaAntiKorupsi.findMany.loading = true; // Use the full path to access the property
|
||||||
kategoriDesaAntiKorupsi.findMany.page = page;
|
kategoriDesaAntiKorupsi.findMany.page = page;
|
||||||
|
kategoriDesaAntiKorupsi.findMany.search = search;
|
||||||
try {
|
try {
|
||||||
const res = await ApiFetch.api.landingpage.kategoridak[
|
const query: any = { page, limit };
|
||||||
"findMany"
|
if (search) query.search = search;
|
||||||
].get({
|
|
||||||
query: { page, limit },
|
const res = await ApiFetch.api.landingpage.kategoridak["findMany"].get({
|
||||||
|
query,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res.status === 200 && res.data?.success) {
|
if (res.status === 200 && res.data?.success) {
|
||||||
kategoriDesaAntiKorupsi.findMany.data = res.data.data || [];
|
kategoriDesaAntiKorupsi.findMany.data = res.data.data || [];
|
||||||
kategoriDesaAntiKorupsi.findMany.total = res.data.total || 0;
|
kategoriDesaAntiKorupsi.findMany.total = res.data.total || 0;
|
||||||
kategoriDesaAntiKorupsi.findMany.totalPages = res.data.totalPages || 1;
|
kategoriDesaAntiKorupsi.findMany.totalPages =
|
||||||
|
res.data.totalPages || 1;
|
||||||
} else {
|
} else {
|
||||||
console.error("Failed to load media sosial:", res.data?.message);
|
console.error("Failed to load media sosial:", res.data?.message);
|
||||||
kategoriDesaAntiKorupsi.findMany.data = [];
|
kategoriDesaAntiKorupsi.findMany.data = [];
|
||||||
@@ -363,27 +374,30 @@ const kategoriDesaAntiKorupsi = proxy({
|
|||||||
try {
|
try {
|
||||||
kategoriDesaAntiKorupsi.delete.loading = true;
|
kategoriDesaAntiKorupsi.delete.loading = true;
|
||||||
|
|
||||||
const response = await fetch(
|
const response = await fetch(`/api/landingpage/kategoridak/del/${id}`, {
|
||||||
`/api/landingpage/kategoridak/del/${id}`,
|
method: "DELETE",
|
||||||
{
|
headers: {
|
||||||
method: "DELETE",
|
"Content-Type": "application/json",
|
||||||
headers: {
|
},
|
||||||
"Content-Type": "application/json",
|
});
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
|
|
||||||
if (response.ok && result?.success) {
|
if (response.ok && result?.success) {
|
||||||
toast.success(result.message || "Kategori desa anti korupsi berhasil dihapus");
|
toast.success(
|
||||||
|
result.message || "Kategori desa anti korupsi berhasil dihapus"
|
||||||
|
);
|
||||||
await kategoriDesaAntiKorupsi.findMany.load(); // refresh list
|
await kategoriDesaAntiKorupsi.findMany.load(); // refresh list
|
||||||
} else {
|
} else {
|
||||||
toast.error(result?.message || "Gagal menghapus kategori desa anti korupsi");
|
toast.error(
|
||||||
|
result?.message || "Gagal menghapus kategori desa anti korupsi"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Gagal delete:", error);
|
console.error("Gagal delete:", error);
|
||||||
toast.error("Terjadi kesalahan saat menghapus kategori desa anti korupsi");
|
toast.error(
|
||||||
|
"Terjadi kesalahan saat menghapus kategori desa anti korupsi"
|
||||||
|
);
|
||||||
} finally {
|
} finally {
|
||||||
kategoriDesaAntiKorupsi.delete.loading = false;
|
kategoriDesaAntiKorupsi.delete.loading = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import ApiFetch from "@/lib/api-fetch";
|
import ApiFetch from "@/lib/api-fetch";
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
@@ -58,16 +59,43 @@ const prestasiDesa = proxy({
|
|||||||
Prisma.PrestasiDesaGetPayload<{
|
Prisma.PrestasiDesaGetPayload<{
|
||||||
include: {
|
include: {
|
||||||
image: true;
|
image: true;
|
||||||
kategori: true;
|
kategori: {
|
||||||
|
select: {
|
||||||
|
id: true;
|
||||||
|
name: true;
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}>
|
}>
|
||||||
> | null,
|
> | null,
|
||||||
async load() {
|
page: 1,
|
||||||
const res = await ApiFetch.api.landingpage.prestasidesa[
|
totalPages: 1,
|
||||||
"find-many"
|
loading: false,
|
||||||
].get();
|
search: "",
|
||||||
if (res.status === 200) {
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
prestasiDesa.findMany.data = res.data?.data ?? [];
|
prestasiDesa.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||||
|
prestasiDesa.findMany.page = page;
|
||||||
|
prestasiDesa.findMany.search = search;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
|
||||||
|
const res = await ApiFetch.api.landingpage.prestasidesa["find-many"].get({ query });
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
prestasiDesa.findMany.data = res.data.data ?? [];
|
||||||
|
prestasiDesa.findMany.totalPages = res.data.totalPages ?? 1;
|
||||||
|
} else {
|
||||||
|
prestasiDesa.findMany.data = [];
|
||||||
|
prestasiDesa.findMany.totalPages = 1;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Gagal fetch prestasi desa paginated:", err);
|
||||||
|
prestasiDesa.findMany.data = [];
|
||||||
|
prestasiDesa.findMany.totalPages = 1;
|
||||||
|
} finally {
|
||||||
|
prestasiDesa.findMany.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -283,12 +311,34 @@ const kategoriPrestasi = proxy({
|
|||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
}> | null,
|
}> | null,
|
||||||
async load() {
|
page: 1,
|
||||||
const res = await ApiFetch.api.landingpage.kategoriprestasi[
|
totalPages: 1,
|
||||||
"find-many"
|
loading: false,
|
||||||
].get();
|
search: "",
|
||||||
if (res.status === 200) {
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
kategoriPrestasi.findMany.data = res.data?.data ?? [];
|
kategoriPrestasi.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||||
|
kategoriPrestasi.findMany.page = page;
|
||||||
|
kategoriPrestasi.findMany.search = search;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
|
||||||
|
const res = await ApiFetch.api.landingpage.kategoriprestasi["find-many"].get({ query });
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
kategoriPrestasi.findMany.data = res.data.data ?? [];
|
||||||
|
kategoriPrestasi.findMany.totalPages = res.data.totalPages ?? 1;
|
||||||
|
} else {
|
||||||
|
kategoriPrestasi.findMany.data = [];
|
||||||
|
kategoriPrestasi.findMany.totalPages = 1;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Gagal fetch kategori prestasi paginated:", err);
|
||||||
|
kategoriPrestasi.findMany.data = [];
|
||||||
|
kategoriPrestasi.findMany.totalPages = 1;
|
||||||
|
} finally {
|
||||||
|
kategoriPrestasi.findMany.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -65,14 +65,19 @@ const programInovasi = proxy({
|
|||||||
totalPages: 1,
|
totalPages: 1,
|
||||||
total: 0,
|
total: 0,
|
||||||
loading: false,
|
loading: false,
|
||||||
load: async (page = 1, limit = 10) => { // Change to arrow function
|
search: "",
|
||||||
|
load: async (page = 1, limit = 10, search = "") => { // Change to arrow function
|
||||||
programInovasi.findMany.loading = true; // Use the full path to access the property
|
programInovasi.findMany.loading = true; // Use the full path to access the property
|
||||||
programInovasi.findMany.page = page;
|
programInovasi.findMany.page = page;
|
||||||
|
programInovasi.findMany.search = search;
|
||||||
try {
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
|
||||||
const res = await ApiFetch.api.landingpage.programinovasi[
|
const res = await ApiFetch.api.landingpage.programinovasi[
|
||||||
"findMany"
|
"findMany"
|
||||||
].get({
|
].get({
|
||||||
query: { page, limit },
|
query
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res.status === 200 && res.data?.success) {
|
if (res.status === 200 && res.data?.success) {
|
||||||
@@ -482,14 +487,19 @@ const mediaSosial = proxy({
|
|||||||
totalPages: 1,
|
totalPages: 1,
|
||||||
total: 0,
|
total: 0,
|
||||||
loading: false,
|
loading: false,
|
||||||
load: async (page = 1, limit = 10) => { // Change to arrow function
|
search: "",
|
||||||
|
load: async (page = 1, limit = 10, search = "") => { // Change to arrow function
|
||||||
mediaSosial.findMany.loading = true; // Use the full path to access the property
|
mediaSosial.findMany.loading = true; // Use the full path to access the property
|
||||||
mediaSosial.findMany.page = page;
|
mediaSosial.findMany.page = page;
|
||||||
try {
|
mediaSosial.findMany.search = search;
|
||||||
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
|
||||||
const res = await ApiFetch.api.landingpage.mediasosial[
|
const res = await ApiFetch.api.landingpage.mediasosial[
|
||||||
"findMany"
|
"findMany"
|
||||||
].get({
|
].get({
|
||||||
query: { page, limit },
|
query,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res.status === 200 && res.data?.success) {
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
|||||||
@@ -58,14 +58,19 @@ const sdgsDesa = proxy({
|
|||||||
totalPages: 1,
|
totalPages: 1,
|
||||||
total: 0,
|
total: 0,
|
||||||
loading: false,
|
loading: false,
|
||||||
load: async (page = 1, limit = 10) => { // Change to arrow function
|
search: "",
|
||||||
|
load: async (page = 1, limit = 10, search = "") => { // Change to arrow function
|
||||||
sdgsDesa.findMany.loading = true; // Use the full path to access the property
|
sdgsDesa.findMany.loading = true; // Use the full path to access the property
|
||||||
sdgsDesa.findMany.page = page;
|
sdgsDesa.findMany.page = page;
|
||||||
|
sdgsDesa.findMany.search = search;
|
||||||
try {
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
|
||||||
const res = await ApiFetch.api.landingpage.sdgsdesa[
|
const res = await ApiFetch.api.landingpage.sdgsdesa[
|
||||||
"findMany"
|
"findMany"
|
||||||
].get({
|
].get({
|
||||||
query: { page, limit },
|
query,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res.status === 200 && res.data?.success) {
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
|||||||
@@ -56,13 +56,17 @@ const dataLingkunganDesaState = proxy({
|
|||||||
totalPages: 1,
|
totalPages: 1,
|
||||||
total: 0,
|
total: 0,
|
||||||
loading: false,
|
loading: false,
|
||||||
load: async (page = 1, limit = 10) => {
|
search: "",
|
||||||
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
// Change to arrow function
|
// Change to arrow function
|
||||||
dataLingkunganDesaState.findMany.loading = true; // Use the full path to access the property
|
dataLingkunganDesaState.findMany.loading = true; // Use the full path to access the property
|
||||||
dataLingkunganDesaState.findMany.page = page;
|
dataLingkunganDesaState.findMany.page = page;
|
||||||
|
dataLingkunganDesaState.findMany.search = search;
|
||||||
try {
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
const res = await ApiFetch.api.lingkungan.datalingkungandesa["find-many"].get({
|
const res = await ApiFetch.api.lingkungan.datalingkungandesa["find-many"].get({
|
||||||
query: { page, limit },
|
query,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res.status === 200 && res.data?.success) {
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import ApiFetch from "@/lib/api-fetch";
|
import ApiFetch from "@/lib/api-fetch";
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
@@ -67,10 +68,46 @@ const kegiatanDesa = proxy({
|
|||||||
};
|
};
|
||||||
}>
|
}>
|
||||||
> | null,
|
> | null,
|
||||||
async load() {
|
page: 1,
|
||||||
const res = await ApiFetch.api.lingkungan.kegiatandesa["find-many"].get();
|
totalPages: 1,
|
||||||
if (res.status === 200) {
|
total: 0,
|
||||||
kegiatanDesa.findMany.data = res.data?.data ?? [];
|
loading: false,
|
||||||
|
search: "",
|
||||||
|
load: async (page = 1, limit = 10, search = "", kategori = "") => {
|
||||||
|
// Change to arrow function
|
||||||
|
kegiatanDesa.findMany.loading = true; // Use the full path to access the property
|
||||||
|
kegiatanDesa.findMany.page = page;
|
||||||
|
kegiatanDesa.findMany.search = search;
|
||||||
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
if (kategori) query.kategori = kategori;
|
||||||
|
const res = await ApiFetch.api.lingkungan.kegiatandesa[
|
||||||
|
"find-many"
|
||||||
|
].get({
|
||||||
|
query,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
kegiatanDesa.findMany.data = res.data.data || [];
|
||||||
|
kegiatanDesa.findMany.total = res.data.total || 0;
|
||||||
|
kegiatanDesa.findMany.totalPages = res.data.totalPages || 1;
|
||||||
|
} else {
|
||||||
|
console.error(
|
||||||
|
"Failed to load kegiatan desa:",
|
||||||
|
res.data?.message
|
||||||
|
);
|
||||||
|
kegiatanDesa.findMany.data = [];
|
||||||
|
kegiatanDesa.findMany.total = 0;
|
||||||
|
kegiatanDesa.findMany.totalPages = 1;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading kegiatan desa:", error);
|
||||||
|
kegiatanDesa.findMany.data = [];
|
||||||
|
kegiatanDesa.findMany.total = 0;
|
||||||
|
kegiatanDesa.findMany.totalPages = 1;
|
||||||
|
} finally {
|
||||||
|
kegiatanDesa.findMany.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -244,6 +281,35 @@ const kegiatanDesa = proxy({
|
|||||||
kegiatanDesa.edit.form = { ...defaultKegiatanDesaForm };
|
kegiatanDesa.edit.form = { ...defaultKegiatanDesaForm };
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
findFirst: {
|
||||||
|
data: null as Prisma.KegiatanDesaGetPayload<{
|
||||||
|
include: {
|
||||||
|
image: true;
|
||||||
|
kategoriKegiatan: true;
|
||||||
|
};
|
||||||
|
}> | null,
|
||||||
|
loading: false,
|
||||||
|
// findFirst.load()
|
||||||
|
async load(kategori?: string) {
|
||||||
|
this.loading = true;
|
||||||
|
try {
|
||||||
|
const res = await ApiFetch.api.lingkungan.kegiatandesa["find-first"].get({
|
||||||
|
query: kategori ? { kategori } : {},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
this.data = res.data.data || null;
|
||||||
|
} else {
|
||||||
|
this.data = null;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Gagal fetch kegiatan desa terbaru:", err);
|
||||||
|
this.data = null;
|
||||||
|
} finally {
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// ========================================= KATEGORI kegiatan ========================================= //
|
// ========================================= KATEGORI kegiatan ========================================= //
|
||||||
@@ -269,9 +335,7 @@ const kategoriKegiatan = proxy({
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
kategoriKegiatan.create.loading = true;
|
kategoriKegiatan.create.loading = true;
|
||||||
const res = await ApiFetch.api.lingkungan.kategorikegiatan[
|
const res = await ApiFetch.api.lingkungan.kategorikegiatan["create"].post(kategoriKegiatan.create.form);
|
||||||
"create"
|
|
||||||
].post(kategoriKegiatan.create.form);
|
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
kategoriKegiatan.findMany.load();
|
kategoriKegiatan.findMany.load();
|
||||||
return toast.success("Data berhasil ditambahkan");
|
return toast.success("Data berhasil ditambahkan");
|
||||||
@@ -305,9 +369,7 @@ const kategoriKegiatan = proxy({
|
|||||||
}> | null,
|
}> | null,
|
||||||
async load(id: string) {
|
async load(id: string) {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(
|
const res = await fetch(`/api/lingkungan/kategorikegiatan/${id}`);
|
||||||
`/api/lingkungan/kategorikegiatan/${id}`
|
|
||||||
);
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
kategoriKegiatan.findUnique.data = data.data ?? null;
|
kategoriKegiatan.findUnique.data = data.data ?? null;
|
||||||
@@ -367,15 +429,12 @@ const kategoriKegiatan = proxy({
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(`/api/lingkungan/kategorikegiatan/${id}`, {
|
||||||
`/api/lingkungan/kategorikegiatan/${id}`,
|
method: "GET",
|
||||||
{
|
headers: {
|
||||||
method: "GET",
|
"Content-Type": "application/json",
|
||||||
headers: {
|
},
|
||||||
"Content-Type": "application/json",
|
});
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,15 +52,19 @@ const pengelolaanSampah = proxy({
|
|||||||
totalPages: 1,
|
totalPages: 1,
|
||||||
total: 0,
|
total: 0,
|
||||||
loading: false,
|
loading: false,
|
||||||
load: async (page = 1, limit = 10) => {
|
search: "",
|
||||||
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
// Change to arrow function
|
// Change to arrow function
|
||||||
pengelolaanSampah.findMany.loading = true; // Use the full path to access the property
|
pengelolaanSampah.findMany.loading = true; // Use the full path to access the property
|
||||||
pengelolaanSampah.findMany.page = page;
|
pengelolaanSampah.findMany.page = page;
|
||||||
|
pengelolaanSampah.findMany.search = search;
|
||||||
try {
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
const res = await ApiFetch.api.lingkungan.pengelolaansampah[
|
const res = await ApiFetch.api.lingkungan.pengelolaansampah[
|
||||||
"find-many"
|
"find-many"
|
||||||
].get({
|
].get({
|
||||||
query: { page, limit },
|
query,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res.status === 200 && res.data?.success) {
|
if (res.status === 200 && res.data?.success) {
|
||||||
@@ -265,7 +269,7 @@ const keteranganSampah = proxy({
|
|||||||
try {
|
try {
|
||||||
keteranganSampah.create.loading = true;
|
keteranganSampah.create.loading = true;
|
||||||
const res =
|
const res =
|
||||||
await ApiFetch.api.lingkungan.pengelolaansampah.keteranganbankterdekat[
|
await ApiFetch.api.lingkungan.keteranganbankterdekat[
|
||||||
"create"
|
"create"
|
||||||
].post(keteranganSampah.create.form);
|
].post(keteranganSampah.create.form);
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
@@ -287,14 +291,47 @@ const keteranganSampah = proxy({
|
|||||||
omit: { isActive: true };
|
omit: { isActive: true };
|
||||||
}>[]
|
}>[]
|
||||||
| null,
|
| null,
|
||||||
async load() {
|
page: 1,
|
||||||
const res = await ApiFetch.api.lingkungan.pengelolaansampah.keteranganbankterdekat[
|
totalPages: 1,
|
||||||
"find-many"
|
total: 0,
|
||||||
].get();
|
loading: false,
|
||||||
if (res.status === 200) {
|
search: "",
|
||||||
keteranganSampah.findMany.data = res.data?.data ?? [];
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
}
|
// Change to arrow function
|
||||||
},
|
keteranganSampah.findMany.loading = true; // Use the full path to access the property
|
||||||
|
keteranganSampah.findMany.page = page;
|
||||||
|
keteranganSampah.findMany.search = search;
|
||||||
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
const res = await ApiFetch.api.lingkungan.keteranganbankterdekat[
|
||||||
|
"find-many"
|
||||||
|
].get({
|
||||||
|
query,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
keteranganSampah.findMany.data = res.data.data || [];
|
||||||
|
keteranganSampah.findMany.total = res.data.total || 0;
|
||||||
|
keteranganSampah.findMany.totalPages = res.data.totalPages || 1;
|
||||||
|
} else {
|
||||||
|
console.error(
|
||||||
|
"Failed to load keterangan bank sampah terdekat:",
|
||||||
|
res.data?.message
|
||||||
|
);
|
||||||
|
keteranganSampah.findMany.data = [];
|
||||||
|
keteranganSampah.findMany.total = 0;
|
||||||
|
keteranganSampah.findMany.totalPages = 1;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading keterangan bank sampah terdekat:", error);
|
||||||
|
keteranganSampah.findMany.data = [];
|
||||||
|
keteranganSampah.findMany.total = 0;
|
||||||
|
keteranganSampah.findMany.totalPages = 1;
|
||||||
|
} finally {
|
||||||
|
keteranganSampah.findMany.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
findUnique: {
|
findUnique: {
|
||||||
data: null as Prisma.KeteranganBankSampahTerdekatGetPayload<{
|
data: null as Prisma.KeteranganBankSampahTerdekatGetPayload<{
|
||||||
@@ -302,7 +339,7 @@ const keteranganSampah = proxy({
|
|||||||
}> | null,
|
}> | null,
|
||||||
async load(id: string) {
|
async load(id: string) {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`/api/lingkungan/pengelolaansampah/keteranganbankterdekat/${id}`);
|
const res = await fetch(`/api/lingkungan/keteranganbankterdekat/${id}`);
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
keteranganSampah.findUnique.data = data.data ?? null;
|
keteranganSampah.findUnique.data = data.data ?? null;
|
||||||
@@ -324,7 +361,7 @@ const keteranganSampah = proxy({
|
|||||||
try {
|
try {
|
||||||
keteranganSampah.delete.loading = true;
|
keteranganSampah.delete.loading = true;
|
||||||
|
|
||||||
const response = await fetch(`/api/lingkungan/pengelolaansampah/keteranganbankterdekat/del/${id}`, {
|
const response = await fetch(`/api/lingkungan/keteranganbankterdekat/del/${id}`, {
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
@@ -359,7 +396,7 @@ const keteranganSampah = proxy({
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/lingkungan/pengelolaansampah/keteranganbankterdekat/${id}`, {
|
const response = await fetch(`/api/lingkungan/keteranganbankterdekat/${id}`, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
@@ -404,7 +441,7 @@ const keteranganSampah = proxy({
|
|||||||
try {
|
try {
|
||||||
keteranganSampah.edit.loading = true;
|
keteranganSampah.edit.loading = true;
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`/api/lingkungan/pengelolaansampah/keteranganbankterdekat/${this.id}`,
|
`/api/lingkungan/keteranganbankterdekat/${this.id}`,
|
||||||
{
|
{
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
headers: {
|
headers: {
|
||||||
|
|||||||
@@ -56,13 +56,17 @@ const programPenghijauanState = proxy({
|
|||||||
totalPages: 1,
|
totalPages: 1,
|
||||||
total: 0,
|
total: 0,
|
||||||
loading: false,
|
loading: false,
|
||||||
load: async (page = 1, limit = 10) => {
|
search: "",
|
||||||
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
// Change to arrow function
|
// Change to arrow function
|
||||||
programPenghijauanState.findMany.loading = true; // Use the full path to access the property
|
programPenghijauanState.findMany.loading = true; // Use the full path to access the property
|
||||||
programPenghijauanState.findMany.page = page;
|
programPenghijauanState.findMany.page = page;
|
||||||
|
programPenghijauanState.findMany.search = search;
|
||||||
try {
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
const res = await ApiFetch.api.lingkungan.programpenghijauan["find-many"].get({
|
const res = await ApiFetch.api.lingkungan.programpenghijauan["find-many"].get({
|
||||||
query: { page, limit },
|
query,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res.status === 200 && res.data?.success) {
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import ApiFetch from "@/lib/api-fetch";
|
import ApiFetch from "@/lib/api-fetch";
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
import { proxy } from "valtio";
|
import { proxy } from "valtio";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
// ========================================= BEASISWA PENDAFTAR ========================================= //
|
||||||
|
|
||||||
const templateBeasiswaPendaftar = z.object({
|
const templateBeasiswaPendaftar = z.object({
|
||||||
namaLengkap: z.string().min(1, "Nama harus diisi"),
|
namaLengkap: z.string().min(1, "Nama harus diisi"),
|
||||||
nik: z.string().min(1, "NIK harus diisi"),
|
nik: z.string().min(1, "NIK harus diisi"),
|
||||||
@@ -76,13 +79,34 @@ const beasiswaPendaftar = proxy({
|
|||||||
isActive: true;
|
isActive: true;
|
||||||
};
|
};
|
||||||
}>[],
|
}>[],
|
||||||
|
page: 1,
|
||||||
|
totalPages: 1,
|
||||||
loading: false,
|
loading: false,
|
||||||
async load() {
|
search: "",
|
||||||
const res = await ApiFetch.api.pendidikan.beasiswa.beasiswapendaftar[
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
"findMany"
|
beasiswaPendaftar.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||||
].get();
|
beasiswaPendaftar.findMany.page = page;
|
||||||
if (res.status === 200) {
|
beasiswaPendaftar.findMany.search = search;
|
||||||
beasiswaPendaftar.findMany.data = res.data?.data ?? [];
|
|
||||||
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
|
||||||
|
const res = await ApiFetch.api.pendidikan.beasiswa.beasiswapendaftar["findMany"].get({ query });
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
beasiswaPendaftar.findMany.data = res.data.data ?? [];
|
||||||
|
beasiswaPendaftar.findMany.totalPages = res.data.totalPages ?? 1;
|
||||||
|
} else {
|
||||||
|
beasiswaPendaftar.findMany.data = [];
|
||||||
|
beasiswaPendaftar.findMany.totalPages = 1;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Gagal fetch beasiswa pendaftar paginated:", err);
|
||||||
|
beasiswaPendaftar.findMany.data = [];
|
||||||
|
beasiswaPendaftar.findMany.totalPages = 1;
|
||||||
|
} finally {
|
||||||
|
beasiswaPendaftar.findMany.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -275,8 +299,260 @@ const beasiswaPendaftar = proxy({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ========================================= KEUNGGULAN PROGRAM ========================================= //
|
||||||
|
const templateKeunggulanProgram = z.object({
|
||||||
|
judul: z.string().min(1, "Judul harus diisi"),
|
||||||
|
deskripsi: z.string().min(1, "Deskripsi harus diisi"),
|
||||||
|
});
|
||||||
|
|
||||||
|
const defaultKeunggulanProgram = {
|
||||||
|
judul: "",
|
||||||
|
deskripsi: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
const keunggulanProgram = proxy({
|
||||||
|
create: {
|
||||||
|
form: { ...defaultKeunggulanProgram },
|
||||||
|
loading: false,
|
||||||
|
async create() {
|
||||||
|
const cek = templateKeunggulanProgram.safeParse(
|
||||||
|
keunggulanProgram.create.form
|
||||||
|
);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = `[${cek.error.issues
|
||||||
|
.map((v) => `${v.path.join(".")}`)
|
||||||
|
.join("\n")}] required`;
|
||||||
|
return toast.error(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
keunggulanProgram.create.loading = true;
|
||||||
|
const res = await ApiFetch.api.pendidikan.beasiswa.keunggulanprogram[
|
||||||
|
"create"
|
||||||
|
].post(keunggulanProgram.create.form);
|
||||||
|
if (res.status === 200) {
|
||||||
|
keunggulanProgram.findMany.load();
|
||||||
|
return toast.success("Data Berhasil Dibuat, Silahkan Menunggu Konfirmasi dari Admin di WhatsApp");
|
||||||
|
}
|
||||||
|
console.log(res);
|
||||||
|
return toast.error("failed create");
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
return toast.error("failed create");
|
||||||
|
} finally {
|
||||||
|
keunggulanProgram.create.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
findMany: {
|
||||||
|
data: [] as Prisma.KeunggulanProgramGetPayload<{
|
||||||
|
omit: {
|
||||||
|
isActive: true;
|
||||||
|
};
|
||||||
|
}>[],
|
||||||
|
page: 1,
|
||||||
|
totalPages: 1,
|
||||||
|
loading: false,
|
||||||
|
search: "",
|
||||||
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
|
keunggulanProgram.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||||
|
keunggulanProgram.findMany.page = page;
|
||||||
|
keunggulanProgram.findMany.search = search;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
|
||||||
|
const res = await ApiFetch.api.pendidikan.beasiswa.keunggulanprogram["findMany"].get({ query });
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
keunggulanProgram.findMany.data = res.data.data ?? [];
|
||||||
|
keunggulanProgram.findMany.totalPages = res.data.totalPages ?? 1;
|
||||||
|
} else {
|
||||||
|
keunggulanProgram.findMany.data = [];
|
||||||
|
keunggulanProgram.findMany.totalPages = 1;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Gagal fetch keunggulan program paginated:", err);
|
||||||
|
keunggulanProgram.findMany.data = [];
|
||||||
|
keunggulanProgram.findMany.totalPages = 1;
|
||||||
|
} finally {
|
||||||
|
keunggulanProgram.findMany.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
findUnique: {
|
||||||
|
data: null as Prisma.KeunggulanProgramGetPayload<{
|
||||||
|
omit: {
|
||||||
|
isActive: true;
|
||||||
|
};
|
||||||
|
}> | null,
|
||||||
|
loading: false,
|
||||||
|
async load(id: string) {
|
||||||
|
try {
|
||||||
|
const res = await fetch(
|
||||||
|
`/api/pendidikan/beasiswa/keunggulanprogram/${id}`
|
||||||
|
);
|
||||||
|
if (res.ok) {
|
||||||
|
const data = await res.json();
|
||||||
|
keunggulanProgram.findUnique.data = data.data ?? null;
|
||||||
|
} else {
|
||||||
|
console.error("Failed to fetch data", res.status, res.statusText);
|
||||||
|
keunggulanProgram.findUnique.data = null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching data:", error);
|
||||||
|
keunggulanProgram.findUnique.data = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
delete: {
|
||||||
|
loading: false,
|
||||||
|
async delete(id: string) {
|
||||||
|
if (!id) return toast.warn("ID tidak valid");
|
||||||
|
|
||||||
|
try {
|
||||||
|
keunggulanProgram.delete.loading = true;
|
||||||
|
|
||||||
|
const response = await fetch(
|
||||||
|
`/api/pendidikan/beasiswa/keunggulanprogram/del/${id}`,
|
||||||
|
{
|
||||||
|
method: "DELETE",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (response.ok && result?.success) {
|
||||||
|
toast.success(result.message || "Keunggulan Program berhasil dihapus");
|
||||||
|
await keunggulanProgram.findMany.load(); // refresh list
|
||||||
|
} else {
|
||||||
|
toast.error(result?.message || "Gagal menghapus keunggulan program");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Gagal delete:", error);
|
||||||
|
toast.error("Terjadi kesalahan saat menghapus keunggulan program");
|
||||||
|
} finally {
|
||||||
|
keunggulanProgram.delete.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
id: "",
|
||||||
|
form: { ...defaultKeunggulanProgram },
|
||||||
|
loading: false,
|
||||||
|
async load(id: string) {
|
||||||
|
if (!id) {
|
||||||
|
toast.warn("ID tidak valid");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
`/api/pendidikan/beasiswa/keunggulanprogram/${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 = {
|
||||||
|
judul: data.judul,
|
||||||
|
deskripsi: data.deskripsi,
|
||||||
|
};
|
||||||
|
return data; // Return the loaded data
|
||||||
|
} else {
|
||||||
|
throw new Error(result?.message || "Gagal memuat data");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading keunggulan program:", error);
|
||||||
|
toast.error(
|
||||||
|
error instanceof Error ? error.message : "Gagal memuat data"
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async update() {
|
||||||
|
const cek = templateKeunggulanProgram.safeParse(
|
||||||
|
keunggulanProgram.update.form
|
||||||
|
);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = `[${cek.error.issues
|
||||||
|
.map((v) => `${v.path.join(".")}`)
|
||||||
|
.join("\n")}] required`;
|
||||||
|
toast.error(err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
keunggulanProgram.update.loading = true;
|
||||||
|
|
||||||
|
const response = await fetch(
|
||||||
|
`/api/pendidikan/beasiswa/keunggulanprogram/${this.id}`,
|
||||||
|
{
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
judul: this.form.judul,
|
||||||
|
deskripsi: this.form.deskripsi,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
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 keunggulan program");
|
||||||
|
await keunggulanProgram.findMany.load(); // refresh list
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
throw new Error(result.message || "Gagal update keunggulan program");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error updating keunggulan program:", error);
|
||||||
|
toast.error(
|
||||||
|
error instanceof Error
|
||||||
|
? error.message
|
||||||
|
: "Terjadi kesalahan saat update keunggulan program"
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
keunggulanProgram.update.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
reset() {
|
||||||
|
keunggulanProgram.update.id = "";
|
||||||
|
keunggulanProgram.update.form = { ...defaultKeunggulanProgram };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
const beasiswaDesaState = proxy({
|
const beasiswaDesaState = proxy({
|
||||||
beasiswaPendaftar,
|
beasiswaPendaftar,
|
||||||
|
keunggulanProgram
|
||||||
});
|
});
|
||||||
|
|
||||||
export default beasiswaDesaState;
|
export default beasiswaDesaState;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import ApiFetch from "@/lib/api-fetch";
|
import ApiFetch from "@/lib/api-fetch";
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
@@ -51,13 +52,46 @@ const jenjangPendidikan = proxy({
|
|||||||
id: string;
|
id: string;
|
||||||
nama: string;
|
nama: string;
|
||||||
}> | null,
|
}> | null,
|
||||||
async load() {
|
page: 1,
|
||||||
const res =
|
totalPages: 1,
|
||||||
await ApiFetch.api.pendidikan.infosekolahpaud.jenjangpendidikan[
|
total: 0,
|
||||||
"find-many"
|
loading: false,
|
||||||
].get();
|
search: "",
|
||||||
if (res.status === 200) {
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
jenjangPendidikan.findMany.data = res.data?.data ?? [];
|
// Change to arrow function
|
||||||
|
jenjangPendidikan.findMany.loading = true; // Use the full path to access the property
|
||||||
|
jenjangPendidikan.findMany.page = page;
|
||||||
|
jenjangPendidikan.findMany.search = search;
|
||||||
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
const res =
|
||||||
|
await ApiFetch.api.pendidikan.infosekolahpaud.jenjangpendidikan[
|
||||||
|
"find-many"
|
||||||
|
].get({
|
||||||
|
query,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
jenjangPendidikan.findMany.data = res.data.data || [];
|
||||||
|
jenjangPendidikan.findMany.total = res.data.total || 0;
|
||||||
|
jenjangPendidikan.findMany.totalPages = res.data.totalPages || 1;
|
||||||
|
} else {
|
||||||
|
console.error(
|
||||||
|
"Failed to load jenjang pendidikan:",
|
||||||
|
res.data?.message
|
||||||
|
);
|
||||||
|
jenjangPendidikan.findMany.data = [];
|
||||||
|
jenjangPendidikan.findMany.total = 0;
|
||||||
|
jenjangPendidikan.findMany.totalPages = 1;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading jenjang pendidikan:", error);
|
||||||
|
jenjangPendidikan.findMany.data = [];
|
||||||
|
jenjangPendidikan.findMany.total = 0;
|
||||||
|
jenjangPendidikan.findMany.totalPages = 1;
|
||||||
|
} finally {
|
||||||
|
jenjangPendidikan.findMany.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -304,13 +338,53 @@ const lembagaPendidikan = proxy({
|
|||||||
};
|
};
|
||||||
}>
|
}>
|
||||||
> | null,
|
> | null,
|
||||||
async load() {
|
page: 1,
|
||||||
const res =
|
totalPages: 1,
|
||||||
await ApiFetch.api.pendidikan.infosekolahpaud.lembagapendidikan[
|
total: 0,
|
||||||
"find-many"
|
loading: false,
|
||||||
].get();
|
search: "",
|
||||||
if (res.status === 200) {
|
load: async (page = 1, limit = 10, search = "", jenjangPendidikan = "") => {
|
||||||
lembagaPendidikan.findMany.data = res.data?.data ?? [];
|
lembagaPendidikan.findMany.loading = true;
|
||||||
|
lembagaPendidikan.findMany.page = page;
|
||||||
|
lembagaPendidikan.findMany.search = search;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const query: any = {
|
||||||
|
page,
|
||||||
|
limit,
|
||||||
|
...(search && { search }),
|
||||||
|
...(jenjangPendidikan && { jenjangPendidikanId: jenjangPendidikan })
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('Fetching lembaga with query:', query);
|
||||||
|
|
||||||
|
const res = await ApiFetch.api.pendidikan.infosekolahpaud.lembagapendidikan["find-many"].get({ query });
|
||||||
|
|
||||||
|
console.log('API Response:', res);
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
lembagaPendidikan.findMany.data = Array.isArray(res.data.data) ? res.data.data : [];
|
||||||
|
lembagaPendidikan.findMany.total = typeof res.data.total === 'number' ? res.data.total : 0;
|
||||||
|
lembagaPendidikan.findMany.totalPages = typeof res.data.totalPages === 'number' ? res.data.totalPages : 1;
|
||||||
|
console.log('Successfully loaded lembaga data:', {
|
||||||
|
count: lembagaPendidikan.findMany.data.length,
|
||||||
|
total: lembagaPendidikan.findMany.total,
|
||||||
|
totalPages: lembagaPendidikan.findMany.totalPages
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.error(
|
||||||
|
"Failed to load lembaga pendidikan:",
|
||||||
|
res.data?.message || 'No error message provided'
|
||||||
|
);
|
||||||
|
throw new Error(res.data?.message || 'Failed to load lembaga pendidikan');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading lembaga pendidikan:", error);
|
||||||
|
lembagaPendidikan.findMany.data = [];
|
||||||
|
lembagaPendidikan.findMany.total = 0;
|
||||||
|
lembagaPendidikan.findMany.totalPages = 1;
|
||||||
|
} finally {
|
||||||
|
lembagaPendidikan.findMany.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -554,16 +628,55 @@ const siswa = proxy({
|
|||||||
data: null as Array<
|
data: null as Array<
|
||||||
Prisma.SiswaGetPayload<{
|
Prisma.SiswaGetPayload<{
|
||||||
include: {
|
include: {
|
||||||
lembaga: true;
|
lembaga: {
|
||||||
|
include: {
|
||||||
|
jenjangPendidikan: true;
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}>
|
}>
|
||||||
> | null,
|
> | null,
|
||||||
async load() {
|
page: 1,
|
||||||
const res = await ApiFetch.api.pendidikan.infosekolahpaud.siswa[
|
totalPages: 1,
|
||||||
"find-many"
|
total: 0,
|
||||||
].get();
|
loading: false,
|
||||||
if (res.status === 200) {
|
search: "",
|
||||||
siswa.findMany.data = res.data?.data ?? [];
|
jenjangPendidikan: "",
|
||||||
|
load: async (page = 1, limit = 10, search = "", jenjangPendidikan = "") => {
|
||||||
|
siswa.findMany.loading = true;
|
||||||
|
siswa.findMany.page = page;
|
||||||
|
siswa.findMany.search = search;
|
||||||
|
siswa.findMany.jenjangPendidikan = jenjangPendidikan;
|
||||||
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
if (jenjangPendidikan) query.jenjangPendidikanName = jenjangPendidikan;
|
||||||
|
const res = await ApiFetch.api.pendidikan.infosekolahpaud.siswa[
|
||||||
|
"find-many"
|
||||||
|
].get({
|
||||||
|
query,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
siswa.findMany.data = res.data.data || [];
|
||||||
|
siswa.findMany.total = res.data.total || 0;
|
||||||
|
siswa.findMany.totalPages = res.data.totalPages || 1;
|
||||||
|
} else {
|
||||||
|
console.error(
|
||||||
|
"Failed to load siswa:",
|
||||||
|
res.data?.message
|
||||||
|
);
|
||||||
|
siswa.findMany.data = [];
|
||||||
|
siswa.findMany.total = 0;
|
||||||
|
siswa.findMany.totalPages = 1;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading siswa:", error);
|
||||||
|
siswa.findMany.data = [];
|
||||||
|
siswa.findMany.total = 0;
|
||||||
|
siswa.findMany.totalPages = 1;
|
||||||
|
} finally {
|
||||||
|
siswa.findMany.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -794,16 +907,56 @@ const pengajar = proxy({
|
|||||||
data: null as Array<
|
data: null as Array<
|
||||||
Prisma.PengajarGetPayload<{
|
Prisma.PengajarGetPayload<{
|
||||||
include: {
|
include: {
|
||||||
lembaga: true;
|
lembaga: {
|
||||||
|
include: {
|
||||||
|
jenjangPendidikan: true
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}>
|
}>
|
||||||
> | null,
|
> | null,
|
||||||
async load() {
|
page: 1,
|
||||||
const res = await ApiFetch.api.pendidikan.infosekolahpaud.pengajar[
|
totalPages: 1,
|
||||||
"find-many"
|
total: 0,
|
||||||
].get();
|
loading: false,
|
||||||
if (res.status === 200) {
|
search: "",
|
||||||
pengajar.findMany.data = res.data?.data ?? [];
|
jenjangPendidikan: "",
|
||||||
|
load: async (page = 1, limit = 10, search = "", jenjangPendidikan = "") => {
|
||||||
|
// Change to arrow function
|
||||||
|
pengajar.findMany.loading = true; // Use the full path to access the property
|
||||||
|
pengajar.findMany.page = page;
|
||||||
|
pengajar.findMany.search = search;
|
||||||
|
pengajar.findMany.jenjangPendidikan = jenjangPendidikan;
|
||||||
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
if (jenjangPendidikan) query.jenjangPendidikanId = jenjangPendidikan;
|
||||||
|
const res = await ApiFetch.api.pendidikan.infosekolahpaud.pengajar[
|
||||||
|
"find-many"
|
||||||
|
].get({
|
||||||
|
query,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
pengajar.findMany.data = res.data.data || [];
|
||||||
|
pengajar.findMany.total = res.data.total || 0;
|
||||||
|
pengajar.findMany.totalPages = res.data.totalPages || 1;
|
||||||
|
} else {
|
||||||
|
console.error(
|
||||||
|
"Failed to load pengajar:",
|
||||||
|
res.data?.message
|
||||||
|
);
|
||||||
|
pengajar.findMany.data = [];
|
||||||
|
pengajar.findMany.total = 0;
|
||||||
|
pengajar.findMany.totalPages = 1;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading pengajar:", error);
|
||||||
|
pengajar.findMany.data = [];
|
||||||
|
pengajar.findMany.total = 0;
|
||||||
|
pengajar.findMany.totalPages = 1;
|
||||||
|
} finally {
|
||||||
|
pengajar.findMany.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -815,7 +968,9 @@ const pengajar = proxy({
|
|||||||
}> | null,
|
}> | null,
|
||||||
async load(id: string) {
|
async load(id: string) {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`/api/pendidikan/infosekolahpaud/pengajar/${id}`);
|
const res = await fetch(
|
||||||
|
`/api/pendidikan/infosekolahpaud/pengajar/${id}`
|
||||||
|
);
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
pengajar.findUnique.data = data.data ?? null;
|
pengajar.findUnique.data = data.data ?? null;
|
||||||
@@ -948,7 +1103,8 @@ const pengajar = proxy({
|
|||||||
result
|
result
|
||||||
);
|
);
|
||||||
throw new Error(
|
throw new Error(
|
||||||
result?.message || `Gagal mengupdate pengajar (${response.status})`
|
result?.message ||
|
||||||
|
`Gagal mengupdate pengajar (${response.status})`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -348,18 +348,34 @@ const posisiOrganisasi = proxy({
|
|||||||
deskripsi: string | null;
|
deskripsi: string | null;
|
||||||
hierarki: number;
|
hierarki: number;
|
||||||
}>,
|
}>,
|
||||||
async load() {
|
page: 1,
|
||||||
|
totalPages: 1,
|
||||||
|
loading: false,
|
||||||
|
search: "",
|
||||||
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
|
posisiOrganisasi.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||||
|
posisiOrganisasi.findMany.page = page;
|
||||||
|
posisiOrganisasi.findMany.search = search;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await ApiFetch.api.ppid.strukturppid.posisiorganisasi[
|
const query: any = { page, limit };
|
||||||
"find-many"
|
if (search) query.search = search;
|
||||||
].get();
|
|
||||||
if (res.status === 200) {
|
const res = await ApiFetch.api.ppid.strukturppid.posisiorganisasi["find-many"].get({ query });
|
||||||
// The API now returns the id field, so we can use it directly
|
|
||||||
this.data = res.data?.data ?? [];
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
posisiOrganisasi.findMany.data = res.data.data ?? [];
|
||||||
|
posisiOrganisasi.findMany.totalPages = res.data.totalPages ?? 1;
|
||||||
|
} else {
|
||||||
|
posisiOrganisasi.findMany.data = [];
|
||||||
|
posisiOrganisasi.findMany.totalPages = 1;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (err) {
|
||||||
console.error("Find many error:", error);
|
console.error("Gagal fetch posisi organisasi paginated:", err);
|
||||||
this.data = [];
|
posisiOrganisasi.findMany.data = [];
|
||||||
|
posisiOrganisasi.findMany.totalPages = 1;
|
||||||
|
} finally {
|
||||||
|
posisiOrganisasi.findMany.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -438,9 +454,9 @@ const pegawai = proxy({
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
pegawai.create.loading = true;
|
pegawai.create.loading = true;
|
||||||
const res = await ApiFetch.api.ppid.strukturppid.pegawai[
|
const res = await ApiFetch.api.ppid.strukturppid.pegawai["create"].post(
|
||||||
"create"
|
pegawai.create.form
|
||||||
].post(pegawai.create.form);
|
);
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
toast.success("Pegawai berhasil ditambahkan");
|
toast.success("Pegawai berhasil ditambahkan");
|
||||||
await pegawai.findMany.load();
|
await pegawai.findMany.load();
|
||||||
@@ -457,42 +473,55 @@ const pegawai = proxy({
|
|||||||
},
|
},
|
||||||
|
|
||||||
// In struktur-organisasi.ts
|
// In struktur-organisasi.ts
|
||||||
findMany: {
|
findMany: {
|
||||||
data: null as any[] | null,
|
data: null as
|
||||||
page: 1,
|
| Prisma.PegawaiPPIDGetPayload<{
|
||||||
totalPages: 1,
|
include: {
|
||||||
total: 0,
|
image: true;
|
||||||
loading: false,
|
posisi: true;
|
||||||
load: async (page = 1, limit = 10) => { // Change to arrow function
|
};
|
||||||
pegawai.findMany.loading = true; // Use the full path to access the property
|
}>[]
|
||||||
pegawai.findMany.page = page;
|
| null,
|
||||||
try {
|
page: 1,
|
||||||
const res = await ApiFetch.api.ppid.strukturppid.pegawai[
|
totalPages: 1,
|
||||||
"find-many"
|
total: 0,
|
||||||
].get({
|
loading: false,
|
||||||
query: { page, limit },
|
search: "",
|
||||||
});
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
|
// Change to arrow function
|
||||||
|
pegawai.findMany.loading = true; // Use the full path to access the property
|
||||||
|
pegawai.findMany.page = page;
|
||||||
|
pegawai.findMany.search = search;
|
||||||
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
|
||||||
if (res.status === 200 && res.data?.success) {
|
const res = await ApiFetch.api.ppid.strukturppid.pegawai[
|
||||||
pegawai.findMany.data = res.data.data || [];
|
"find-many"
|
||||||
pegawai.findMany.total = res.data.total || 0;
|
].get({
|
||||||
pegawai.findMany.totalPages = res.data.totalPages || 1;
|
query,
|
||||||
} else {
|
});
|
||||||
console.error("Failed to load pegawai:", res.data?.message);
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
pegawai.findMany.data = res.data.data || [];
|
||||||
|
pegawai.findMany.total = res.data.total || 0;
|
||||||
|
pegawai.findMany.totalPages = res.data.totalPages || 1;
|
||||||
|
} else {
|
||||||
|
console.error("Failed to load pegawai:", res.data?.message);
|
||||||
|
pegawai.findMany.data = [];
|
||||||
|
pegawai.findMany.total = 0;
|
||||||
|
pegawai.findMany.totalPages = 1;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading pegawai:", error);
|
||||||
pegawai.findMany.data = [];
|
pegawai.findMany.data = [];
|
||||||
pegawai.findMany.total = 0;
|
pegawai.findMany.total = 0;
|
||||||
pegawai.findMany.totalPages = 1;
|
pegawai.findMany.totalPages = 1;
|
||||||
|
} finally {
|
||||||
|
pegawai.findMany.loading = false;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
},
|
||||||
console.error("Error loading pegawai:", error);
|
|
||||||
pegawai.findMany.data = [];
|
|
||||||
pegawai.findMany.total = 0;
|
|
||||||
pegawai.findMany.totalPages = 1;
|
|
||||||
} finally {
|
|
||||||
pegawai.findMany.loading = false;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
|
||||||
findUnique: {
|
findUnique: {
|
||||||
data: null as
|
data: null as
|
||||||
| (Prisma.PegawaiGetPayload<{
|
| (Prisma.PegawaiGetPayload<{
|
||||||
@@ -521,12 +550,9 @@ findMany: {
|
|||||||
if (!id) return toast.warn("ID tidak valid");
|
if (!id) return toast.warn("ID tidak valid");
|
||||||
try {
|
try {
|
||||||
pegawai.delete.loading = true;
|
pegawai.delete.loading = true;
|
||||||
const res = await fetch(
|
const res = await fetch(`/api/ppid/strukturppid/pegawai/del/${id}`, {
|
||||||
`/api/ppid/strukturppid/pegawai/del/${id}`,
|
method: "DELETE",
|
||||||
{
|
});
|
||||||
method: "DELETE",
|
|
||||||
}
|
|
||||||
);
|
|
||||||
const json = await res.json();
|
const json = await res.json();
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
toast.success(json.message ?? "Berhasil hapus pegawai");
|
toast.success(json.message ?? "Berhasil hapus pegawai");
|
||||||
@@ -555,15 +581,12 @@ findMany: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(`/api/ppid/strukturppid/pegawai/${id}`, {
|
||||||
`/api/ppid/strukturppid/pegawai/${id}`,
|
method: "GET",
|
||||||
{
|
headers: {
|
||||||
method: "GET",
|
"Content-Type": "application/json",
|
||||||
headers: {
|
},
|
||||||
"Content-Type": "application/json",
|
});
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
@@ -677,7 +700,7 @@ findMany: {
|
|||||||
const stateStrukturPPID = proxy({
|
const stateStrukturPPID = proxy({
|
||||||
stateStruktur,
|
stateStruktur,
|
||||||
posisiOrganisasi,
|
posisiOrganisasi,
|
||||||
pegawai
|
pegawai,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default stateStrukturPPID;
|
export default stateStrukturPPID;
|
||||||
|
|||||||
@@ -143,8 +143,8 @@ function ListDemografiPekerjaan({ search }: { search: string }) {
|
|||||||
dataKey="pekerjaan"
|
dataKey="pekerjaan"
|
||||||
type="stacked"
|
type="stacked"
|
||||||
series={[
|
series={[
|
||||||
{ name: 'lakiLaki', color: 'red.6', label: 'Laki - Laki' },
|
{ name: 'lakiLaki', color: '#5082EE', label: 'Laki - Laki' },
|
||||||
{ name: 'perempuan', color: 'orange.6', label: 'Perempuan' },
|
{ name: 'perempuan', color: '#6EDF9C', label: 'Perempuan' },
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ function EditJumlahPendudukMiskin() {
|
|||||||
// Set the ID before submitting
|
// Set the ID before submitting
|
||||||
stateJPM.update.id = id;
|
stateJPM.update.id = id;
|
||||||
await stateJPM.update.submit();
|
await stateJPM.update.submit();
|
||||||
router.push('/admin/ekonomi/jumlah-penduduk-miskin-2024-2025')
|
router.push('/admin/ekonomi/jumlah-penduduk-miskin')
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ function CreateJumlahPendudukMiskin() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
resetForm();
|
resetForm();
|
||||||
router.push("/admin/ekonomi/jumlah-penduduk-miskin-2024-2025");
|
router.push("/admin/ekonomi/jumlah-penduduk-miskin");
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ function ListJumlahPendudukMiskin({ search }: { search: string }) {
|
|||||||
<Paper bg={colors['white-1']} p={'md'}>
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
<JudulList
|
<JudulList
|
||||||
title='List Jumlah Penduduk Miskin'
|
title='List Jumlah Penduduk Miskin'
|
||||||
href='/admin/ekonomi/jumlah-penduduk-miskin-2024-2025/create'
|
href='/admin/ekonomi/jumlah-penduduk-miskin/create'
|
||||||
/>
|
/>
|
||||||
<Table striped withTableBorder withRowBorders>
|
<Table striped withTableBorder withRowBorders>
|
||||||
<TableThead>
|
<TableThead>
|
||||||
@@ -108,7 +108,7 @@ function ListJumlahPendudukMiskin({ search }: { search: string }) {
|
|||||||
<TableTd>{item.year}</TableTd>
|
<TableTd>{item.year}</TableTd>
|
||||||
<TableTd>{item.totalPoorPopulation}</TableTd>
|
<TableTd>{item.totalPoorPopulation}</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button color='green' onClick={() => router.push(`/admin/ekonomi/jumlah-penduduk-miskin-2024-2025/${item.id}`)}>
|
<Button color='green' onClick={() => router.push(`/admin/ekonomi/jumlah-penduduk-miskin/${item.id}`)}>
|
||||||
<IconEdit size={20} />
|
<IconEdit size={20} />
|
||||||
</Button>
|
</Button>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
|
|||||||
@@ -8,29 +8,36 @@ import { useRouter } from 'next/navigation';
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { Cell, Pie, PieChart } from 'recharts';
|
import { Cell, Pie, PieChart } from 'recharts';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import JudulListTab from '../../../_com/judulListTab';
|
import HeaderSearch from '../../../_com/header';
|
||||||
|
import JudulList from '../../../_com/judulList';
|
||||||
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||||
import grafikNganggur from '../../../_state/ekonomi/usia-kerja-nganggur';
|
import grafikNganggur from '../../../_state/ekonomi/usia-kerja-nganggur';
|
||||||
|
|
||||||
function GrafikBerdasarkanPendidikan() {
|
function GrafikBerdasarkanPendidikan() {
|
||||||
|
const [search, setSearch] = useState("")
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Stack gap={"xs"}>
|
<Stack gap={"xs"}>
|
||||||
<Title order={3}>Grafik Pengangguran Berdasarkan Pendidikan</Title>
|
<HeaderSearch
|
||||||
<ListGrafikBerdasarkanPendidikan />
|
title='Detail Data Pengangguran Berdasarkan Pendidikan'
|
||||||
|
placeholder='pencarian'
|
||||||
|
searchIcon={<IconSearch size={20} />}
|
||||||
|
value={search}
|
||||||
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
<ListGrafikBerdasarkanPendidikan search={search}/>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ListGrafikBerdasarkanPendidikan() {
|
function ListGrafikBerdasarkanPendidikan({search}: {search: string}) {
|
||||||
const stategrafik = useProxy(grafikNganggur.grafikBerdasarkanPendidikan)
|
const stategrafik = useProxy(grafikNganggur.grafikBerdasarkanPendidikan)
|
||||||
const [donutData, setDonutData] = useState<any[]>([]);
|
const [donutData, setDonutData] = useState<any[]>([]);
|
||||||
const [mounted, setMounted] = useState(false);
|
const [mounted, setMounted] = useState(false);
|
||||||
const [modalHapus, setModalHapus] = useState(false)
|
const [modalHapus, setModalHapus] = useState(false)
|
||||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [search, setSearch] = useState("");
|
|
||||||
|
|
||||||
|
|
||||||
const handleDelete = async () => {
|
const handleDelete = async () => {
|
||||||
@@ -56,11 +63,11 @@ function ListGrafikBerdasarkanPendidikan() {
|
|||||||
const D3 = stategrafik.findMany.data.reduce((acc: number, cur: any) => acc + Number(cur.D3 || 0), 0);
|
const D3 = stategrafik.findMany.data.reduce((acc: number, cur: any) => acc + Number(cur.D3 || 0), 0);
|
||||||
const S1 = stategrafik.findMany.data.reduce((acc: number, cur: any) => acc + Number(cur.S1 || 0), 0);
|
const S1 = stategrafik.findMany.data.reduce((acc: number, cur: any) => acc + Number(cur.S1 || 0), 0);
|
||||||
setDonutData([
|
setDonutData([
|
||||||
{ name: 'SD', value: SD, color: colors['blue-button'], key: 'SD' },
|
{ name: 'SD', value: SD, color: '#4b6Ef5', key: 'SD' },
|
||||||
{ name: 'SMP', value: SMP, color: '#10A85AFF', key: 'SMP' },
|
{ name: 'SMP', value: SMP, color: '#14b885', key: 'SMP' },
|
||||||
{ name: 'SMA', value: SMA, color: '#C07B13FF', key: 'SMA' },
|
{ name: 'SMA', value: SMA, color: '#E6A03B', key: 'SMA' },
|
||||||
{ name: 'D3', value: D3, color: '#1094A8FF', key: 'D3' },
|
{ name: 'D3', value: D3, color: '#DB524D', key: 'D3' },
|
||||||
{ name: 'S1', value: S1, color: '#A83610FF', key: 'S1' },
|
{ name: 'S1', value: S1, color: '#1018A8FF', key: 'S1' },
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}, [stategrafik.findMany.data])
|
}, [stategrafik.findMany.data])
|
||||||
@@ -88,13 +95,9 @@ function ListGrafikBerdasarkanPendidikan() {
|
|||||||
<Box>
|
<Box>
|
||||||
<Stack gap={"xs"}>
|
<Stack gap={"xs"}>
|
||||||
<Paper bg={colors['white-1']} p={"md"}>
|
<Paper bg={colors['white-1']} p={"md"}>
|
||||||
<JudulListTab
|
<JudulList
|
||||||
value={search}
|
|
||||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
|
||||||
title='List Grafik Pengangguran Berdasarkan Pendidikan'
|
title='List Grafik Pengangguran Berdasarkan Pendidikan'
|
||||||
href='/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_pendidikan/create'
|
href='/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_pendidikan/create'
|
||||||
placeholder='pencarian'
|
|
||||||
searchIcon={<IconSearch size={16} />}
|
|
||||||
/>
|
/>
|
||||||
<Table striped withTableBorder withRowBorders>
|
<Table striped withTableBorder withRowBorders>
|
||||||
<TableThead>
|
<TableThead>
|
||||||
|
|||||||
@@ -8,29 +8,37 @@ import { useRouter } from 'next/navigation';
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { Cell, Pie, PieChart } from 'recharts';
|
import { Cell, Pie, PieChart } from 'recharts';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import JudulListTab from '../../../_com/judulListTab';
|
import HeaderSearch from '../../../_com/header';
|
||||||
|
import JudulList from '../../../_com/judulList';
|
||||||
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||||
import grafikNganggur from '../../../_state/ekonomi/usia-kerja-nganggur';
|
import grafikNganggur from '../../../_state/ekonomi/usia-kerja-nganggur';
|
||||||
|
|
||||||
|
|
||||||
function GrafikBerdasarkanUsiaKerjaYangMenganggur() {
|
function GrafikBerdasarkanUsiaKerjaYangMenganggur() {
|
||||||
|
const [search, setSearch] = useState("")
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Stack gap={"xs"}>
|
<Stack gap={"xs"}>
|
||||||
<Title order={3}>Grafik Pengangguran Berdasarkan Usia Kerja</Title>
|
<HeaderSearch
|
||||||
<ListGrafikBerdasarkanUsiaKerjaYangMenganggur />
|
title='Detail Data Pengangguran'
|
||||||
|
placeholder='pencarian'
|
||||||
|
searchIcon={<IconSearch size={20} />}
|
||||||
|
value={search}
|
||||||
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
<ListGrafikBerdasarkanUsiaKerjaYangMenganggur search={search} />
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ListGrafikBerdasarkanUsiaKerjaYangMenganggur() {
|
function ListGrafikBerdasarkanUsiaKerjaYangMenganggur({search}: {search: string}) {
|
||||||
const stategrafik = useProxy(grafikNganggur.grafikBerdasarkanUsiaKerjaNganggur)
|
const stategrafik = useProxy(grafikNganggur.grafikBerdasarkanUsiaKerjaNganggur)
|
||||||
const [donutData, setDonutData] = useState<any[]>([]);
|
const [donutData, setDonutData] = useState<any[]>([]);
|
||||||
const [mounted, setMounted] = useState(false);
|
const [mounted, setMounted] = useState(false);
|
||||||
const [modalHapus, setModalHapus] = useState(false)
|
const [modalHapus, setModalHapus] = useState(false)
|
||||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [search, setSearch] = useState("");
|
|
||||||
|
|
||||||
|
|
||||||
const handleDelete = async () => {
|
const handleDelete = async () => {
|
||||||
@@ -85,13 +93,9 @@ function ListGrafikBerdasarkanUsiaKerjaYangMenganggur() {
|
|||||||
<Box>
|
<Box>
|
||||||
<Stack gap={"xs"}>
|
<Stack gap={"xs"}>
|
||||||
<Paper bg={colors['white-1']} p={"md"}>
|
<Paper bg={colors['white-1']} p={"md"}>
|
||||||
<JudulListTab
|
<JudulList
|
||||||
value={search}
|
|
||||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
|
||||||
title='List Pengangguran Berdasarkan Usia Kerja'
|
title='List Pengangguran Berdasarkan Usia Kerja'
|
||||||
href='/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_usia/create'
|
href='/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_usia/create'
|
||||||
placeholder='pencarian'
|
|
||||||
searchIcon={<IconSearch size={16} />}
|
|
||||||
/>
|
/>
|
||||||
<Table striped withTableBorder withRowBorders>
|
<Table striped withTableBorder withRowBorders>
|
||||||
<TableThead>
|
<TableThead>
|
||||||
|
|||||||
@@ -66,19 +66,23 @@ function EditDetailDataPengangguran() {
|
|||||||
const data = stateDetail.findUnique.data;
|
const data = stateDetail.findUnique.data;
|
||||||
|
|
||||||
if (data) {
|
if (data) {
|
||||||
|
// Convert year from Date to number
|
||||||
|
const yearValue = data.year instanceof Date ? data.year.getFullYear() : data.year;
|
||||||
|
|
||||||
// Set the ID for update
|
// Set the ID for update
|
||||||
stateDetail.update.id = id;
|
stateDetail.update.id = id;
|
||||||
|
|
||||||
// Isi state Valtio untuk update
|
// Update Valtio state with converted year
|
||||||
stateDetail.update.form = {
|
stateDetail.update.form = {
|
||||||
...data,
|
...data,
|
||||||
|
year: yearValue,
|
||||||
percentageChange: data.percentageChange || 0 // Ensure it's always a number
|
percentageChange: data.percentageChange || 0 // Ensure it's always a number
|
||||||
};
|
};
|
||||||
|
|
||||||
// Isi local formData supaya input bisa dikontrol
|
// Update local formData with converted year
|
||||||
setFormData({
|
setFormData({
|
||||||
month: data.month,
|
month: data.month,
|
||||||
year: data.year,
|
year: yearValue,
|
||||||
totalUnemployment: data.totalUnemployment,
|
totalUnemployment: data.totalUnemployment,
|
||||||
educatedUnemployment: data.educatedUnemployment,
|
educatedUnemployment: data.educatedUnemployment,
|
||||||
uneducatedUnemployment: data.uneducatedUnemployment,
|
uneducatedUnemployment: data.uneducatedUnemployment,
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ function DetailJumlahPengangguran() {
|
|||||||
stateDetail.delete.byId(selectedId)
|
stateDetail.delete.byId(selectedId)
|
||||||
setModalHapus(false)
|
setModalHapus(false)
|
||||||
setSelectedId(null)
|
setSelectedId(null)
|
||||||
router.push("/admin/ekonomi/jumlah-pengangguran/detail-data-pengangguran")
|
router.push("/admin/ekonomi/jumlah-pengangguran")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,7 +63,7 @@ function DetailJumlahPengangguran() {
|
|||||||
</Box>
|
</Box>
|
||||||
<Box>
|
<Box>
|
||||||
<Text fw={"bold"}>Tahun</Text>
|
<Text fw={"bold"}>Tahun</Text>
|
||||||
<Text>{stateDetail.findUnique.data?.year}</Text>
|
<Text>{stateDetail.findUnique.data?.year ? new Date(stateDetail.findUnique.data.year).getFullYear() : ''}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<Box>
|
<Box>
|
||||||
<Text fw={"bold"}>Bulan</Text>
|
<Text fw={"bold"}>Bulan</Text>
|
||||||
@@ -86,7 +86,7 @@ function DetailJumlahPengangguran() {
|
|||||||
color={"red"}>
|
color={"red"}>
|
||||||
<IconX size={20} />
|
<IconX size={20} />
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={() => router.push(`/admin/ekonomi/jumlah-pengangguran/detail-data-pengangguran/${stateDetail.findUnique.data?.id}/edit`)} color="green">
|
<Button onClick={() => router.push(`/admin/ekonomi/jumlah-pengangguran/${stateDetail.findUnique.data?.id}/edit`)} color="green">
|
||||||
<IconEdit size={20} />
|
<IconEdit size={20} />
|
||||||
</Button>
|
</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
@@ -108,4 +108,3 @@ function DetailJumlahPengangguran() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default DetailJumlahPengangguran;
|
export default DetailJumlahPengangguran;
|
||||||
|
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ function CreateJumlahPengangguran() {
|
|||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
label="Tahun"
|
label="Tahun"
|
||||||
type="number"
|
type="date"
|
||||||
value={stateDetail.create.form.year}
|
value={stateDetail.create.form.year}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
(stateDetail.create.form.year = Number(e.currentTarget.value))
|
(stateDetail.create.form.year = Number(e.currentTarget.value))
|
||||||
|
|||||||
@@ -8,21 +8,29 @@ import { useEffect, useState } from 'react';
|
|||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
import { BarChart } from '@mantine/charts';
|
import { BarChart } from '@mantine/charts';
|
||||||
|
import HeaderSearch from '../../_com/header';
|
||||||
|
import JudulList from '../../_com/judulList';
|
||||||
import jumlahPengangguranState from '../../_state/ekonomi/jumlah-pengangguran';
|
import jumlahPengangguranState from '../../_state/ekonomi/jumlah-pengangguran';
|
||||||
import JudulListTab from '../../_com/judulListTab';
|
|
||||||
|
|
||||||
function DetailDataPengangguran() {
|
function DetailDataPengangguran() {
|
||||||
|
const [search, setSearch] = useState("")
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Stack gap={"xs"}>
|
<Stack gap={"xs"}>
|
||||||
<Title order={3}>Detail Data Pengangguran</Title>
|
<HeaderSearch
|
||||||
<ListDetailDataPengangguran />
|
title='Detail Data Pengangguran'
|
||||||
|
placeholder='pencarian'
|
||||||
|
searchIcon={<IconSearch size={20} />}
|
||||||
|
value={search}
|
||||||
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
<ListDetailDataPengangguran search={search} />
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ListDetailDataPengangguran() {
|
function ListDetailDataPengangguran({search}: {search: string}) {
|
||||||
|
|
||||||
type DetailDataPengangguran = {
|
type DetailDataPengangguran = {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -37,7 +45,6 @@ function ListDetailDataPengangguran() {
|
|||||||
const [mounted, setMounted] = useState(false); // untuk memastikan DOM sudah ready
|
const [mounted, setMounted] = useState(false); // untuk memastikan DOM sudah ready
|
||||||
const stateDetail = useProxy(jumlahPengangguranState.jumlahPengangguran)
|
const stateDetail = useProxy(jumlahPengangguranState.jumlahPengangguran)
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [search, setSearch] = useState("")
|
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
setMounted(true)
|
setMounted(true)
|
||||||
@@ -50,7 +57,7 @@ function ListDetailDataPengangguran() {
|
|||||||
setChartData(stateDetail.findMany.data.map((item) => ({
|
setChartData(stateDetail.findMany.data.map((item) => ({
|
||||||
id: item.id,
|
id: item.id,
|
||||||
month: item.month,
|
month: item.month,
|
||||||
year: item.year,
|
year: item.year instanceof Date ? item.year.getFullYear() : Number(item.year),
|
||||||
educatedUnemployment: Number(item.educatedUnemployment),
|
educatedUnemployment: Number(item.educatedUnemployment),
|
||||||
uneducatedUnemployment: Number(item.uneducatedUnemployment),
|
uneducatedUnemployment: Number(item.uneducatedUnemployment),
|
||||||
percentageChange: Number(item.percentageChange),
|
percentageChange: Number(item.percentageChange),
|
||||||
@@ -78,13 +85,9 @@ function ListDetailDataPengangguran() {
|
|||||||
<Box>
|
<Box>
|
||||||
<Stack gap={"md"}>
|
<Stack gap={"md"}>
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
<JudulListTab
|
<JudulList
|
||||||
title='List Detail Data Pengangguran'
|
title='List Detail Data Pengangguran'
|
||||||
href='/admin/ekonomi/jumlah-pengangguran/detail-data-pengangguran/create'
|
href='/admin/ekonomi/jumlah-pengangguran/create'
|
||||||
value={search}
|
|
||||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
|
||||||
placeholder='pencarian'
|
|
||||||
searchIcon={<IconSearch size={16} />}
|
|
||||||
/>
|
/>
|
||||||
<Table striped withTableBorder withRowBorders>
|
<Table striped withTableBorder withRowBorders>
|
||||||
<TableThead>
|
<TableThead>
|
||||||
@@ -102,7 +105,7 @@ function ListDetailDataPengangguran() {
|
|||||||
<TableTd>{item.educatedUnemployment}</TableTd>
|
<TableTd>{item.educatedUnemployment}</TableTd>
|
||||||
<TableTd>{item.uneducatedUnemployment}</TableTd>
|
<TableTd>{item.uneducatedUnemployment}</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button onClick={() => router.push(`/admin/ekonomi/jumlah-pengangguran/detail-data-pengangguran/${item.id}`)}>
|
<Button onClick={() => router.push(`/admin/ekonomi/jumlah-pengangguran/${item.id}`)}>
|
||||||
<IconDeviceImac size={20} />
|
<IconDeviceImac size={20} />
|
||||||
</Button>
|
</Button>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
|
|||||||
@@ -55,17 +55,17 @@ function EditLowonganKerja() {
|
|||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
try {
|
try {
|
||||||
lowonganKerjaState.update.form = {
|
// Set the ID for the update
|
||||||
...lowonganKerjaState.update.form,
|
lowonganState.update.id = params?.id as string;
|
||||||
posisi: formData.posisi,
|
|
||||||
namaPerusahaan: formData.namaPerusahaan,
|
// Update the form state
|
||||||
lokasi: formData.lokasi,
|
lowonganState.update.form = {
|
||||||
tipePekerjaan: formData.tipePekerjaan,
|
...lowonganState.update.form,
|
||||||
gaji: formData.gaji,
|
...formData
|
||||||
deskripsi: formData.deskripsi,
|
};
|
||||||
kualifikasi: formData.kualifikasi,
|
|
||||||
}
|
// Call the update function
|
||||||
await lowonganState.update.update()
|
await lowonganState.update.update();
|
||||||
toast.success("Lowongan kerja berhasil diperbarui!");
|
toast.success("Lowongan kerja berhasil diperbarui!");
|
||||||
router.push("/admin/ekonomi/lowongan-kerja-lokal");
|
router.push("/admin/ekonomi/lowongan-kerja-lokal");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -88,7 +88,7 @@ function EditLowonganKerja() {
|
|||||||
<TextInput
|
<TextInput
|
||||||
value={formData.posisi}
|
value={formData.posisi}
|
||||||
onChange={(val) => {
|
onChange={(val) => {
|
||||||
formData.posisi = val.target.value;
|
setFormData(prev => ({ ...prev, posisi: val.target.value }));
|
||||||
}}
|
}}
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Posisi</Text>}
|
label={<Text fw={"bold"} fz={"sm"}>Posisi</Text>}
|
||||||
placeholder='Masukkan posisi'
|
placeholder='Masukkan posisi'
|
||||||
@@ -96,7 +96,7 @@ function EditLowonganKerja() {
|
|||||||
<TextInput
|
<TextInput
|
||||||
value={formData.namaPerusahaan}
|
value={formData.namaPerusahaan}
|
||||||
onChange={(val) => {
|
onChange={(val) => {
|
||||||
formData.namaPerusahaan = val.target.value;
|
setFormData(prev => ({ ...prev, namaPerusahaan: val.target.value }));
|
||||||
}}
|
}}
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Nama Perusahaan</Text>}
|
label={<Text fw={"bold"} fz={"sm"}>Nama Perusahaan</Text>}
|
||||||
placeholder='Masukkan nama perusahaan'
|
placeholder='Masukkan nama perusahaan'
|
||||||
@@ -104,7 +104,7 @@ function EditLowonganKerja() {
|
|||||||
<TextInput
|
<TextInput
|
||||||
value={formData.lokasi}
|
value={formData.lokasi}
|
||||||
onChange={(val) => {
|
onChange={(val) => {
|
||||||
formData.lokasi = val.target.value;
|
setFormData(prev => ({ ...prev, lokasi: val.target.value }));
|
||||||
}}
|
}}
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Lokasi</Text>}
|
label={<Text fw={"bold"} fz={"sm"}>Lokasi</Text>}
|
||||||
placeholder='Masukkan lokasi'
|
placeholder='Masukkan lokasi'
|
||||||
@@ -112,7 +112,7 @@ function EditLowonganKerja() {
|
|||||||
<TextInput
|
<TextInput
|
||||||
value={formData.tipePekerjaan}
|
value={formData.tipePekerjaan}
|
||||||
onChange={(val) => {
|
onChange={(val) => {
|
||||||
formData.tipePekerjaan = val.target.value;
|
setFormData(prev => ({ ...prev, tipePekerjaan: val.target.value }));
|
||||||
}}
|
}}
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Tipe Pekerjaan</Text>}
|
label={<Text fw={"bold"} fz={"sm"}>Tipe Pekerjaan</Text>}
|
||||||
placeholder='Masukkan tipe pekerjaan'
|
placeholder='Masukkan tipe pekerjaan'
|
||||||
@@ -120,7 +120,7 @@ function EditLowonganKerja() {
|
|||||||
<TextInput
|
<TextInput
|
||||||
value={formData.gaji}
|
value={formData.gaji}
|
||||||
onChange={(val) => {
|
onChange={(val) => {
|
||||||
formData.gaji = val.target.value;
|
setFormData(prev => ({ ...prev, gaji: val.target.value }));
|
||||||
}}
|
}}
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Gaji selama 1 bulan</Text>}
|
label={<Text fw={"bold"} fz={"sm"}>Gaji selama 1 bulan</Text>}
|
||||||
placeholder='Masukkan gaji'
|
placeholder='Masukkan gaji'
|
||||||
@@ -130,7 +130,7 @@ function EditLowonganKerja() {
|
|||||||
<EditEditor
|
<EditEditor
|
||||||
value={formData.deskripsi}
|
value={formData.deskripsi}
|
||||||
onChange={(val) => {
|
onChange={(val) => {
|
||||||
formData.deskripsi = val;
|
setFormData(prev => ({ ...prev, deskripsi: val }));
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -139,7 +139,7 @@ function EditLowonganKerja() {
|
|||||||
<EditEditor
|
<EditEditor
|
||||||
value={formData.kualifikasi}
|
value={formData.kualifikasi}
|
||||||
onChange={(val) => {
|
onChange={(val) => {
|
||||||
formData.kualifikasi = val;
|
setFormData(prev => ({ ...prev, kualifikasi: val }));
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } 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 { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
||||||
import HeaderSearch from '../../_com/header';
|
import HeaderSearch from '../../_com/header';
|
||||||
import JudulList from '../../_com/judulList';
|
import JudulList from '../../_com/judulList';
|
||||||
@@ -30,20 +30,21 @@ function ListLowonganKerjaLokal({ search }: { search: string }) {
|
|||||||
const lowonganState = useProxy(lowonganKerjaState)
|
const lowonganState = useProxy(lowonganKerjaState)
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
page,
|
||||||
|
totalPages,
|
||||||
|
loading,
|
||||||
|
load,
|
||||||
|
} = lowonganState.findMany
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
lowonganState.findMany.load();
|
load(page, 10, search)
|
||||||
}, [])
|
}, [page, search])
|
||||||
|
|
||||||
const filteredData = (lowonganState.findMany.data || []).filter(item => {
|
const filteredData = data || []
|
||||||
const keyword = search.toLowerCase();
|
|
||||||
return (
|
|
||||||
item.posisi.toLowerCase().includes(keyword) ||
|
|
||||||
item.namaPerusahaan.toLowerCase().includes(keyword) ||
|
|
||||||
item.lokasi.toLowerCase().includes(keyword)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!lowonganState.findMany.data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={10}>
|
||||||
<Skeleton h={500} />
|
<Skeleton h={500} />
|
||||||
@@ -60,18 +61,24 @@ function ListLowonganKerjaLokal({ search }: { search: string }) {
|
|||||||
<Table striped withTableBorder withRowBorders>
|
<Table striped withTableBorder withRowBorders>
|
||||||
<TableThead>
|
<TableThead>
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh>Bekerja Sebagai</TableTh>
|
<TableTh>Pekerjaan</TableTh>
|
||||||
<TableTh>Nama Usaha</TableTh>
|
<TableTh>Nama Perusahaan</TableTh>
|
||||||
<TableTh>Alamat Usaha</TableTh>
|
<TableTh>Lokasi</TableTh>
|
||||||
<TableTh>Detail</TableTh>
|
<TableTh>Detail</TableTh>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
<TableTbody>
|
<TableTbody>
|
||||||
{filteredData.map((item) => (
|
{filteredData.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd>{item.posisi}</TableTd>
|
<TableTd>
|
||||||
<TableTd>{item.namaPerusahaan}</TableTd>
|
<Text fz={"md"}>{item.posisi}</Text>
|
||||||
<TableTd>{item.lokasi}</TableTd>
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Text fz={"md"}>{item.namaPerusahaan}</Text>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Text fz={"md"}>{item.lokasi}</Text>
|
||||||
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button onClick={() => router.push(`/admin/ekonomi/lowongan-kerja-lokal/${item.id}`)}>
|
<Button onClick={() => router.push(`/admin/ekonomi/lowongan-kerja-lokal/${item.id}`)}>
|
||||||
<IconDeviceImac size={20} />
|
<IconDeviceImac size={20} />
|
||||||
@@ -82,6 +89,14 @@ function ListLowonganKerjaLokal({ search }: { search: string }) {
|
|||||||
</TableTbody>
|
</TableTbody>
|
||||||
</Table>
|
</Table>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
<Center>
|
||||||
|
<Pagination
|
||||||
|
value={page}
|
||||||
|
onChange={(newPage) => load(newPage)}
|
||||||
|
total={totalPages}
|
||||||
|
my="md"
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
|
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconEdit, IconSearch, IconX } from '@tabler/icons-react';
|
import { IconEdit, IconSearch, IconX } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
@@ -13,31 +13,39 @@ import pasarDesaState from '../../../_state/ekonomi/pasar-desa/pasar-desa';
|
|||||||
|
|
||||||
|
|
||||||
function PasarDesa() {
|
function PasarDesa() {
|
||||||
const [search, setSearch] = useState("")
|
const [search2, setSearch2] = useState("")
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<HeaderSearch
|
<HeaderSearch
|
||||||
title='Kategori Produk'
|
title='Kategori Produk'
|
||||||
placeholder='pencarian'
|
placeholder='pencarian'
|
||||||
searchIcon={<IconSearch size={20} />}
|
searchIcon={<IconSearch size={20} />}
|
||||||
value={search}
|
value={search2}
|
||||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
onChange={(e) => setSearch2(e.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
<ListPasarDesa search={search} />
|
<ListPasarDesa search2={search2} />
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ListPasarDesa({ search }: { search: string }) {
|
function ListPasarDesa({ search2 }: { search2: string }) {
|
||||||
const statePasar = useProxy(pasarDesaState.kategoriProduk)
|
const statePasar = useProxy(pasarDesaState.kategoriProduk)
|
||||||
const [modalHapus, setModalHapus] = useState(false)
|
const [modalHapus, setModalHapus] = useState(false)
|
||||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||||
// const params = useParams()
|
// const params = useParams()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
page,
|
||||||
|
totalPages,
|
||||||
|
loading,
|
||||||
|
load,
|
||||||
|
} = statePasar.findMany
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
statePasar.findMany.load()
|
load(page, 10, search2)
|
||||||
}, [])
|
}, [page, search2])
|
||||||
|
|
||||||
const handleHapus = () => {
|
const handleHapus = () => {
|
||||||
if (selectedId) {
|
if (selectedId) {
|
||||||
@@ -47,14 +55,9 @@ function ListPasarDesa({ search }: { search: string }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const filteredData = (statePasar.findMany.data || []).filter(item => {
|
const filteredData = data || []
|
||||||
const keyword = search.toLowerCase();
|
|
||||||
return (
|
|
||||||
item.nama.toLowerCase().includes(keyword)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!statePasar.findMany.data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={10}>
|
||||||
<Skeleton h={500} />
|
<Skeleton h={500} />
|
||||||
@@ -99,6 +102,14 @@ function ListPasarDesa({ search }: { search: string }) {
|
|||||||
</TableTbody>
|
</TableTbody>
|
||||||
</Table>
|
</Table>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
<Center>
|
||||||
|
<Pagination
|
||||||
|
value={page}
|
||||||
|
onChange={(newPage) => load(newPage)}
|
||||||
|
total={totalPages}
|
||||||
|
my={"md"}
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
{/* Modal Konfirmasi Hapus */}
|
{/* Modal Konfirmasi Hapus */}
|
||||||
<ModalKonfirmasiHapus
|
<ModalKonfirmasiHapus
|
||||||
opened={modalHapus}
|
opened={modalHapus}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
|
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
|
||||||
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
@@ -30,21 +30,21 @@ function ListPasarDesa({ search }: { search: string }) {
|
|||||||
const statePasar = useProxy(pasarDesaState.pasarDesa)
|
const statePasar = useProxy(pasarDesaState.pasarDesa)
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
page,
|
||||||
|
totalPages,
|
||||||
|
loading,
|
||||||
|
load,
|
||||||
|
} = statePasar.findMany
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
statePasar.findMany.load()
|
load(page, 10, search)
|
||||||
}, [])
|
}, [page, search])
|
||||||
|
|
||||||
const filteredData = (statePasar.findMany.data || []).filter(item => {
|
const filteredData = data || []
|
||||||
const keyword = search.toLowerCase();
|
|
||||||
return (
|
|
||||||
item.nama.toLowerCase().includes(keyword) ||
|
|
||||||
item.harga.toString().toLowerCase().includes(keyword) ||
|
|
||||||
item.rating.toString().toLowerCase().includes(keyword) ||
|
|
||||||
item.alamatUsaha.toLowerCase().includes(keyword)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!statePasar.findMany.data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={10}>
|
||||||
<Skeleton h={500} />
|
<Skeleton h={500} />
|
||||||
@@ -86,6 +86,14 @@ function ListPasarDesa({ search }: { search: string }) {
|
|||||||
</TableTbody>
|
</TableTbody>
|
||||||
</Table>
|
</Table>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
<Center>
|
||||||
|
<Pagination
|
||||||
|
value={page}
|
||||||
|
onChange={(newPage) => load(newPage)}
|
||||||
|
total={totalPages}
|
||||||
|
my={"md"}
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
|
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
|
||||||
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
||||||
import HeaderSearch from '../../_com/header';
|
import HeaderSearch from '../../_com/header';
|
||||||
import JudulList from '../../_com/judulList';
|
import JudulList from '../../_com/judulList';
|
||||||
@@ -23,7 +23,7 @@ function ProgramKemiskinan() {
|
|||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
<ListProgramKemiskinan search={search}/>
|
<ListProgramKemiskinan search={search} />
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -34,14 +34,22 @@ function ListProgramKemiskinan({ search }: { search: string }) {
|
|||||||
const [lineChart, setLineChart] = useState<any[]>([]);
|
const [lineChart, setLineChart] = useState<any[]>([]);
|
||||||
const [mounted, setMounted] = useState(false);
|
const [mounted, setMounted] = useState(false);
|
||||||
|
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
page,
|
||||||
|
totalPages,
|
||||||
|
loading,
|
||||||
|
load,
|
||||||
|
} = programState.findMany;
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
setMounted(true)
|
setMounted(true)
|
||||||
programState.findMany.load()
|
load(page, 10, search)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (programState.findMany.data) {
|
if (data) {
|
||||||
const chartData = programState.findMany.data
|
const chartData = data
|
||||||
.filter(item => item.statistik)
|
.filter(item => item.statistik)
|
||||||
.map(item => ({
|
.map(item => ({
|
||||||
tahun: item.statistik?.tahun,
|
tahun: item.statistik?.tahun,
|
||||||
@@ -52,18 +60,11 @@ function ListProgramKemiskinan({ search }: { search: string }) {
|
|||||||
setLineChart(chartData);
|
setLineChart(chartData);
|
||||||
|
|
||||||
}
|
}
|
||||||
}, [programState.findMany.data])
|
}, [data])
|
||||||
|
|
||||||
const filteredData = (programState.findMany.data || []).filter(item => {
|
const filteredData = data || []
|
||||||
const keyword = search.toLowerCase();
|
|
||||||
return (
|
|
||||||
item.nama.toLowerCase().includes(keyword) ||
|
|
||||||
item.deskripsi.toLowerCase().includes(keyword) ||
|
|
||||||
item.statistik?.tahun.toString().includes(keyword)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!programState.findMany.data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={10}>
|
||||||
<Skeleton h={500} />
|
<Skeleton h={500} />
|
||||||
@@ -112,7 +113,7 @@ function ListProgramKemiskinan({ search }: { search: string }) {
|
|||||||
<Box >
|
<Box >
|
||||||
<Title pb={10} order={3}>Grafik Berdasarkan Responden</Title>
|
<Title pb={10} order={3}>Grafik Berdasarkan Responden</Title>
|
||||||
{mounted && lineChart.length > 0 ? (<Box style={{ width: '100%', height: 'auto', }}>
|
{mounted && lineChart.length > 0 ? (<Box style={{ width: '100%', height: 'auto', }}>
|
||||||
<Box w={"100%"} style={{overflowX: 'auto'}}>
|
<Box w={"100%"} style={{ overflowX: 'auto' }}>
|
||||||
<LineChart
|
<LineChart
|
||||||
width={820}
|
width={820}
|
||||||
height={300}
|
height={300}
|
||||||
@@ -143,6 +144,14 @@ function ListProgramKemiskinan({ search }: { search: string }) {
|
|||||||
</Box>
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
<Center>
|
||||||
|
<Pagination
|
||||||
|
value={page}
|
||||||
|
onChange={(newPage) => load(newPage)}
|
||||||
|
total={totalPages}
|
||||||
|
my={"md"}
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,15 +2,16 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Group, Paper, Stack, TextInput, Title } from '@mantine/core';
|
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import grafikSektorUnggulan from '../../../_state/ekonomi/sektor-unggulan-desa';
|
import grafikSektorUnggulan from '../../../_state/ekonomi/sektor-unggulan-desa';
|
||||||
|
import CreateEditor from '../../../_com/createEditor';
|
||||||
|
|
||||||
function CreateSektorUnggulanDesa() {
|
function CreateSektorUnggulanDesa() {
|
||||||
const stateGrafik= useProxy(grafikSektorUnggulan);
|
const stateGrafik = useProxy(grafikSektorUnggulan);
|
||||||
const [chartData, setChartData] = useState<any[]>([]);
|
const [chartData, setChartData] = useState<any[]>([]);
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
@@ -54,15 +55,15 @@ function CreateSektorUnggulanDesa() {
|
|||||||
stateGrafik.create.form.name = val.currentTarget.value;
|
stateGrafik.create.form.name = val.currentTarget.value;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<Box>
|
||||||
label="Deskripsi Sektor Unggulan"
|
<Text fw={"bold"} fz={"sm"}>Deskripsi Sektor Ungggulan</Text>
|
||||||
type="text"
|
<CreateEditor
|
||||||
value={stateGrafik.create.form.description}
|
value={stateGrafik.create.form.description}
|
||||||
placeholder="Masukkan deskripsi sektor unggulan"
|
onChange={(val) => {
|
||||||
onChange={(val) => {
|
stateGrafik.create.form.description = val;
|
||||||
stateGrafik.create.form.description = val.currentTarget.value;
|
}}
|
||||||
}}
|
/>
|
||||||
/>
|
</Box>
|
||||||
<TextInput
|
<TextInput
|
||||||
label="Jumlah"
|
label="Jumlah"
|
||||||
type="number"
|
type="number"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Paper, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
|
import { Box, Button, Paper, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
|
||||||
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
||||||
import HeaderSearch from '../../_com/header';
|
import HeaderSearch from '../../_com/header';
|
||||||
import JudulList from '../../_com/judulList';
|
import JudulList from '../../_com/judulList';
|
||||||
@@ -30,7 +30,7 @@ function SektorUnggulanDesa() {
|
|||||||
function ListSektorUnggulanDesa({ search }: { search: string }) {
|
function ListSektorUnggulanDesa({ search }: { search: string }) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const state = useProxy(grafikSektorUnggulan);
|
const state = useProxy(grafikSektorUnggulan);
|
||||||
const [chartData, setChartData] = useState<{id: string; name: string; description: string | null; value: number | null}[]>([]);
|
const [chartData, setChartData] = useState<{ id: string; name: string; description: string | null; value: number | null }[]>([]);
|
||||||
const [mounted, setMounted] = useState(false); // untuk memastikan DOM sudah ready
|
const [mounted, setMounted] = useState(false); // untuk memastikan DOM sudah ready
|
||||||
const isTablet = useMediaQuery('(max-width: 1024px)')
|
const isTablet = useMediaQuery('(max-width: 1024px)')
|
||||||
const isMobile = useMediaQuery('(max-width: 768px)')
|
const isMobile = useMediaQuery('(max-width: 768px)')
|
||||||
@@ -61,38 +61,41 @@ function ListSektorUnggulanDesa({ search }: { search: string }) {
|
|||||||
}, [state.findMany.data]);
|
}, [state.findMany.data]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box>
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
<Stack gap={"xs"}>
|
||||||
<JudulList
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
title='List Sektor Unggulan Desa'
|
<JudulList
|
||||||
href='/admin/ekonomi/sektor-unggulan-desa/create'
|
title='List Sektor Unggulan Desa'
|
||||||
/>
|
href='/admin/ekonomi/sektor-unggulan-desa/create'
|
||||||
<Table striped withTableBorder withRowBorders>
|
/>
|
||||||
<TableThead>
|
<Table striped withTableBorder withRowBorders>
|
||||||
<TableTr>
|
<TableThead>
|
||||||
<TableTh>Nama Sektor Unggulan</TableTh>
|
<TableTr>
|
||||||
<TableTh>Deskripsi Sektor Unggulan</TableTh>
|
<TableTh>Nama Sektor Unggulan</TableTh>
|
||||||
<TableTh>Detail</TableTh>
|
<TableTh>Deskripsi Sektor Unggulan</TableTh>
|
||||||
</TableTr>
|
<TableTh>Detail</TableTh>
|
||||||
</TableThead>
|
|
||||||
<TableTbody>
|
|
||||||
{filteredData.map((item) => (
|
|
||||||
<TableTr key={item.id}>
|
|
||||||
<TableTd>{item.name}</TableTd>
|
|
||||||
<TableTd>{item.description}</TableTd>
|
|
||||||
<TableTd>
|
|
||||||
<Button onClick={() => router.push(`/admin/ekonomi/sektor-unggulan-desa/${item.id}`)}>
|
|
||||||
<IconDeviceImac size={20} />
|
|
||||||
</Button>
|
|
||||||
</TableTd>
|
|
||||||
</TableTr>
|
</TableTr>
|
||||||
))}
|
</TableThead>
|
||||||
</TableTbody>
|
<TableTbody>
|
||||||
</Table>
|
{filteredData.map((item) => (
|
||||||
</Paper>
|
<TableTr key={item.id}>
|
||||||
|
<TableTd>{item.name}</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Text truncate={"end"} fz={'sm'} lineClamp={1} dangerouslySetInnerHTML={{ __html: item.description || '' }}></Text>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Button onClick={() => router.push(`/admin/ekonomi/sektor-unggulan-desa/${item.id}`)}>
|
||||||
|
<IconDeviceImac size={20} />
|
||||||
|
</Button>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
))}
|
||||||
|
</TableTbody>
|
||||||
|
</Table>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
{/* Chart */}
|
{/* Chart */}
|
||||||
{!mounted && !chartData ? (
|
{!mounted && !chartData ? (
|
||||||
<Box style={{ width: '100%', minWidth: 300, height: 400, minHeight: 300 }}>
|
<Box style={{ width: '100%', minWidth: 300, height: 400, minHeight: 300 }}>
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
<Title pb={10} order={3}>Grafik Hasil Kepuasan Masyarakat</Title>
|
<Title pb={10} order={3}>Grafik Hasil Kepuasan Masyarakat</Title>
|
||||||
@@ -115,6 +118,7 @@ function ListSektorUnggulanDesa({ search }: { search: string }) {
|
|||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
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, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Pagination } from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
@@ -29,19 +29,22 @@ function DesaDigitalSmartVillage() {
|
|||||||
function ListDesaDigitalSmartVillage({ search }: { search: string }) {
|
function ListDesaDigitalSmartVillage({ search }: { search: string }) {
|
||||||
const state = useProxy(desaDigitalState)
|
const state = useProxy(desaDigitalState)
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
page,
|
||||||
|
totalPages,
|
||||||
|
loading,
|
||||||
|
load,
|
||||||
|
} = state.findMany
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
state.findMany.load()
|
load(page, 10, search)
|
||||||
}, [])
|
}, [page, search])
|
||||||
|
|
||||||
const filteredData = (state.findMany.data || []).filter(item => {
|
const filteredData = data || []
|
||||||
const keyword = search.toLowerCase();
|
|
||||||
return (
|
|
||||||
item.name.toLowerCase().includes(keyword) ||
|
|
||||||
item.deskripsi.toLowerCase().includes(keyword)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!state.findMany.data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={10}>
|
||||||
<Skeleton h={500} />
|
<Skeleton h={500} />
|
||||||
@@ -68,18 +71,27 @@ function ListDesaDigitalSmartVillage({ search }: { search: string }) {
|
|||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd>{item.name}</TableTd>
|
<TableTd>{item.name}</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Text truncate="end" fz={"sm"} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
<Text lineClamp={1} truncate="end" fz={"sm"} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button onClick={() => router.push(`/admin/inovasi/desa-digital-smart-village/${item.id}`)}>
|
<Button onClick={() => router.push(`/admin/inovasi/desa-digital-smart-village/${item.id}`)}>
|
||||||
<IconDeviceImac size={20} />
|
<IconDeviceImac size={20} />
|
||||||
</Button>
|
</Button>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
))}
|
))}
|
||||||
</TableTbody>
|
</TableTbody>
|
||||||
</Table>
|
</Table>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
<Center>
|
||||||
|
<Pagination
|
||||||
|
value={page}
|
||||||
|
onChange={(newPage) => load(newPage)} // ini penting!
|
||||||
|
total={totalPages}
|
||||||
|
mt="md"
|
||||||
|
mb="md"
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
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 { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
@@ -29,19 +29,22 @@ function InfoTeknologiTepatGuna() {
|
|||||||
function ListInfoTeknologiTepatGuna({ search }: { search: string }) {
|
function ListInfoTeknologiTepatGuna({ search }: { search: string }) {
|
||||||
const state = useProxy(infoTeknoState)
|
const state = useProxy(infoTeknoState)
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
page,
|
||||||
|
totalPages,
|
||||||
|
loading,
|
||||||
|
load,
|
||||||
|
} = state.findMany
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
state.findMany.load()
|
load(page, 10, search)
|
||||||
}, [])
|
}, [page, search])
|
||||||
|
|
||||||
const filteredData = (state.findMany.data || []).filter(item => {
|
const filteredData = data || []
|
||||||
const keyword = search.toLowerCase();
|
|
||||||
return (
|
|
||||||
item.name.toLowerCase().includes(keyword) ||
|
|
||||||
item.deskripsi.toLowerCase().includes(keyword)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!state.findMany.data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={10}>
|
||||||
<Skeleton h={500} />
|
<Skeleton h={500} />
|
||||||
@@ -68,17 +71,25 @@ function ListInfoTeknologiTepatGuna({ search }: { search: string }) {
|
|||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd>{item.name}</TableTd>
|
<TableTd>{item.name}</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Text truncate="end" fz={"sm"} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
<Text lineClamp={1} truncate="end" fz={"sm"} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button onClick={() => router.push(`/admin/inovasi/info-teknologi-tepat-guna/${item.id}`)}>
|
<Button onClick={() => router.push(`/admin/inovasi/info-teknologi-tepat-guna/${item.id}`)}>
|
||||||
<IconDeviceImac size={20} />
|
<IconDeviceImac size={20} />
|
||||||
</Button>
|
</Button>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
))}
|
))}
|
||||||
</TableTbody>
|
</TableTbody>
|
||||||
</Table>
|
</Table>
|
||||||
|
<Center>
|
||||||
|
<Pagination
|
||||||
|
value={page}
|
||||||
|
onChange={(newPage) => load(newPage)} // ini penting!
|
||||||
|
total={totalPages}
|
||||||
|
my="md"
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
/* 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 LayoutTabsKolaborasi({ children }: { children: React.ReactNode }) {
|
||||||
|
const router = useRouter()
|
||||||
|
const pathname = usePathname()
|
||||||
|
const tabs = [
|
||||||
|
{
|
||||||
|
label: "List Kolaborasi Inovasi",
|
||||||
|
value: "listkolaborasiinovasi",
|
||||||
|
href: "/admin/inovasi/kolaborasi-inovasi/list-kolaborasi-inovasi"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Mitra Kolaborasi",
|
||||||
|
value: "mitarakolaborasi",
|
||||||
|
href: "/admin/inovasi/kolaborasi-inovasi/mitra-kolaborasi"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
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}>Kolaborasi Inovasi</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 LayoutTabsKolaborasi;
|
||||||
@@ -1,155 +0,0 @@
|
|||||||
'use client'
|
|
||||||
import colors from '@/con/colors';
|
|
||||||
import { Box, Button, Center, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
|
||||||
import { IconArrowBack, IconImageInPicture, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
|
||||||
import { useRouter } from 'next/navigation';
|
|
||||||
import { useProxy } from 'valtio/utils';
|
|
||||||
import CreateEditor from '../../../_com/createEditor';
|
|
||||||
import kolaborasiInovasiState from '../../../_state/inovasi/kolaborasi-inovasi';
|
|
||||||
import { useState } from 'react';
|
|
||||||
import { toast } from 'react-toastify';
|
|
||||||
import ApiFetch from '@/lib/api-fetch';
|
|
||||||
import { Dropzone } from '@mantine/dropzone';
|
|
||||||
|
|
||||||
|
|
||||||
function CreateProgramKreatifDesa() {
|
|
||||||
const stateCreate = useProxy(kolaborasiInovasiState)
|
|
||||||
const router = useRouter();
|
|
||||||
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
|
||||||
const [file, setFile] = useState<File | null>(null);
|
|
||||||
|
|
||||||
const resetForm = () => {
|
|
||||||
stateCreate.create.form = {
|
|
||||||
name: "",
|
|
||||||
tahun: 0,
|
|
||||||
slug: "",
|
|
||||||
deskripsi: "",
|
|
||||||
kolaborator: "",
|
|
||||||
imageId: "",
|
|
||||||
}
|
|
||||||
|
|
||||||
setPreviewImage(null);
|
|
||||||
setFile(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
|
||||||
if (!file) {
|
|
||||||
return toast.warn("Pilih file gambar terlebih dahulu");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Upload gambar dulu
|
|
||||||
const res = await ApiFetch.api.fileStorage.create.post({
|
|
||||||
file,
|
|
||||||
name: file.name,
|
|
||||||
});
|
|
||||||
|
|
||||||
const uploaded = res.data?.data;
|
|
||||||
if (!uploaded?.id) {
|
|
||||||
return toast.error("Gagal upload gambar");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Simpan ID gambar ke form
|
|
||||||
stateCreate.create.form.imageId = uploaded.id;
|
|
||||||
|
|
||||||
// Submit data berita
|
|
||||||
await stateCreate.create.create();
|
|
||||||
|
|
||||||
// Reset form setelah submit
|
|
||||||
resetForm();
|
|
||||||
router.push("/admin/inovasi/kolaborasi-inovasi")
|
|
||||||
}
|
|
||||||
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={3}>Create Kolaborasi Inovasi</Title>
|
|
||||||
<TextInput
|
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Nama Kolaborasi Inovasi</Text>}
|
|
||||||
placeholder="masukkan nama kolaborasi inovasi"
|
|
||||||
onChange={(val) => stateCreate.create.form.name = val.target.value}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
type='number'
|
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Tahun</Text>}
|
|
||||||
placeholder="masukkan tahun"
|
|
||||||
onChange={(val) => stateCreate.create.form.tahun = parseInt(val.target.value)}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
onChange={(e) => stateCreate.create.form.slug = e.currentTarget.value}
|
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Deskripsi Singkat Kolaborasi Inovasi</Text>}
|
|
||||||
placeholder='Masukkan deskripsi singkat kolaborasi inovasi'
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
onChange={(e) => stateCreate.create.form.kolaborator = e.currentTarget.value}
|
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Kolaborator</Text>}
|
|
||||||
placeholder='Masukkan kolaborator'
|
|
||||||
/>
|
|
||||||
<Box>
|
|
||||||
<Stack gap={"xs"}>
|
|
||||||
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
|
||||||
<Box>
|
|
||||||
<Dropzone
|
|
||||||
onDrop={(files) => {
|
|
||||||
const newImages = files.map((file) => ({
|
|
||||||
file,
|
|
||||||
preview: URL.createObjectURL(file),
|
|
||||||
label: '',
|
|
||||||
}));
|
|
||||||
setFile(newImages[0].file);
|
|
||||||
setPreviewImage(newImages[0].preview); // ← ini yang kurang
|
|
||||||
}}
|
|
||||||
|
|
||||||
>
|
|
||||||
<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>
|
|
||||||
</Box>
|
|
||||||
{previewImage ? (
|
|
||||||
<Image alt="" src={previewImage} w={200} h={200} />
|
|
||||||
) : (
|
|
||||||
<Center w={200} h={200} bg={"gray"}>
|
|
||||||
<IconImageInPicture />
|
|
||||||
</Center>
|
|
||||||
)}
|
|
||||||
</Stack>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text fw={"bold"} fz={"sm"}>Deskripsi Kolaborasi Inovasi</Text>
|
|
||||||
<CreateEditor
|
|
||||||
value={stateCreate.create.form.deskripsi}
|
|
||||||
onChange={(htmlContent) => stateCreate.create.form.deskripsi = htmlContent}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
<Group>
|
|
||||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Submit</Button>
|
|
||||||
</Group>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default CreateProgramKreatifDesa;
|
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import LayoutTabsKolaborasi from './_lib/layoutTabs';
|
||||||
|
|
||||||
|
function Layout({ children }: { children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<LayoutTabsKolaborasi>
|
||||||
|
{children}
|
||||||
|
</LayoutTabsKolaborasi>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Layout;
|
||||||
@@ -2,41 +2,34 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import EditEditor from "@/app/admin/(dashboard)/_com/editEditor";
|
import EditEditor from "@/app/admin/(dashboard)/_com/editEditor";
|
||||||
|
import kolaborasiInovasiState from "@/app/admin/(dashboard)/_state/inovasi/kolaborasi-inovasi";
|
||||||
import colors from "@/con/colors";
|
import colors from "@/con/colors";
|
||||||
import ApiFetch from "@/lib/api-fetch";
|
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
Center,
|
|
||||||
FileInput,
|
|
||||||
Image,
|
|
||||||
Paper,
|
Paper,
|
||||||
Stack,
|
Stack,
|
||||||
Text,
|
Text,
|
||||||
TextInput,
|
TextInput,
|
||||||
Title
|
Title
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import { IconArrowBack, IconImageInPicture } from "@tabler/icons-react";
|
import { IconArrowBack } from "@tabler/icons-react";
|
||||||
import { useParams, useRouter } from "next/navigation";
|
import { useParams, useRouter } from "next/navigation";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
import { useProxy } from "valtio/utils";
|
import { useProxy } from "valtio/utils";
|
||||||
import kolaborasiInovasiState from "@/app/admin/(dashboard)/_state/inovasi/kolaborasi-inovasi";
|
|
||||||
|
|
||||||
function EditKolaborasiInovasi() {
|
function EditKolaborasiInovasi() {
|
||||||
const kolaborasiState = useProxy(kolaborasiInovasiState);
|
const kolaborasiState = useProxy(kolaborasiInovasiState);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
|
|
||||||
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
|
||||||
const [file, setFile] = useState<File | null>(null);
|
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
name: kolaborasiState.update.form.name || '',
|
name: kolaborasiState.update.form.name || '',
|
||||||
deskripsi: kolaborasiState.update.form.deskripsi || '',
|
deskripsi: kolaborasiState.update.form.deskripsi || '',
|
||||||
tahun: kolaborasiState.update.form.tahun || '',
|
tahun: kolaborasiState.update.form.tahun || '',
|
||||||
slug: kolaborasiState.update.form.slug || '',
|
slug: kolaborasiState.update.form.slug || '',
|
||||||
kolaborator: kolaborasiState.update.form.kolaborator || '',
|
kolaborator: kolaborasiState.update.form.kolaborator || '',
|
||||||
imageId: kolaborasiState.update.form.imageId || ''
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Load berita by id saat pertama kali
|
// Load berita by id saat pertama kali
|
||||||
@@ -54,13 +47,7 @@ function EditKolaborasiInovasi() {
|
|||||||
tahun: data.tahun || '',
|
tahun: data.tahun || '',
|
||||||
slug: data.slug || '',
|
slug: data.slug || '',
|
||||||
kolaborator: data.kolaborator || '',
|
kolaborator: data.kolaborator || '',
|
||||||
imageId: data.imageId || '',
|
|
||||||
});
|
});
|
||||||
if (data.image) {
|
|
||||||
if (data?.image?.link) {
|
|
||||||
setPreviewImage(data.image.link);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error loading berita:", error);
|
console.error("Error loading berita:", error);
|
||||||
@@ -82,22 +69,7 @@ function EditKolaborasiInovasi() {
|
|||||||
tahun: Number(formData.tahun),
|
tahun: Number(formData.tahun),
|
||||||
slug: formData.slug,
|
slug: formData.slug,
|
||||||
kolaborator: formData.kolaborator,
|
kolaborator: formData.kolaborator,
|
||||||
imageId: formData.imageId // Keep existing imageId if not changed
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Jika ada file baru, upload
|
|
||||||
if (file) {
|
|
||||||
const res = await ApiFetch.api.fileStorage.create.post({ file, name: file.name });
|
|
||||||
const uploaded = res.data?.data;
|
|
||||||
|
|
||||||
if (!uploaded?.id) {
|
|
||||||
return toast.error("Gagal upload gambar");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update imageId in global state
|
|
||||||
kolaborasiState.update.form.imageId = uploaded.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
await kolaborasiState.update.submit();
|
await kolaborasiState.update.submit();
|
||||||
toast.success("Berita berhasil diperbarui!");
|
toast.success("Berita berhasil diperbarui!");
|
||||||
router.push("/admin/inovasi/kolaborasi-inovasi");
|
router.push("/admin/inovasi/kolaborasi-inovasi");
|
||||||
@@ -144,28 +116,6 @@ function EditKolaborasiInovasi() {
|
|||||||
label={<Text fz={"sm"} fw={"bold"}>Kolaborator</Text>}
|
label={<Text fz={"sm"} fw={"bold"}>Kolaborator</Text>}
|
||||||
placeholder="masukkan kolaborator"
|
placeholder="masukkan kolaborator"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FileInput
|
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Upload Gambar Baru (Opsional)</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>
|
<Box>
|
||||||
<Text fz={"sm"} fw={"bold"}>Konten</Text>
|
<Text fz={"sm"} fw={"bold"}>Konten</Text>
|
||||||
<EditEditor
|
<EditEditor
|
||||||
@@ -1,15 +1,15 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
import { Box, Button, Flex, Image, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
import { Box, Button, Flex, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
|
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
||||||
|
import kolaborasiInovasiState from '@/app/admin/(dashboard)/_state/inovasi/kolaborasi-inovasi';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
|
||||||
import kolaborasiInovasiState from '../../../_state/inovasi/kolaborasi-inovasi';
|
|
||||||
|
|
||||||
function DetailKolaborasiInovasi() {
|
function DetailKolaborasiInovasi() {
|
||||||
const kolaborasiState = useProxy(kolaborasiInovasiState)
|
const kolaborasiState = useProxy(kolaborasiInovasiState)
|
||||||
@@ -69,10 +69,6 @@ function DetailKolaborasiInovasi() {
|
|||||||
<Text fw={"bold"} fz={"lg"}>Deskripsi</Text>
|
<Text fw={"bold"} fz={"lg"}>Deskripsi</Text>
|
||||||
<Text fz={"lg"} dangerouslySetInnerHTML={{ __html: kolaborasiState.findUnique.data?.deskripsi }} />
|
<Text fz={"lg"} dangerouslySetInnerHTML={{ __html: kolaborasiState.findUnique.data?.deskripsi }} />
|
||||||
</Box>
|
</Box>
|
||||||
<Box>
|
|
||||||
<Text fw={"bold"} fz={"lg"}>Gambar</Text>
|
|
||||||
<Image w={{ base: 150, md: 150, lg: 150 }} src={kolaborasiState.findUnique.data?.image?.link} alt="gambar" />
|
|
||||||
</Box>
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fw={"bold"} fz={"lg"}>Kolaborator</Text>
|
<Text fw={"bold"} fz={"lg"}>Kolaborator</Text>
|
||||||
<Text fz={"lg"}>{kolaborasiState.findUnique.data?.kolaborator}</Text>
|
<Text fz={"lg"}>{kolaborasiState.findUnique.data?.kolaborator}</Text>
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
'use client'
|
||||||
|
import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
|
||||||
|
import kolaborasiInovasiState from '@/app/admin/(dashboard)/_state/inovasi/kolaborasi-inovasi';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
|
import { YearPickerInput } from '@mantine/dates';
|
||||||
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function CreateProgramKreatifDesa() {
|
||||||
|
const stateCreate = useProxy(kolaborasiInovasiState)
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
stateCreate.create.form = {
|
||||||
|
name: "",
|
||||||
|
tahun: 0,
|
||||||
|
slug: "",
|
||||||
|
deskripsi: "",
|
||||||
|
kolaborator: "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate slug from name
|
||||||
|
useEffect(() => {
|
||||||
|
const { name } = stateCreate.create.form;
|
||||||
|
if (name) {
|
||||||
|
const slug = name
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/[^\w\s-]/g, '')
|
||||||
|
.replace(/\s+/g, '-')
|
||||||
|
.replace(/-+/g, '-');
|
||||||
|
stateCreate.create.form.slug = slug;
|
||||||
|
}
|
||||||
|
}, [stateCreate.create.form.name]);
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
try {
|
||||||
|
// Submit data kolaborasi inovasi
|
||||||
|
await stateCreate.create.create();
|
||||||
|
|
||||||
|
// Reset form setelah submit
|
||||||
|
resetForm();
|
||||||
|
router.push("/admin/inovasi/kolaborasi-inovasi");
|
||||||
|
toast.success("Berhasil menambahkan kolaborasi inovasi");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error creating kolaborasi inovasi:", error);
|
||||||
|
toast.error("Terjadi kesalahan saat menyimpan data");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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={3}>Create Kolaborasi Inovasi</Title>
|
||||||
|
<TextInput
|
||||||
|
label={<Text fz={"sm"} fw={"bold"}>Nama Kolaborasi Inovasi</Text>}
|
||||||
|
placeholder="masukkan nama kolaborasi inovasi"
|
||||||
|
onChange={(val) => stateCreate.create.form.name = val.target.value}
|
||||||
|
/>
|
||||||
|
<YearPickerInput
|
||||||
|
clearable
|
||||||
|
value={stateCreate.create.form.tahun ? new Date(stateCreate.create.form.tahun, 0, 1) : null}
|
||||||
|
label="Tahun"
|
||||||
|
placeholder="Pilih tahun"
|
||||||
|
onChange={(dateString: string) => {
|
||||||
|
const year = dateString ? new Date(dateString).getFullYear() : 0;
|
||||||
|
stateCreate.create.form.tahun = year;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Box>
|
||||||
|
<Text fw={"bold"} fz={"sm"}>Deskripsi</Text>
|
||||||
|
<CreateEditor
|
||||||
|
value={stateCreate.create.form.deskripsi}
|
||||||
|
onChange={(val) => {
|
||||||
|
stateCreate.create.form.deskripsi = val;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<TextInput
|
||||||
|
onChange={(e) => stateCreate.create.form.kolaborator = e.currentTarget.value}
|
||||||
|
label={<Text fw={"bold"} fz={"sm"}>Kolaborator</Text>}
|
||||||
|
placeholder='Masukkan kolaborator'
|
||||||
|
/>
|
||||||
|
<Box>
|
||||||
|
<Text fw={"bold"} fz={"sm"}>Deskripsi Kolaborasi Inovasi</Text>
|
||||||
|
<CreateEditor
|
||||||
|
value={stateCreate.create.form.deskripsi}
|
||||||
|
onChange={(htmlContent) => stateCreate.create.form.deskripsi = htmlContent}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Group>
|
||||||
|
<Button bg={colors['blue-button']} onClick={handleSubmit}>Submit</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CreateProgramKreatifDesa;
|
||||||
@@ -3,11 +3,11 @@
|
|||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Center, Pagination, 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 { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
||||||
import HeaderSearch from '../../_com/header';
|
import HeaderSearch from '../../../_com/header';
|
||||||
import JudulList from '../../_com/judulList';
|
import JudulList from '../../../_com/judulList';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import kolaborasiInovasiState from '../../_state/inovasi/kolaborasi-inovasi';
|
import kolaborasiInovasiState from '../../../_state/inovasi/kolaborasi-inovasi';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
function KolaborasiInovasi() {
|
function KolaborasiInovasi() {
|
||||||
@@ -28,22 +28,21 @@ function KolaborasiInovasi() {
|
|||||||
|
|
||||||
function ListKolaborasiInovasi({ search }: { search: string }) {
|
function ListKolaborasiInovasi({ search }: { search: string }) {
|
||||||
const listState = useProxy(kolaborasiInovasiState)
|
const listState = useProxy(kolaborasiInovasiState)
|
||||||
const { data, loading, page, totalPages, load } = listState.findMany
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
useEffect(() => {
|
const {
|
||||||
load(page, 10)
|
data,
|
||||||
}, [page])
|
loading,
|
||||||
|
page,
|
||||||
|
totalPages,
|
||||||
|
load,
|
||||||
|
} = listState.findMany
|
||||||
|
|
||||||
const filteredData = (data || []).filter(item => {
|
useEffect(() => {
|
||||||
const keyword = search.toLowerCase();
|
load(page, 10, search)
|
||||||
return (
|
}, [page, search])
|
||||||
item.name.toLowerCase().includes(keyword) ||
|
|
||||||
item.deskripsi.toLowerCase().includes(keyword) ||
|
const filteredData = data || []
|
||||||
item.slug.toLowerCase().includes(keyword) ||
|
|
||||||
item.kolaborator.toLowerCase().includes(keyword)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (loading || !data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
@@ -64,11 +63,11 @@ function ListKolaborasiInovasi({ search }: { search: string }) {
|
|||||||
<Table striped withTableBorder withRowBorders>
|
<Table striped withTableBorder withRowBorders>
|
||||||
<TableThead>
|
<TableThead>
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh style={{ width: '2%', textAlign: 'center' }}>No</TableTh>
|
<TableTh>No</TableTh>
|
||||||
<TableTh style={{ width: '15%' }}>Nama Kolaborasi Inovasi</TableTh>
|
<TableTh>Nama Kolaborasi Inovasi</TableTh>
|
||||||
<TableTh style={{ width: '15%', textAlign: 'center' }}>Tahun</TableTh>
|
<TableTh>Tahun</TableTh>
|
||||||
<TableTh style={{ width: '20%' }}>Deskripsi Singkat</TableTh>
|
<TableTh>Deskripsi Singkat</TableTh>
|
||||||
<TableTh style={{ width: '5%', textAlign: 'center' }}>Detail</TableTh>
|
<TableTh>Detail</TableTh>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
</Table>
|
</Table>
|
||||||
@@ -89,21 +88,21 @@ function ListKolaborasiInovasi({ search }: { search: string }) {
|
|||||||
<Table striped withTableBorder withRowBorders>
|
<Table striped withTableBorder withRowBorders>
|
||||||
<TableThead>
|
<TableThead>
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh style={{ width: '1%', textAlign: 'center' }}>No</TableTh>
|
<TableTh>No</TableTh>
|
||||||
<TableTh style={{ width: '15%' }}>Nama Kolaborasi Inovasi</TableTh>
|
<TableTh>Nama Kolaborasi Inovasi</TableTh>
|
||||||
<TableTh style={{ width: '5%', textAlign: 'center' }}>Tahun</TableTh>
|
<TableTh>Tahun</TableTh>
|
||||||
<TableTh style={{ width: '20%' }}>Deskripsi Singkat</TableTh>
|
<TableTh>Deskripsi Singkat</TableTh>
|
||||||
<TableTh style={{ width: '5%', textAlign: 'center' }}>Detail</TableTh>
|
<TableTh>Detail</TableTh>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
<TableTbody>
|
<TableTbody>
|
||||||
{filteredData.map((item, index) => (
|
{filteredData.map((item, index) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd style={{ width: '1%', textAlign: 'center' }}>{index + 1}</TableTd>
|
<TableTd>{index + 1}</TableTd>
|
||||||
<TableTd style={{ width: '15%' }}>{item.name}</TableTd>
|
<TableTd>{item.name}</TableTd>
|
||||||
<TableTd style={{ width: '5%', textAlign: 'center' }}>{item.tahun}</TableTd>
|
<TableTd>{item.tahun}</TableTd>
|
||||||
<TableTd style={{ width: '20%' }}>{item.slug}</TableTd>
|
<TableTd>{item.slug}</TableTd>
|
||||||
<TableTd style={{ width: '5%', textAlign: 'center' }}>
|
<TableTd>
|
||||||
<Button onClick={() => router.push(`/admin/inovasi/kolaborasi-inovasi/${item.id}`)}>
|
<Button onClick={() => router.push(`/admin/inovasi/kolaborasi-inovasi/${item.id}`)}>
|
||||||
<IconDeviceImac size={20} />
|
<IconDeviceImac size={20} />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -116,17 +115,13 @@ function ListKolaborasiInovasi({ search }: { search: string }) {
|
|||||||
<Center>
|
<Center>
|
||||||
<Pagination
|
<Pagination
|
||||||
value={page}
|
value={page}
|
||||||
onChange={(newPage) => {
|
onChange={(newPage) => load(newPage)} // ini penting!
|
||||||
load(newPage, 10);
|
|
||||||
window.scrollTo(0, 0);
|
|
||||||
}}
|
|
||||||
total={totalPages}
|
total={totalPages}
|
||||||
mt="md"
|
mt="md"
|
||||||
mb="md"
|
mb="md"
|
||||||
/>
|
/>
|
||||||
</Center>
|
</Center>
|
||||||
|
</Box >
|
||||||
</Box>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
'use client'
|
||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
import mitraKolaborasi from '@/app/admin/(dashboard)/_state/inovasi/mitra-kolaborasi';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
|
import { Box, Button, Center, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
|
import { Dropzone } from '@mantine/dropzone';
|
||||||
|
import { IconArrowBack, IconImageInPicture, 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 EditFoto() {
|
||||||
|
const state = useProxy(mitraKolaborasi)
|
||||||
|
const router = useRouter();
|
||||||
|
const params = useParams();
|
||||||
|
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
||||||
|
const [file, setFile] = useState<File | null>(null);
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
name: state.update.form.name || '',
|
||||||
|
imageId: state.update.form.imageId || ''
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const loadFoto = async () => {
|
||||||
|
const id = params?.id as string;
|
||||||
|
if (!id) return;
|
||||||
|
try {
|
||||||
|
const data = await state.update.load(id);
|
||||||
|
if (data) {
|
||||||
|
setFormData({
|
||||||
|
name: data.name || '',
|
||||||
|
imageId: data.imageId || ''
|
||||||
|
});
|
||||||
|
if (data?.image?.link) {
|
||||||
|
setPreviewImage(data.image.link);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading foto:', error);
|
||||||
|
toast.error('Gagal memuat data foto');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
loadFoto();
|
||||||
|
}, [params?.id]);
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
try {
|
||||||
|
state.update.form = {
|
||||||
|
...state.update.form,
|
||||||
|
name: formData.name,
|
||||||
|
imageId: formData.imageId
|
||||||
|
};
|
||||||
|
if (file) {
|
||||||
|
const res = await ApiFetch.api.fileStorage.create.post({
|
||||||
|
file,
|
||||||
|
name: file.name,
|
||||||
|
});
|
||||||
|
const uploaded = res.data?.data;
|
||||||
|
if (!uploaded?.id) {
|
||||||
|
return toast.error("Gagal upload gambar");
|
||||||
|
}
|
||||||
|
state.update.form.imageId = uploaded.id;
|
||||||
|
}
|
||||||
|
await state.update.update();
|
||||||
|
toast.success('Mitra berhasil diperbarui!');
|
||||||
|
router.push('/admin/inovasi/kolaborasi-inovasi/mitra-kolaborasi');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error updating mitra:', error);
|
||||||
|
toast.error('Terjadi kesalahan saat memperbarui mitra');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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}>Edit Mitra</Title>
|
||||||
|
<TextInput
|
||||||
|
label={<Text fw={"bold"} fz={"sm"}>Nama Mitra</Text>}
|
||||||
|
placeholder='Masukkan nama mitra'
|
||||||
|
value={formData.name}
|
||||||
|
onChange={(e) =>
|
||||||
|
(formData.name = e.target.value)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Box>
|
||||||
|
<Text>Upload Foto</Text>
|
||||||
|
<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>
|
||||||
|
|
||||||
|
{previewImage ? (
|
||||||
|
<Image alt="" src={previewImage} w={200} h={200} />
|
||||||
|
) : (
|
||||||
|
<Center w={200} h={200} bg={"gray"}>
|
||||||
|
<IconImageInPicture />
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
<Group>
|
||||||
|
<Button onClick={handleSubmit} bg={colors['blue-button']}>Submit</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EditFoto;
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
'use client'
|
||||||
|
import mitraKolaborasi from '@/app/admin/(dashboard)/_state/inovasi/mitra-kolaborasi';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
|
import { Box, Button, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
|
import { 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';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function CreateFoto() {
|
||||||
|
const state = useProxy(mitraKolaborasi)
|
||||||
|
const router = useRouter();
|
||||||
|
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
||||||
|
const [file, setFile] = useState<File | null>(null);
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
state.create.form = {
|
||||||
|
name: "",
|
||||||
|
imageId: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
setPreviewImage(null)
|
||||||
|
setFile(null)
|
||||||
|
};
|
||||||
|
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
|
state.create.form.imageId = uploaded.id;
|
||||||
|
await state.create.create();
|
||||||
|
resetForm();
|
||||||
|
router.push("/admin/inovasi/kolaborasi-inovasi/mitra-kolaborasi")
|
||||||
|
};
|
||||||
|
|
||||||
|
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 Mitra</Title>
|
||||||
|
<TextInput
|
||||||
|
label={<Text fw={"bold"} fz={"sm"}>Nama Mitra</Text>}
|
||||||
|
placeholder='Masukkan nama mitra'
|
||||||
|
value={state.create.form.name}
|
||||||
|
onChange={(val) => {
|
||||||
|
state.create.form.name = val.target.value;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<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>
|
||||||
|
<Group>
|
||||||
|
<Button onClick={handleSubmit} bg={colors['blue-button']}>Submit</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CreateFoto;
|
||||||
@@ -0,0 +1,187 @@
|
|||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
'use client'
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Center, Image, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||||
|
import { IconEdit, IconSearch, IconX } 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 mitraKolaborasi from '../../../_state/inovasi/mitra-kolaborasi';
|
||||||
|
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||||
|
|
||||||
|
function MitraKolaborasi() {
|
||||||
|
const [search, setSearch] = useState('');
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<HeaderSearch
|
||||||
|
title='Mitra Kolaborasi'
|
||||||
|
placeholder='pencarian'
|
||||||
|
searchIcon={<IconSearch size={20} />}
|
||||||
|
value={search}
|
||||||
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
<ListMitraKolaborasi search={search} />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ListMitraKolaborasi({ search }: { search: string }) {
|
||||||
|
const listState = useProxy(mitraKolaborasi)
|
||||||
|
const router = useRouter();
|
||||||
|
const [modalHapus, setModalHapus] = useState(false)
|
||||||
|
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||||
|
|
||||||
|
const handleHapus = () => {
|
||||||
|
if (selectedId) {
|
||||||
|
mitraKolaborasi.delete.byId(selectedId)
|
||||||
|
setModalHapus(false)
|
||||||
|
setSelectedId(null)
|
||||||
|
router.push("/admin/inovasi/kolaborasi-inovasi")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
loading,
|
||||||
|
page,
|
||||||
|
totalPages,
|
||||||
|
load,
|
||||||
|
} = listState.findMany
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
load(page, 10, search)
|
||||||
|
}, [page, search])
|
||||||
|
|
||||||
|
const filteredData = data || []
|
||||||
|
|
||||||
|
if (loading || !data) {
|
||||||
|
return (
|
||||||
|
<Stack py={10}>
|
||||||
|
<Skeleton height={650} />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (data.length === 0) {
|
||||||
|
return (
|
||||||
|
<Box py={10}>
|
||||||
|
<Paper p="md" >
|
||||||
|
<Stack>
|
||||||
|
<JudulList
|
||||||
|
title='List Mitra Kolaborasi'
|
||||||
|
href='/admin/inovasi/kolaborasi-inovasi/mitra-kolaborasi/create'
|
||||||
|
/>
|
||||||
|
<Table striped withTableBorder withRowBorders>
|
||||||
|
<TableThead>
|
||||||
|
<TableTr>
|
||||||
|
<TableTh>No</TableTh>
|
||||||
|
<TableTh>Nama Mitra</TableTh>
|
||||||
|
<TableTh>Image</TableTh>
|
||||||
|
<TableTh>Delete</TableTh>
|
||||||
|
<TableTh>Edit</TableTh>
|
||||||
|
</TableTr>
|
||||||
|
</TableThead>
|
||||||
|
</Table>
|
||||||
|
<Text ta="center">Tidak ada data mitra kolaborasi yang tersedia</Text>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box >
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box py={10}>
|
||||||
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
|
<JudulList
|
||||||
|
title='List Mitra Kolaborasi'
|
||||||
|
href='/admin/inovasi/kolaborasi-inovasi/mitra-kolaborasi/create'
|
||||||
|
/>
|
||||||
|
<Table striped withTableBorder withRowBorders>
|
||||||
|
<TableThead>
|
||||||
|
<TableTr>
|
||||||
|
<TableTh>No</TableTh>
|
||||||
|
<TableTh>Nama Mitra</TableTh>
|
||||||
|
<TableTh>Image</TableTh>
|
||||||
|
<TableTh>Delete</TableTh>
|
||||||
|
<TableTh>Edit</TableTh>
|
||||||
|
</TableTr>
|
||||||
|
</TableThead>
|
||||||
|
<TableTbody>
|
||||||
|
{filteredData.map((item, index) => (
|
||||||
|
<TableTr key={item.id}>
|
||||||
|
<TableTd>{index + 1}</TableTd>
|
||||||
|
<TableTd>{item.name}</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Box style={{
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
position: 'relative',
|
||||||
|
overflow: 'hidden',
|
||||||
|
borderRadius: 4
|
||||||
|
}}>
|
||||||
|
<Image
|
||||||
|
src={item.image?.link || ''}
|
||||||
|
alt={item.name}
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
objectFit: 'cover',
|
||||||
|
objectPosition: 'center'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
if (item) {
|
||||||
|
setSelectedId(item.id);
|
||||||
|
setModalHapus(true);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={mitraKolaborasi.delete.loading || !item}
|
||||||
|
color={"red"}
|
||||||
|
>
|
||||||
|
<IconX size={20} />
|
||||||
|
</Button>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
if (item) {
|
||||||
|
router.push(`/admin/inovasi/kolaborasi-inovasi/mitra-kolaborasi/${item.id}`);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={!item}
|
||||||
|
color={"green"}
|
||||||
|
>
|
||||||
|
<IconEdit size={20} />
|
||||||
|
</Button>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
))}
|
||||||
|
</TableTbody>
|
||||||
|
</Table>
|
||||||
|
</Paper>
|
||||||
|
<Center>
|
||||||
|
<Pagination
|
||||||
|
value={page}
|
||||||
|
onChange={(newPage) => load(newPage)} // ini penting!
|
||||||
|
total={totalPages}
|
||||||
|
mt="md"
|
||||||
|
mb="md"
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
{/* Modal Konfirmasi Hapus */}
|
||||||
|
<ModalKonfirmasiHapus
|
||||||
|
opened={modalHapus}
|
||||||
|
onClose={() => setModalHapus(false)}
|
||||||
|
onConfirm={handleHapus}
|
||||||
|
text='Apakah anda yakin ingin menghapus mitra kolaborasi ini?'
|
||||||
|
/>
|
||||||
|
</Box >
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MitraKolaborasi;
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
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 { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
||||||
import HeaderSearch from '../../_com/header';
|
import HeaderSearch from '../../_com/header';
|
||||||
import JudulList from '../../_com/judulList';
|
import JudulList from '../../_com/judulList';
|
||||||
@@ -30,19 +30,21 @@ function ListTipsKeamanan({ search }: { search: string }) {
|
|||||||
const stateKeamanan = useProxy(tipsKeamananState)
|
const stateKeamanan = useProxy(tipsKeamananState)
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
page,
|
||||||
|
totalPages,
|
||||||
|
loading,
|
||||||
|
load,
|
||||||
|
} = stateKeamanan.findMany
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
stateKeamanan.findMany.load()
|
load(page, 10, search)
|
||||||
}, [])
|
}, [page, search])
|
||||||
|
|
||||||
const filteredData = (stateKeamanan.findMany.data || []).filter(item => {
|
const filteredData = data || []
|
||||||
const keyword = search.toLowerCase();
|
|
||||||
return (
|
|
||||||
item.judul.toLowerCase().includes(keyword) ||
|
|
||||||
item.deskripsi.toLowerCase().includes(keyword)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!stateKeamanan.findMany.data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={10}>
|
||||||
<Skeleton h={500} />
|
<Skeleton h={500} />
|
||||||
@@ -67,9 +69,15 @@ function ListTipsKeamanan({ search }: { search: string }) {
|
|||||||
<TableTbody>
|
<TableTbody>
|
||||||
{filteredData.map((item) => (
|
{filteredData.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd>{item.judul}</TableTd>
|
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Text fz={"xs"} dangerouslySetInnerHTML={{__html: item.deskripsi}} />
|
<Box w={150}>
|
||||||
|
<Text fz={"md"} truncate={"end"} lineClamp={1}>{item.judul}</Text>
|
||||||
|
</Box>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Box w={250}>
|
||||||
|
<Text fz={"md"} truncate={"end"} lineClamp={1} dangerouslySetInnerHTML={{__html: item.deskripsi}} />
|
||||||
|
</Box>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button onClick={() => router.push(`/admin/keamanan/tips-keamanan/${item.id}`)}>
|
<Button onClick={() => router.push(`/admin/keamanan/tips-keamanan/${item.id}`)}>
|
||||||
@@ -81,6 +89,14 @@ function ListTipsKeamanan({ search }: { search: string }) {
|
|||||||
</TableTbody>
|
</TableTbody>
|
||||||
</Table>
|
</Table>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
<Center>
|
||||||
|
<Pagination
|
||||||
|
value={page}
|
||||||
|
onChange={(newPage) => load(newPage)}
|
||||||
|
total={totalPages}
|
||||||
|
my="md"
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ function DetailFasilitasKesehatan() {
|
|||||||
<GridCol span={12}>
|
<GridCol span={12}>
|
||||||
<Text fz={"xl"} fw={"bold"}>Detail Fasilitas Kesehatan</Text>
|
<Text fz={"xl"} fw={"bold"}>Detail Fasilitas Kesehatan</Text>
|
||||||
</GridCol>
|
</GridCol>
|
||||||
<GridCol span={12}>
|
{/* <GridCol span={12}>
|
||||||
<Flex gap={"xs"}>
|
<Flex gap={"xs"}>
|
||||||
<Button color={colors['blue-button']} onClick={() => router.push(`/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/${params?.id}/dokter-tenaga-medis`)}>
|
<Button color={colors['blue-button']} onClick={() => router.push(`/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/${params?.id}/dokter-tenaga-medis`)}>
|
||||||
Tambah Dokter
|
Tambah Dokter
|
||||||
@@ -60,7 +60,7 @@ function DetailFasilitasKesehatan() {
|
|||||||
Tambah Layanan
|
Tambah Layanan
|
||||||
</Button>
|
</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
</GridCol>
|
</GridCol> */}
|
||||||
</Grid>
|
</Grid>
|
||||||
{stateFasilitasKesehatan.findUnique.data ? (
|
{stateFasilitasKesehatan.findUnique.data ? (
|
||||||
<Paper bg={colors['BG-trans']} p={'md'}>
|
<Paper bg={colors['BG-trans']} p={'md'}>
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
|
||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { ActionIcon, Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
import { ActionIcon, Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconDeviceImacCog, IconFile, IconSearch } from '@tabler/icons-react';
|
import { IconDeviceImacCog, IconFile, IconSearch } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import HeaderSearch from '../../_com/header';
|
import HeaderSearch from '../../_com/header';
|
||||||
import JudulList from '../../_com/judulList';
|
import JudulList from '../../_com/judulList';
|
||||||
@@ -30,19 +30,22 @@ function APBDes() {
|
|||||||
function ListAPBDes({ search }: { search: string }) {
|
function ListAPBDes({ search }: { search: string }) {
|
||||||
const listState = useProxy(apbdes)
|
const listState = useProxy(apbdes)
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
useEffect(() => {
|
|
||||||
listState.findMany.load()
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const filteredData = (listState.findMany.data || []).filter(item => {
|
const {
|
||||||
const keyword = search.toLowerCase();
|
data,
|
||||||
return (
|
page,
|
||||||
item.name.toLowerCase().includes(keyword) ||
|
totalPages,
|
||||||
item.jumlah.toLowerCase().includes(keyword)
|
loading,
|
||||||
)
|
load,
|
||||||
});
|
} = listState.findMany
|
||||||
|
|
||||||
if (!listState.findMany.data) {
|
useShallowEffect(() => {
|
||||||
|
load(page, 10, search)
|
||||||
|
}, [page, search])
|
||||||
|
|
||||||
|
const filteredData = data || []
|
||||||
|
|
||||||
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={10}>
|
||||||
<Skeleton h={500} />
|
<Skeleton h={500} />
|
||||||
@@ -88,7 +91,7 @@ function ListAPBDes({ search }: { search: string }) {
|
|||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
variant='transparent'
|
variant='transparent'
|
||||||
>
|
>
|
||||||
<IconFile size={25} color={colors['blue-button']}/>
|
<IconFile size={25} color={colors['blue-button']} />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
) : (
|
) : (
|
||||||
<Text>Tidak ada dokumen tersedia</Text>
|
<Text>Tidak ada dokumen tersedia</Text>
|
||||||
@@ -106,6 +109,14 @@ function ListAPBDes({ search }: { search: string }) {
|
|||||||
</Box>
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
<Center>
|
||||||
|
<Pagination
|
||||||
|
value={page}
|
||||||
|
onChange={(newPage) => load(newPage)}
|
||||||
|
total={totalPages}
|
||||||
|
my={"md"}
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
</Box>
|
</Box>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,60 +1,101 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
|
||||||
import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
|
|
||||||
import { usePathname, useRouter } from 'next/navigation';
|
import { usePathname, useRouter } from 'next/navigation';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title, Tooltip } from '@mantine/core';
|
||||||
|
import { IconList, IconCategory } from '@tabler/icons-react';
|
||||||
|
|
||||||
function LayoutTabs({ children }: { children: React.ReactNode }) {
|
function LayoutTabs({ children }: { children: React.ReactNode }) {
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
const pathname = usePathname()
|
const pathname = usePathname();
|
||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{
|
{
|
||||||
label: "List Desa Anti Korupsi",
|
label: "List Desa Anti Korupsi",
|
||||||
value: "listDesaAntiKorupsi",
|
value: "listDesaAntiKorupsi",
|
||||||
href: "/admin/landing-page/desa-anti-korupsi/list-desa-anti-korupsi"
|
href: "/admin/landing-page/desa-anti-korupsi/list-desa-anti-korupsi",
|
||||||
|
icon: <IconList size={18} stroke={1.8} />,
|
||||||
|
tooltip: "Kelola daftar program desa anti korupsi",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Kategori Desa Anti Korupsi",
|
label: "Kategori Desa Anti Korupsi",
|
||||||
value: "kategoriDesaAntiKorupsi",
|
value: "kategoriDesaAntiKorupsi",
|
||||||
href: "/admin/landing-page/desa-anti-korupsi/kategori-desa-anti-korupsi"
|
href: "/admin/landing-page/desa-anti-korupsi/kategori-desa-anti-korupsi",
|
||||||
|
icon: <IconCategory size={18} stroke={1.8} />,
|
||||||
|
tooltip: "Kelola kategori desa anti korupsi",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
const curentTab = tabs.find(tab => tab.href === pathname)
|
|
||||||
const [activeTab, setActiveTab] = useState<string | null>(curentTab?.value || tabs[0].value);
|
const currentTab = tabs.find(tab => tab.href === pathname);
|
||||||
|
const [activeTab, setActiveTab] = useState<string | null>(currentTab?.value || tabs[0].value);
|
||||||
|
|
||||||
const handleTabChange = (value: string | null) => {
|
const handleTabChange = (value: string | null) => {
|
||||||
const tab = tabs.find(t => t.value === value)
|
const tab = tabs.find(t => t.value === value);
|
||||||
if (tab) {
|
if (tab) {
|
||||||
router.push(tab.href)
|
router.push(tab.href);
|
||||||
}
|
}
|
||||||
setActiveTab(value)
|
setActiveTab(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const match = tabs.find(tab => tab.href === pathname)
|
const match = tabs.find(tab => tab.href === pathname);
|
||||||
if (match) {
|
if (match) {
|
||||||
setActiveTab(match.value)
|
setActiveTab(match.value);
|
||||||
}
|
}
|
||||||
}, [pathname])
|
}, [pathname]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack>
|
<Stack gap="lg">
|
||||||
<Title order={3}>Desa Anti Korupsi</Title>
|
<Title order={2} fw={700} style={{ color: "#1A1B1E" }}>
|
||||||
<Tabs color={colors['blue-button']} variant='pills' value={activeTab} onChange={handleTabChange}>
|
Desa Anti Korupsi
|
||||||
<TabsList p={"xs"} bg={"#BBC8E7FF"}>
|
</Title>
|
||||||
{tabs.map((e, i) => (
|
<Tabs
|
||||||
<TabsTab key={i} value={e.value}>{e.label}</TabsTab>
|
variant="pills"
|
||||||
|
value={activeTab}
|
||||||
|
onChange={handleTabChange}
|
||||||
|
radius="lg"
|
||||||
|
keepMounted={false}
|
||||||
|
>
|
||||||
|
<TabsList
|
||||||
|
p="sm"
|
||||||
|
style={{
|
||||||
|
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
|
||||||
|
borderRadius: "1rem",
|
||||||
|
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{tabs.map((tab, i) => (
|
||||||
|
<Tooltip key={i} label={tab.tooltip} position="bottom" withArrow>
|
||||||
|
<TabsTab
|
||||||
|
value={tab.value}
|
||||||
|
leftSection={tab.icon}
|
||||||
|
style={{
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: "0.9rem",
|
||||||
|
transition: "all 0.2s ease",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{tab.label}
|
||||||
|
</TabsTab>
|
||||||
|
</Tooltip>
|
||||||
))}
|
))}
|
||||||
</TabsList>
|
</TabsList>
|
||||||
{tabs.map((e, i) => (
|
|
||||||
<TabsPanel key={i} value={e.value}>
|
{tabs.map((tab, i) => (
|
||||||
{/* Konten dummy, bisa diganti tergantung routing */}
|
<TabsPanel
|
||||||
<></>
|
key={i}
|
||||||
|
value={tab.value}
|
||||||
|
style={{
|
||||||
|
padding: "1.5rem",
|
||||||
|
background: "linear-gradient(180deg, #ffffff, #f5f6fa)",
|
||||||
|
borderRadius: "1rem",
|
||||||
|
boxShadow: "0 4px 16px rgba(0,0,0,0.05)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
</TabsPanel>
|
</TabsPanel>
|
||||||
))}
|
))}
|
||||||
</Tabs>
|
</Tabs>
|
||||||
{children}
|
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,14 +2,14 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import korupsiState from '@/app/admin/(dashboard)/_state/landing-page/desa-anti-korupsi';
|
import korupsiState from '@/app/admin/(dashboard)/_state/landing-page/desa-anti-korupsi';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import { Box, Button, Group, Paper, Stack, TextInput, Title, Tooltip } from '@mantine/core';
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
function EditKategoriDesaAntiKorupsi() {
|
export default function EditKategoriDesaAntiKorupsi() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const id = params?.id as string;
|
const id = params?.id as string;
|
||||||
@@ -18,16 +18,17 @@ function EditKategoriDesaAntiKorupsi() {
|
|||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
name: "",
|
name: "",
|
||||||
});
|
});
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadKategorikegiatan = async () => {
|
const loadKategori = async () => {
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
|
setIsLoading(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const data = await stateKategori.edit.load(id);
|
const data = await stateKategori.edit.load(id);
|
||||||
|
|
||||||
if (data) {
|
if (data) {
|
||||||
// pastikan id-nya masuk ke state edit
|
|
||||||
stateKategori.edit.id = id;
|
stateKategori.edit.id = id;
|
||||||
setFormData({
|
setFormData({
|
||||||
name: data.name || '',
|
name: data.name || '',
|
||||||
@@ -36,63 +37,88 @@ function EditKategoriDesaAntiKorupsi() {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error loading kategori desa anti korupsi:", error);
|
console.error("Error loading kategori desa anti korupsi:", error);
|
||||||
toast.error("Gagal memuat data kategori desa anti korupsi");
|
toast.error("Gagal memuat data kategori desa anti korupsi");
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
loadKategorikegiatan();
|
loadKategori();
|
||||||
}, [id]);
|
}, [id]);
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
try {
|
if (!formData.name.trim()) {
|
||||||
if (!formData.name.trim()) {
|
return toast.error('Nama kategori tidak boleh kosong');
|
||||||
toast.error('Nama kategori desa anti korupsi tidak boleh kosong');
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
setIsLoading(true);
|
||||||
stateKategori.edit.form = {
|
stateKategori.edit.form = {
|
||||||
name: formData.name.trim(),
|
name: formData.name.trim(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Safety check tambahan: pastikan ID tidak kosong
|
|
||||||
if (!stateKategori.edit.id) {
|
if (!stateKategori.edit.id) {
|
||||||
stateKategori.edit.id = id; // fallback
|
stateKategori.edit.id = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
const success = await stateKategori.edit.update();
|
await stateKategori.edit.update();
|
||||||
|
toast.success('Kategori berhasil diperbarui');
|
||||||
if (success) {
|
router.push("/admin/landing-page/desa-anti-korupsi/kategori-desa-anti-korupsi");
|
||||||
router.push("/admin/landing-page/desa-anti-korupsi/kategori-desa-anti-korupsi");
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error updating kategori desa anti korupsi:", error);
|
console.error("Error updating kategori desa anti korupsi:", error);
|
||||||
// toast akan ditampilkan dari fungsi update
|
toast.error(error instanceof Error ? error.message : 'Gagal memperbarui kategori');
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Box mb={10}>
|
<Group mb="md">
|
||||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
<Tooltip label="Kembali ke halaman sebelumnya" color="blue" withArrow>
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
</Button>
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
</Box>
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Title order={4} ml="sm" c="dark">
|
||||||
|
Edit Kategori Desa Anti Korupsi
|
||||||
|
</Title>
|
||||||
|
</Group>
|
||||||
|
|
||||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
<Paper
|
||||||
<Stack gap={"xs"}>
|
w={{ base: '100%', md: '50%' }}
|
||||||
<Title order={4}>Edit Kategori Desa Anti Korupsi</Title>
|
bg={colors['white-1']}
|
||||||
|
p="lg"
|
||||||
|
radius="md"
|
||||||
|
shadow="sm"
|
||||||
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
<TextInput
|
<TextInput
|
||||||
|
label="Nama Kategori"
|
||||||
|
placeholder="Masukkan nama kategori"
|
||||||
value={formData.name}
|
value={formData.name}
|
||||||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Nama Kategori Desa Anti Korupsi</Text>}
|
required
|
||||||
placeholder='Masukkan nama kategori desa anti korupsi'
|
disabled={isLoading}
|
||||||
/>
|
/>
|
||||||
<Group>
|
|
||||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Submit</Button>
|
<Group justify="right" mt="md">
|
||||||
|
<Button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
loading={isLoading}
|
||||||
|
style={{
|
||||||
|
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||||
|
color: '#fff',
|
||||||
|
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default EditKategoriDesaAntiKorupsi;
|
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import { Box, Button, Group, Paper, Stack, TextInput, Title, Tooltip } from '@mantine/core';
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import korupsiState from '../../../../_state/landing-page/desa-anti-korupsi';
|
import korupsiState from '../../../../_state/landing-page/desa-anti-korupsi';
|
||||||
|
|
||||||
function CreateKategoriDesaAntiKorupsi() {
|
export default function CreateKategoriDesaAntiKorupsi() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const stateKategori = useProxy(korupsiState.kategoriDesaAntiKorupsi)
|
const stateKategori = useProxy(korupsiState.kategoriDesaAntiKorupsi);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
stateKategori.findMany.load();
|
stateKategori.findMany.load();
|
||||||
@@ -20,42 +20,64 @@ function CreateKategoriDesaAntiKorupsi() {
|
|||||||
stateKategori.create.form = {
|
stateKategori.create.form = {
|
||||||
name: "",
|
name: "",
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
|
if (!stateKategori.create.form.name) {
|
||||||
|
return alert('Nama kategori harus diisi');
|
||||||
|
}
|
||||||
|
|
||||||
await stateKategori.create.create();
|
await stateKategori.create.create();
|
||||||
resetForm();
|
resetForm();
|
||||||
router.push("/admin/landing-page/desa-anti-korupsi/kategori-desa-anti-korupsi")
|
router.push("/admin/landing-page/desa-anti-korupsi/kategori-desa-anti-korupsi");
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Box>
|
<Group mb="md">
|
||||||
<Box mb={10}>
|
<Tooltip label="Kembali ke halaman sebelumnya" color="blue" withArrow>
|
||||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Tooltip>
|
||||||
|
<Title order={4} ml="sm" c="dark">
|
||||||
|
Tambah Kategori Desa Anti Korupsi
|
||||||
|
</Title>
|
||||||
|
</Group>
|
||||||
|
|
||||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
<Paper
|
||||||
<Stack gap={"xs"}>
|
w={{ base: '100%', md: '50%' }}
|
||||||
<Title order={4}>Create Kategori Desa Anti Korupsi</Title>
|
bg={colors['white-1']}
|
||||||
<TextInput
|
p="lg"
|
||||||
value={stateKategori.create.form.name}
|
radius="md"
|
||||||
onChange={(val) => {
|
shadow="sm"
|
||||||
stateKategori.create.form.name = val.target.value;
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
|
<TextInput
|
||||||
|
label="Nama Kategori"
|
||||||
|
placeholder="Masukkan nama kategori"
|
||||||
|
value={stateKategori.create.form.name || ''}
|
||||||
|
onChange={(e) => (stateKategori.create.form.name = e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Group justify="right" mt="md">
|
||||||
|
<Button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
style={{
|
||||||
|
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||||
|
color: '#fff',
|
||||||
|
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||||
}}
|
}}
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Nama Kategori Desa Anti Korupsi</Text>}
|
>
|
||||||
placeholder='Masukkan nama kategori desa anti korupsi'
|
Simpan
|
||||||
/>
|
</Button>
|
||||||
<Group>
|
</Group>
|
||||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Submit</Button>
|
</Stack>
|
||||||
</Group>
|
</Paper>
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CreateKategoriDesaAntiKorupsi;
|
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
|
||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
|
import { Box, Button, Center, Group, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title, Tooltip } from '@mantine/core';
|
||||||
import { IconEdit, IconSearch, IconX } from '@tabler/icons-react';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import HeaderSearch from '../../../_com/header';
|
import HeaderSearch from '../../../_com/header';
|
||||||
import JudulList from '../../../_com/judulList';
|
|
||||||
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||||
import korupsiState from '../../../_state/landing-page/desa-anti-korupsi';
|
import korupsiState from '../../../_state/landing-page/desa-anti-korupsi';
|
||||||
|
|
||||||
@@ -50,88 +49,90 @@ function ListKategoriKegiatan({ search }: { search: string }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useShallowEffect(() => {
|
||||||
load(page, 10)
|
load(page, 10, search)
|
||||||
}, [page])
|
}, [page, search])
|
||||||
|
|
||||||
const filteredData = useMemo(() => {
|
const filteredData = data || []
|
||||||
if (!data) return [];
|
|
||||||
return data.filter(item => {
|
|
||||||
const keyword = search.toLowerCase();
|
|
||||||
return (
|
|
||||||
item.name?.toLowerCase().includes(keyword)
|
|
||||||
);
|
|
||||||
})
|
|
||||||
}, [data, search]);
|
|
||||||
|
|
||||||
// Handle loading state
|
|
||||||
if (loading || !data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={10}>
|
||||||
<Skeleton height={550} />
|
<Skeleton height={600} radius="md" />
|
||||||
</Stack>
|
</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 (
|
return (
|
||||||
<Box py={10}>
|
<Box py={10}>
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
<Paper bg={colors['white-1']} p={'lg'} shadow="md" radius="md">
|
||||||
<JudulList
|
<Group justify="space-between" mb="md">
|
||||||
title='List Kategori Kegiatan'
|
<Title order={4}>Daftar Kategori Kegiatan</Title>
|
||||||
href='/admin/landing-page/desa-anti-korupsi/kategori-desa-anti-korupsi/create'
|
<Tooltip label="Tambah Kategori" withArrow>
|
||||||
/>
|
<Button
|
||||||
<Box style={{ overflowY: "auto" }}>
|
leftSection={<IconPlus size={18} />}
|
||||||
<Table striped withTableBorder withRowBorders>
|
color="blue"
|
||||||
|
variant="light"
|
||||||
|
onClick={() => router.push('/admin/landing-page/desa-anti-korupsi/kategori-desa-anti-korupsi/create')}
|
||||||
|
>
|
||||||
|
Tambah Baru
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</Group>
|
||||||
|
<Box style={{ overflowX: "auto" }}>
|
||||||
|
<Table highlightOnHover>
|
||||||
<TableThead>
|
<TableThead>
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh>Nama Kategori</TableTh>
|
<TableTh>Nama Kategori</TableTh>
|
||||||
<TableTh>Edit</TableTh>
|
<TableTh>Edit</TableTh>
|
||||||
<TableTh>Delete</TableTh>
|
<TableTh>Hapus</TableTh>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
<TableTbody>
|
<TableTbody>
|
||||||
{filteredData.map((item) => (
|
{filteredData.length > 0 ? (
|
||||||
<TableTr key={item.id}>
|
filteredData.map((item) => (
|
||||||
<TableTd>{item.name}</TableTd>
|
<TableTr key={item.id}>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button color="green" onClick={() => router.push(`/admin/landing-page/desa-anti-korupsi/kategori-desa-anti-korupsi/${item.id}`)}>
|
<Text fw={500}>{item.name}</Text>
|
||||||
<IconEdit size={20} />
|
</TableTd>
|
||||||
</Button>
|
<TableTd>
|
||||||
</TableTd>
|
<Tooltip label="Edit" withArrow>
|
||||||
<TableTd>
|
<Button
|
||||||
<Button color="red" onClick={() => {
|
variant="light"
|
||||||
setSelectedId(item.id)
|
color="blue"
|
||||||
setModalHapus(true)
|
size="sm"
|
||||||
}}>
|
onClick={() => router.push(`/admin/landing-page/desa-anti-korupsi/kategori-desa-anti-korupsi/${item.id}`)}
|
||||||
<IconX size={20} />
|
>
|
||||||
</Button>
|
<IconEdit size={18} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Tooltip label="Hapus" withArrow>
|
||||||
|
<Button
|
||||||
|
variant="light"
|
||||||
|
color="red"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedId(item.id);
|
||||||
|
setModalHapus(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconTrash size={18} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<TableTr>
|
||||||
|
<TableTd colSpan={2}>
|
||||||
|
<Center py={20}>
|
||||||
|
<Text c="dimmed">Tidak ada data kategori yang ditemukan</Text>
|
||||||
|
</Center>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
))}
|
)}
|
||||||
</TableTbody>
|
</TableTbody>
|
||||||
</Table>
|
</Table>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -141,11 +142,13 @@ function ListKategoriKegiatan({ search }: { search: string }) {
|
|||||||
value={page}
|
value={page}
|
||||||
onChange={(newPage) => {
|
onChange={(newPage) => {
|
||||||
load(newPage, 10);
|
load(newPage, 10);
|
||||||
window.scrollTo(0, 0);
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
}}
|
}}
|
||||||
total={totalPages}
|
total={totalPages}
|
||||||
mt="md"
|
mt="md"
|
||||||
mb="md"
|
mb="md"
|
||||||
|
color="blue"
|
||||||
|
radius="md"
|
||||||
/>
|
/>
|
||||||
</Center>
|
</Center>
|
||||||
{/* Modal Konfirmasi Hapus */}
|
{/* Modal Konfirmasi Hapus */}
|
||||||
|
|||||||
@@ -1,18 +1,16 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
'use client'
|
'use client';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { Box, Button, Group, Paper, Select, Stack, Text, TextInput, Title, Tooltip } from '@mantine/core';
|
||||||
|
|
||||||
import { Box, Button, Group, Paper, Select, Stack, Text, TextInput } from '@mantine/core';
|
|
||||||
import { IconArrowBack, IconFile, IconUpload, IconX } from '@tabler/icons-react';
|
import { IconArrowBack, IconFile, IconUpload, IconX } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
|
|
||||||
import korupsiState from '@/app/admin/(dashboard)/_state/landing-page/desa-anti-korupsi';
|
import korupsiState from '@/app/admin/(dashboard)/_state/landing-page/desa-anti-korupsi';
|
||||||
import ApiFetch from '@/lib/api-fetch';
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
import { Dropzone } from '@mantine/dropzone';
|
import { Dropzone } from '@mantine/dropzone';
|
||||||
import { toast } from 'react-toastify';
|
|
||||||
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
||||||
|
|
||||||
interface FormDesaAntiKorupsi {
|
interface FormDesaAntiKorupsi {
|
||||||
@@ -22,18 +20,20 @@ interface FormDesaAntiKorupsi {
|
|||||||
fileId: string;
|
fileId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function EditDesaAntiKorupsi() {
|
export default function EditDesaAntiKorupsi() {
|
||||||
const desaAntiKorupsiState = useProxy(korupsiState.desaAntikorupsi)
|
const desaAntiKorupsiState = useProxy(korupsiState.desaAntikorupsi);
|
||||||
const [previewFile, setPreviewFile] = useState<string | null>(null);
|
const [previewFile, setPreviewFile] = useState<string | null>(null);
|
||||||
const [file, setFile] = useState<File | null>(null);
|
const [file, setFile] = useState<File | null>(null);
|
||||||
const params = useParams()
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const router = useRouter()
|
const params = useParams();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
const [formData, setFormData] = useState<FormDesaAntiKorupsi>({
|
const [formData, setFormData] = useState<FormDesaAntiKorupsi>({
|
||||||
name: '',
|
name: '',
|
||||||
deskripsi: '',
|
deskripsi: '',
|
||||||
kategoriId: '',
|
kategoriId: '',
|
||||||
fileId: '',
|
fileId: '',
|
||||||
})
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadDesaAntiKorupsi = async () => {
|
const loadDesaAntiKorupsi = async () => {
|
||||||
@@ -43,7 +43,6 @@ function EditDesaAntiKorupsi() {
|
|||||||
try {
|
try {
|
||||||
const data = await desaAntiKorupsiState.edit.load(id);
|
const data = await desaAntiKorupsiState.edit.load(id);
|
||||||
if (data) {
|
if (data) {
|
||||||
// ⬇️ FIX PENTING: tambahkan ini
|
|
||||||
desaAntiKorupsiState.edit.id = id;
|
desaAntiKorupsiState.edit.id = id;
|
||||||
|
|
||||||
desaAntiKorupsiState.edit.form = {
|
desaAntiKorupsiState.edit.form = {
|
||||||
@@ -61,169 +60,198 @@ function EditDesaAntiKorupsi() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (data?.file?.link) {
|
if (data?.file?.link) {
|
||||||
setPreviewFile(data.file.link)
|
setPreviewFile(data.file.link);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error loading program penghijauan:", error);
|
console.error('Error loading data:', error);
|
||||||
toast.error("Gagal memuat data program penghijauan");
|
toast.error('Gagal memuat data Desa Anti Korupsi');
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
loadDesaAntiKorupsi();
|
loadDesaAntiKorupsi();
|
||||||
}, [params?.id]);
|
}, [params?.id]);
|
||||||
|
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
|
if (!formData.name) {
|
||||||
|
return toast.warn('Masukkan judul dokumen');
|
||||||
|
}
|
||||||
|
if (!formData.kategoriId) {
|
||||||
|
return toast.warn('Pilih kategori dokumen');
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsLoading(true);
|
||||||
try {
|
try {
|
||||||
// Update global state with form data
|
// Update global state with form data
|
||||||
desaAntiKorupsiState.edit.form = {
|
desaAntiKorupsiState.edit.form = {
|
||||||
...desaAntiKorupsiState.edit.form,
|
...desaAntiKorupsiState.edit.form,
|
||||||
name: formData.name,
|
...formData,
|
||||||
deskripsi: formData.deskripsi,
|
|
||||||
kategoriId: formData.kategoriId || '',
|
kategoriId: formData.kategoriId || '',
|
||||||
fileId: formData.fileId // Keep existing imageId if not changed
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Jika ada file baru, upload
|
// Upload new file if exists
|
||||||
if (file) {
|
if (file) {
|
||||||
const res = await ApiFetch.api.fileStorage.create.post({ file, name: file.name });
|
const res = await ApiFetch.api.fileStorage.create.post({
|
||||||
|
file,
|
||||||
|
name: file.name
|
||||||
|
});
|
||||||
const uploaded = res.data?.data;
|
const uploaded = res.data?.data;
|
||||||
|
|
||||||
if (!uploaded?.id) {
|
if (!uploaded?.id) {
|
||||||
return toast.error("Gagal upload gambar");
|
throw new Error('Gagal mengunggah dokumen');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update imageId in global state
|
|
||||||
desaAntiKorupsiState.edit.form.fileId = uploaded.id;
|
desaAntiKorupsiState.edit.form.fileId = uploaded.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
await desaAntiKorupsiState.edit.update();
|
await desaAntiKorupsiState.edit.update();
|
||||||
toast.success("desa anti korupsi berhasil diperbarui!");
|
toast.success('Data berhasil diperbarui');
|
||||||
router.push("/admin/landing-page/desa-anti-korupsi/list-desa-anti-korupsi");
|
router.push('/admin/landing-page/desa-anti-korupsi/list-desa-anti-korupsi');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error updating desa anti korupsi:", error);
|
console.error('Error updating data:', error);
|
||||||
toast.error("Terjadi kesalahan saat memperbarui desa anti korupsi");
|
toast.error('Terjadi kesalahan saat memperbarui data');
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Box mb={10}>
|
<Group mb="md">
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
<Tooltip label="Kembali ke halaman sebelumnya" color="blue" withArrow>
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
</Button>
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
</Box>
|
</Button>
|
||||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
</Tooltip>
|
||||||
<Stack>
|
<Title order={4} ml="sm" c="dark">
|
||||||
<Text fz={"xl"} fw={"bold"}>Edit List Desa Anti Korupsi</Text>
|
Edit Desa Anti Korupsi
|
||||||
{desaAntiKorupsiState.findUnique.data ? (
|
</Title>
|
||||||
<Paper key={desaAntiKorupsiState.findUnique.data.id}>
|
</Group>
|
||||||
<Stack gap={"xs"}>
|
|
||||||
<TextInput
|
<Paper
|
||||||
value={formData.name}
|
w={{ base: '100%', md: '50%' }}
|
||||||
onChange={(val) => {
|
bg={colors['white-1']}
|
||||||
setFormData({
|
p="lg"
|
||||||
...formData,
|
radius="md"
|
||||||
name: val.target.value
|
shadow="sm"
|
||||||
})
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
|
<TextInput
|
||||||
|
label="Judul Dokumen"
|
||||||
|
placeholder="Masukkan judul dokumen"
|
||||||
|
value={formData.name}
|
||||||
|
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fw="bold" fz="sm" mb={6}>
|
||||||
|
Deskripsi
|
||||||
|
</Text>
|
||||||
|
<EditEditor
|
||||||
|
value={formData.deskripsi}
|
||||||
|
onChange={(val) => setFormData({ ...formData, deskripsi: val })}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Select
|
||||||
|
label="Kategori"
|
||||||
|
placeholder="Pilih kategori"
|
||||||
|
value={formData.kategoriId}
|
||||||
|
onChange={(val) => setFormData({ ...formData, kategoriId: val || '' })}
|
||||||
|
data={
|
||||||
|
korupsiState.kategoriDesaAntiKorupsi.findMany.data?.map((v) => ({
|
||||||
|
value: v.id,
|
||||||
|
label: v.name,
|
||||||
|
})) || []
|
||||||
|
}
|
||||||
|
required
|
||||||
|
searchable
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fw="bold" fz="sm" mb={6}>
|
||||||
|
Dokumen
|
||||||
|
</Text>
|
||||||
|
<Dropzone
|
||||||
|
onDrop={(files) => {
|
||||||
|
const selectedFile = files[0];
|
||||||
|
if (selectedFile) {
|
||||||
|
setFile(selectedFile);
|
||||||
|
setPreviewFile(URL.createObjectURL(selectedFile));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onReject={() => toast.error('File tidak valid, gunakan format dokumen')}
|
||||||
|
maxSize={5 * 1024 ** 2}
|
||||||
|
accept={{
|
||||||
|
'application/*': ['.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx'],
|
||||||
|
}}
|
||||||
|
radius="md"
|
||||||
|
>
|
||||||
|
<Group justify="center" gap="xl" mih={180} style={{ pointerEvents: 'none' }}>
|
||||||
|
<Dropzone.Accept>
|
||||||
|
<IconUpload size={48} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Accept>
|
||||||
|
<Dropzone.Reject>
|
||||||
|
<IconX size={48} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Reject>
|
||||||
|
<Dropzone.Idle>
|
||||||
|
<IconFile size={48} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||||
|
</Dropzone.Idle>
|
||||||
|
<div>
|
||||||
|
<Text size="lg" inline>
|
||||||
|
Seret dokumen ke sini atau klik untuk memilih file
|
||||||
|
</Text>
|
||||||
|
<Text size="sm" c="dimmed" inline mt={7}>
|
||||||
|
Maksimal 5MB (PDF, DOC, DOCX, XLS, XLSX, PPT, PPTX)
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</Group>
|
||||||
|
</Dropzone>
|
||||||
|
|
||||||
|
{previewFile && (
|
||||||
|
<Box mt="md">
|
||||||
|
<Text fw="bold" fz="sm" mb={6}>
|
||||||
|
Pratinjau Dokumen
|
||||||
|
</Text>
|
||||||
|
<Box
|
||||||
|
style={{
|
||||||
|
border: '1px solid #e0e0e0',
|
||||||
|
borderRadius: '8px',
|
||||||
|
overflow: 'hidden',
|
||||||
|
height: '500px',
|
||||||
|
width: '100%',
|
||||||
}}
|
}}
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Judul</Text>}
|
>
|
||||||
placeholder='Masukkan judul'
|
<iframe
|
||||||
/>
|
src={previewFile}
|
||||||
<Box>
|
width="100%"
|
||||||
<Text fz={"sm"} fw={"bold"}>Deskripsi</Text>
|
height="100%"
|
||||||
<EditEditor
|
style={{ border: 'none' }}
|
||||||
value={formData.deskripsi}
|
|
||||||
onChange={(val) => {
|
|
||||||
setFormData({
|
|
||||||
...formData,
|
|
||||||
deskripsi: val
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Select
|
</Box>
|
||||||
value={formData.kategoriId}
|
)}
|
||||||
onChange={(val) => {
|
</Box>
|
||||||
setFormData({
|
|
||||||
...formData,
|
|
||||||
kategoriId: val ?? ""
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Kategori</Text>}
|
|
||||||
placeholder="Pilih kategori"
|
|
||||||
data={
|
|
||||||
korupsiState.kategoriDesaAntiKorupsi.findMany.data?.map((v) => ({
|
|
||||||
value: v.id,
|
|
||||||
label: v.name,
|
|
||||||
})) || []
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Box>
|
|
||||||
<Text fz={"md"} fw={"bold"}>File Document</Text>
|
|
||||||
<Box>
|
|
||||||
<Dropzone
|
|
||||||
onDrop={(files) => {
|
|
||||||
const selectedFile = files[0]; // Ambil file pertama
|
|
||||||
if (selectedFile) {
|
|
||||||
setFile(selectedFile);
|
|
||||||
setPreviewFile(URL.createObjectURL(selectedFile)); // Buat preview
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onReject={() => toast.error('File tidak valid.')}
|
|
||||||
maxSize={5 * 1024 ** 2} // Maks 5MB
|
|
||||||
accept={{
|
|
||||||
'application/*': ['.pdf', '.doc', '.docx'],
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<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>
|
|
||||||
<IconFile size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
|
||||||
</Dropzone.Idle>
|
|
||||||
|
|
||||||
<div>
|
<Group justify="right" mt="xl">
|
||||||
<Text size="xl" inline>
|
<Button
|
||||||
Drag file ke sini atau klik untuk pilih file
|
onClick={handleSubmit}
|
||||||
</Text>
|
radius="md"
|
||||||
<Text size="sm" c="dimmed" inline mt={7}>
|
size="md"
|
||||||
Maksimal 5MB dan harus format document
|
loading={isLoading}
|
||||||
</Text>
|
style={{
|
||||||
</div>
|
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||||
</Group>
|
color: '#fff',
|
||||||
</Dropzone>
|
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||||
<Box>
|
}}
|
||||||
<Text fw={"bold"} fz={"lg"}>Dokumen</Text>
|
>
|
||||||
{previewFile ? (
|
Simpan
|
||||||
<iframe
|
</Button>
|
||||||
src={previewFile}
|
</Group>
|
||||||
width="100%"
|
|
||||||
height="500px"
|
|
||||||
style={{ border: "1px solid #ccc", borderRadius: "8px" }}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Text>Tidak ada dokumen tersedia</Text>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
<Group>
|
|
||||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Submit</Button>
|
|
||||||
</Group>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
) : null}
|
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default EditDesaAntiKorupsi;
|
|
||||||
@@ -2,15 +2,15 @@
|
|||||||
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
||||||
import korupsiState from '@/app/admin/(dashboard)/_state/landing-page/desa-anti-korupsi';
|
import korupsiState from '@/app/admin/(dashboard)/_state/landing-page/desa-anti-korupsi';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Flex, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
import { Box, Button, Group, Paper, Skeleton, Stack, Text, Tooltip } from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
|
import { IconArrowBack, IconEdit, IconTrash } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
|
||||||
function DetailKegiatanDesa() {
|
export default function DetailKegiatanDesa() {
|
||||||
const detailState = useProxy(korupsiState.desaAntikorupsi)
|
const detailState = useProxy(korupsiState.desaAntikorupsi)
|
||||||
const [modalHapus, setModalHapus] = useState(false)
|
const [modalHapus, setModalHapus] = useState(false)
|
||||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||||
@@ -34,89 +34,122 @@ function DetailKegiatanDesa() {
|
|||||||
if (!detailState.findUnique.data) {
|
if (!detailState.findUnique.data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={10}>
|
||||||
<Skeleton h={40} />
|
<Skeleton height={500} radius="md" />
|
||||||
</Stack>
|
</Stack>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const data = detailState.findUnique.data;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box py={10}>
|
||||||
<Box mb={10}>
|
<Button
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
variant="subtle"
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
onClick={() => router.back()}
|
||||||
</Button>
|
leftSection={<IconArrowBack size={24} color={colors['blue-button']} />}
|
||||||
</Box>
|
mb={15}
|
||||||
<Paper bg={colors['white-1']} w={{ base: "100%", md: "100%", lg: "50%" }} p={'md'}>
|
>
|
||||||
<Stack>
|
Kembali
|
||||||
<Text fz={"xl"} fw={"bold"}>Detail List Desa Anti Korupsi</Text>
|
</Button>
|
||||||
{detailState.findUnique.data ? (
|
|
||||||
<Paper key={detailState.findUnique.data.id} bg={colors['BG-trans']} p={'md'}>
|
<Paper
|
||||||
<Stack gap={"xs"}>
|
w={{ base: "100%", md: "50%" }}
|
||||||
<Box>
|
bg={colors['white-1']}
|
||||||
<Text fw={"bold"} fz={"lg"}>Judul</Text>
|
p="lg"
|
||||||
<Text fz={"lg"}>{detailState.findUnique.data?.name}</Text>
|
radius="md"
|
||||||
</Box>
|
shadow="sm"
|
||||||
<Box>
|
>
|
||||||
<Text fw={"bold"} fz={"lg"}>Deskripsi</Text>
|
<Stack gap="md">
|
||||||
<Text fz={"lg"} dangerouslySetInnerHTML={{ __html: detailState.findUnique.data?.deskripsi }} />
|
<Text fz="2xl" fw="bold" c={colors['blue-button']}>
|
||||||
</Box>
|
Detail Desa Anti Korupsi
|
||||||
<Box>
|
</Text>
|
||||||
<Text fw={"bold"} fz={"lg"}>Kategori</Text>
|
|
||||||
<Text fz={"lg"}>{detailState.findUnique.data?.kategori?.name}</Text>
|
<Paper bg="#ECEEF8" p="md" radius="md" shadow="xs">
|
||||||
</Box>
|
<Stack gap="md">
|
||||||
<Box>
|
<Box>
|
||||||
<Text fw={"bold"} fz={"lg"}>Dokumen</Text>
|
<Text fz="lg" fw="bold">Judul</Text>
|
||||||
{detailState.findUnique.data?.file?.link ? (
|
<Text fz="md" c="dimmed">{data.name || '-'}</Text>
|
||||||
<iframe
|
</Box>
|
||||||
src={detailState.findUnique.data.file.link}
|
|
||||||
width="100%"
|
<Box>
|
||||||
height="500px"
|
<Text fz="lg" fw="bold">Kategori</Text>
|
||||||
style={{ border: "1px solid #ccc", borderRadius: "8px" }}
|
<Text fz="md" c="dimmed">{data.kategori?.name || '-'}</Text>
|
||||||
/>
|
</Box>
|
||||||
) : (
|
|
||||||
<Text>Tidak ada dokumen tersedia</Text>
|
<Box>
|
||||||
)}
|
<Text fz="lg" fw="bold" mb="xs">Deskripsi</Text>
|
||||||
</Box>
|
<Box
|
||||||
<Flex gap={"xs"} mt={10}>
|
fz="md"
|
||||||
<Button
|
c="dimmed"
|
||||||
onClick={() => {
|
dangerouslySetInnerHTML={{ __html: data.deskripsi || '-' }}
|
||||||
if (detailState.findUnique.data) {
|
style={{ lineHeight: 1.6 }}
|
||||||
setSelectedId(detailState.findUnique.data.id);
|
/>
|
||||||
setModalHapus(true);
|
</Box>
|
||||||
}
|
|
||||||
|
<Box>
|
||||||
|
<Text fz="lg" fw="bold" mb="xs">Dokumen</Text>
|
||||||
|
{data.file?.link ? (
|
||||||
|
<Box
|
||||||
|
style={{
|
||||||
|
border: '1px solid #e0e0e0',
|
||||||
|
borderRadius: '8px',
|
||||||
|
overflow: 'hidden',
|
||||||
|
height: '500px',
|
||||||
|
width: '100%'
|
||||||
}}
|
}}
|
||||||
disabled={detailState.delete.loading || !detailState.findUnique.data}
|
|
||||||
color={"red"}
|
|
||||||
>
|
>
|
||||||
<IconX size={20} />
|
<iframe
|
||||||
</Button>
|
src={data.file.link}
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
style={{ border: 'none' }}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
<Text fz="sm" c="dimmed">Tidak ada dokumen tersedia</Text>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Group gap="sm" mt="md">
|
||||||
|
<Tooltip label="Hapus Data" withArrow position="top">
|
||||||
<Button
|
<Button
|
||||||
|
color="red"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (detailState.findUnique.data) {
|
setSelectedId(data.id);
|
||||||
router.push(`/admin/landing-page/desa-anti-korupsi/list-desa-anti-korupsi/${detailState.findUnique.data.id}/edit`);
|
setModalHapus(true);
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
disabled={!detailState.findUnique.data}
|
variant="light"
|
||||||
color={"green"}
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
disabled={detailState.delete.loading}
|
||||||
|
>
|
||||||
|
<IconTrash size={20} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
<Tooltip label="Edit Data" withArrow position="top">
|
||||||
|
<Button
|
||||||
|
color="green"
|
||||||
|
onClick={() => router.push(`/admin/landing-page/desa-anti-korupsi/list-desa-anti-korupsi/${data.id}/edit`)}
|
||||||
|
variant="light"
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
>
|
>
|
||||||
<IconEdit size={20} />
|
<IconEdit size={20} />
|
||||||
</Button>
|
</Button>
|
||||||
</Flex>
|
</Tooltip>
|
||||||
</Stack>
|
</Group>
|
||||||
</Paper>
|
</Stack>
|
||||||
) : null}
|
</Paper>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
{/* Modal Konfirmasi Hapus */}
|
|
||||||
<ModalKonfirmasiHapus
|
<ModalKonfirmasiHapus
|
||||||
opened={modalHapus}
|
opened={modalHapus}
|
||||||
onClose={() => setModalHapus(false)}
|
onClose={() => setModalHapus(false)}
|
||||||
onConfirm={handleHapus}
|
onConfirm={handleHapus}
|
||||||
text='Apakah anda yakin ingin menghapus desa anti korupsi ini?'
|
text="Apakah Anda yakin ingin menghapus data Desa Anti Korupsi ini?"
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default DetailKegiatanDesa;
|
|
||||||
@@ -1,10 +1,21 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
'use client'
|
'use client';
|
||||||
import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
|
import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
|
||||||
import korupsiState from '@/app/admin/(dashboard)/_state/landing-page/desa-anti-korupsi';
|
import korupsiState from '@/app/admin/(dashboard)/_state/landing-page/desa-anti-korupsi';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import ApiFetch from '@/lib/api-fetch';
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
import { Box, Button, Group, Paper, Select, Stack, Text, TextInput, Title } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Paper,
|
||||||
|
Select,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
} from '@mantine/core';
|
||||||
import { Dropzone } from '@mantine/dropzone';
|
import { Dropzone } from '@mantine/dropzone';
|
||||||
import { IconArrowBack, IconFile, IconUpload, IconX } from '@tabler/icons-react';
|
import { IconArrowBack, IconFile, IconUpload, IconX } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
@@ -13,12 +24,12 @@ import { toast } from 'react-toastify';
|
|||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
|
||||||
function CreateDesaAntiKorupsi() {
|
export default function CreateDesaAntiKorupsi() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const stateKorupsi = useProxy(korupsiState.desaAntikorupsi)
|
const stateKorupsi = useProxy(korupsiState.desaAntikorupsi);
|
||||||
const [previewFile, setPreviewFile] = useState<string | null>(null);
|
const [previewFile, setPreviewFile] = useState<string | null>(null);
|
||||||
const [file, setFile] = useState<File | null>(null);
|
const [file, setFile] = useState<File | null>(null);
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
stateKorupsi.findMany.load();
|
stateKorupsi.findMany.load();
|
||||||
@@ -27,140 +38,181 @@ function CreateDesaAntiKorupsi() {
|
|||||||
|
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
stateKorupsi.create.form = {
|
stateKorupsi.create.form = {
|
||||||
name: "",
|
name: '',
|
||||||
deskripsi: "",
|
deskripsi: '',
|
||||||
kategoriId: "",
|
kategoriId: '',
|
||||||
fileId: "",
|
fileId: '',
|
||||||
};
|
};
|
||||||
setFile(null);
|
setFile(null);
|
||||||
setPreviewFile(null);
|
setPreviewFile(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
if (!file) {
|
if (!file) {
|
||||||
return toast.warn("Pilih file pdf terlebih dahulu");
|
return toast.warn('Pilih file dokumen terlebih dahulu');
|
||||||
|
}
|
||||||
|
if (!stateKorupsi.create.form.name) {
|
||||||
|
return toast.warn('Masukkan judul dokumen');
|
||||||
|
}
|
||||||
|
if (!stateKorupsi.create.form.kategoriId) {
|
||||||
|
return toast.warn('Pilih kategori dokumen');
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await ApiFetch.api.fileStorage.create.post({
|
setIsLoading(true);
|
||||||
file,
|
try {
|
||||||
name: file.name,
|
const res = await ApiFetch.api.fileStorage.create.post({
|
||||||
})
|
file,
|
||||||
|
name: file.name,
|
||||||
|
});
|
||||||
|
|
||||||
const uploaded = res.data?.data;
|
const uploaded = res.data?.data;
|
||||||
|
|
||||||
if (!uploaded?.id) {
|
if (!uploaded?.id) {
|
||||||
return toast.error("Gagal mengupload file");
|
throw new Error('Gagal mengunggah dokumen');
|
||||||
|
}
|
||||||
|
|
||||||
|
stateKorupsi.create.form.fileId = uploaded.id;
|
||||||
|
await stateKorupsi.create.create();
|
||||||
|
|
||||||
|
toast.success('Data berhasil disimpan');
|
||||||
|
resetForm();
|
||||||
|
router.push('/admin/landing-page/desa-anti-korupsi/list-desa-anti-korupsi');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error:', error);
|
||||||
|
toast.error('Terjadi kesalahan saat menyimpan data');
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
stateKorupsi.create.form.fileId = uploaded.id;
|
|
||||||
|
|
||||||
await stateKorupsi.create.create();
|
|
||||||
|
|
||||||
resetForm();
|
|
||||||
router.push("/admin/landing-page/desa-anti-korupsi/list-desa-anti-korupsi")
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Box mb={10}>
|
<Group mb="md">
|
||||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
<Tooltip label="Kembali ke halaman sebelumnya" color="blue" withArrow>
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
</Button>
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
</Box>
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Title order={4} ml="sm" c="dark">
|
||||||
|
Tambah Dokumen Desa Anti Korupsi
|
||||||
|
</Title>
|
||||||
|
</Group>
|
||||||
|
|
||||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
<Paper
|
||||||
<Stack gap={"xs"}>
|
w={{ base: '100%', md: '50%' }}
|
||||||
<Title order={4}>Create Kegiatan Desa</Title>
|
bg={colors['white-1']}
|
||||||
|
p="lg"
|
||||||
|
radius="md"
|
||||||
|
shadow="sm"
|
||||||
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"md"} fw={"bold"}>File Document</Text>
|
<Text fw="bold" fz="sm" mb={6}>
|
||||||
<Box>
|
Dokumen
|
||||||
<Dropzone
|
</Text>
|
||||||
onDrop={(files) => {
|
<Dropzone
|
||||||
const selectedFile = files[0]; // Ambil file pertama
|
onDrop={(files) => {
|
||||||
if (selectedFile) {
|
const selectedFile = files[0];
|
||||||
setFile(selectedFile);
|
if (selectedFile) {
|
||||||
setPreviewFile(URL.createObjectURL(selectedFile)); // Buat preview
|
setFile(selectedFile);
|
||||||
}
|
setPreviewFile(URL.createObjectURL(selectedFile));
|
||||||
}}
|
}
|
||||||
onReject={() => toast.error('File tidak valid.')}
|
|
||||||
maxSize={5 * 1024 ** 2} // Maks 5MB
|
|
||||||
accept={{
|
|
||||||
'application/*': ['.pdf', '.doc', '.docx'],
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<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>
|
|
||||||
<IconFile size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
|
||||||
</Dropzone.Idle>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<Text size="xl" inline>
|
|
||||||
Drag file ke sini atau klik untuk pilih file
|
|
||||||
</Text>
|
|
||||||
<Text size="sm" c="dimmed" inline mt={7}>
|
|
||||||
Maksimal 5MB dan harus format document
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
</Group>
|
|
||||||
</Dropzone>
|
|
||||||
<Box>
|
|
||||||
<Text fw={"bold"} fz={"lg"}>Dokumen</Text>
|
|
||||||
{previewFile ? (
|
|
||||||
<iframe
|
|
||||||
src={previewFile}
|
|
||||||
width="100%"
|
|
||||||
height="500px"
|
|
||||||
style={{ border: "1px solid #ccc", borderRadius: "8px" }}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Text>Tidak ada dokumen tersedia</Text>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
<TextInput
|
|
||||||
value={stateKorupsi.create.form.name}
|
|
||||||
onChange={(val) => {
|
|
||||||
stateKorupsi.create.form.name = val.target.value;
|
|
||||||
}}
|
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Judul</Text>}
|
|
||||||
placeholder='Masukkan judul'
|
|
||||||
/>
|
|
||||||
<Box>
|
|
||||||
<Text fz={"sm"} fw={"bold"}>Deskripsi</Text>
|
|
||||||
<CreateEditor
|
|
||||||
value={stateKorupsi.create.form.deskripsi}
|
|
||||||
onChange={(val) => {
|
|
||||||
stateKorupsi.create.form.deskripsi = val;
|
|
||||||
}}
|
}}
|
||||||
|
onReject={() => toast.error('File tidak valid, gunakan format dokumen')}
|
||||||
|
maxSize={5 * 1024 ** 2}
|
||||||
|
accept={{
|
||||||
|
'application/*': ['.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx'],
|
||||||
|
}}
|
||||||
|
radius="md"
|
||||||
|
>
|
||||||
|
<Group justify="center" gap="xl" mih={180} style={{ pointerEvents: 'none' }}>
|
||||||
|
<Dropzone.Accept>
|
||||||
|
<IconUpload size={48} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Accept>
|
||||||
|
<Dropzone.Reject>
|
||||||
|
<IconX size={48} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Reject>
|
||||||
|
<Dropzone.Idle>
|
||||||
|
<IconFile size={48} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||||
|
</Dropzone.Idle>
|
||||||
|
<div>
|
||||||
|
<Text size="lg" inline>
|
||||||
|
Seret dokumen ke sini atau klik untuk memilih file
|
||||||
|
</Text>
|
||||||
|
<Text size="sm" c="dimmed" inline mt={7}>
|
||||||
|
Maksimal 5MB (PDF, DOC, DOCX, XLS, XLSX, PPT, PPTX)
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</Group>
|
||||||
|
</Dropzone>
|
||||||
|
|
||||||
|
{previewFile && (
|
||||||
|
<Box mt="md" style={{ textAlign: 'center' }}>
|
||||||
|
<iframe
|
||||||
|
src={previewFile}
|
||||||
|
width="100%"
|
||||||
|
height="500px"
|
||||||
|
style={{
|
||||||
|
border: '1px solid #e0e0e0',
|
||||||
|
borderRadius: '8px',
|
||||||
|
maxWidth: '100%',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<TextInput
|
||||||
|
label="Judul Dokumen"
|
||||||
|
placeholder="Masukkan judul dokumen"
|
||||||
|
value={stateKorupsi.create.form.name || ''}
|
||||||
|
onChange={(e) => (stateKorupsi.create.form.name = e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fw="bold" fz="sm" mb={6}>
|
||||||
|
Deskripsi
|
||||||
|
</Text>
|
||||||
|
<CreateEditor
|
||||||
|
value={stateKorupsi.create.form.deskripsi || ''}
|
||||||
|
onChange={(val) => (stateKorupsi.create.form.deskripsi = val)}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Select
|
<Select
|
||||||
value={stateKorupsi.create.form.kategoriId}
|
label="Kategori"
|
||||||
onChange={(val) => {
|
|
||||||
stateKorupsi.create.form.kategoriId = val ?? "";
|
|
||||||
}}
|
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Kategori</Text>}
|
|
||||||
placeholder="Pilih kategori"
|
placeholder="Pilih kategori"
|
||||||
|
value={stateKorupsi.create.form.kategoriId || ''}
|
||||||
|
onChange={(val) => (stateKorupsi.create.form.kategoriId = val || '')}
|
||||||
data={
|
data={
|
||||||
korupsiState.kategoriDesaAntiKorupsi.findMany.data?.map((v) => ({
|
korupsiState.kategoriDesaAntiKorupsi.findMany.data?.map((v) => ({
|
||||||
value: v.id,
|
value: v.id,
|
||||||
label: v.name,
|
label: v.name,
|
||||||
})) || []
|
})) || []
|
||||||
}
|
}
|
||||||
|
required
|
||||||
|
searchable
|
||||||
|
clearable
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Group>
|
<Group justify="right" mt="xl">
|
||||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Submit</Button>
|
<Button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
loading={isLoading}
|
||||||
|
style={{
|
||||||
|
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||||
|
color: '#fff',
|
||||||
|
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CreateDesaAntiKorupsi;
|
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
|
||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
import { Box, Button, Center, Group, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title, Tooltip } from '@mantine/core';
|
||||||
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import HeaderSearch from '../../../_com/header';
|
import HeaderSearch from '../../../_com/header';
|
||||||
import JudulList from '../../../_com/judulList';
|
|
||||||
import korupsiState from '../../../_state/landing-page/desa-anti-korupsi';
|
import korupsiState from '../../../_state/landing-page/desa-anti-korupsi';
|
||||||
|
|
||||||
function DesaAntiKorupsi() {
|
function DesaAntiKorupsi() {
|
||||||
@@ -16,7 +15,7 @@ function DesaAntiKorupsi() {
|
|||||||
<Box>
|
<Box>
|
||||||
<HeaderSearch
|
<HeaderSearch
|
||||||
title='List Desa Anti Korupsi'
|
title='List Desa Anti Korupsi'
|
||||||
placeholder='pencarian'
|
placeholder='Cari nama program atau kategori...'
|
||||||
searchIcon={<IconSearch size={20} />}
|
searchIcon={<IconSearch size={20} />}
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
@@ -27,8 +26,8 @@ function DesaAntiKorupsi() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function ListDesaAntiKorupsi({ search }: { search: string }) {
|
function ListDesaAntiKorupsi({ search }: { search: string }) {
|
||||||
const listState = useProxy(korupsiState.desaAntikorupsi)
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const listState = useProxy(korupsiState.desaAntikorupsi);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
@@ -38,114 +37,100 @@ function ListDesaAntiKorupsi({ search }: { search: string }) {
|
|||||||
load,
|
load,
|
||||||
} = listState.findMany;
|
} = listState.findMany;
|
||||||
|
|
||||||
useEffect(() => {
|
useShallowEffect(() => {
|
||||||
load(page, 10);
|
load(page, 10, search);
|
||||||
}, [page]);
|
}, [page, search]);
|
||||||
|
|
||||||
const filteredData = useMemo(() => {
|
const filteredData = data || [];
|
||||||
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]);
|
|
||||||
|
|
||||||
// Handle loading state
|
|
||||||
if (loading || !data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={10}>
|
||||||
<Skeleton height={550} />
|
<Skeleton height={600} radius="md" />
|
||||||
</Stack>
|
</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 (
|
return (
|
||||||
<Box py={10}>
|
<Box py={10}>
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
<Paper bg={colors['white-1']} p={'lg'} shadow="md" radius="md">
|
||||||
<Stack>
|
<Group justify="space-between" mb="md">
|
||||||
<JudulList
|
<Title order={4}>Daftar Program Desa Anti Korupsi</Title>
|
||||||
title='List Desa Anti Korupsi'
|
<Tooltip label="Tambah Program Desa Anti Korupsi" withArrow>
|
||||||
href='/admin/landing-page/desa-anti-korupsi/list-desa-anti-korupsi/create'
|
<Button
|
||||||
/>
|
leftSection={<IconPlus size={18} />}
|
||||||
<Box style={{ overflowX: 'auto' }}>
|
color="blue"
|
||||||
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
|
variant="light"
|
||||||
<TableThead>
|
onClick={() => router.push('/admin/landing-page/desa-anti-korupsi/list-desa-anti-korupsi/create')}
|
||||||
<TableTr>
|
>
|
||||||
<TableTh>Nama Desa Anti Korupsi</TableTh>
|
Tambah Baru
|
||||||
<TableTh>Deskripsi Desa Anti Korupsi</TableTh>
|
</Button>
|
||||||
<TableTh>Kategori Desa Anti Korupsi</TableTh>
|
</Tooltip>
|
||||||
<TableTh>Detail</TableTh>
|
</Group>
|
||||||
</TableTr>
|
<Box style={{ overflowX: "auto" }}>
|
||||||
</TableThead>
|
<Table highlightOnHover>
|
||||||
<TableTbody>
|
<TableThead>
|
||||||
{filteredData.map((item) => (
|
<TableTr>
|
||||||
|
<TableTh>Nama Program</TableTh>
|
||||||
|
<TableTh>Kategori</TableTh>
|
||||||
|
<TableTh>Aksi</TableTh>
|
||||||
|
</TableTr>
|
||||||
|
</TableThead>
|
||||||
|
<TableTbody>
|
||||||
|
{filteredData.length > 0 ? (
|
||||||
|
filteredData.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Box w={100}>
|
<Box w={350}>
|
||||||
<Text truncate="end" fz={"sm"}>{item.name}</Text>
|
<Text lineClamp={1} fw={500}>{item.name || '-'}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Box w={200}>
|
<Text fz="sm" c="dimmed">
|
||||||
<Text truncate="end" fz={"sm"} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
{item.kategori?.name || '-'}
|
||||||
</Box>
|
</Text>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>{item.kategori?.name}</TableTd>
|
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button onClick={() => router.push(`/admin/landing-page/desa-anti-korupsi/list-desa-anti-korupsi/${item.id}`)}>
|
<Button
|
||||||
<IconDeviceImacCog size={25} />
|
variant="light"
|
||||||
|
color="blue"
|
||||||
|
onClick={() => router.push(`/admin/landing-page/desa-anti-korupsi/list-desa-anti-korupsi/${item.id}`)}
|
||||||
|
>
|
||||||
|
<IconDeviceImacCog size={20} />
|
||||||
|
<Text ml={5}>Detail</Text>
|
||||||
</Button>
|
</Button>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
))}
|
))
|
||||||
</TableTbody>
|
) : (
|
||||||
</Table>
|
<TableTr>
|
||||||
</Box>
|
<TableTd colSpan={4}>
|
||||||
</Stack>
|
<Center py={20}>
|
||||||
|
<Text c="dimmed">Tidak ada data program yang cocok</Text>
|
||||||
|
</Center>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
)}
|
||||||
|
</TableTbody>
|
||||||
|
</Table>
|
||||||
|
</Box>
|
||||||
</Paper>
|
</Paper>
|
||||||
<Center>
|
<Center>
|
||||||
<Pagination
|
<Pagination
|
||||||
value={page}
|
value={page}
|
||||||
onChange={(newPage) => {
|
onChange={(newPage) => {
|
||||||
load(newPage, 10);
|
load(newPage, 10);
|
||||||
window.scrollTo(0, 0);
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
}}
|
}}
|
||||||
total={totalPages}
|
total={totalPages}
|
||||||
mt="md"
|
mt="md"
|
||||||
mb="md"
|
mb="md"
|
||||||
|
color="blue"
|
||||||
|
radius="md"
|
||||||
/>
|
/>
|
||||||
</Center>
|
</Center>
|
||||||
</Box>
|
</Box>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default DesaAntiKorupsi;
|
export default DesaAntiKorupsi;
|
||||||
|
|||||||
@@ -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 LayoutTabsKepuasan({ children }: { children: React.ReactNode }) {
|
||||||
|
const router = useRouter()
|
||||||
|
const pathname = usePathname()
|
||||||
|
const tabs = [
|
||||||
|
{
|
||||||
|
label: "Grafik Kepuasan Masyarakat",
|
||||||
|
value: "grafikkepuasannamasyarakat",
|
||||||
|
href: "/admin/landing-page/indeks-kepuasan-masyarakat/grafik-kepuasan-masyarakat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Responden",
|
||||||
|
value: "responden",
|
||||||
|
href: "/admin/landing-page/indeks-kepuasan-masyarakat/responden"
|
||||||
|
},
|
||||||
|
|
||||||
|
];
|
||||||
|
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}>Indeks Kepuasan Masyarakat</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 LayoutTabsKepuasan;
|
||||||
@@ -0,0 +1,256 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
'use client';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { PieChart, BarChart } from '@mantine/charts'; // ✅ Ganti recharts dengan Mantine
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Center,
|
||||||
|
Flex,
|
||||||
|
Paper,
|
||||||
|
SimpleGrid,
|
||||||
|
Skeleton,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
Title,
|
||||||
|
} from '@mantine/core';
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import indeksKepuasanState from '../../../_state/landing-page/indeks-kepuasan';
|
||||||
|
|
||||||
|
interface ChartDataItem {
|
||||||
|
name: string;
|
||||||
|
value: number;
|
||||||
|
color: string;
|
||||||
|
label?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function Page() {
|
||||||
|
const state = useProxy(indeksKepuasanState.responden);
|
||||||
|
const { data, loading } = state.findMany;
|
||||||
|
const [donutDataJenisKelamin, setDonutDataJenisKelamin] = useState<ChartDataItem[]>([]);
|
||||||
|
const [donutDataRating, setDonutDataRating] = useState<ChartDataItem[]>([]);
|
||||||
|
const [donutDataKelompokUmur, setDonutDataKelompokUmur] = useState<ChartDataItem[]>([]);
|
||||||
|
const [barChartData, setBarChartData] = useState<Array<{ month: string; count: number }>>([]);
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
if (!data && !loading) {
|
||||||
|
state.findMany.load();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (data) {
|
||||||
|
// Hitung total berdasarkan jenis kelamin
|
||||||
|
const totalLaki = data.filter((item: any) => item.jenisKelamin?.name?.toLowerCase() === 'laki-laki').length;
|
||||||
|
const totalPerempuan = data.filter((item: any) => item.jenisKelamin?.name?.toLowerCase() === 'perempuan').length;
|
||||||
|
|
||||||
|
// Hitung total berdasarkan rating
|
||||||
|
const totalSangatBaik = data.filter((item: any) => item.rating?.name?.toLowerCase() === 'sangat baik').length;
|
||||||
|
const totalBaik = data.filter((item: any) => item.rating?.name?.toLowerCase() === 'baik').length;
|
||||||
|
const totalKurangBaik = data.filter((item: any) => item.rating?.name?.toLowerCase() === 'kurang baik').length;
|
||||||
|
const totalSangatKurangBaik = data.filter((item: any) => item.rating?.name?.toLowerCase() === 'sangat kurang baik').length;
|
||||||
|
|
||||||
|
// Hitung total berdasarkan kelompok umur
|
||||||
|
const totalMuda = data.filter((item: any) => item.kelompokUmur?.name?.toLowerCase() === 'muda').length;
|
||||||
|
const totalDewasa = data.filter((item: any) => item.kelompokUmur?.name?.toLowerCase() === 'dewasa').length;
|
||||||
|
const totalLansia = data.filter((item: any) => item.kelompokUmur?.name?.toLowerCase() === 'lansia').length;
|
||||||
|
|
||||||
|
// Update gender chart data
|
||||||
|
setDonutDataJenisKelamin([
|
||||||
|
{ name: 'Laki-laki', value: totalLaki, color: colors['blue-button'] },
|
||||||
|
{ name: 'Perempuan', value: totalPerempuan, color: '#10A85AFF' },
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Update rating chart data
|
||||||
|
setDonutDataRating([
|
||||||
|
{ name: 'Sangat Baik', value: totalSangatBaik, color: colors['blue-button'] },
|
||||||
|
{ name: 'Baik', value: totalBaik, color: '#10A85AFF' },
|
||||||
|
{ name: 'Kurang Baik', value: totalKurangBaik, color: '#FFA500' },
|
||||||
|
{ name: 'Sangat Kurang Baik', value: totalSangatKurangBaik, color: '#FF4500' },
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Update age group chart data
|
||||||
|
setDonutDataKelompokUmur([
|
||||||
|
{ name: 'Muda', value: totalMuda, color: colors['blue-button'] },
|
||||||
|
{ name: 'Dewasa', value: totalDewasa, color: '#10A85AFF' },
|
||||||
|
{ name: 'Lansia', value: totalLansia, color: '#FFA500' },
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Process data for bar chart (group by month)
|
||||||
|
const monthYearMap = new Map<string, number>();
|
||||||
|
|
||||||
|
data.forEach((item: any) => {
|
||||||
|
// Try both createdAt and tanggal fields
|
||||||
|
const dateValue = item.tanggal || item.createdAt;
|
||||||
|
if (!dateValue) return;
|
||||||
|
|
||||||
|
const parsedDate = new Date(dateValue);
|
||||||
|
if (isNaN(parsedDate.getTime())) return;
|
||||||
|
|
||||||
|
const month = parsedDate.getMonth() + 1;
|
||||||
|
const year = parsedDate.getFullYear();
|
||||||
|
const monthYearKey = `${year}-${String(month).padStart(2, '0')}`;
|
||||||
|
|
||||||
|
monthYearMap.set(monthYearKey, (monthYearMap.get(monthYearKey) || 0) + 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Convert map to array and sort by date
|
||||||
|
const barData = Array.from(monthYearMap.entries())
|
||||||
|
.map(([key, count]) => {
|
||||||
|
const [year, month] = key.split('-');
|
||||||
|
const monthName = new Date(Number(year), Number(month) - 1, 1)
|
||||||
|
.toLocaleString('id-ID', { month: 'long' });
|
||||||
|
return {
|
||||||
|
month: `${monthName} ${year}`,
|
||||||
|
count,
|
||||||
|
sortKey: parseInt(`${year}${String(month).padStart(2, '0')}`, 10)
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.sort((a, b) => a.sortKey - b.sortKey)
|
||||||
|
.map(({ month, count }) => ({ month, count }));
|
||||||
|
|
||||||
|
setBarChartData(barData);
|
||||||
|
}
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
|
if (loading || !data) {
|
||||||
|
return (
|
||||||
|
<Stack py={10}>
|
||||||
|
<Skeleton height={730} />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.length === 0) {
|
||||||
|
return (
|
||||||
|
<Stack py={10}>
|
||||||
|
<Text c="dimmed" ta="center" my="md">
|
||||||
|
Belum ada data untuk ditampilkan
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack gap="xs">
|
||||||
|
{/* Bar Chart - Data per Tanggal */}
|
||||||
|
<Paper bg={colors['white-1']} p="md" radius="md" mb="md">
|
||||||
|
<Title order={4} mb="md" ta="center">Jumlah Responden per Bulan</Title>
|
||||||
|
<Box h={300}>
|
||||||
|
<BarChart
|
||||||
|
h={300}
|
||||||
|
data={barChartData}
|
||||||
|
dataKey="month"
|
||||||
|
series={[{ name: 'count', color: colors['blue-button'] }]}
|
||||||
|
tickLine="y"
|
||||||
|
xAxisLabel="Bulan"
|
||||||
|
yAxisLabel="Jumlah Responden"
|
||||||
|
withTooltip
|
||||||
|
tooltipAnimationDuration={200}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
<SimpleGrid cols={{ base: 1, md: 3 }}>
|
||||||
|
{/* Chart Jenis Kelamin */}
|
||||||
|
<Paper bg={colors['white-1']} p="md" radius="md">
|
||||||
|
<Stack>
|
||||||
|
<Title order={4}>Jenis Kelamin</Title>
|
||||||
|
{donutDataJenisKelamin.every(item => item.value === 0) ? (
|
||||||
|
<Text c="dimmed" ta="center" my="md">
|
||||||
|
Belum ada data untuk ditampilkan dalam grafik
|
||||||
|
</Text>
|
||||||
|
) : (
|
||||||
|
<Box>
|
||||||
|
<Center>
|
||||||
|
<PieChart
|
||||||
|
withLabels
|
||||||
|
withTooltip
|
||||||
|
labelsType="percent"
|
||||||
|
size={250}
|
||||||
|
data={donutDataJenisKelamin}
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
<Stack gap="sm" mt="md">
|
||||||
|
{donutDataJenisKelamin.map((entry) => (
|
||||||
|
<Flex key={entry.name} gap="md" align="center">
|
||||||
|
<Box bg={entry.color} w={20} h={20} style={{ flexShrink: 0 }} />
|
||||||
|
<Text size="sm">{entry.name}: {entry.value}</Text>
|
||||||
|
</Flex>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
{/* Chart Rating */}
|
||||||
|
<Paper bg={colors['white-1']} p="md" radius="md">
|
||||||
|
<Stack>
|
||||||
|
<Title order={4}>Pilihan</Title>
|
||||||
|
{donutDataRating.every(item => item.value === 0) ? (
|
||||||
|
<Text c="dimmed" ta="center" my="md">
|
||||||
|
Belum ada data untuk ditampilkan dalam grafik
|
||||||
|
</Text>
|
||||||
|
) : (
|
||||||
|
<Box>
|
||||||
|
<Center>
|
||||||
|
<PieChart
|
||||||
|
withLabels
|
||||||
|
withTooltip
|
||||||
|
labelsType="percent"
|
||||||
|
size={250}
|
||||||
|
data={donutDataRating}
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
<Stack gap="sm" mt="md">
|
||||||
|
{donutDataRating.map((entry) => (
|
||||||
|
<Flex key={entry.name} gap="md" align="center">
|
||||||
|
<Box bg={entry.color} w={20} h={20} style={{ flexShrink: 0 }} />
|
||||||
|
<Text size="sm">{entry.name}: {entry.value}</Text>
|
||||||
|
</Flex>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
{/* Chart Kelompok Umur */}
|
||||||
|
<Paper bg={colors['white-1']} p="md" radius="md">
|
||||||
|
<Stack>
|
||||||
|
<Title order={4}>Umur</Title>
|
||||||
|
{donutDataKelompokUmur.every(item => item.value === 0) ? (
|
||||||
|
<Text c="dimmed" ta="center" my="md">
|
||||||
|
Belum ada data untuk ditampilkan dalam grafik
|
||||||
|
</Text>
|
||||||
|
) : (
|
||||||
|
<Box>
|
||||||
|
<Center>
|
||||||
|
<PieChart
|
||||||
|
withLabels
|
||||||
|
withTooltip
|
||||||
|
labelsType="percent"
|
||||||
|
size={250}
|
||||||
|
data={donutDataKelompokUmur}
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
<Stack gap="sm" mt="md">
|
||||||
|
{donutDataKelompokUmur.map((entry) => (
|
||||||
|
<Flex key={entry.name} gap="md" align="center">
|
||||||
|
<Box bg={entry.color} w={20} h={20} style={{ flexShrink: 0 }} />
|
||||||
|
<Text size="sm">{entry.name}: {entry.value}</Text>
|
||||||
|
</Flex>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</SimpleGrid>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Page;
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import LayoutTabsKepuasan from './_lib/layoutTab';
|
||||||
|
|
||||||
|
function Layout({ children }: { children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<LayoutTabsKepuasan>
|
||||||
|
{children}
|
||||||
|
</LayoutTabsKepuasan>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Layout;
|
||||||
@@ -0,0 +1,190 @@
|
|||||||
|
'use client'
|
||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { useRouter, useParams } from 'next/navigation';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Paper, Stack, Title, TextInput, Text, Select } from '@mantine/core';
|
||||||
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
|
import indeksKepuasanState from '@/app/admin/(dashboard)/_state/landing-page/indeks-kepuasan';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
|
interface FormResponden {
|
||||||
|
name: string;
|
||||||
|
tanggal: string;
|
||||||
|
jenisKelaminId: string;
|
||||||
|
ratingId: string;
|
||||||
|
kelompokUmurId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function EditResponden() {
|
||||||
|
const router = useRouter()
|
||||||
|
const params = useParams() as { id: string }
|
||||||
|
const state = useProxy(indeksKepuasanState.responden)
|
||||||
|
const id = params.id
|
||||||
|
const [formData, setFormData] = useState<FormResponden>({
|
||||||
|
name: '',
|
||||||
|
tanggal: '',
|
||||||
|
jenisKelaminId: '',
|
||||||
|
ratingId: '',
|
||||||
|
kelompokUmurId: '',
|
||||||
|
})
|
||||||
|
useEffect(() => {
|
||||||
|
indeksKepuasanState.jenisKelaminResponden.findMany.load();
|
||||||
|
indeksKepuasanState.pilihanRatingResponden.findMany.load();
|
||||||
|
indeksKepuasanState.kelompokUmurResponden.findMany.load();
|
||||||
|
|
||||||
|
const loadResponden = async () => {
|
||||||
|
const id = params?.id as string;
|
||||||
|
if (!id) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await state.update.load(id);
|
||||||
|
if (data) {
|
||||||
|
state.update.id = id;
|
||||||
|
|
||||||
|
state.update.form = {
|
||||||
|
name: data.name,
|
||||||
|
tanggal: data.tanggal,
|
||||||
|
jenisKelaminId: data.jenisKelaminId,
|
||||||
|
ratingId: data.ratingId,
|
||||||
|
kelompokUmurId: data.kelompokUmurId,
|
||||||
|
};
|
||||||
|
|
||||||
|
setFormData({
|
||||||
|
name: data.name,
|
||||||
|
tanggal: data.tanggal,
|
||||||
|
jenisKelaminId: data.jenisKelaminId,
|
||||||
|
ratingId: data.ratingId,
|
||||||
|
kelompokUmurId: data.kelompokUmurId,
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading program penghijauan:", error);
|
||||||
|
toast.error("Gagal memuat data program penghijauan");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadResponden();
|
||||||
|
}, [params?.id]);
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
state.update.id = id;
|
||||||
|
state.update.form = { ...formData }; // <-- sinkronisasi manual
|
||||||
|
await state.update.submit();
|
||||||
|
router.push('/admin/ppid/ikm-desa-darmasaba/responden')
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Box mb={10}>
|
||||||
|
<Button variant="subtle" onClick={() => router.back()}>
|
||||||
|
<IconArrowBack size={20} />
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
<Paper bg={colors['white-1']} w={{ base: '100%', md: '50%' }} p={'md'}>
|
||||||
|
<Stack>
|
||||||
|
<Title order={3}>Edit Responden</Title>
|
||||||
|
<TextInput
|
||||||
|
label="Nama"
|
||||||
|
type='text'
|
||||||
|
placeholder="masukkan nama"
|
||||||
|
value={formData.name}
|
||||||
|
onChange={(val) => {
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
name: val.currentTarget.value
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label="Tanggal"
|
||||||
|
type="date"
|
||||||
|
placeholder='Pilih tanggal'
|
||||||
|
value={formData.tanggal ? new Date(formData.tanggal).toISOString().split('T')[0] : ''}
|
||||||
|
onChange={(e) => {
|
||||||
|
const selectedDate = e.currentTarget.value;
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
tanggal: selectedDate,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Select
|
||||||
|
key={"jenisKelamin"}
|
||||||
|
label={<Text fw="bold" fz="sm">Jenis Kelamin</Text>}
|
||||||
|
placeholder="Pilih jenis kelamin"
|
||||||
|
value={formData.jenisKelaminId}
|
||||||
|
onChange={(val) => setFormData({ ...formData, jenisKelaminId: val || "" })}
|
||||||
|
data={
|
||||||
|
(indeksKepuasanState.jenisKelaminResponden.findMany.data || [])
|
||||||
|
.filter(Boolean) // Hapus null/undefined
|
||||||
|
.map((v) => ({
|
||||||
|
value: v.id || '',
|
||||||
|
label: typeof v.name === 'string' ? v.name : 'Tanpa Nama'
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
disabled={indeksKepuasanState.jenisKelaminResponden.findMany.loading} // ✅ disable saat loading
|
||||||
|
clearable
|
||||||
|
searchable
|
||||||
|
required
|
||||||
|
error={!formData.jenisKelaminId ? "Pilih jenis kelamin" : undefined}
|
||||||
|
/>
|
||||||
|
<Select
|
||||||
|
key={"rating"}
|
||||||
|
value={formData.ratingId}
|
||||||
|
onChange={(val) => setFormData({ ...formData, ratingId: val || "" })}
|
||||||
|
label={<Text fw={"bold"} fz={"sm"}>Rating</Text>}
|
||||||
|
placeholder='Pilih rating'
|
||||||
|
data={
|
||||||
|
(indeksKepuasanState.pilihanRatingResponden.findMany.data || [])
|
||||||
|
.filter(Boolean)
|
||||||
|
.map((v) => ({
|
||||||
|
value: v.id || '',
|
||||||
|
label: typeof v.name === 'string' ? v.name : 'Tanpa Nama'
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
disabled={indeksKepuasanState.pilihanRatingResponden.findMany.loading}
|
||||||
|
clearable
|
||||||
|
searchable
|
||||||
|
required
|
||||||
|
error={!formData.ratingId ? "Pilih rating" : undefined}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Select
|
||||||
|
key={"kelompokUmur"}
|
||||||
|
value={formData.kelompokUmurId}
|
||||||
|
onChange={(val) => setFormData({ ...formData, kelompokUmurId: val || "" })}
|
||||||
|
label={<Text fw={"bold"} fz={"sm"}>Kelompok Umur</Text>}
|
||||||
|
placeholder='Pilih kelompok umur'
|
||||||
|
data={
|
||||||
|
(indeksKepuasanState.kelompokUmurResponden.findMany.data || [])
|
||||||
|
.filter(Boolean)
|
||||||
|
.map((v) => ({
|
||||||
|
value: v.id || '',
|
||||||
|
label: typeof v.name === 'string' ? v.name : 'Tanpa Nama'
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
disabled={indeksKepuasanState.kelompokUmurResponden.findMany.loading}
|
||||||
|
clearable
|
||||||
|
searchable
|
||||||
|
required
|
||||||
|
error={!formData.kelompokUmurId ? "Pilih kelompok umur" : undefined}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
mt={10}
|
||||||
|
bg={colors['blue-button']}
|
||||||
|
onClick={handleSubmit}
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EditResponden;
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { ModalKonfirmasiHapus } from "@/app/admin/(dashboard)/_com/modalKonfirmasiHapus"
|
||||||
|
import indeksKepuasanState from "@/app/admin/(dashboard)/_state/landing-page/indeks-kepuasan"
|
||||||
|
import colors from "@/con/colors"
|
||||||
|
import { Box, Button, Flex, Paper, Skeleton, Stack, Text } from "@mantine/core"
|
||||||
|
import { useShallowEffect } from "@mantine/hooks"
|
||||||
|
import { IconArrowBack, IconEdit, IconX } from "@tabler/icons-react"
|
||||||
|
import { useRouter, useParams } from "next/navigation"
|
||||||
|
import { useState } from "react"
|
||||||
|
import { useProxy } from "valtio/utils"
|
||||||
|
|
||||||
|
export default function DetailResponden() {
|
||||||
|
const [modalHapus, setModalHapus] = useState(false)
|
||||||
|
const stateDetail = useProxy(indeksKepuasanState.responden)
|
||||||
|
const router = useRouter()
|
||||||
|
const params = useParams()
|
||||||
|
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
stateDetail.findUnique.load(params?.id as string)
|
||||||
|
}, [params?.id])
|
||||||
|
|
||||||
|
const handleHapus = () => {
|
||||||
|
if (selectedId) {
|
||||||
|
stateDetail.delete.byId(selectedId)
|
||||||
|
setModalHapus(false)
|
||||||
|
setSelectedId(null)
|
||||||
|
router.push("/admin/ppid/ikm-desa-darmasaba/responden")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!stateDetail.findUnique.data) {
|
||||||
|
return (
|
||||||
|
<Stack>
|
||||||
|
<Skeleton h={500} />
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Box mb={10}>
|
||||||
|
<Button variant="subtle" onClick={() => router.back()}>
|
||||||
|
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
<Paper w={{ base: "100%", md: "50%" }} bg={colors['white-1']} p={'md'}>
|
||||||
|
<Stack>
|
||||||
|
<Text fz={"xl"} fw={"bold"}>Detail Responden</Text>
|
||||||
|
|
||||||
|
<Paper bg={colors['BG-trans']} p={'md'}>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Box>
|
||||||
|
<Text fz={"lg"} fw={"bold"}>Nama Responden</Text>
|
||||||
|
<Text fz={"lg"}>{stateDetail.findUnique.data?.name}</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz={"lg"} fw={"bold"}>Tanggal</Text>
|
||||||
|
<Text fz={"lg"}>{
|
||||||
|
stateDetail.findUnique.data?.tanggal
|
||||||
|
? new Date(stateDetail.findUnique.data.tanggal).toLocaleDateString('id-ID')
|
||||||
|
: '-'
|
||||||
|
}</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz={"lg"} fw={"bold"}>Jenis Kelamin</Text>
|
||||||
|
<Text fz={"lg"}>{stateDetail.findUnique.data?.jenisKelamin?.name}</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz={"lg"} fw={"bold"}>Rating</Text>
|
||||||
|
<Text fz={"lg"}>{stateDetail.findUnique.data?.rating?.name}</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz={"lg"} fw={"bold"}>Kelompok Umur</Text>
|
||||||
|
<Text fz={"lg"}>{stateDetail.findUnique.data?.kelompokUmur?.name}</Text>
|
||||||
|
</Box>
|
||||||
|
<Flex gap={"xs"} mt={10}>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
if (stateDetail.findUnique.data) {
|
||||||
|
setSelectedId(stateDetail.findUnique.data.id);
|
||||||
|
setModalHapus(true);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={stateDetail.delete.loading || !stateDetail.findUnique.data}
|
||||||
|
color={"red"}
|
||||||
|
>
|
||||||
|
<IconX size={20} />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
if (stateDetail.findUnique.data) {
|
||||||
|
router.push(`/admin/ppid/ikm-desa-darmasaba/responden/${stateDetail.findUnique.data.id}/edit`);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={!stateDetail.findUnique.data}
|
||||||
|
color={"green"}
|
||||||
|
>
|
||||||
|
<IconEdit size={20} />
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
{/* Modal Hapus */}
|
||||||
|
<ModalKonfirmasiHapus
|
||||||
|
opened={modalHapus}
|
||||||
|
onClose={() => setModalHapus(false)}
|
||||||
|
onConfirm={handleHapus}
|
||||||
|
text="Apakah anda yakin ingin menghapus responden ini?"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
'use client'
|
||||||
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
import React from 'react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import grafikBerdasarkanJenisKelamin from '@/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanJenisKelamin';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Paper, Stack, Title, TextInput, Select, Text } from '@mantine/core';
|
||||||
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
|
import indeksKepuasanState from '@/app/admin/(dashboard)/_state/landing-page/indeks-kepuasan';
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
|
||||||
|
function RespondenCreate() {
|
||||||
|
const router = useRouter();
|
||||||
|
const stategrafikBerdasarkanResponden = useProxy(indeksKepuasanState.responden)
|
||||||
|
const [donutData, setDonutData] = useState<any[]>([]);
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
stategrafikBerdasarkanResponden.create.form = {
|
||||||
|
...stategrafikBerdasarkanResponden.create.form,
|
||||||
|
name: "",
|
||||||
|
tanggal: "",
|
||||||
|
jenisKelaminId: "",
|
||||||
|
ratingId: "",
|
||||||
|
kelompokUmurId: "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
indeksKepuasanState.jenisKelaminResponden.findMany.load()
|
||||||
|
indeksKepuasanState.pilihanRatingResponden.findMany.load()
|
||||||
|
indeksKepuasanState.kelompokUmurResponden.findMany.load()
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
try {
|
||||||
|
const id = await stategrafikBerdasarkanResponden.create.create();
|
||||||
|
if (typeof id !== 'undefined') {
|
||||||
|
const idStr = String(id);
|
||||||
|
await stategrafikBerdasarkanResponden.findUnique.load(idStr);
|
||||||
|
if (stategrafikBerdasarkanResponden.findUnique.data) {
|
||||||
|
setDonutData([stategrafikBerdasarkanResponden.findUnique.data]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resetForm();
|
||||||
|
router.push("/admin/ppid/ikm-desa-darmasaba/responden");
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error submitting form:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Box mb={10}>
|
||||||
|
<Button variant="subtle" onClick={() => router.back()}>
|
||||||
|
<IconArrowBack size={20} />
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
<Paper bg={colors['white-1']} w={{ base: '100%', md: '50%' }} p={'md'}>
|
||||||
|
<Stack>
|
||||||
|
<Title order={3}>Grafik Hasil Kepuasan Masyarakat Terhadap Pelayanan Publik</Title>
|
||||||
|
<TextInput
|
||||||
|
label="Nama"
|
||||||
|
type='text'
|
||||||
|
placeholder="masukkan nama"
|
||||||
|
value={stategrafikBerdasarkanResponden.create.form.name}
|
||||||
|
onChange={(val) => {
|
||||||
|
stategrafikBerdasarkanResponden.create.form.name = val.currentTarget.value;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label="Tanggal"
|
||||||
|
type="date"
|
||||||
|
placeholder="masukkan tanggal"
|
||||||
|
value={stategrafikBerdasarkanResponden.create.form.tanggal}
|
||||||
|
onChange={(val) => {
|
||||||
|
stategrafikBerdasarkanResponden.create.form.tanggal = val.currentTarget.value;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Select
|
||||||
|
key={"jenisKelamin"}
|
||||||
|
label={"Jenis Kelamin"}
|
||||||
|
placeholder={indeksKepuasanState.jenisKelaminResponden.findMany.loading ? 'Memuat...' : 'Pilih jenis kelamin'}
|
||||||
|
value={stategrafikBerdasarkanResponden.create.form.jenisKelaminId || ""}
|
||||||
|
onChange={(val) => {
|
||||||
|
stategrafikBerdasarkanResponden.create.form.jenisKelaminId = val ?? "";
|
||||||
|
}}
|
||||||
|
data={
|
||||||
|
(indeksKepuasanState.jenisKelaminResponden.findMany.data || [])
|
||||||
|
.filter(Boolean) // Hapus null, undefined, dll
|
||||||
|
.map((item) => ({
|
||||||
|
value: item.id,
|
||||||
|
label: item.name || 'Tanpa Nama',
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
disabled={indeksKepuasanState.jenisKelaminResponden.findMany.loading}
|
||||||
|
/>
|
||||||
|
<Select
|
||||||
|
key={"rating_responden"}
|
||||||
|
label={"Rating"}
|
||||||
|
placeholder={indeksKepuasanState.pilihanRatingResponden.findMany.loading ? 'Memuat...' : 'Pilih rating'}
|
||||||
|
value={stategrafikBerdasarkanResponden.create.form.ratingId || ""}
|
||||||
|
onChange={(val) => {
|
||||||
|
stategrafikBerdasarkanResponden.create.form.ratingId = val ?? "";
|
||||||
|
}}
|
||||||
|
data={
|
||||||
|
(indeksKepuasanState.pilihanRatingResponden.findMany.data || [])
|
||||||
|
.filter(Boolean) // Hapus null, undefined, dll
|
||||||
|
.map((item) => ({
|
||||||
|
value: item.id,
|
||||||
|
label: item.name || 'Tanpa Nama',
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
disabled={indeksKepuasanState.pilihanRatingResponden.findMany.loading}
|
||||||
|
/>
|
||||||
|
<Select
|
||||||
|
key={"kelompokUmur"}
|
||||||
|
label={"Kelompok Umur"}
|
||||||
|
placeholder={indeksKepuasanState.kelompokUmurResponden.findMany.loading ? 'Memuat...' : 'Pilih kelompok umur'}
|
||||||
|
value={stategrafikBerdasarkanResponden.create.form.kelompokUmurId || ""}
|
||||||
|
onChange={(val) => {
|
||||||
|
stategrafikBerdasarkanResponden.create.form.kelompokUmurId = val ?? "";
|
||||||
|
}}
|
||||||
|
data={
|
||||||
|
(indeksKepuasanState.kelompokUmurResponden.findMany.data || [])
|
||||||
|
.filter(Boolean) // Hapus null, undefined, dll
|
||||||
|
.map((item) => ({
|
||||||
|
value: item.id,
|
||||||
|
label: item.name || 'Tanpa Nama',
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
disabled={indeksKepuasanState.kelompokUmurResponden.findMany.loading}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
mt={10}
|
||||||
|
bg={colors['blue-button']}
|
||||||
|
onClick={handleSubmit}
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RespondenCreate;
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
'use client';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import HeaderSearch from '../../../_com/header';
|
||||||
|
import indeksKepuasanState from '../../../_state/landing-page/indeks-kepuasan';
|
||||||
|
|
||||||
|
function Responden() {
|
||||||
|
const [search, setSearch] = useState("");
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<HeaderSearch
|
||||||
|
title='Responden'
|
||||||
|
placeholder='pencarian'
|
||||||
|
searchIcon={<IconSearch size={20} />}
|
||||||
|
value={search}
|
||||||
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
<ListResponden search={search} />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ListRespondenProps {
|
||||||
|
search: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ListResponden({ search }: ListRespondenProps) {
|
||||||
|
const state = useProxy(indeksKepuasanState.responden);
|
||||||
|
const router = useRouter();
|
||||||
|
const { data, page, totalPages, loading, load } = state.findMany;
|
||||||
|
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
load(page, 10)
|
||||||
|
}, [page]);
|
||||||
|
|
||||||
|
|
||||||
|
const filteredData = (data || []).filter(item => {
|
||||||
|
const keyword = search.toLowerCase();
|
||||||
|
return (
|
||||||
|
item.name.toLowerCase().includes(keyword)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (loading || !data) {
|
||||||
|
return (
|
||||||
|
<Stack py={10}>
|
||||||
|
<Skeleton height={730} />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (data.length === 0) {
|
||||||
|
return (
|
||||||
|
<Box py={10}>
|
||||||
|
<Paper p="md">
|
||||||
|
<Stack>
|
||||||
|
<Title>Responden</Title>
|
||||||
|
<Table striped withTableBorder withRowBorders>
|
||||||
|
<TableThead>
|
||||||
|
<TableTr>
|
||||||
|
<TableTh>No</TableTh>
|
||||||
|
<TableTh>Nama</TableTh>
|
||||||
|
<TableTh>Tanggal</TableTh>
|
||||||
|
<TableTh>Jenis Kelamin</TableTh>
|
||||||
|
<TableTh>Detail</TableTh>
|
||||||
|
</TableTr>
|
||||||
|
</TableThead>
|
||||||
|
</Table>
|
||||||
|
<Text ta="center">Tidak ada data berdasarkan jenis kelamin responden yang tersedia</Text>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box >
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Stack gap="xs">
|
||||||
|
<Paper bg={colors['white-1']} p="md" h={{ base: 730, md: 650 }}>
|
||||||
|
<Title mb={10} order={3}>List Responden</Title>
|
||||||
|
<Table striped withTableBorder withRowBorders>
|
||||||
|
<TableThead>
|
||||||
|
<TableTr>
|
||||||
|
<TableTh style={{ width: '5%', textAlign: 'center' }}>No</TableTh>
|
||||||
|
<TableTh style={{ width: '20%', textAlign: 'center' }}>Nama</TableTh>
|
||||||
|
<TableTh style={{ width: '20%', textAlign: 'center' }}>Tanggal</TableTh>
|
||||||
|
<TableTh style={{ width: '20%', textAlign: 'center' }}>Jenis Kelamin</TableTh>
|
||||||
|
<TableTh style={{ width: '15%', textAlign: 'center' }}>Detail</TableTh>
|
||||||
|
</TableTr>
|
||||||
|
</TableThead>
|
||||||
|
<TableTbody>
|
||||||
|
{filteredData.length === 0 ? (
|
||||||
|
<TableTr>
|
||||||
|
<TableTd colSpan={6}>
|
||||||
|
<Text ta='center' c='dimmed'>Belum ada data responden</Text>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
) : (
|
||||||
|
filteredData.map((item, index) => (
|
||||||
|
<TableTr key={item.id}>
|
||||||
|
<TableTd style={{ width: '5%', textAlign: 'center' }}>{index + 1}</TableTd>
|
||||||
|
<TableTd style={{ width: '20%', textAlign: 'center' }}>{item.name}</TableTd>
|
||||||
|
<TableTd style={{ width: '20%', textAlign: 'center' }}>{item.tanggal
|
||||||
|
? new Date(item.tanggal).toLocaleDateString('id-ID')
|
||||||
|
: '-'}</TableTd>
|
||||||
|
<TableTd style={{ width: '20%', textAlign: 'center' }}>{item.jenisKelamin.name}</TableTd>
|
||||||
|
<TableTd style={{ width: '15%', textAlign: 'center' }}>
|
||||||
|
<Button color='green' onClick={() => router.push(`/admin/ppid/ikm-desa-darmasaba/responden/${item.id}`)}>
|
||||||
|
<IconDeviceImac size={20} />
|
||||||
|
</Button>
|
||||||
|
</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>
|
||||||
|
|
||||||
|
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Responden;
|
||||||
|
|
||||||
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
|
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconEdit, IconSearch, IconX } from '@tabler/icons-react';
|
import { IconEdit, IconSearch, IconX } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
@@ -42,18 +42,21 @@ function ListKategoriPrestasi({ search }: { search: string }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
page,
|
||||||
|
totalPages,
|
||||||
|
loading,
|
||||||
|
load,
|
||||||
|
} = stateKategori.findMany
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
stateKategori.findMany.load()
|
load(page, 10, search)
|
||||||
}, [])
|
}, [page, search])
|
||||||
|
|
||||||
const filteredData = (stateKategori.findMany.data || []).filter(item => {
|
const filteredData = data || []
|
||||||
const keyword = search.toLowerCase();
|
|
||||||
return (
|
|
||||||
item.name.toLowerCase().includes(keyword)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!stateKategori.findMany.data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={10}>
|
||||||
<Skeleton h={500} />
|
<Skeleton h={500} />
|
||||||
@@ -100,6 +103,14 @@ function ListKategoriPrestasi({ search }: { search: string }) {
|
|||||||
</Table>
|
</Table>
|
||||||
</Box>
|
</Box>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
<Center>
|
||||||
|
<Pagination
|
||||||
|
value={page}
|
||||||
|
onChange={(newPage) => load(newPage)}
|
||||||
|
total={totalPages}
|
||||||
|
my={"md"}
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
{/* Modal Konfirmasi Hapus */}
|
{/* Modal Konfirmasi Hapus */}
|
||||||
<ModalKonfirmasiHapus
|
<ModalKonfirmasiHapus
|
||||||
opened={modalHapus}
|
opened={modalHapus}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
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 { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
@@ -30,20 +30,36 @@ function ListPrestasiDesa() {
|
|||||||
function ListPrestasi({ search }: { search: string }) {
|
function ListPrestasi({ search }: { search: string }) {
|
||||||
const listState = useProxy(prestasiState.prestasiDesa)
|
const listState = useProxy(prestasiState.prestasiDesa)
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
useEffect(() => {
|
|
||||||
listState.findMany.load()
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const filteredData = (listState.findMany.data || []).filter(item => {
|
const{
|
||||||
const keyword = search.toLowerCase();
|
data,
|
||||||
return (
|
page,
|
||||||
item.name.toLowerCase().includes(keyword) ||
|
totalPages,
|
||||||
item.deskripsi.toLowerCase().includes(keyword) ||
|
loading,
|
||||||
item.kategori?.name?.toLowerCase().includes(keyword)
|
load,
|
||||||
);
|
} = listState.findMany
|
||||||
|
|
||||||
|
// Debug log
|
||||||
|
console.log('ListPrestasi state:', {
|
||||||
|
loading,
|
||||||
|
data: data?.length,
|
||||||
|
page,
|
||||||
|
totalPages,
|
||||||
|
search
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!listState.findMany.data) {
|
useEffect(() => {
|
||||||
|
console.log('Loading data...', { page, search });
|
||||||
|
load(page, 10, search).then(() => {
|
||||||
|
console.log('Data loaded:', listState.findMany.data);
|
||||||
|
}).catch(error => {
|
||||||
|
console.error('Error loading data:', error);
|
||||||
|
});
|
||||||
|
}, [page, search])
|
||||||
|
|
||||||
|
const filteredData = data || []
|
||||||
|
|
||||||
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={10}>
|
||||||
<Skeleton h={500} />
|
<Skeleton h={500} />
|
||||||
@@ -82,7 +98,7 @@ function ListPrestasi({ search }: { search: string }) {
|
|||||||
<Text truncate="end" fz={"sm"} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
<Text truncate="end" fz={"sm"} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
||||||
</Box>
|
</Box>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>{item.kategori?.name}</TableTd>
|
<TableTd>{item.kategori?.name || 'No Category'}</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button onClick={() => router.push(`/admin/landing-page/prestasi-desa/list-prestasi-desa/${item.id}`)}>
|
<Button onClick={() => router.push(`/admin/landing-page/prestasi-desa/list-prestasi-desa/${item.id}`)}>
|
||||||
<IconDeviceImacCog size={25} />
|
<IconDeviceImacCog size={25} />
|
||||||
@@ -95,6 +111,14 @@ function ListPrestasi({ search }: { search: string }) {
|
|||||||
</Box>
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
<Center>
|
||||||
|
<Pagination
|
||||||
|
value={page}
|
||||||
|
onChange={(newPage) => load(newPage)}
|
||||||
|
total={totalPages}
|
||||||
|
my={"md"}
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
</Box>
|
</Box>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,67 +1,110 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
|
||||||
import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
|
|
||||||
import { usePathname, useRouter } from 'next/navigation';
|
import { usePathname, useRouter } from 'next/navigation';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title, Tooltip } from '@mantine/core';
|
||||||
|
import { IconBulb, IconUsers, IconBrandFacebook } from '@tabler/icons-react';
|
||||||
|
|
||||||
function LayoutTabs({ children }: { children: React.ReactNode }) {
|
function LayoutTabs({ children }: { children: React.ReactNode }) {
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
const pathname = usePathname()
|
const pathname = usePathname();
|
||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{
|
{
|
||||||
label: "Program Inovasi",
|
label: "Program Inovasi",
|
||||||
value: "program-inovasi",
|
value: "program-inovasi",
|
||||||
href: "/admin/landing-page/profile/program-inovasi"
|
href: "/admin/landing-page/profile/program-inovasi",
|
||||||
|
icon: <IconBulb size={18} stroke={1.8} />,
|
||||||
|
tooltip: "Lihat dan kelola program inovasi desa",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Pejabat Desa",
|
label: "Pejabat Desa",
|
||||||
value: "pejabat-desa",
|
value: "pejabat-desa",
|
||||||
href: "/admin/landing-page/profile/pejabat-desa"
|
href: "/admin/landing-page/profile/pejabat-desa",
|
||||||
|
icon: <IconUsers size={18} stroke={1.8} />,
|
||||||
|
tooltip: "Kelola data pejabat desa",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Media Sosial",
|
label: "Media Sosial",
|
||||||
value: "media-sosial",
|
value: "media-sosial",
|
||||||
href: "/admin/landing-page/profile/media-sosial"
|
href: "/admin/landing-page/profile/media-sosial",
|
||||||
|
icon: <IconBrandFacebook size={18} stroke={1.8} />,
|
||||||
|
tooltip: "Atur tautan media sosial desa",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
const curentTab = tabs.find(tab => tab.href === pathname)
|
|
||||||
const [activeTab, setActiveTab] = useState<string | null>(curentTab?.value || tabs[0].value);
|
const currentTab = tabs.find(tab => tab.href === pathname);
|
||||||
|
const [activeTab, setActiveTab] = useState<string | null>(currentTab?.value || tabs[0].value);
|
||||||
|
|
||||||
const handleTabChange = (value: string | null) => {
|
const handleTabChange = (value: string | null) => {
|
||||||
const tab = tabs.find(t => t.value === value)
|
const tab = tabs.find(t => t.value === value);
|
||||||
if (tab) {
|
if (tab) {
|
||||||
router.push(tab.href)
|
router.push(tab.href);
|
||||||
}
|
}
|
||||||
setActiveTab(value)
|
setActiveTab(value);
|
||||||
}
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const match = tabs.find(tab => tab.href === pathname)
|
const match = tabs.find(tab => tab.href === pathname);
|
||||||
if (match) {
|
if (match) {
|
||||||
setActiveTab(match.value)
|
setActiveTab(match.value);
|
||||||
}
|
}
|
||||||
}, [pathname])
|
}, [pathname]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack>
|
<Stack gap="lg">
|
||||||
<Title order={3}>Profile</Title>
|
<Title order={2} fw={700} style={{ color: "#1A1B1E" }}>
|
||||||
<Tabs color={colors['blue-button']} variant='pills' value={activeTab} onChange={handleTabChange}>
|
Profil Desa
|
||||||
<TabsList p={"xs"} bg={"#BBC8E7FF"}>
|
</Title>
|
||||||
{tabs.map((e, i) => (
|
<Tabs
|
||||||
<TabsTab key={i} value={e.value}>{e.label}</TabsTab>
|
variant="pills"
|
||||||
|
value={activeTab}
|
||||||
|
onChange={handleTabChange}
|
||||||
|
radius="lg"
|
||||||
|
keepMounted={false}
|
||||||
|
>
|
||||||
|
<TabsList
|
||||||
|
p="sm"
|
||||||
|
style={{
|
||||||
|
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
|
||||||
|
borderRadius: "1rem",
|
||||||
|
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{tabs.map((tab, i) => (
|
||||||
|
<Tooltip key={i} label={tab.tooltip} position="bottom" withArrow>
|
||||||
|
<TabsTab
|
||||||
|
value={tab.value}
|
||||||
|
leftSection={tab.icon}
|
||||||
|
style={{
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: "0.9rem",
|
||||||
|
transition: "all 0.2s ease",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{tab.label}
|
||||||
|
</TabsTab>
|
||||||
|
</Tooltip>
|
||||||
))}
|
))}
|
||||||
</TabsList>
|
</TabsList>
|
||||||
{tabs.map((e, i) => (
|
|
||||||
<TabsPanel key={i} value={e.value}>
|
{tabs.map((tab, i) => (
|
||||||
{/* Konten dummy, bisa diganti tergantung routing */}
|
<TabsPanel
|
||||||
<></>
|
key={i}
|
||||||
|
value={tab.value}
|
||||||
|
style={{
|
||||||
|
padding: "1.5rem",
|
||||||
|
background: "linear-gradient(180deg, #ffffff, #f5f6fa)",
|
||||||
|
borderRadius: "1rem",
|
||||||
|
boxShadow: "0 4px 16px rgba(0,0,0,0.05)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
</TabsPanel>
|
</TabsPanel>
|
||||||
))}
|
))}
|
||||||
</Tabs>
|
</Tabs>
|
||||||
{children}
|
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default LayoutTabs;
|
export default LayoutTabs;
|
||||||
|
|||||||
@@ -1,9 +1,20 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
'use client'
|
'use client';
|
||||||
import profileLandingPageState from '@/app/admin/(dashboard)/_state/landing-page/profile';
|
import profileLandingPageState from '@/app/admin/(dashboard)/_state/landing-page/profile';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import ApiFetch from '@/lib/api-fetch';
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
import { Box, Button, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Image,
|
||||||
|
Paper,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
} from '@mantine/core';
|
||||||
import { Dropzone } from '@mantine/dropzone';
|
import { Dropzone } from '@mantine/dropzone';
|
||||||
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
@@ -12,17 +23,17 @@ import { toast } from 'react-toastify';
|
|||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
function EditMediaSosial() {
|
function EditMediaSosial() {
|
||||||
const stateMediaSosial = useProxy(profileLandingPageState.mediaSosial)
|
const stateMediaSosial = useProxy(profileLandingPageState.mediaSosial);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
|
|
||||||
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
||||||
const [file, setFile] = useState<File | null>(null);
|
const [file, setFile] = useState<File | null>(null);
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
name: stateMediaSosial.update.form.name || "",
|
name: stateMediaSosial.update.form.name || '',
|
||||||
iconUrl: stateMediaSosial.update.form.iconUrl || "",
|
iconUrl: stateMediaSosial.update.form.iconUrl || '',
|
||||||
imageId: stateMediaSosial.update.form.imageId || ""
|
imageId: stateMediaSosial.update.form.imageId || '',
|
||||||
})
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const id = params?.id as string;
|
const id = params?.id as string;
|
||||||
@@ -34,136 +45,147 @@ function EditMediaSosial() {
|
|||||||
|
|
||||||
if (data) {
|
if (data) {
|
||||||
setFormData({
|
setFormData({
|
||||||
name: data.name || "",
|
name: data.name || '',
|
||||||
iconUrl: data.iconUrl || "",
|
iconUrl: data.iconUrl || '',
|
||||||
imageId: data.imageId || "",
|
imageId: data.imageId || '',
|
||||||
});
|
});
|
||||||
// Tampilkan preview gambar
|
if (data.image?.link) setPreviewImage(data.image.link);
|
||||||
if (data.image?.link) {
|
|
||||||
setPreviewImage(data.image.link);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error loading program inovasi:", error);
|
console.error('Error loading media sosial:', error);
|
||||||
toast.error(
|
toast.error(
|
||||||
error instanceof Error ? error.message : "Gagal mengambil data program inovasi"
|
error instanceof Error ? error.message : 'Gagal mengambil data media sosial'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
loadMediaSosial();
|
loadMediaSosial();
|
||||||
}, [params?.id]);
|
}, [params?.id]);
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
try {
|
try {
|
||||||
stateMediaSosial.update.form = {
|
stateMediaSosial.update.form = { ...stateMediaSosial.update.form, ...formData };
|
||||||
...stateMediaSosial.update.form,
|
|
||||||
name: formData.name,
|
|
||||||
iconUrl: formData.iconUrl,
|
|
||||||
imageId: formData.imageId ?? "",
|
|
||||||
}
|
|
||||||
if (file) {
|
if (file) {
|
||||||
const res = await ApiFetch.api.fileStorage.create.post({ file, name: file.name });
|
const res = await ApiFetch.api.fileStorage.create.post({ file, name: file.name });
|
||||||
const uploaded = res.data?.data;
|
const uploaded = res.data?.data;
|
||||||
|
|
||||||
if (!uploaded?.id) {
|
if (!uploaded?.id) return toast.error('Gagal upload gambar');
|
||||||
return toast.error("Gagal upload gambar");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update imageId in global state
|
|
||||||
stateMediaSosial.update.form.imageId = uploaded.id;
|
stateMediaSosial.update.form.imageId = uploaded.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
await stateMediaSosial.update.update();
|
await stateMediaSosial.update.update();
|
||||||
toast.success("Media Sosial berhasil diperbarui!");
|
toast.success('Media sosial berhasil diperbarui!');
|
||||||
router.push("/admin/landing-page/profile/media-sosial");
|
router.push('/admin/landing-page/profile/media-sosial');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error updating media sosial:", error);
|
console.error('Error updating media sosial:', error);
|
||||||
toast.error("Terjadi kesalahan saat memperbarui media sosial");
|
toast.error('Terjadi kesalahan saat memperbarui media sosial');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Box mb={10}>
|
<Group mb="md">
|
||||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
<Tooltip label="Kembali ke halaman sebelumnya" color="blue" withArrow>
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
</Button>
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
</Box>
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Title order={4} ml="sm" c="dark">
|
||||||
|
Edit Media Sosial
|
||||||
|
</Title>
|
||||||
|
</Group>
|
||||||
|
|
||||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
<Paper
|
||||||
<Stack gap={"xs"}>
|
w={{ base: '100%', md: '50%' }}
|
||||||
<Title order={4}>Edit Media Sosial</Title>
|
bg={colors['white-1']}
|
||||||
|
p="lg"
|
||||||
|
radius="md"
|
||||||
|
shadow="sm"
|
||||||
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
<Text fw="bold" fz="sm" mb={6}>
|
||||||
<Box>
|
Gambar Media Sosial
|
||||||
<Dropzone
|
</Text>
|
||||||
onDrop={(files) => {
|
<Dropzone
|
||||||
const selectedFile = files[0]; // Ambil file pertama
|
onDrop={(files) => {
|
||||||
if (selectedFile) {
|
const selectedFile = files[0];
|
||||||
setFile(selectedFile);
|
if (selectedFile) {
|
||||||
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
|
setFile(selectedFile);
|
||||||
}
|
setPreviewImage(URL.createObjectURL(selectedFile));
|
||||||
}}
|
}
|
||||||
onReject={() => toast.error('File tidak valid.')}
|
}}
|
||||||
maxSize={5 * 1024 ** 2} // Maks 5MB
|
onReject={() => toast.error('File tidak valid, gunakan format gambar')}
|
||||||
accept={{ 'image/*': [] }}
|
maxSize={5 * 1024 ** 2}
|
||||||
>
|
accept={{ 'image/*': [] }}
|
||||||
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
radius="md"
|
||||||
<Dropzone.Accept>
|
p="xl"
|
||||||
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
>
|
||||||
</Dropzone.Accept>
|
<Group justify="center" gap="xl" mih={180}>
|
||||||
<Dropzone.Reject>
|
<Dropzone.Accept>
|
||||||
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
<IconUpload size={48} color={colors['blue-button']} stroke={1.5} />
|
||||||
</Dropzone.Reject>
|
</Dropzone.Accept>
|
||||||
<Dropzone.Idle>
|
<Dropzone.Reject>
|
||||||
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
<IconX size={48} color="red" stroke={1.5} />
|
||||||
</Dropzone.Idle>
|
</Dropzone.Reject>
|
||||||
|
<Dropzone.Idle>
|
||||||
|
<IconPhoto size={48} color="#868e96" stroke={1.5} />
|
||||||
|
</Dropzone.Idle>
|
||||||
|
<Stack gap="xs" align="center">
|
||||||
|
<Text size="md" fw={500}>
|
||||||
|
Seret gambar atau klik untuk memilih file
|
||||||
|
</Text>
|
||||||
|
<Text size="sm" c="dimmed">
|
||||||
|
Maksimal 5MB, format gambar wajib
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
</Group>
|
||||||
|
</Dropzone>
|
||||||
|
|
||||||
<div>
|
{previewImage && (
|
||||||
<Text size="xl" inline>
|
<Box mt="sm" style={{ display: 'flex', justifyContent: 'center' }}>
|
||||||
Drag gambar ke sini atau klik untuk pilih file
|
<Image
|
||||||
</Text>
|
src={previewImage}
|
||||||
<Text size="sm" c="dimmed" inline mt={7}>
|
alt="Preview Gambar"
|
||||||
Maksimal 5MB dan harus format gambar
|
radius="md"
|
||||||
</Text>
|
style={{ maxHeight: 220, objectFit: 'contain', border: `1px solid ${colors['blue-button']}` }}
|
||||||
</div>
|
/>
|
||||||
</Group>
|
</Box>
|
||||||
</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>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
|
label="Nama Media Sosial / Kontak"
|
||||||
|
placeholder="Masukkan nama media sosial atau kontak"
|
||||||
value={formData.name}
|
value={formData.name}
|
||||||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Nama Media Sosial / Nama Kontak</Text>}
|
required
|
||||||
placeholder='Masukkan nama media sosial'
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
|
label="Link Media Sosial / Nomor Telepon"
|
||||||
|
placeholder="Masukkan link media sosial atau nomor telepon"
|
||||||
value={formData.iconUrl}
|
value={formData.iconUrl}
|
||||||
onChange={(e) => setFormData({ ...formData, iconUrl: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, iconUrl: e.target.value })}
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Icon URL / No Telephone</Text>}
|
required
|
||||||
placeholder='Masukkan icon url'
|
|
||||||
/>
|
/>
|
||||||
<Group>
|
|
||||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Submit</Button>
|
<Group justify="right">
|
||||||
|
<Button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
style={{
|
||||||
|
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||||
|
color: '#fff',
|
||||||
|
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
@@ -2,103 +2,132 @@
|
|||||||
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
||||||
import profileLandingPageState from '@/app/admin/(dashboard)/_state/landing-page/profile';
|
import profileLandingPageState from '@/app/admin/(dashboard)/_state/landing-page/profile';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Flex, Image, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
import { Box, Button, Group, Image, Paper, Skeleton, Stack, Text, Tooltip } from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
|
import { IconArrowBack, IconEdit, IconTrash } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
function DetailMediaSosial() {
|
function DetailMediaSosial() {
|
||||||
const stateMediaSosial = useProxy(profileLandingPageState.mediaSosial)
|
const stateMediaSosial = useProxy(profileLandingPageState.mediaSosial);
|
||||||
const [modalHapus, setModalHapus] = useState(false)
|
const [modalHapus, setModalHapus] = useState(false);
|
||||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||||
const params = useParams()
|
const params = useParams();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
stateMediaSosial.findUnique.load(params?.id as string)
|
stateMediaSosial.findUnique.load(params?.id as string);
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
const handleHapus = () => {
|
const handleHapus = () => {
|
||||||
if (selectedId) {
|
if (selectedId) {
|
||||||
stateMediaSosial.delete.byId(selectedId)
|
stateMediaSosial.delete.byId(selectedId);
|
||||||
setModalHapus(false)
|
setModalHapus(false);
|
||||||
setSelectedId(null)
|
setSelectedId(null);
|
||||||
router.push("/admin/landing-page/profile/media-sosial")
|
router.push("/admin/landing-page/profile/media-sosial");
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
if (!stateMediaSosial.findUnique.data) {
|
if (!stateMediaSosial.findUnique.data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={10}>
|
||||||
<Skeleton h={500} />
|
<Skeleton height={500} radius="md" />
|
||||||
</Stack>
|
</Stack>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const data = stateMediaSosial.findUnique.data;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box py={10}>
|
||||||
<Box mb={10}>
|
<Button
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
variant="subtle"
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
onClick={() => router.back()}
|
||||||
</Button>
|
leftSection={<IconArrowBack size={24} color={colors['blue-button']} />}
|
||||||
</Box>
|
mb={15}
|
||||||
<Paper w={{ base: "100%", md: "50%" }} bg={colors['white-1']} p={'md'}>
|
>
|
||||||
<Stack>
|
Kembali
|
||||||
<Text fz={"xl"} fw={"bold"}>Detail Media Sosial</Text>
|
</Button>
|
||||||
<Paper bg={colors['BG-trans']} p={'md'}>
|
|
||||||
<Stack gap={"xs"}>
|
<Paper
|
||||||
|
w={{ base: "100%", md: "60%" }}
|
||||||
|
bg={colors['white-1']}
|
||||||
|
p="lg"
|
||||||
|
radius="md"
|
||||||
|
shadow="sm"
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
|
<Text fz="2xl" fw="bold" c={colors['blue-button']}>
|
||||||
|
Detail Media Sosial
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Paper bg="#ECEEF8" p="md" radius="md" shadow="xs">
|
||||||
|
<Stack gap="sm">
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"lg"} fw={"bold"}>Nama Media Sosial / Nama Kontak</Text>
|
<Text fz="lg" fw="bold">Nama Media Sosial / Kontak</Text>
|
||||||
<Text fz={"lg"}>{stateMediaSosial.findUnique.data?.name}</Text>
|
<Text fz="md" c="dimmed">{data.name || '-'}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"lg"} fw={"bold"}>Icon URL / No Telephone</Text>
|
<Text fz="lg" fw="bold">Icon / Nomor Telepon</Text>
|
||||||
<Text fz={"lg"}>{stateMediaSosial.findUnique.data?.iconUrl}</Text>
|
<Text fz="md" c="dimmed">{data.iconUrl || '-'}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"lg"} fw={"bold"}>Gambar</Text>
|
<Text fz="lg" fw="bold">Gambar</Text>
|
||||||
<Box w={100} h={100}>
|
{data.image?.link ? (
|
||||||
<Image src={stateMediaSosial.findUnique.data?.image?.link} alt="gambar" />
|
<Image
|
||||||
</Box>
|
src={data.image.link}
|
||||||
|
alt={data.name || 'Gambar Media Sosial'}
|
||||||
|
w={120}
|
||||||
|
h={120}
|
||||||
|
radius="md"
|
||||||
|
fit="cover"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Text fz="sm" c="dimmed">Tidak ada gambar</Text>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
<Box>
|
|
||||||
<Flex gap={"xs"}>
|
<Group gap="sm">
|
||||||
|
<Tooltip label="Hapus Media Sosial" withArrow position="top">
|
||||||
<Button
|
<Button
|
||||||
|
color="red"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (stateMediaSosial.findUnique.data) {
|
setSelectedId(data.id);
|
||||||
setSelectedId(stateMediaSosial.findUnique.data.id);
|
setModalHapus(true);
|
||||||
setModalHapus(true);
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
disabled={!stateMediaSosial.findUnique.data}
|
variant="light"
|
||||||
color="red">
|
radius="md"
|
||||||
<IconX size={20} />
|
size="md"
|
||||||
|
>
|
||||||
|
<IconTrash size={20} />
|
||||||
</Button>
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
<Tooltip label="Edit Media Sosial" withArrow position="top">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
color="green"
|
||||||
if (stateMediaSosial.findUnique.data) {
|
onClick={() => router.push(`/admin/landing-page/profile/media-sosial/${data.id}/edit`)}
|
||||||
router.push(`/admin/landing-page/profile/media-sosial/${stateMediaSosial.findUnique.data.id}/edit`);
|
variant="light"
|
||||||
}
|
radius="md"
|
||||||
}}
|
size="md"
|
||||||
disabled={!stateMediaSosial.findUnique.data}
|
>
|
||||||
color="green">
|
|
||||||
<IconEdit size={20} />
|
<IconEdit size={20} />
|
||||||
</Button>
|
</Button>
|
||||||
</Flex>
|
</Tooltip>
|
||||||
</Box>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
{/* Modal Hapus */}
|
|
||||||
<ModalKonfirmasiHapus
|
<ModalKonfirmasiHapus
|
||||||
opened={modalHapus}
|
opened={modalHapus}
|
||||||
onClose={() => setModalHapus(false)}
|
onClose={() => setModalHapus(false)}
|
||||||
onConfirm={handleHapus}
|
onConfirm={handleHapus}
|
||||||
text="Apakah anda yakin ingin menghapus media sosial ini?"
|
text="Apakah Anda yakin ingin menghapus media sosial ini?"
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,8 +1,19 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
'use client'
|
'use client';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import ApiFetch from '@/lib/api-fetch';
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
import { Box, Button, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Image,
|
||||||
|
Paper,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
} from '@mantine/core';
|
||||||
import { Dropzone } from '@mantine/dropzone';
|
import { Dropzone } from '@mantine/dropzone';
|
||||||
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
@@ -11,9 +22,9 @@ import { toast } from 'react-toastify';
|
|||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import profileLandingPageState from '../../../../_state/landing-page/profile';
|
import profileLandingPageState from '../../../../_state/landing-page/profile';
|
||||||
|
|
||||||
function CreateMediaSosial() {
|
export default function CreateMediaSosial() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const stateMediaSosial = useProxy(profileLandingPageState.mediaSosial)
|
const stateMediaSosial = useProxy(profileLandingPageState.mediaSosial);
|
||||||
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
||||||
const [file, setFile] = useState<File | null>(null);
|
const [file, setFile] = useState<File | null>(null);
|
||||||
|
|
||||||
@@ -23,27 +34,28 @@ function CreateMediaSosial() {
|
|||||||
|
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
stateMediaSosial.create.form = {
|
stateMediaSosial.create.form = {
|
||||||
name: "",
|
name: '',
|
||||||
imageId: "",
|
imageId: '',
|
||||||
iconUrl: "",
|
iconUrl: '',
|
||||||
};
|
};
|
||||||
setPreviewImage(null);
|
setPreviewImage(null);
|
||||||
setFile(null);
|
setFile(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
if (!file) {
|
if (!file) {
|
||||||
return toast.warn("Pilih file gambar terlebih dahulu");
|
return toast.warn('Silakan pilih file gambar terlebih dahulu');
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await ApiFetch.api.fileStorage.create.post({
|
const res = await ApiFetch.api.fileStorage.create.post({
|
||||||
file,
|
file,
|
||||||
name: file.name,
|
name: file.name,
|
||||||
})
|
});
|
||||||
|
|
||||||
const uploaded = res.data?.data;
|
const uploaded = res.data?.data;
|
||||||
|
|
||||||
if (!uploaded?.id) {
|
if (!uploaded?.id) {
|
||||||
return toast.error("Gagal mengupload file");
|
return toast.error('Gagal mengunggah gambar, silakan coba lagi');
|
||||||
}
|
}
|
||||||
|
|
||||||
stateMediaSosial.create.form.imageId = uploaded.id;
|
stateMediaSosial.create.form.imageId = uploaded.id;
|
||||||
@@ -51,98 +63,108 @@ function CreateMediaSosial() {
|
|||||||
await stateMediaSosial.create.create();
|
await stateMediaSosial.create.create();
|
||||||
|
|
||||||
resetForm();
|
resetForm();
|
||||||
router.push("/admin/landing-page/profile/media-sosial")
|
router.push('/admin/landing-page/profile/media-sosial');
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Box mb={10}>
|
<Group mb="md">
|
||||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
<Tooltip label="Kembali ke halaman sebelumnya" color="blue" withArrow>
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
</Button>
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
</Box>
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Title order={4} ml="sm" c="dark">
|
||||||
|
Tambah Media Sosial
|
||||||
|
</Title>
|
||||||
|
</Group>
|
||||||
|
|
||||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
<Paper
|
||||||
<Stack gap={"xs"}>
|
w={{ base: '100%', md: '50%' }}
|
||||||
<Title order={4}>Create Media Sosial</Title>
|
bg={colors['white-1']}
|
||||||
|
p="lg"
|
||||||
|
radius="md"
|
||||||
|
shadow="sm"
|
||||||
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
<Text fw="bold" fz="sm" mb={6}>
|
||||||
<Box>
|
Gambar Media Sosial
|
||||||
<Dropzone
|
</Text>
|
||||||
onDrop={(files) => {
|
<Dropzone
|
||||||
const selectedFile = files[0]; // Ambil file pertama
|
onDrop={(files) => {
|
||||||
if (selectedFile) {
|
const selectedFile = files[0];
|
||||||
setFile(selectedFile);
|
if (selectedFile) {
|
||||||
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
|
setFile(selectedFile);
|
||||||
}
|
setPreviewImage(URL.createObjectURL(selectedFile));
|
||||||
}}
|
}
|
||||||
onReject={() => toast.error('File tidak valid.')}
|
}}
|
||||||
maxSize={5 * 1024 ** 2} // Maks 5MB
|
onReject={() => toast.error('File tidak valid, gunakan format gambar')}
|
||||||
accept={{ 'image/*': [] }}
|
maxSize={5 * 1024 ** 2}
|
||||||
>
|
accept={{ 'image/*': [] }}
|
||||||
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
radius="md"
|
||||||
<Dropzone.Accept>
|
p="xl"
|
||||||
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
>
|
||||||
</Dropzone.Accept>
|
<Group justify="center" gap="xl" mih={180}>
|
||||||
<Dropzone.Reject>
|
<Dropzone.Accept>
|
||||||
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
<IconUpload size={48} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||||
</Dropzone.Reject>
|
</Dropzone.Accept>
|
||||||
<Dropzone.Idle>
|
<Dropzone.Reject>
|
||||||
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
<IconX size={48} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||||
</Dropzone.Idle>
|
</Dropzone.Reject>
|
||||||
|
<Dropzone.Idle>
|
||||||
|
<IconPhoto size={48} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||||
|
</Dropzone.Idle>
|
||||||
|
</Group>
|
||||||
|
<Text ta="center" mt="sm" size="sm" color="dimmed">
|
||||||
|
Seret gambar atau klik untuk memilih file (maks 5MB)
|
||||||
|
</Text>
|
||||||
|
</Dropzone>
|
||||||
|
|
||||||
<div>
|
{previewImage && (
|
||||||
<Text size="xl" inline>
|
<Box mt="sm" style={{ textAlign: 'center' }}>
|
||||||
Drag gambar ke sini atau klik untuk pilih file
|
<Image
|
||||||
</Text>
|
src={previewImage}
|
||||||
<Text size="sm" c="dimmed" inline mt={7}>
|
alt="Preview Gambar"
|
||||||
Maksimal 5MB dan harus format gambar
|
radius="md"
|
||||||
</Text>
|
style={{ maxHeight: 200, objectFit: 'contain', border: '1px solid #ddd' }}
|
||||||
</div>
|
/>
|
||||||
</Group>
|
</Box>
|
||||||
</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>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
|
label="Nama Media Sosial / Kontak"
|
||||||
|
placeholder="Masukkan nama media sosial atau kontak"
|
||||||
value={stateMediaSosial.create.form.name || ''}
|
value={stateMediaSosial.create.form.name || ''}
|
||||||
onChange={(val) => {
|
onChange={(e) => (stateMediaSosial.create.form.name = e.target.value)}
|
||||||
stateMediaSosial.create.form.name = val.target.value;
|
required
|
||||||
}}
|
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Nama Media Sosial / Nama Kontak</Text>}
|
|
||||||
placeholder='Masukkan nama media sosial / nama kontak'
|
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
|
label="Link Media Sosial / Nomor Telepon"
|
||||||
|
placeholder="Masukkan link media sosial atau nomor telepon"
|
||||||
value={stateMediaSosial.create.form.iconUrl || ''}
|
value={stateMediaSosial.create.form.iconUrl || ''}
|
||||||
onChange={(val) => {
|
onChange={(e) => (stateMediaSosial.create.form.iconUrl = e.target.value)}
|
||||||
stateMediaSosial.create.form.iconUrl = val.target.value;
|
required
|
||||||
}}
|
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Link Media Sosial / No Telephone</Text>}
|
|
||||||
placeholder='Masukkan link media sosial / no telephone'
|
|
||||||
/>
|
/>
|
||||||
<Group>
|
|
||||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Submit</Button>
|
<Group justify="right">
|
||||||
|
<Button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
style={{
|
||||||
|
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||||
|
color: '#fff',
|
||||||
|
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CreateMediaSosial;
|
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
|
||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Center, Image, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
import { Box, Button, Center, Group, Image, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title, Tooltip } from '@mantine/core';
|
||||||
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { IconDeviceImac, IconPlus, IconSearch } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import HeaderSearch from '../../../_com/header';
|
import HeaderSearch from '../../../_com/header';
|
||||||
import JudulList from '../../../_com/judulList';
|
|
||||||
import profileLandingPageState from '../../../_state/landing-page/profile';
|
import profileLandingPageState from '../../../_state/landing-page/profile';
|
||||||
|
|
||||||
function MediaSosial() {
|
function MediaSosial() {
|
||||||
@@ -16,7 +15,7 @@ function MediaSosial() {
|
|||||||
<Box>
|
<Box>
|
||||||
<HeaderSearch
|
<HeaderSearch
|
||||||
title='Media Sosial'
|
title='Media Sosial'
|
||||||
placeholder='pencarian'
|
placeholder='Cari nama media sosial atau kontak...'
|
||||||
searchIcon={<IconSearch size={20} />}
|
searchIcon={<IconSearch size={20} />}
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
@@ -38,95 +37,83 @@ function ListMediaSosial({ search }: { search: string }) {
|
|||||||
load,
|
load,
|
||||||
} = stateMediaSosial.findMany;
|
} = stateMediaSosial.findMany;
|
||||||
|
|
||||||
useEffect(() => {
|
useShallowEffect(() => {
|
||||||
load(page, 10)
|
load(page, 10, search)
|
||||||
}, [page])
|
}, [page, search])
|
||||||
|
|
||||||
const filteredData = useMemo(() => {
|
const filteredData = data || []
|
||||||
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) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={10}>
|
||||||
<Skeleton height={550} />
|
<Skeleton height={600} radius="md" />
|
||||||
</Stack>
|
</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 (
|
return (
|
||||||
<Box py={10}>
|
<Box py={10}>
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
<Paper bg={colors['white-1']} p={'lg'} shadow="md" radius="md">
|
||||||
<JudulList
|
<Group justify="space-between" mb="md">
|
||||||
title='List Media Sosial'
|
<Title order={4}>Daftar Media Sosial</Title>
|
||||||
href='/admin/landing-page/profile/media-sosial/create'
|
<Tooltip label="Tambah Media Sosial" withArrow>
|
||||||
/>
|
<Button leftSection={<IconPlus size={18} />} color="blue" variant="light" onClick={() => router.push('/admin/landing-page/profile/media-sosial/create')}>
|
||||||
<Box style={{ overflowY: "auto" }}>
|
Tambah Baru
|
||||||
<Table striped withTableBorder withRowBorders>
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</Group>
|
||||||
|
<Box style={{ overflowX: "auto" }}>
|
||||||
|
<Table highlightOnHover>
|
||||||
<TableThead>
|
<TableThead>
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh>Nama Media Sosial / Nama Kontak</TableTh>
|
<TableTh>Nama Media Sosial / Kontak</TableTh>
|
||||||
<TableTh>Image</TableTh>
|
<TableTh>Gambar</TableTh>
|
||||||
<TableTh>Icon URL / No Telephone</TableTh>
|
<TableTh>Icon / No. Telepon</TableTh>
|
||||||
<TableTh>Detail</TableTh>
|
<TableTh>Aksi</TableTh>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
<TableTbody>
|
<TableTbody>
|
||||||
{filteredData.map((item) => (
|
{filteredData.length > 0 ? (
|
||||||
<TableTr key={item.id}>
|
filteredData.map((item) => (
|
||||||
<TableTd>{item.name}</TableTd>
|
<TableTr key={item.id}>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Box w={50} h={50}>
|
<Text fw={500}>{item.name}</Text>
|
||||||
<Image src={item.image?.link} alt={item.name} />
|
</TableTd>
|
||||||
</Box>
|
<TableTd>
|
||||||
</TableTd>
|
<Box w={50} h={50} style={{ borderRadius: 8, overflow: 'hidden' }}>
|
||||||
<TableTd>
|
{item.image?.link ? (
|
||||||
<Box w={250}>
|
<Image src={item.image.link} alt={item.name} fit="cover" />
|
||||||
<a style={{color: "black"}} href={item.iconUrl} target="_blank" rel="noopener noreferrer">
|
) : (
|
||||||
<Text truncate fz={'sm'}>{item.iconUrl}</Text>
|
<Box bg={colors['blue-button']} w="100%" h="100%" />
|
||||||
</a>
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button onClick={() => router.push(`/admin/landing-page/profile/media-sosial/${item.id}`)}>
|
<Text truncate fz="sm" color="dimmed">
|
||||||
<IconDeviceImac size={20} />
|
{item.iconUrl || item.noTelp || '-'}
|
||||||
</Button>
|
</Text>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Button
|
||||||
|
variant="light"
|
||||||
|
color="blue"
|
||||||
|
onClick={() => router.push(`/admin/landing-page/profile/media-sosial/${item.id}`)}
|
||||||
|
>
|
||||||
|
<IconDeviceImac size={20} />
|
||||||
|
<Text ml={5}>Detail</Text>
|
||||||
|
</Button>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<TableTr>
|
||||||
|
<TableTd colSpan={4}>
|
||||||
|
<Center py={20}>
|
||||||
|
<Text color="dimmed">Tidak ada data media sosial yang cocok</Text>
|
||||||
|
</Center>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
))}
|
)}
|
||||||
</TableTbody>
|
</TableTbody>
|
||||||
</Table>
|
</Table>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -136,11 +123,13 @@ function ListMediaSosial({ search }: { search: string }) {
|
|||||||
value={page}
|
value={page}
|
||||||
onChange={(newPage) => {
|
onChange={(newPage) => {
|
||||||
load(newPage, 10);
|
load(newPage, 10);
|
||||||
window.scrollTo(0, 0);
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
}}
|
}}
|
||||||
total={totalPages}
|
total={totalPages}
|
||||||
mt="md"
|
mt="md"
|
||||||
mb="md"
|
mb="md"
|
||||||
|
color="blue"
|
||||||
|
radius="md"
|
||||||
/>
|
/>
|
||||||
</Center>
|
</Center>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Alert, Box, Button, Center, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import { Alert, Box, Button, Center, Group, Image, Paper, Stack, Text, TextInput, Title, Tooltip } from '@mantine/core';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
@@ -144,124 +144,134 @@ function EditPejabatDesa() {
|
|||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Stack gap="xs">
|
<Stack gap="xs">
|
||||||
<Box>
|
<Group mb="md">
|
||||||
<Button variant="subtle" onClick={handleBack}>
|
<Tooltip label="Kembali ke halaman sebelumnya" color="blue" withArrow>
|
||||||
<IconArrowBack color={colors['blue-button']} size={20} />
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
</Button>
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
</Box>
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Title order={4} ml="sm" c="dark">
|
||||||
|
Edit Pejabat Desa
|
||||||
|
</Title>
|
||||||
|
</Group>
|
||||||
|
|
||||||
<Box>
|
<Paper
|
||||||
<Paper w={{ base: "100%", md: "50%" }} bg={colors['white-1']} p="md" radius={10}>
|
w={{ base: "100%", md: "50%" }}
|
||||||
<Stack gap="xs">
|
bg={colors['white-1']}
|
||||||
<Title order={3}>Edit Profile Pejabat Desa</Title>
|
p="md"
|
||||||
|
radius="md"
|
||||||
|
shadow="sm"
|
||||||
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
|
>
|
||||||
|
<Stack gap="xs">
|
||||||
|
<Title order={3}>Edit Profile Pejabat Desa</Title>
|
||||||
|
|
||||||
{/* Nama Field */}
|
{/* Nama Field */}
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fw="bold">Nama Perbekel</Text>}
|
label={<Text fw="bold">Nama Perbekel</Text>}
|
||||||
placeholder="Masukkan nama perbekel"
|
placeholder="Masukkan nama perbekel"
|
||||||
value={allState.edit.form.name}
|
value={allState.edit.form.name}
|
||||||
onChange={(e) => handleFieldChange('name', e.currentTarget.value)}
|
onChange={(e) => handleFieldChange('name', e.currentTarget.value)}
|
||||||
error={!allState.edit.form.name && "Nama wajib diisi"}
|
error={!allState.edit.form.name && "Nama wajib diisi"}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Posisi Field */}
|
{/* Posisi Field */}
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fw="bold">Posisi</Text>}
|
label={<Text fw="bold">Posisi</Text>}
|
||||||
placeholder="Masukkan posisi"
|
placeholder="Masukkan posisi"
|
||||||
value={allState.edit.form.position}
|
value={allState.edit.form.position}
|
||||||
onChange={(e) => handleFieldChange('position', e.currentTarget.value)}
|
onChange={(e) => handleFieldChange('position', e.currentTarget.value)}
|
||||||
error={!allState.edit.form.position && "Posisi wajib diisi"}
|
error={!allState.edit.form.position && "Posisi wajib diisi"}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* File Upload */}
|
{/* File Upload */}
|
||||||
|
<Box>
|
||||||
|
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
<Dropzone
|
||||||
<Box>
|
onDrop={(files) => handleFileChange(files[0])}
|
||||||
<Dropzone
|
onReject={() => toast.error('File tidak valid.')}
|
||||||
onDrop={(files) => handleFileChange(files[0])}
|
maxSize={5 * 1024 ** 2} // Maks 5MB
|
||||||
onReject={() => toast.error('File tidak valid.')}
|
accept={{ 'image/*': [] }}
|
||||||
maxSize={5 * 1024 ** 2} // Maks 5MB
|
>
|
||||||
accept={{ 'image/*': [] }}
|
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
||||||
>
|
<Dropzone.Accept>
|
||||||
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||||
<Dropzone.Accept>
|
</Dropzone.Accept>
|
||||||
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
<Dropzone.Reject>
|
||||||
</Dropzone.Accept>
|
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||||
<Dropzone.Reject>
|
</Dropzone.Reject>
|
||||||
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
<Dropzone.Idle>
|
||||||
</Dropzone.Reject>
|
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||||
<Dropzone.Idle>
|
</Dropzone.Idle>
|
||||||
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
|
||||||
</Dropzone.Idle>
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Text size="xl" inline>
|
<Text size="xl" inline>
|
||||||
Drag gambar ke sini atau klik untuk pilih file
|
Drag gambar ke sini atau klik untuk pilih file
|
||||||
</Text>
|
</Text>
|
||||||
<Text size="sm" c="dimmed" inline mt={7}>
|
<Text size="sm" c="dimmed" inline mt={7}>
|
||||||
Maksimal 5MB dan harus format gambar
|
Maksimal 5MB dan harus format gambar
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
</Group>
|
</Group>
|
||||||
</Dropzone>
|
</Dropzone>
|
||||||
|
|
||||||
{/* Tampilkan preview kalau ada */}
|
{/* Tampilkan preview kalau ada */}
|
||||||
{previewImage && (
|
{previewImage && (
|
||||||
<Box mt="sm">
|
<Box mt="sm">
|
||||||
<Image
|
<Image
|
||||||
src={previewImage}
|
src={previewImage}
|
||||||
alt="Preview"
|
alt="Preview"
|
||||||
style={{
|
style={{
|
||||||
maxWidth: '100%',
|
maxWidth: '100%',
|
||||||
maxHeight: '200px',
|
maxHeight: '200px',
|
||||||
objectFit: 'contain',
|
objectFit: 'contain',
|
||||||
borderRadius: '8px',
|
borderRadius: '8px',
|
||||||
border: '1px solid #ddd',
|
border: '1px solid #ddd',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
|
||||||
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{/* Preview Gambar */}
|
|
||||||
<Box>
|
|
||||||
<Text fz="sm" fw="bold" mb="xs">Preview Gambar</Text>
|
|
||||||
{previewImage ? (
|
|
||||||
<Image alt="Profile preview" src={previewImage} w={200} h={200} fit="cover" radius="md" />
|
|
||||||
) : (
|
|
||||||
<Center w={200} h={200} bg="gray.2">
|
|
||||||
<Stack align="center" gap="xs">
|
|
||||||
<IconImageInPicture size={48} color="gray" />
|
|
||||||
<Text size="sm" c="gray">Tidak ada gambar</Text>
|
|
||||||
</Stack>
|
|
||||||
</Center>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
</Box>
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
{/* Submit Button */}
|
{/* Preview Gambar */}
|
||||||
<Group>
|
<Box>
|
||||||
<Button
|
<Text fz="sm" fw="bold" mb="xs">Preview Gambar</Text>
|
||||||
bg={colors['blue-button']}
|
{previewImage ? (
|
||||||
onClick={handleSubmit}
|
<Image alt="Profile preview" src={previewImage} w={200} h={200} fit="cover" radius="md" />
|
||||||
loading={isSubmitting || allState.edit.loading}
|
) : (
|
||||||
disabled={!allState.edit.form.name}
|
<Center w={200} h={200} bg="gray.2">
|
||||||
>
|
<Stack align="center" gap="xs">
|
||||||
{isSubmitting ? 'Menyimpan...' : 'Simpan Perubahan'}
|
<IconImageInPicture size={48} color="gray" />
|
||||||
</Button>
|
<Text size="sm" c="gray">Tidak ada gambar</Text>
|
||||||
|
</Stack>
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
<Button
|
{/* Submit Button */}
|
||||||
variant="outline"
|
<Group>
|
||||||
onClick={handleBack}
|
<Button
|
||||||
disabled={isSubmitting || allState.edit.loading}
|
bg={colors['blue-button']}
|
||||||
>
|
onClick={handleSubmit}
|
||||||
Batal
|
loading={isSubmitting || allState.edit.loading}
|
||||||
</Button>
|
disabled={!allState.edit.form.name}
|
||||||
</Group>
|
>
|
||||||
</Stack>
|
{isSubmitting ? 'Menyimpan...' : 'Simpan Perubahan'}
|
||||||
</Paper>
|
</Button>
|
||||||
</Box>
|
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={handleBack}
|
||||||
|
disabled={isSubmitting || allState.edit.loading}
|
||||||
|
>
|
||||||
|
Batal
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,24 +1,26 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Center, Divider, Grid, GridCol, Image, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
|
import { Box, Button, Center, Divider, Grid, GridCol, Image, Paper, Skeleton, Stack, Text, Title, Tooltip } from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconEdit } from '@tabler/icons-react';
|
import { IconEdit } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import profileLandingPageState from '@/app/admin/(dashboard)/_state/landing-page/profile';
|
import profileLandingPageState from '@/app/admin/(dashboard)/_state/landing-page/profile';
|
||||||
|
|
||||||
|
|
||||||
function Page() {
|
function Page() {
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
const allList = useProxy(profileLandingPageState.pejabatDesa)
|
const allList = useProxy(profileLandingPageState.pejabatDesa);
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
allList.findUnique.load("edit") // Assuming "1" is your default ID, adjust as needed
|
allList.findUnique.load("edit");
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
if (!allList.findUnique.data) {
|
if (!allList.findUnique.data) {
|
||||||
return <Stack>
|
return (
|
||||||
<Skeleton radius={10} h={800} />
|
<Stack align="center" justify="center" py="xl">
|
||||||
</Stack>
|
<Skeleton radius="md" height={800} />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const dataArray = Array.isArray(allList.findUnique.data)
|
const dataArray = Array.isArray(allList.findUnique.data)
|
||||||
@@ -26,79 +28,82 @@ function Page() {
|
|||||||
: [allList.findUnique.data];
|
: [allList.findUnique.data];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
<Paper bg={colors['white-1']} p="lg" radius="md" shadow="sm">
|
||||||
<Stack gap={"xs"}>
|
<Stack gap="md">
|
||||||
<Grid>
|
<Grid align="center">
|
||||||
<GridCol span={{ base: 12, md: 11 }}>
|
<GridCol span={{ base: 12, md: 11 }}>
|
||||||
<Title order={3}>Preview Pejabat Desa</Title>
|
<Title order={3} c={colors['blue-button']}>Preview Pejabat Desa</Title>
|
||||||
</GridCol>
|
</GridCol>
|
||||||
<GridCol span={{ base: 12, md: 1 }}>
|
<GridCol span={{ base: 12, md: 1 }}>
|
||||||
<Button bg={colors['blue-button']} onClick={() => router.push(`/admin/landing-page/profile/pejabat-desa/${allList.findUnique.data?.id}`)}>
|
<Tooltip label="Edit Profil Pejabat" withArrow>
|
||||||
<IconEdit size={16} />
|
<Button
|
||||||
</Button>
|
c="blue"
|
||||||
|
variant="light"
|
||||||
|
leftSection={<IconEdit size={18} stroke={2} />}
|
||||||
|
radius="md"
|
||||||
|
onClick={() => router.push(`/admin/landing-page/profile/pejabat-desa/${allList.findUnique.data?.id}`)}
|
||||||
|
>
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
</GridCol>
|
</GridCol>
|
||||||
</Grid>
|
</Grid>
|
||||||
{dataArray.map((item) => (
|
{dataArray.map((item) => (
|
||||||
<Box key={item.id} >
|
<Paper key={item.id} p="xl" bg={colors['BG-trans']} radius="md" shadow="xs">
|
||||||
<Paper p={"xl"} bg={colors['BG-trans']}>
|
<Box px={{ base: "sm", md: 100 }}>
|
||||||
<Box px={{ base: "md", md: 100 }}>
|
<Grid>
|
||||||
<Grid>
|
<GridCol span={12}>
|
||||||
<GridCol span={{ base: 12, md: 12 }}>
|
<Center>
|
||||||
<Center>
|
<Image src="/darmasaba-icon.png" w={{ base: 100, md: 150 }} alt="Logo Desa" />
|
||||||
<Image src={"/darmasaba-icon.png"} w={{ base: 100, md: 150 }} alt='' />
|
</Center>
|
||||||
</Center>
|
</GridCol>
|
||||||
</GridCol>
|
<GridCol span={12}>
|
||||||
<GridCol span={{ base: 12, md: 12 }}>
|
<Text ta="center" fz={{ base: "1.2rem", md: "1.8rem" }} fw="bold" c={colors['blue-button']}>
|
||||||
<Text ta={"center"} fz={{ base: "1.2rem", md: "1.8rem" }} fw={'bold'}>PROFIL PIMPINAN BADAN PUBLIK DESA DARMASABA </Text>
|
Profil Pimpinan Badan Publik Desa Darmasaba
|
||||||
</GridCol>
|
</Text>
|
||||||
</Grid>
|
</GridCol>
|
||||||
</Box>
|
</Grid>
|
||||||
<Divider my={"md"} color={colors['blue-button']} />
|
</Box>
|
||||||
{/* biodata perbekel */}
|
<Divider my="md" color={colors['blue-button']} />
|
||||||
<Box px={{ base: 0, md: 50 }} pb={30}>
|
<Box px={{ base: 0, md: 50 }} pb="xl">
|
||||||
<Box pb={20} px={{ base: 0, md: 50 }}>
|
<Paper bg={colors['BG-trans']} radius="md" shadow="xs" p="lg">
|
||||||
<Paper bg={colors['BG-trans']} w={{ base: "100%", md: "100%" }}>
|
<Stack gap={0}>
|
||||||
<Stack gap={0}>
|
<Center>
|
||||||
<Center>
|
<Image
|
||||||
<Image
|
pt={{ base: 0, md: 60 }}
|
||||||
pt={{ base: 0, md: 90 }}
|
src={item.image?.link || "/perbekel.png"}
|
||||||
src={item.image?.link || "/perbekel.png"}
|
w={{ base: 250, md: 350 }}
|
||||||
w={{ base: 250, md: 350 }}
|
alt="Foto Profil Pejabat"
|
||||||
alt='Foto Profil PPID'
|
radius="md"
|
||||||
onError={(e) => {
|
onError={(e) => { e.currentTarget.src = "/perbekel.png"; }}
|
||||||
e.currentTarget.src = "/perbekel.png";
|
/>
|
||||||
}}
|
</Center>
|
||||||
/>
|
<Paper
|
||||||
</Center>
|
bg={colors['blue-button']}
|
||||||
<Paper
|
py="md"
|
||||||
bg={colors['blue-button']}
|
px="sm"
|
||||||
py={20}
|
radius="md"
|
||||||
className="glass3"
|
className="glass3"
|
||||||
px={{ base: 10, md: 10 }}
|
style={{ mt: -30, boxShadow: '0 4px 20px rgba(0,0,0,0.15)' }}
|
||||||
|
>
|
||||||
>
|
<Text ta="center" c={colors['white-1']} fw="bolder" fz={{ base: "1.2rem", md: "1.6rem" }}>
|
||||||
<Text ta={"center"} c={colors['white-1']} fw={"bolder"} fz={{ base: "1.2rem", md: "1.6rem" }}>
|
{item.name}
|
||||||
{item.name}
|
</Text>
|
||||||
</Text>
|
|
||||||
</Paper>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Stack>
|
||||||
<Box pt={10}>
|
</Paper>
|
||||||
<Box>
|
<Box mt="lg">
|
||||||
<Text fz={{ base: "1.125rem", md: "1.6rem" }} fw={'bold'}>Position</Text>
|
<Text fz={{ base: "1.125rem", md: "1.5rem" }} fw="bold" mb={4}>Jabatan</Text>
|
||||||
<Text fz={{ base: "1rem", md: "1.5rem" }} ta={"justify"}>{item.position}</Text>
|
<Text fz={{ base: "1rem", md: "1.4rem" }} ta="justify" c={colors['blue-button']}>
|
||||||
</Box>
|
{item.position}
|
||||||
</Box>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
</Paper>
|
</Box>
|
||||||
</Box>
|
</Paper>
|
||||||
))}
|
))}
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default Page;
|
||||||
|
|
||||||
export default Page;
|
|
||||||
|
|||||||
@@ -3,7 +3,18 @@
|
|||||||
import profileLandingPageState from '@/app/admin/(dashboard)/_state/landing-page/profile';
|
import profileLandingPageState from '@/app/admin/(dashboard)/_state/landing-page/profile';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import ApiFetch from '@/lib/api-fetch';
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
import { Box, Button, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Image,
|
||||||
|
Paper,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
} from '@mantine/core';
|
||||||
import { Dropzone } from '@mantine/dropzone';
|
import { Dropzone } from '@mantine/dropzone';
|
||||||
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
@@ -86,92 +97,113 @@ function EditProgramInovasi() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Box mb={10}>
|
<Group mb="md">
|
||||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
<Tooltip label="Kembali ke halaman sebelumnya" color="blue" withArrow>
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
</Button>
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
</Box>
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Title order={4} ml="sm" c="dark">
|
||||||
|
Edit Program Inovasi
|
||||||
|
</Title>
|
||||||
|
</Group>
|
||||||
|
|
||||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
<Paper
|
||||||
<Stack gap={"xs"}>
|
w={{ base: '100%', md: '50%' }}
|
||||||
<Title order={4}>Edit Program Inovasi</Title>
|
bg={colors['white-1']}
|
||||||
|
p="lg"
|
||||||
|
radius="md"
|
||||||
|
shadow="sm"
|
||||||
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
<Text fw="bold" fz="sm" mb={6}>
|
||||||
<Box>
|
Gambar Program Inovasi
|
||||||
<Dropzone
|
</Text>
|
||||||
onDrop={(files) => {
|
<Dropzone
|
||||||
const selectedFile = files[0]; // Ambil file pertama
|
onDrop={(files) => {
|
||||||
if (selectedFile) {
|
const selectedFile = files[0];
|
||||||
setFile(selectedFile);
|
if (selectedFile) {
|
||||||
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
|
setFile(selectedFile);
|
||||||
}
|
setPreviewImage(URL.createObjectURL(selectedFile));
|
||||||
}}
|
}
|
||||||
onReject={() => toast.error('File tidak valid.')}
|
}}
|
||||||
maxSize={5 * 1024 ** 2} // Maks 5MB
|
onReject={() => toast.error('File tidak valid, gunakan format gambar')}
|
||||||
accept={{ 'image/*': [] }}
|
maxSize={5 * 1024 ** 2}
|
||||||
>
|
accept={{ 'image/*': [] }}
|
||||||
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
radius="md"
|
||||||
<Dropzone.Accept>
|
p="xl"
|
||||||
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
>
|
||||||
</Dropzone.Accept>
|
<Group justify="center" gap="xl" mih={180}>
|
||||||
<Dropzone.Reject>
|
<Dropzone.Accept>
|
||||||
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
<IconUpload size={48} color={colors['blue-button']} stroke={1.5} />
|
||||||
</Dropzone.Reject>
|
</Dropzone.Accept>
|
||||||
<Dropzone.Idle>
|
<Dropzone.Reject>
|
||||||
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
<IconX size={48} color="red" stroke={1.5} />
|
||||||
</Dropzone.Idle>
|
</Dropzone.Reject>
|
||||||
|
<Dropzone.Idle>
|
||||||
|
<IconPhoto size={48} color="#868e96" stroke={1.5} />
|
||||||
|
</Dropzone.Idle>
|
||||||
|
<Stack gap="xs" align="center">
|
||||||
|
<Text size="md" fw={500}>
|
||||||
|
Seret gambar atau klik untuk memilih file
|
||||||
|
</Text>
|
||||||
|
<Text size="sm" c="dimmed">
|
||||||
|
Maksimal 5MB, format gambar wajib
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
</Group>
|
||||||
|
</Dropzone>
|
||||||
|
|
||||||
<div>
|
{previewImage && (
|
||||||
<Text size="xl" inline>
|
<Box mt="sm" style={{ display: 'flex', justifyContent: 'center' }}>
|
||||||
Drag gambar ke sini atau klik untuk pilih file
|
<Image
|
||||||
</Text>
|
src={previewImage}
|
||||||
<Text size="sm" c="dimmed" inline mt={7}>
|
alt="Preview Gambar"
|
||||||
Maksimal 5MB dan harus format gambar
|
radius="md"
|
||||||
</Text>
|
style={{ maxHeight: 220, objectFit: 'contain', border: `1px solid ${colors['blue-button']}` }}
|
||||||
</div>
|
/>
|
||||||
</Group>
|
</Box>
|
||||||
</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>
|
||||||
<TextInput
|
<TextInput
|
||||||
|
label="Nama Program Inovasi"
|
||||||
|
placeholder="Masukkan nama program inovasi"
|
||||||
value={formData.name}
|
value={formData.name}
|
||||||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Nama Produk</Text>}
|
required
|
||||||
placeholder='Masukkan nama produk'
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
|
label="Deskripsi"
|
||||||
|
placeholder="Masukkan deskripsi program inovasi"
|
||||||
value={formData.description}
|
value={formData.description}
|
||||||
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Deskripsi</Text>}
|
required
|
||||||
placeholder='Masukkan deskripsi'
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
|
label="Link Program Inovasi"
|
||||||
|
placeholder="Masukkan link program inovasi (opsional)"
|
||||||
value={formData.link}
|
value={formData.link}
|
||||||
onChange={(e) => setFormData({ ...formData, link: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, link: e.target.value })}
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Link</Text>}
|
|
||||||
placeholder='Masukkan link'
|
|
||||||
/>
|
/>
|
||||||
<Group>
|
|
||||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Submit</Button>
|
<Group justify="right">
|
||||||
|
<Button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
style={{
|
||||||
|
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||||
|
color: '#fff',
|
||||||
|
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
||||||
import profileLandingPageState from '@/app/admin/(dashboard)/_state/landing-page/profile';
|
import profileLandingPageState from '@/app/admin/(dashboard)/_state/landing-page/profile';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Flex, Image, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
import { Box, Button, Group, Image, Paper, Skeleton, Stack, Text, Tooltip } from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
|
import { IconArrowBack, IconEdit, IconTrash } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
@@ -31,82 +31,116 @@ function DetailProgramInovasi() {
|
|||||||
|
|
||||||
if (!stateProgramInovasi.findUnique.data) {
|
if (!stateProgramInovasi.findUnique.data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={12}>
|
||||||
<Skeleton h={500} />
|
<Skeleton height={520} radius="md" />
|
||||||
</Stack>
|
</Stack>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const data = stateProgramInovasi.findUnique.data
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box px={{ base: 'md', md: 'xl' }} py="lg">
|
||||||
<Box mb={10}>
|
<Button variant="subtle" onClick={() => router.back()} leftSection={<IconArrowBack size={22} color={colors['blue-button']} />}>
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
Kembali
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
</Button>
|
||||||
</Button>
|
|
||||||
</Box>
|
<Paper
|
||||||
<Paper w={{ base: "100%", md: "50%" }} bg={colors['white-1']} p={'md'}>
|
w={{ base: "100%", md: "60%" }}
|
||||||
<Stack>
|
bg={colors['white-1']}
|
||||||
<Text fz={"xl"} fw={"bold"}>Detail Program Inovasi</Text>
|
p="lg"
|
||||||
<Paper bg={colors['BG-trans']} p={'md'}>
|
radius="md"
|
||||||
<Stack gap={"xs"}>
|
shadow="sm"
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
|
<Text fz="2xl" fw="bold" c={colors['blue-button']}>
|
||||||
|
Detail Program Inovasi
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Paper bg="#ECEEF8" p="md" radius="md" shadow="xs">
|
||||||
|
<Stack gap="sm">
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"lg"} fw={"bold"}>Nama Program Inovasi</Text>
|
<Text fz="lg" fw="bold">Nama Program Inovasi</Text>
|
||||||
<Text fz={"lg"}>{stateProgramInovasi.findUnique.data?.name}</Text>
|
<Text fz="md" c="dimmed">{data.name || '-'}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"lg"} fw={"bold"}>Deskripsi</Text>
|
<Text fz="lg" fw="bold">Deskripsi</Text>
|
||||||
<Text fz={"lg"}>{stateProgramInovasi.findUnique.data?.description}</Text>
|
<Text fz="md" c="dimmed" style={{ whiteSpace: 'pre-wrap' }}>{data.description || '-'}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"lg"} fw={"bold"}>Link</Text>
|
<Text fz="lg" fw="bold">Link</Text>
|
||||||
<a
|
{data.link ? (
|
||||||
href={stateProgramInovasi.findUnique.data?.link || "#"}
|
<a
|
||||||
target="_blank"
|
href={data.link}
|
||||||
rel="noopener noreferrer"
|
target="_blank"
|
||||||
>
|
rel="noopener noreferrer"
|
||||||
{stateProgramInovasi.findUnique.data?.link || "Tidak ada link"}
|
style={{
|
||||||
</a>
|
color: colors['blue-button'],
|
||||||
</Box>
|
textDecoration: 'underline',
|
||||||
<Box>
|
wordBreak: 'break-word',
|
||||||
<Text fz={"lg"} fw={"bold"}>Gambar</Text>
|
|
||||||
<Image src={stateProgramInovasi.findUnique.data?.image?.link} alt="gambar" />
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Flex gap={"xs"}>
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
if (stateProgramInovasi.findUnique.data) {
|
|
||||||
setSelectedId(stateProgramInovasi.findUnique.data.id);
|
|
||||||
setModalHapus(true);
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
disabled={!stateProgramInovasi.findUnique.data}
|
>
|
||||||
color="red">
|
{data.link}
|
||||||
<IconX size={20} />
|
</a>
|
||||||
|
) : (
|
||||||
|
<Text fz="md" c="dimmed">-</Text>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fz="lg" fw="bold">Gambar</Text>
|
||||||
|
{data.image?.link ? (
|
||||||
|
<Image
|
||||||
|
src={data.image.link}
|
||||||
|
alt="Gambar Program"
|
||||||
|
radius="md"
|
||||||
|
style={{ maxWidth: '100%', maxHeight: 300, objectFit: 'contain' }}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Text fz="md" c="dimmed">-</Text>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Group gap="sm">
|
||||||
|
<Tooltip label="Hapus Program Inovasi" withArrow position="top">
|
||||||
|
<Button
|
||||||
|
color="red"
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedId(data.id);
|
||||||
|
setModalHapus(true);
|
||||||
|
}}
|
||||||
|
variant="light"
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
>
|
||||||
|
<IconTrash size={20} />
|
||||||
</Button>
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
<Tooltip label="Edit Program Inovasi" withArrow position="top">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
color="green"
|
||||||
if (stateProgramInovasi.findUnique.data) {
|
onClick={() => router.push(`/admin/landing-page/profile/program-inovasi/${data.id}/edit`)}
|
||||||
router.push(`/admin/landing-page/profile/program-inovasi/${stateProgramInovasi.findUnique.data.id}/edit`);
|
variant="light"
|
||||||
}
|
radius="md"
|
||||||
}}
|
size="md"
|
||||||
disabled={!stateProgramInovasi.findUnique.data}
|
>
|
||||||
color="green">
|
|
||||||
<IconEdit size={20} />
|
<IconEdit size={20} />
|
||||||
</Button>
|
</Button>
|
||||||
</Flex>
|
</Tooltip>
|
||||||
</Box>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
{/* Modal Hapus */}
|
|
||||||
<ModalKonfirmasiHapus
|
<ModalKonfirmasiHapus
|
||||||
opened={modalHapus}
|
opened={modalHapus}
|
||||||
onClose={() => setModalHapus(false)}
|
onClose={() => setModalHapus(false)}
|
||||||
onConfirm={handleHapus}
|
onConfirm={handleHapus}
|
||||||
text="Apakah anda yakin ingin menghapus program inovasi ini?"
|
text="Apakah Anda yakin ingin menghapus program inovasi ini?"
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,8 +1,20 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
'use client'
|
'use client';
|
||||||
|
import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import ApiFetch from '@/lib/api-fetch';
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
import { Box, Button, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Image,
|
||||||
|
Paper,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
Title,
|
||||||
|
Tooltip
|
||||||
|
} from '@mantine/core';
|
||||||
import { Dropzone } from '@mantine/dropzone';
|
import { Dropzone } from '@mantine/dropzone';
|
||||||
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
@@ -13,7 +25,7 @@ import profileLandingPageState from '../../../../_state/landing-page/profile';
|
|||||||
|
|
||||||
function CreateProgramInovasi() {
|
function CreateProgramInovasi() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const stateProgramInovasi = useProxy(profileLandingPageState.programInovasi)
|
const stateProgramInovasi = useProxy(profileLandingPageState.programInovasi);
|
||||||
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
||||||
const [file, setFile] = useState<File | null>(null);
|
const [file, setFile] = useState<File | null>(null);
|
||||||
|
|
||||||
@@ -31,20 +43,21 @@ function CreateProgramInovasi() {
|
|||||||
setPreviewImage(null);
|
setPreviewImage(null);
|
||||||
setFile(null);
|
setFile(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
if (!file) {
|
if (!file) {
|
||||||
return toast.warn("Pilih file gambar terlebih dahulu");
|
return toast.warn("Silakan pilih file gambar terlebih dahulu");
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await ApiFetch.api.fileStorage.create.post({
|
const res = await ApiFetch.api.fileStorage.create.post({
|
||||||
file,
|
file,
|
||||||
name: file.name,
|
name: file.name,
|
||||||
})
|
});
|
||||||
|
|
||||||
const uploaded = res.data?.data;
|
const uploaded = res.data?.data;
|
||||||
|
|
||||||
if (!uploaded?.id) {
|
if (!uploaded?.id) {
|
||||||
return toast.error("Gagal mengupload file");
|
return toast.error("Gagal mengunggah gambar, silakan coba lagi");
|
||||||
}
|
}
|
||||||
|
|
||||||
stateProgramInovasi.create.form.imageId = uploaded.id;
|
stateProgramInovasi.create.form.imageId = uploaded.id;
|
||||||
@@ -55,99 +68,116 @@ function CreateProgramInovasi() {
|
|||||||
router.push("/admin/landing-page/profile/program-inovasi")
|
router.push("/admin/landing-page/profile/program-inovasi")
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Box mb={10}>
|
<Group mb="md">
|
||||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
<Tooltip label="Kembali ke halaman sebelumnya" color="blue" withArrow>
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
</Button>
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
</Box>
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Title order={4} ml="sm" c="dark">
|
||||||
|
Tambah Program Inovasi
|
||||||
|
</Title>
|
||||||
|
</Group>
|
||||||
|
|
||||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
<Paper
|
||||||
<Stack gap={"xs"}>
|
w={{ base: '100%', md: '50%' }}
|
||||||
<Title order={4}>Create Program Inovasi</Title>
|
bg={colors['white-1']}
|
||||||
|
p="lg"
|
||||||
|
radius="md"
|
||||||
|
shadow="sm"
|
||||||
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
<Text fw="bold" fz="sm" mb={6}>
|
||||||
<Box>
|
Gambar Program Inovasi
|
||||||
<Dropzone
|
</Text>
|
||||||
onDrop={(files) => {
|
<Dropzone
|
||||||
const selectedFile = files[0]; // Ambil file pertama
|
onDrop={(files) => {
|
||||||
if (selectedFile) {
|
const selectedFile = files[0];
|
||||||
setFile(selectedFile);
|
if (selectedFile) {
|
||||||
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
|
setFile(selectedFile);
|
||||||
}
|
setPreviewImage(URL.createObjectURL(selectedFile));
|
||||||
}}
|
}
|
||||||
onReject={() => toast.error('File tidak valid.')}
|
}}
|
||||||
maxSize={5 * 1024 ** 2} // Maks 5MB
|
onReject={() => toast.error('File tidak valid, gunakan format gambar')}
|
||||||
accept={{ 'image/*': [] }}
|
maxSize={5 * 1024 ** 2}
|
||||||
>
|
accept={{ 'image/*': [] }}
|
||||||
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
radius="md"
|
||||||
<Dropzone.Accept>
|
p="xl"
|
||||||
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
>
|
||||||
</Dropzone.Accept>
|
<Group justify="center" gap="xl" mih={180}>
|
||||||
<Dropzone.Reject>
|
<Dropzone.Accept>
|
||||||
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
<IconUpload size={48} color={colors['blue-button']} stroke={1.5} />
|
||||||
</Dropzone.Reject>
|
</Dropzone.Accept>
|
||||||
<Dropzone.Idle>
|
<Dropzone.Reject>
|
||||||
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
<IconX size={48} color="red" stroke={1.5} />
|
||||||
</Dropzone.Idle>
|
</Dropzone.Reject>
|
||||||
|
<Dropzone.Idle>
|
||||||
|
<IconPhoto size={48} color="#868e96" stroke={1.5} />
|
||||||
|
</Dropzone.Idle>
|
||||||
|
<Stack gap="xs" align="center">
|
||||||
|
<Text size="md" fw={500}>
|
||||||
|
Seret gambar atau klik untuk memilih file
|
||||||
|
</Text>
|
||||||
|
<Text size="sm" c="dimmed">
|
||||||
|
Maksimal 5MB, format gambar wajib
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
</Group>
|
||||||
|
</Dropzone>
|
||||||
|
|
||||||
<div>
|
{previewImage && (
|
||||||
<Text size="xl" inline>
|
<Box mt="sm" style={{ textAlign: 'center' }}>
|
||||||
Drag gambar ke sini atau klik untuk pilih file
|
<Image
|
||||||
</Text>
|
src={previewImage}
|
||||||
<Text size="sm" c="dimmed" inline mt={7}>
|
alt="Preview Gambar"
|
||||||
Maksimal 5MB dan harus format gambar
|
radius="md"
|
||||||
</Text>
|
style={{ maxHeight: 200, objectFit: 'contain', border: '1px solid #ddd' }}
|
||||||
</div>
|
/>
|
||||||
</Group>
|
</Box>
|
||||||
</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>
|
||||||
<TextInput
|
|
||||||
value={stateProgramInovasi.create.form.name || ''}
|
|
||||||
onChange={(val) => {
|
|
||||||
stateProgramInovasi.create.form.name = val.target.value;
|
|
||||||
}}
|
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Nama Program Inovasi</Text>}
|
|
||||||
placeholder='Masukkan nama program inovasi'
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
value={stateProgramInovasi.create.form.description || ''}
|
|
||||||
onChange={(val) => {
|
|
||||||
stateProgramInovasi.create.form.description = val.target.value;
|
|
||||||
}}
|
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Deskripsi</Text>}
|
|
||||||
placeholder='Masukkan deskripsi'
|
|
||||||
/>
|
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
value={stateProgramInovasi.create.form.link || ''}
|
label="Nama Program Inovasi"
|
||||||
onChange={(val) => {
|
placeholder="Masukkan nama program inovasi"
|
||||||
stateProgramInovasi.create.form.link = val.target.value;
|
value={stateProgramInovasi.create.form.name}
|
||||||
}}
|
onChange={(e) => (stateProgramInovasi.create.form.name = e.target.value)}
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Link</Text>}
|
required
|
||||||
placeholder='Masukkan link'
|
|
||||||
/>
|
/>
|
||||||
<Group>
|
|
||||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Submit</Button>
|
<Box>
|
||||||
|
<Text fz={"sm"} fw={"bold"}>Deskripsi</Text>
|
||||||
|
<CreateEditor
|
||||||
|
value={stateProgramInovasi.create.form.description || ''}
|
||||||
|
onChange={(htmlContent: string) => {
|
||||||
|
stateProgramInovasi.create.form.description = htmlContent;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<TextInput
|
||||||
|
label="Link Program Inovasi"
|
||||||
|
placeholder="Masukkan link program inovasi (opsional)"
|
||||||
|
value={stateProgramInovasi.create.form.link || ''}
|
||||||
|
onChange={(e) => (stateProgramInovasi.create.form.link = e.target.value)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Group justify="right">
|
||||||
|
<Button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
style={{
|
||||||
|
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||||
|
color: '#fff',
|
||||||
|
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
@@ -1,22 +1,22 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
|
||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Center, Pagination, 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, Title, Tooltip } from '@mantine/core';
|
||||||
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { IconDeviceImac, IconPlus, IconSearch } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import HeaderSearch from '../../../_com/header';
|
import HeaderSearch from '../../../_com/header';
|
||||||
import JudulList from '../../../_com/judulList';
|
|
||||||
import profileLandingPageState from '../../../_state/landing-page/profile';
|
import profileLandingPageState from '../../../_state/landing-page/profile';
|
||||||
|
|
||||||
function ProgramInovasi() {
|
function ProgramInovasi() {
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box px="md" py="lg">
|
||||||
<HeaderSearch
|
<HeaderSearch
|
||||||
title='Program Inovasi'
|
title="Program Inovasi"
|
||||||
placeholder='pencarian'
|
placeholder="Cari program inovasi..."
|
||||||
searchIcon={<IconSearch size={20} />}
|
searchIcon={<IconSearch size={20} />}
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
@@ -27,117 +27,118 @@ function ProgramInovasi() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function ListProgramInovasi({ search }: { search: string }) {
|
function ListProgramInovasi({ search }: { search: string }) {
|
||||||
const stateProgramInovasi = useProxy(profileLandingPageState.programInovasi)
|
const stateProgramInovasi = useProxy(profileLandingPageState.programInovasi);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const {
|
const { data, page, totalPages, loading, load } = stateProgramInovasi.findMany;
|
||||||
data,
|
|
||||||
page,
|
|
||||||
totalPages,
|
|
||||||
loading,
|
|
||||||
load,
|
|
||||||
} = stateProgramInovasi.findMany;
|
|
||||||
|
|
||||||
useEffect(() => {
|
useShallowEffect(() => {
|
||||||
load(page, 10);
|
load(page, 10, search);
|
||||||
}, [page]);
|
}, [page, search]);
|
||||||
|
|
||||||
const filteredData = useMemo(() => {
|
const filteredData = data || [];
|
||||||
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) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={20}>
|
||||||
<Skeleton height={550} />
|
<Skeleton height={500} radius="md" />
|
||||||
</Stack>
|
</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 (
|
return (
|
||||||
<Box py={10}>
|
<Box py={15}>
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
<Paper bg={colors['white-1']} p="lg" radius="md" shadow="sm">
|
||||||
<JudulList
|
<Box mb="md" display="flex"
|
||||||
title='List Program Inovasi'
|
style={{ justifyContent: 'space-between', alignItems: 'center' }}
|
||||||
href='/admin/landing-page/profile/program-inovasi/create'
|
>
|
||||||
/>
|
<Title order={4}>Daftar Program Inovasi</Title>
|
||||||
<Box style={{ overflowY: "auto" }}>
|
<Tooltip label="Tambah Program Inovasi" withArrow>
|
||||||
<Table striped withTableBorder withRowBorders>
|
<Button
|
||||||
|
color="blue"
|
||||||
|
leftSection={<IconPlus size={18} />}
|
||||||
|
variant="light"
|
||||||
|
radius="md"
|
||||||
|
onClick={() => router.push('/admin/landing-page/profile/program-inovasi/create')}
|
||||||
|
>
|
||||||
|
Tambah Program
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</Box>
|
||||||
|
<Box style={{ overflowX: 'auto' }}>
|
||||||
|
<Table highlightOnHover striped verticalSpacing="sm">
|
||||||
<TableThead>
|
<TableThead>
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh>Nama Program</TableTh>
|
<TableTh>Nama Program</TableTh>
|
||||||
<TableTh>Deskripsi</TableTh>
|
<TableTh>Deskripsi</TableTh>
|
||||||
<TableTh>Link</TableTh>
|
<TableTh>Link</TableTh>
|
||||||
<TableTh>Detail</TableTh>
|
<TableTh>Aksi</TableTh>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
<TableTbody>
|
<TableTbody>
|
||||||
{filteredData.map((item) => (
|
{filteredData.length === 0 ? (
|
||||||
<TableTr key={item.id}>
|
<TableTr>
|
||||||
<TableTd>{item.name}</TableTd>
|
<TableTd colSpan={4}>
|
||||||
<TableTd w={200}>{item.description}</TableTd>
|
<Center py={20}>
|
||||||
<TableTd>
|
<Text color="dimmed">Belum ada data program inovasi</Text>
|
||||||
<Box w={250}>
|
</Center>
|
||||||
<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} />
|
|
||||||
</Button>
|
|
||||||
</TableTd>
|
</TableTd>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
))}
|
) : (
|
||||||
|
filteredData.map((item) => (
|
||||||
|
<TableTr key={item.id}>
|
||||||
|
<TableTd>
|
||||||
|
<Text fw={500}>{item.name}</Text>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd style={{ maxWidth: 250 }}>
|
||||||
|
<Text fz="sm" lineClamp={2}>
|
||||||
|
{item.description}
|
||||||
|
</Text>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd style={{ maxWidth: 250 }}>
|
||||||
|
<Tooltip label="Buka tautan program" position="top" withArrow>
|
||||||
|
<a
|
||||||
|
href={item.link}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
style={{ color: colors['blue-button'], textDecoration: 'underline' }}
|
||||||
|
>
|
||||||
|
<Text truncate fz="sm">{item.link}</Text>
|
||||||
|
</a>
|
||||||
|
</Tooltip>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Button
|
||||||
|
variant="light"
|
||||||
|
color="blue"
|
||||||
|
radius="md"
|
||||||
|
onClick={() =>
|
||||||
|
router.push(`/admin/landing-page/profile/program-inovasi/${item.id}`)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<IconDeviceImac size={20} />
|
||||||
|
</Button>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
))
|
||||||
|
)}
|
||||||
</TableTbody>
|
</TableTbody>
|
||||||
</Table>
|
</Table>
|
||||||
</Box>
|
</Box>
|
||||||
|
{filteredData.length > 0 && (
|
||||||
|
<Center mt="md">
|
||||||
|
<Pagination
|
||||||
|
value={page}
|
||||||
|
onChange={(newPage) => {
|
||||||
|
load(newPage, 10);
|
||||||
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
|
}}
|
||||||
|
total={totalPages}
|
||||||
|
color="blue"
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
</Paper>
|
</Paper>
|
||||||
<Center>
|
|
||||||
<Pagination
|
|
||||||
value={page}
|
|
||||||
onChange={(newPage) => {
|
|
||||||
load(newPage, 10);
|
|
||||||
window.scrollTo(0, 0);
|
|
||||||
}}
|
|
||||||
total={totalPages}
|
|
||||||
mt="md"
|
|
||||||
mb="md"
|
|
||||||
/>
|
|
||||||
</Center>
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
|
||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Center, Pagination, 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 { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import HeaderSearch from '../../_com/header';
|
import HeaderSearch from '../../_com/header';
|
||||||
import sdgsDesa from '../../_state/landing-page/sdgs-desa';
|
|
||||||
import JudulList from '../../_com/judulList';
|
import JudulList from '../../_com/judulList';
|
||||||
|
import sdgsDesa from '../../_state/landing-page/sdgs-desa';
|
||||||
|
|
||||||
|
|
||||||
function SdgsDesa() {
|
function SdgsDesa() {
|
||||||
@@ -39,20 +39,11 @@ function ListSdgsDesa({ search }: { search: string }) {
|
|||||||
load,
|
load,
|
||||||
} = listState.findMany;
|
} = listState.findMany;
|
||||||
|
|
||||||
useEffect(() => {
|
useShallowEffect(() => {
|
||||||
load(page, 10)
|
load(page, 10, search)
|
||||||
}, [])
|
}, [page, search])
|
||||||
|
|
||||||
const filteredData = useMemo(() => {
|
const filteredData = data || []
|
||||||
if (!data) return [];
|
|
||||||
return data.filter(item => {
|
|
||||||
const keyword = search.toLowerCase();
|
|
||||||
return (
|
|
||||||
item.name?.toLowerCase().includes(keyword) ||
|
|
||||||
item.jumlah?.toLowerCase().includes(keyword)
|
|
||||||
);
|
|
||||||
})
|
|
||||||
}, [data, search]);
|
|
||||||
|
|
||||||
// Handle loading state
|
// Handle loading state
|
||||||
if (loading || !data) {
|
if (loading || !data) {
|
||||||
|
|||||||
@@ -42,18 +42,10 @@ function ListDataLingkunganDesa({ search }: { search: string }) {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
load(page, 10)
|
load(page, 10, search)
|
||||||
}, [page])
|
}, [page, search])
|
||||||
|
|
||||||
const filteredData = (data || []).filter(item => {
|
const filteredData = data || []
|
||||||
const keyword = search.toLowerCase();
|
|
||||||
return (
|
|
||||||
item.name.toLowerCase().includes(keyword) ||
|
|
||||||
item.deskripsi.toLowerCase().includes(keyword) ||
|
|
||||||
item.jumlah.toLowerCase().includes(keyword) ||
|
|
||||||
item.icon.toLowerCase().includes(keyword)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
const iconMap: Record<string, React.FC<any>> = {
|
const iconMap: Record<string, React.FC<any>> = {
|
||||||
ekowisata: IconLeaf,
|
ekowisata: IconLeaf,
|
||||||
|
|||||||
@@ -1,63 +1,93 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
|
||||||
import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
|
|
||||||
import { usePathname, useRouter } from 'next/navigation';
|
import { usePathname, useRouter } from 'next/navigation';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title, Tooltip } from '@mantine/core';
|
||||||
|
import { IconTrash, IconRecycle } from '@tabler/icons-react';
|
||||||
|
|
||||||
function LayoutTabsPengelolaanSampahBankSampah({ children }: { children: React.ReactNode }) {
|
function LayoutTabsPengelolaanSampahBankSampah({ children }: { children: React.ReactNode }) {
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
const pathname = usePathname()
|
const pathname = usePathname();
|
||||||
const tabs = [
|
|
||||||
{
|
|
||||||
label: "List Pengelolaan Sampah Bank Sampah",
|
|
||||||
value: "listpengelolaansampahbanksampah",
|
|
||||||
href: "/admin/lingkungan/pengelolaan-sampah-bank-sampah/list-pengelolaan-sampah-bank-sampah"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Keterangan Bank Sampah Terdekat",
|
|
||||||
value: "keteranganbanksampahterdekat",
|
|
||||||
href: "/admin/lingkungan/pengelolaan-sampah-bank-sampah/keterangan-bank-sampah-terdekat"
|
|
||||||
},
|
|
||||||
|
|
||||||
];
|
|
||||||
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 tabs = [
|
||||||
const tab = tabs.find(t => t.value === value)
|
{
|
||||||
if (tab) {
|
label: "List Pengelolaan Sampah Bank Sampah",
|
||||||
router.push(tab.href)
|
value: "listpengelolaansampahbanksampah",
|
||||||
}
|
href: "/admin/lingkungan/pengelolaan-sampah-bank-sampah/list-pengelolaan-sampah-bank-sampah",
|
||||||
setActiveTab(value)
|
icon: <IconTrash size={18} stroke={1.8} />,
|
||||||
|
tooltip: "Kelola data pengelolaan sampah bank sampah",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Keterangan Bank Sampah Terdekat",
|
||||||
|
value: "keteranganbanksampahterdekat",
|
||||||
|
href: "/admin/lingkungan/pengelolaan-sampah-bank-sampah/keterangan-bank-sampah-terdekat",
|
||||||
|
icon: <IconRecycle size={18} stroke={1.8} />,
|
||||||
|
tooltip: "Kelola data bank sampah terdekat",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const currentTab = tabs.find(tab => tab.href === pathname);
|
||||||
|
const [activeTab, setActiveTab] = useState<string | null>(currentTab?.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(() => {
|
useEffect(() => {
|
||||||
const match = tabs.find(tab => tab.href === pathname)
|
const match = tabs.find(tab => tab.href === pathname);
|
||||||
if (match) {
|
if (match) {
|
||||||
setActiveTab(match.value)
|
setActiveTab(match.value);
|
||||||
}
|
}
|
||||||
}, [pathname])
|
}, [pathname]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack>
|
<Stack gap="md">
|
||||||
<Title order={3}>Layanan Online Desa</Title>
|
<Title order={3} mb="sm">Pengelolaan Sampah Bank Sampah</Title>
|
||||||
<Tabs color={colors['blue-button']} variant='pills' value={activeTab} onChange={handleTabChange}>
|
<Tabs
|
||||||
<TabsList p={"xs"} bg={"#BBC8E7FF"}>
|
value={activeTab}
|
||||||
{tabs.map((e, i) => (
|
onChange={handleTabChange}
|
||||||
<TabsTab key={i} value={e.value}>{e.label}</TabsTab>
|
variant="pills"
|
||||||
))}
|
radius="md"
|
||||||
</TabsList>
|
>
|
||||||
{tabs.map((e, i) => (
|
<TabsList>
|
||||||
<TabsPanel key={i} value={e.value}>
|
{tabs.map((tab) => (
|
||||||
{/* Konten dummy, bisa diganti tergantung routing */}
|
<Tooltip
|
||||||
<></>
|
key={tab.value}
|
||||||
</TabsPanel>
|
label={tab.tooltip}
|
||||||
))}
|
position="top"
|
||||||
</Tabs>
|
withArrow
|
||||||
{children}
|
transitionProps={{ transition: 'pop', duration: 300 }}
|
||||||
</Stack>
|
>
|
||||||
);
|
<TabsTab
|
||||||
|
value={tab.value}
|
||||||
|
leftSection={tab.icon}
|
||||||
|
style={{
|
||||||
|
padding: '10px 20px',
|
||||||
|
height: 'auto',
|
||||||
|
minHeight: 44,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{tab.label}
|
||||||
|
</TabsTab>
|
||||||
|
</Tooltip>
|
||||||
|
))}
|
||||||
|
</TabsList>
|
||||||
|
<TabsPanel
|
||||||
|
value={activeTab || ''}
|
||||||
|
pt="lg"
|
||||||
|
style={{
|
||||||
|
minHeight: '60vh',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</TabsPanel>
|
||||||
|
</Tabs>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default LayoutTabsPengelolaanSampahBankSampah;
|
export default LayoutTabsPengelolaanSampahBankSampah;
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
'use client'
|
'use client';
|
||||||
import pengelolaanSampahState from '@/app/admin/(dashboard)/_state/lingkungan/pengelolaan-sampah';
|
import pengelolaanSampahState from '@/app/admin/(dashboard)/_state/lingkungan/pengelolaan-sampah';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title, Tooltip } from '@mantine/core';
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
import dynamic from 'next/dynamic';
|
import dynamic from 'next/dynamic';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
@@ -64,63 +64,97 @@ function EditKeteranganBankSampahTerdekat() {
|
|||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
try {
|
try {
|
||||||
|
if (!formData.name.trim()) {
|
||||||
|
return toast.error('Nama bank sampah harus diisi');
|
||||||
|
}
|
||||||
|
if (!formData.alamat.trim()) {
|
||||||
|
return toast.error('Alamat harus diisi');
|
||||||
|
}
|
||||||
|
if (!formData.namaTempatMaps.trim()) {
|
||||||
|
return toast.error('Nama tempat di Maps harus diisi');
|
||||||
|
}
|
||||||
|
if (!markerPosition) {
|
||||||
|
return toast.error('Silakan pilih lokasi di peta');
|
||||||
|
}
|
||||||
|
|
||||||
keteranganState.edit.form = {
|
keteranganState.edit.form = {
|
||||||
...keteranganState.edit.form,
|
...keteranganState.edit.form,
|
||||||
name: formData.name.trim(),
|
name: formData.name.trim(),
|
||||||
alamat: formData.alamat.trim(),
|
alamat: formData.alamat.trim(),
|
||||||
namaTempatMaps: formData.namaTempatMaps.trim(),
|
namaTempatMaps: formData.namaTempatMaps.trim(),
|
||||||
lat: formData.lat,
|
lat: markerPosition.lat,
|
||||||
lng: formData.lng,
|
lng: markerPosition.lng,
|
||||||
}
|
};
|
||||||
|
|
||||||
await keteranganState.edit.update();
|
await keteranganState.edit.update();
|
||||||
|
toast.success('Data bank sampah berhasil diperbarui');
|
||||||
keteranganState.findUnique.data = null;
|
keteranganState.findUnique.data = null;
|
||||||
router.push("/admin/lingkungan/pengelolaan-sampah-bank-sampah/keterangan-bank-sampah-terdekat");
|
router.push("/admin/lingkungan/pengelolaan-sampah-bank-sampah/keterangan-bank-sampah-terdekat");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error updating pengelolaan sampah:", error);
|
console.error("Error updating pengelolaan sampah:", error);
|
||||||
toast.error("Gagal memuat data pengelolaan sampah");
|
toast.error(error instanceof Error ? error.message : 'Gagal memperbarui data bank sampah');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Box mb={10}>
|
<Group mb="md">
|
||||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
<Tooltip label="Kembali ke halaman sebelumnya" color="blue" withArrow>
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
</Button>
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
</Box>
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Title order={4} ml="sm" c="dark">
|
||||||
|
Edit Bank Sampah Terdekat
|
||||||
|
</Title>
|
||||||
|
</Group>
|
||||||
|
|
||||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
<Paper
|
||||||
<Stack gap={"xs"}>
|
w={{ base: '100%', md: '50%' }}
|
||||||
<Title order={4}>Edit Keterangan Bank Sampah Terdekat</Title>
|
bg={colors['white-1']}
|
||||||
|
p="lg"
|
||||||
|
radius="md"
|
||||||
|
shadow="sm"
|
||||||
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
<TextInput
|
<TextInput
|
||||||
|
label="Nama Bank Sampah"
|
||||||
|
placeholder="Masukkan nama bank sampah"
|
||||||
value={formData.name}
|
value={formData.name}
|
||||||
onChange={(val) => setFormData({ ...formData, name: val.target.value })}
|
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||||
label={<Text fw="bold" fz="sm">Nama Bank Sampah Terdekat</Text>}
|
required
|
||||||
placeholder='Masukkan nama Bank Sampah Terdekat'
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
|
label="Alamat"
|
||||||
|
placeholder="Masukkan alamat lengkap"
|
||||||
value={formData.alamat}
|
value={formData.alamat}
|
||||||
onChange={(val) => setFormData({ ...formData, alamat: val.target.value })}
|
onChange={(e) => setFormData({ ...formData, alamat: e.target.value })}
|
||||||
label={<Text fw="bold" fz="sm">Alamat</Text>}
|
required
|
||||||
placeholder='Masukkan alamat Bank Sampah'
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
|
label="Nama Tempat di Maps"
|
||||||
|
placeholder="Masukkan nama tempat yang terdaftar di Google Maps"
|
||||||
value={formData.namaTempatMaps}
|
value={formData.namaTempatMaps}
|
||||||
onChange={(val) => setFormData({ ...formData, namaTempatMaps: val.target.value })}
|
onChange={(e) => setFormData({ ...formData, namaTempatMaps: e.target.value })}
|
||||||
label={<Text fw="bold" fz="sm">Nama Tempat Maps</Text>}
|
required
|
||||||
placeholder='Masukkan nama tempat maps Bank Sampah'
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fw="bold" fz="sm">Pilih Lokasi di Peta</Text>
|
<Text fw="bold" fz="sm" mb={6}>
|
||||||
<Box style={{ height: 300, width: '100%' }}>
|
Pilih Lokasi di Peta
|
||||||
|
</Text>
|
||||||
|
<Text fz="xs" c="dimmed" mb={4}>
|
||||||
|
Klik pada peta untuk menandai lokasi
|
||||||
|
</Text>
|
||||||
|
<Box style={{ height: 300, width: '100%', borderRadius: '8px', overflow: 'hidden' }}>
|
||||||
<LeafletMapEdit
|
<LeafletMapEdit
|
||||||
key={markerPosition?.lat ?? 'default'}
|
key={markerPosition?.lat ?? 'default'}
|
||||||
initialPosition={markerPosition || { lat: -8.65, lng: 115.2 }}
|
initialPosition={markerPosition || { lat: -8.65, lng: 115.2 }}
|
||||||
onChange={(pos) => {
|
onChange={(pos) => {
|
||||||
setMarkerPosition(pos);
|
setMarkerPosition(pos);
|
||||||
setFormData((prev) => ({
|
setFormData(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
lat: pos.lat,
|
lat: pos.lat,
|
||||||
lng: pos.lng,
|
lng: pos.lng,
|
||||||
@@ -128,9 +162,26 @@ function EditKeteranganBankSampahTerdekat() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
{markerPosition && (
|
||||||
|
<Text fz="xs" mt={4} c="green">
|
||||||
|
Lokasi dipilih: {markerPosition.lat.toFixed(6)}, {markerPosition.lng.toFixed(6)}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
<Group>
|
|
||||||
<Button onClick={handleSubmit} bg={colors['blue-button']}>Submit</Button>
|
<Group justify="right" mt="md">
|
||||||
|
<Button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
style={{
|
||||||
|
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||||
|
color: '#fff',
|
||||||
|
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
@@ -3,132 +3,148 @@
|
|||||||
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
||||||
import pengelolaanSampahState from '@/app/admin/(dashboard)/_state/lingkungan/pengelolaan-sampah';
|
import pengelolaanSampahState from '@/app/admin/(dashboard)/_state/lingkungan/pengelolaan-sampah';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Flex, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
import { Anchor, Box, Button, Flex, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
|
||||||
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
|
import { IconArrowLeft, IconEdit, IconTrash } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import dynamic from 'next/dynamic';
|
||||||
import dynamic from 'next/dynamic'
|
|
||||||
|
|
||||||
const LeafletMap = dynamic(() => import('@/app/admin/(dashboard)/_com/leafletMapCreate'), {
|
const LeafletMap = dynamic(() => import('@/app/admin/(dashboard)/_com/leafletMapCreate'), {
|
||||||
ssr: false
|
ssr: false
|
||||||
})
|
});
|
||||||
|
|
||||||
|
|
||||||
function DetailKeteranganBankSampahTerdekat() {
|
function DetailKeteranganBankSampahTerdekat() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const keteranganState = useProxy(pengelolaanSampahState.keteranganSampah)
|
const keteranganState = useProxy(pengelolaanSampahState.keteranganSampah);
|
||||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||||
const [modalHapus, setModalHapus] = useState(false)
|
const [modalHapus, setModalHapus] = useState(false);
|
||||||
const params = useParams()
|
const params = useParams();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
keteranganState.findUnique.load(params?.id as string)
|
keteranganState.findUnique.load(params?.id as string);
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
const handleHapus = () => {
|
const handleHapus = () => {
|
||||||
if (selectedId) {
|
if (selectedId) {
|
||||||
keteranganState.delete.byId(selectedId)
|
keteranganState.delete.byId(selectedId);
|
||||||
setModalHapus(false)
|
setModalHapus(false);
|
||||||
setSelectedId(null)
|
setSelectedId(null);
|
||||||
router.push("/admin/lingkungan/pengelolaan-sampah-bank-sampah/keterangan-bank-sampah-terdekat")
|
router.push("/admin/lingkungan/pengelolaan-sampah-bank-sampah/keterangan-bank-sampah-terdekat");
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
if (!keteranganState.findUnique.data) {
|
if (!keteranganState.findUnique.data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack p="md">
|
||||||
<Skeleton h={500} />
|
<Skeleton h={500} radius="md" />
|
||||||
</Stack>
|
</Stack>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Box mb={10}>
|
<Box mb="md">
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
<Button
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
variant="light"
|
||||||
|
leftSection={<IconArrowLeft size={20} />}
|
||||||
|
onClick={() => router.back()}
|
||||||
|
radius="xl"
|
||||||
|
color="blue"
|
||||||
|
>
|
||||||
|
Kembali
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Paper w={{ base: "100%", md: "50%" }} bg={colors['white-1']} p={'md'}>
|
<Paper
|
||||||
<Stack>
|
w={{ base: "100%", md: "60%" }}
|
||||||
<Text fz={"xl"} fw={"bold"}>Detail Keterangan Bank Sampah Terdekat</Text>
|
p="xl"
|
||||||
|
radius="lg"
|
||||||
|
withBorder
|
||||||
|
shadow="md"
|
||||||
|
style={{ background: colors['white-1'] }}
|
||||||
|
>
|
||||||
|
<Stack gap="lg">
|
||||||
|
<Title order={2} c="dark">
|
||||||
|
Detail Bank Sampah Terdekat
|
||||||
|
</Title>
|
||||||
|
|
||||||
<Paper bg={colors['BG-trans']} p={'md'}>
|
<Paper p="lg" radius="md" withBorder >
|
||||||
<Stack gap={"xs"}>
|
<Stack gap="md">
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"lg"} fw={"bold"}>Nama Bank Sampah Terdekat</Text>
|
<Text fz="sm" c="dimmed">Nama Bank Sampah</Text>
|
||||||
<Text fz={"lg"}>{keteranganState.findUnique.data?.name}</Text>
|
<Text fz="lg" fw={600}>{keteranganState.findUnique.data?.name}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"lg"} fw={"bold"}>Alamat</Text>
|
<Text fz="sm" c="dimmed">Alamat</Text>
|
||||||
<Text fz={"lg"}>{keteranganState.findUnique.data?.alamat}</Text>
|
<Text fz="lg">{keteranganState.findUnique.data?.alamat}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"lg"} fw={"bold"}>Nama Tempat Maps</Text>
|
<Text fz="sm" c="dimmed">Nama Tempat di Maps</Text>
|
||||||
<Text fz={"lg"}>{keteranganState.findUnique.data?.namaTempatMaps}</Text>
|
<Text fz="lg">{keteranganState.findUnique.data?.namaTempatMaps}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"lg"} fw={"bold"}>Peta Lokasi</Text>
|
<Text fz="sm" c="dimmed" mb={6}>Peta Lokasi</Text>
|
||||||
{keteranganState.findUnique.data?.lat && keteranganState.findUnique.data?.lng ? (
|
{keteranganState.findUnique.data?.lat && keteranganState.findUnique.data?.lng ? (
|
||||||
<Box
|
<Box style={{ height: "300px", borderRadius: "12px", overflow: "hidden" }}>
|
||||||
style={{
|
|
||||||
height: "300px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<LeafletMap
|
<LeafletMap
|
||||||
defaultCenter={{ lat: keteranganState.findUnique.data.lat, lng: keteranganState.findUnique.data.lng }}
|
defaultCenter={{ lat: keteranganState.findUnique.data.lat, lng: keteranganState.findUnique.data.lng }}
|
||||||
readOnly
|
readOnly
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
) : (
|
) : (
|
||||||
<Text c="dimmed" fz="sm">Koordinat belum tersedia</Text>
|
<Text c="dimmed" fz="sm">Belum ada koordinat</Text>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"lg"} fw={"bold"}>Link Petunjuk Arah</Text>
|
<Text fz="sm" c="dimmed" mb={6}>Petunjuk Arah</Text>
|
||||||
{keteranganState.findUnique.data?.lat && keteranganState.findUnique.data?.lng ? (
|
{keteranganState.findUnique.data?.lat && keteranganState.findUnique.data?.lng ? (
|
||||||
<a
|
<Anchor
|
||||||
href={`https://www.google.com/maps/dir/?api=1&destination=${keteranganState.findUnique.data.lat},${keteranganState.findUnique.data.lng}`}
|
href={`https://www.google.com/maps/dir/?api=1&destination=${keteranganState.findUnique.data.lat},${keteranganState.findUnique.data.lng}`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
style={{ color: 'black', textDecoration: 'underline' }}
|
underline="always"
|
||||||
|
c="blue"
|
||||||
>
|
>
|
||||||
Buka Petunjuk Arah di Google Maps
|
Buka di Google Maps
|
||||||
</a>
|
</Anchor>
|
||||||
) : (
|
) : (
|
||||||
<Text c="dimmed" fz="sm">Koordinat belum tersedia</Text>
|
<Text c="dimmed" fz="sm">Belum ada koordinat</Text>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box>
|
<Flex gap="sm" mt="md">
|
||||||
<Flex gap={"xs"}>
|
<Button
|
||||||
<Button
|
onClick={() => {
|
||||||
onClick={() => {
|
if (keteranganState.findUnique.data) {
|
||||||
if (keteranganState.findUnique.data) {
|
setSelectedId(keteranganState.findUnique.data.id);
|
||||||
setSelectedId(keteranganState.findUnique.data.id);
|
setModalHapus(true);
|
||||||
setModalHapus(true);
|
}
|
||||||
}
|
}}
|
||||||
}}
|
disabled={!keteranganState.findUnique.data}
|
||||||
disabled={!keteranganState.findUnique.data}
|
leftSection={<IconTrash size={18} />}
|
||||||
color="red"
|
color="red"
|
||||||
>
|
radius="md"
|
||||||
<IconX size={20} />
|
variant='light'
|
||||||
</Button>
|
>
|
||||||
<Button
|
Hapus
|
||||||
onClick={() => router.push(`/admin/lingkungan/pengelolaan-sampah-bank-sampah/keterangan-bank-sampah-terdekat/${keteranganState.findUnique.data?.id}/edit`)}
|
</Button>
|
||||||
color="green"
|
<Button
|
||||||
>
|
onClick={() =>
|
||||||
<IconEdit size={20} />
|
router.push(`/admin/lingkungan/pengelolaan-sampah-bank-sampah/keterangan-bank-sampah-terdekat/${keteranganState.findUnique.data?.id}/edit`)
|
||||||
</Button>
|
}
|
||||||
</Flex>
|
leftSection={<IconEdit size={18} />}
|
||||||
</Box>
|
color="green"
|
||||||
|
radius="md"
|
||||||
|
variant='light'
|
||||||
|
>
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -138,10 +154,9 @@ function DetailKeteranganBankSampahTerdekat() {
|
|||||||
opened={modalHapus}
|
opened={modalHapus}
|
||||||
onClose={() => setModalHapus(false)}
|
onClose={() => setModalHapus(false)}
|
||||||
onConfirm={handleHapus}
|
onConfirm={handleHapus}
|
||||||
text="Apakah anda yakin ingin menghapus keterangan bank sampah terdekat ini?"
|
text="Apakah Anda yakin ingin menghapus data bank sampah ini?"
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
'use client'
|
'use client';
|
||||||
|
|
||||||
import pengelolaanSampahState from '@/app/admin/(dashboard)/_state/lingkungan/pengelolaan-sampah';
|
import pengelolaanSampahState from '@/app/admin/(dashboard)/_state/lingkungan/pengelolaan-sampah';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title, Tooltip } from '@mantine/core';
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
import dynamic from 'next/dynamic';
|
import dynamic from 'next/dynamic';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
@@ -28,58 +29,107 @@ function CreateKeteranganBankSampahTerdekat() {
|
|||||||
setMarkerPosition(null)
|
setMarkerPosition(null)
|
||||||
}
|
}
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
if (markerPosition) {
|
try {
|
||||||
keteranganState.create.form.lat = markerPosition.lat
|
if (!keteranganState.create.form.name) {
|
||||||
keteranganState.create.form.lng = markerPosition.lng
|
return toast.error('Nama bank sampah harus diisi');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (markerPosition) {
|
||||||
|
keteranganState.create.form.lat = markerPosition.lat;
|
||||||
|
keteranganState.create.form.lng = markerPosition.lng;
|
||||||
|
} else {
|
||||||
|
return toast.error('Silakan pilih lokasi di peta');
|
||||||
|
}
|
||||||
|
|
||||||
|
await keteranganState.create.create();
|
||||||
|
toast.success('Data bank sampah berhasil ditambahkan');
|
||||||
|
resetForm();
|
||||||
|
router.push("/admin/lingkungan/pengelolaan-sampah-bank-sampah/keterangan-bank-sampah-terdekat");
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error creating bank sampah:', error);
|
||||||
|
toast.error('Gagal menambahkan data bank sampah');
|
||||||
}
|
}
|
||||||
await keteranganState.create.create()
|
|
||||||
resetForm()
|
|
||||||
router.push("/admin/lingkungan/pengelolaan-sampah-bank-sampah/keterangan-bank-sampah-terdekat")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Box mb={10}>
|
<Group mb="md">
|
||||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
<Tooltip label="Kembali ke halaman sebelumnya" color="blue" withArrow>
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
</Button>
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
</Box>
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Title order={4} ml="sm" c="dark">
|
||||||
|
Tambah Bank Sampah Terdekat
|
||||||
|
</Title>
|
||||||
|
</Group>
|
||||||
|
|
||||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
<Paper
|
||||||
<Stack gap={"xs"}>
|
w={{ base: '100%', md: '50%' }}
|
||||||
<Title order={4}>Create Keterangan Bank Sampah Terdekat</Title>
|
bg={colors['white-1']}
|
||||||
|
p="lg"
|
||||||
|
radius="md"
|
||||||
|
shadow="sm"
|
||||||
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
<TextInput
|
<TextInput
|
||||||
|
label="Nama Bank Sampah"
|
||||||
|
placeholder="Masukkan nama bank sampah"
|
||||||
value={keteranganState.create.form.name}
|
value={keteranganState.create.form.name}
|
||||||
onChange={(val) => keteranganState.create.form.name = val.target.value}
|
onChange={(e) => (keteranganState.create.form.name = e.target.value)}
|
||||||
label={<Text fw="bold" fz="sm">Nama Bank Sampah Terdekat</Text>}
|
required
|
||||||
placeholder='Masukkan nama Bank Sampah Terdekat'
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
|
label="Alamat"
|
||||||
|
placeholder="Masukkan alamat lengkap"
|
||||||
value={keteranganState.create.form.alamat}
|
value={keteranganState.create.form.alamat}
|
||||||
onChange={(val) => keteranganState.create.form.alamat = val.target.value}
|
onChange={(e) => (keteranganState.create.form.alamat = e.target.value)}
|
||||||
label={<Text fw="bold" fz="sm">Alamat</Text>}
|
required
|
||||||
placeholder='Masukkan alamat Bank Sampah'
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
|
label="Nama Tempat di Maps"
|
||||||
|
placeholder="Masukkan nama tempat yang terdaftar di Google Maps"
|
||||||
value={keteranganState.create.form.namaTempatMaps}
|
value={keteranganState.create.form.namaTempatMaps}
|
||||||
onChange={(val) => keteranganState.create.form.namaTempatMaps = val.target.value}
|
onChange={(e) => (keteranganState.create.form.namaTempatMaps = e.target.value)}
|
||||||
label={<Text fw="bold" fz="sm">Nama Tempat Maps</Text>}
|
required
|
||||||
placeholder='Masukkan nama tempat maps Bank Sampah'
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fw="bold" fz="sm">Pilih Lokasi di Peta</Text>
|
<Text fw="bold" fz="sm" mb={6}>
|
||||||
<Box style={{ height: 300, width: '100%' }}>
|
Pilih Lokasi di Peta
|
||||||
|
</Text>
|
||||||
|
<Text fz="xs" c="dimmed" mb={4}>
|
||||||
|
Klik pada peta untuk menandai lokasi
|
||||||
|
</Text>
|
||||||
|
<Box style={{ height: 300, width: '100%', borderRadius: '8px', overflow: 'hidden' }}>
|
||||||
<LeafletMap
|
<LeafletMap
|
||||||
onSelect={(pos) => setMarkerPosition(pos)}
|
onSelect={(pos) => setMarkerPosition(pos)}
|
||||||
defaultCenter={{ lat: -8.65, lng: 115.2 }}
|
defaultCenter={{ lat: -8.65, lng: 115.2 }}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
{markerPosition && (
|
||||||
|
<Text fz="xs" mt={4} c="green">
|
||||||
|
Lokasi dipilih: {markerPosition.lat.toFixed(6)}, {markerPosition.lng.toFixed(6)}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
<Group>
|
|
||||||
<Button onClick={handleSubmit} bg={colors['blue-button']}>Submit</Button>
|
<Group justify="right" mt="md">
|
||||||
|
<Button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
style={{
|
||||||
|
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||||
|
color: '#fff',
|
||||||
|
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
|
||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
|
import { Box, Button, Center, Group, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title, Tooltip } from '@mantine/core';
|
||||||
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { IconDeviceImac, IconPlus, IconSearch } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import { useState } from 'react';
|
||||||
import { useEffect, useState } from 'react';
|
|
||||||
import HeaderSearch from '../../../_com/header';
|
import HeaderSearch from '../../../_com/header';
|
||||||
import JudulList from '../../../_com/judulList';
|
|
||||||
import pengelolaanSampahState from '../../../_state/lingkungan/pengelolaan-sampah';
|
import pengelolaanSampahState from '../../../_state/lingkungan/pengelolaan-sampah';
|
||||||
|
|
||||||
function KeteranganBankSampahTerdekat() {
|
function KeteranganBankSampahTerdekat() {
|
||||||
@@ -17,71 +15,124 @@ function KeteranganBankSampahTerdekat() {
|
|||||||
<Box>
|
<Box>
|
||||||
<HeaderSearch
|
<HeaderSearch
|
||||||
title='Keterangan Bank Sampah Terdekat'
|
title='Keterangan Bank Sampah Terdekat'
|
||||||
placeholder='pencarian'
|
placeholder='Cari nama bank sampah...'
|
||||||
searchIcon={<IconSearch size={20} />}
|
searchIcon={<IconSearch size={20} />}
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
<ListKeteranganBankSampahTerdekat search={search}/>
|
<ListKeteranganBankSampahTerdekat search={search} />
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ListKeteranganBankSampahTerdekat({ search }: { search: string }) {
|
function ListKeteranganBankSampahTerdekat({ search }: { search: string }) {
|
||||||
const keteranganState = useProxy(pengelolaanSampahState.keteranganSampah)
|
const keteranganState = useProxy(pengelolaanSampahState.keteranganSampah);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
useEffect(() => {
|
const {
|
||||||
keteranganState.findMany.load()
|
data,
|
||||||
}, [])
|
page,
|
||||||
|
totalPages,
|
||||||
|
loading,
|
||||||
|
load,
|
||||||
|
} = keteranganState.findMany;
|
||||||
|
|
||||||
const filteredData = (keteranganState.findMany.data || []).filter(item => {
|
useShallowEffect(() => {
|
||||||
const keyword = search.toLowerCase();
|
load(page, 10, search);
|
||||||
return (
|
}, [page, search]);
|
||||||
item.name.toLowerCase().includes(keyword) ||
|
|
||||||
item.alamat.toLowerCase().includes(keyword) ||
|
|
||||||
item.namaTempatMaps.toLowerCase().includes(keyword)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!keteranganState.findMany.data) {
|
const filteredData = data || [];
|
||||||
|
|
||||||
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={10}>
|
||||||
<Skeleton h={500} />
|
<Skeleton height={600} radius="md" />
|
||||||
</Stack>
|
</Stack>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py={10}>
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
<Paper bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
||||||
<JudulList
|
<Group justify="space-between" mb="md">
|
||||||
title='List Keterangan Bank Sampah Terdekat'
|
<Title order={4}>Daftar Bank Sampah Terdekat</Title>
|
||||||
href='/admin/lingkungan/pengelolaan-sampah-bank-sampah/keterangan-bank-sampah-terdekat/create'
|
<Tooltip label="Tambah Bank Sampah" withArrow>
|
||||||
/>
|
<Button
|
||||||
<Table striped withTableBorder withRowBorders>
|
leftSection={<IconPlus size={18} />}
|
||||||
<TableThead>
|
color="blue"
|
||||||
<TableTr>
|
variant="light"
|
||||||
<TableTh>Nama Bank Sampah Terdekat</TableTh>
|
onClick={() => router.push('/admin/lingkungan/pengelolaan-sampah-bank-sampah/keterangan-bank-sampah-terdekat/create')}
|
||||||
<TableTh>Alamat</TableTh>
|
>
|
||||||
<TableTh>Nama Tempat Maps</TableTh>
|
Tambah Baru
|
||||||
<TableTh>Detail</TableTh>
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
<Box style={{ overflowX: 'auto' }}>
|
||||||
|
<Table highlightOnHover>
|
||||||
|
<TableThead>
|
||||||
|
<TableTr>
|
||||||
|
<TableTh>Nama Bank Sampah</TableTh>
|
||||||
|
<TableTh>Alamat</TableTh>
|
||||||
|
<TableTh>Nama Tempat di Maps</TableTh>
|
||||||
|
<TableTh>Aksi</TableTh>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
<TableTbody>
|
<TableTbody>
|
||||||
{filteredData.map((item) => (
|
{filteredData.length > 0 ? (
|
||||||
<TableTr key={item.id}>
|
filteredData.map((item) => (
|
||||||
<TableTd>{item.name}</TableTd>
|
<TableTr key={item.id}>
|
||||||
<TableTd>{item.alamat}</TableTd>
|
<TableTd>
|
||||||
<TableTd>{item.namaTempatMaps}</TableTd>
|
<Text fw={500}>{item.name}</Text>
|
||||||
<TableTd>
|
</TableTd>
|
||||||
<Button onClick={() => router.push(`/admin/lingkungan/pengelolaan-sampah-bank-sampah/keterangan-bank-sampah-terdekat/${item.id}`)}>
|
<TableTd>
|
||||||
<IconDeviceImac size={20} />
|
<Box w={200}>
|
||||||
</Button>
|
<Text lineClamp={2} truncate="end" fz="sm">
|
||||||
</TableTd>
|
{item.alamat || '-'}
|
||||||
</TableTr>
|
</Text>
|
||||||
))}
|
</Box>
|
||||||
</TableTbody>
|
</TableTd>
|
||||||
</Table>
|
<TableTd>
|
||||||
|
<Text fz="sm">
|
||||||
|
{item.namaTempatMaps || '-'}
|
||||||
|
</Text>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Tooltip label="Lihat Detail" withArrow>
|
||||||
|
<Button
|
||||||
|
variant="light"
|
||||||
|
color="blue"
|
||||||
|
onClick={() => router.push(`/admin/lingkungan/pengelolaan-sampah-bank-sampah/keterangan-bank-sampah-terdekat/${item.id}`)}
|
||||||
|
>
|
||||||
|
<IconDeviceImac size={20} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<TableTr>
|
||||||
|
<TableTd colSpan={4} align="center" py="xl">
|
||||||
|
<Text c="dimmed">Tidak ada data bank sampah terdekat</Text>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
)}
|
||||||
|
</TableTbody>
|
||||||
|
</Table>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{totalPages > 1 && (
|
||||||
|
<Center mt="xl">
|
||||||
|
<Pagination
|
||||||
|
value={page}
|
||||||
|
onChange={(newPage) => load(newPage)}
|
||||||
|
total={totalPages}
|
||||||
|
siblings={1}
|
||||||
|
boundaries={1}
|
||||||
|
withEdges
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import pengelolaanSampahState from '@/app/admin/(dashboard)/_state/lingkungan/pengelolaan-sampah';
|
import pengelolaanSampahState from '@/app/admin/(dashboard)/_state/lingkungan/pengelolaan-sampah';
|
||||||
import SelectIconProgramEdit from '@/app/admin/(dashboard)/_com/selectIconEdit';
|
import SelectIconProgramEdit from '@/app/admin/(dashboard)/_com/selectIconEdit';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title, Tooltip } from '@mantine/core';
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
@@ -65,47 +65,85 @@ function EditProgramKreatifDesa() {
|
|||||||
...stateSampah.update.form,
|
...stateSampah.update.form,
|
||||||
name: formData.name.trim(),
|
name: formData.name.trim(),
|
||||||
icon: formData.icon.trim(),
|
icon: formData.icon.trim(),
|
||||||
}
|
};
|
||||||
|
|
||||||
await stateSampah.update.submit();
|
await stateSampah.update.submit();
|
||||||
|
toast.success('Data pengelolaan sampah berhasil diperbarui!');
|
||||||
router.push("/admin/lingkungan/pengelolaan-sampah-bank-sampah/list-pengelolaan-sampah-bank-sampah");
|
router.push("/admin/lingkungan/pengelolaan-sampah-bank-sampah/list-pengelolaan-sampah-bank-sampah");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error updating pengelolaan sampah:", error);
|
console.error("Error updating pengelolaan sampah:", error);
|
||||||
toast.error("Gagal memuat data pengelolaan sampah");
|
toast.error(error instanceof Error ? error.message : "Gagal memperbarui data pengelolaan sampah");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Box mb={10}>
|
<Group mb="md">
|
||||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
<Tooltip label="Kembali ke halaman sebelumnya" color="blue" withArrow>
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
<Button
|
||||||
</Button>
|
variant="subtle"
|
||||||
</Box>
|
onClick={() => router.back()}
|
||||||
|
p="xs"
|
||||||
|
radius="md"
|
||||||
|
>
|
||||||
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Title order={4} ml="sm" c="dark">
|
||||||
|
Edit Pengelolaan Sampah Bank Sampah
|
||||||
|
</Title>
|
||||||
|
</Group>
|
||||||
|
|
||||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
<Paper
|
||||||
<Stack gap={"xs"}>
|
w={{ base: '100%', md: '50%' }}
|
||||||
<Title order={3}>Edit List Pengelolaan Sampah Bank Sampah</Title>
|
bg={colors['white-1']}
|
||||||
|
p="lg"
|
||||||
|
radius="md"
|
||||||
|
shadow="sm"
|
||||||
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
<TextInput
|
<TextInput
|
||||||
|
label="Nama Pengelolaan Sampah"
|
||||||
|
placeholder="Masukkan nama pengelolaan sampah"
|
||||||
value={formData.name}
|
value={formData.name}
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Nama List Pengelolaan Sampah Bank Sampah</Text>}
|
onChange={(e) => {
|
||||||
placeholder="masukkan nama list pengelolaan sampah bank sampah"
|
const value = e.target.value;
|
||||||
onChange={(val) => {
|
setFormData(prev => ({
|
||||||
setFormData({
|
...prev,
|
||||||
...formData,
|
name: value
|
||||||
name: val.target.value
|
}));
|
||||||
})
|
stateSampah.update.form.name = value;
|
||||||
}}
|
}}
|
||||||
|
required
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"sm"} fw={"bold"}>Ikon List Pengelolaan Sampah Bank Sampah</Text>
|
<Text fw="bold" fz="sm" mb={6}>
|
||||||
|
Pilih Ikon
|
||||||
|
</Text>
|
||||||
<SelectIconProgramEdit
|
<SelectIconProgramEdit
|
||||||
value={formData.icon as IconKey}
|
value={formData.icon as IconKey}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
setFormData((prev) => ({ ...prev, icon: value }));
|
setFormData(prev => ({ ...prev, icon: value }));
|
||||||
stateSampah.update.form.icon = value;
|
stateSampah.update.form.icon = value;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Simpan</Button>
|
|
||||||
|
<Group justify="right" mt="md">
|
||||||
|
<Button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
style={{
|
||||||
|
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||||
|
color: '#fff',
|
||||||
|
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
'use client'
|
'use client';
|
||||||
import pengelolaanSampahState from '@/app/admin/(dashboard)/_state/lingkungan/pengelolaan-sampah';
|
import pengelolaanSampahState from '@/app/admin/(dashboard)/_state/lingkungan/pengelolaan-sampah';
|
||||||
import SelectIconProgram from '@/app/admin/(dashboard)/_com/selectIcon';
|
import SelectIconProgram from '@/app/admin/(dashboard)/_com/selectIcon';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title, Tooltip } from '@mantine/core';
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
@@ -11,43 +11,83 @@ import { useProxy } from 'valtio/utils';
|
|||||||
|
|
||||||
|
|
||||||
function CreatePengelolaanSampahBankSampah() {
|
function CreatePengelolaanSampahBankSampah() {
|
||||||
const stateCreate = useProxy(pengelolaanSampahState.pengelolaanSampah)
|
const stateCreate = useProxy(pengelolaanSampahState.pengelolaanSampah);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
stateCreate.create.form = {
|
stateCreate.create.form = {
|
||||||
name: "",
|
name: "",
|
||||||
icon: "",
|
icon: "",
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
await stateCreate.create.create();
|
try {
|
||||||
resetForm();
|
await stateCreate.create.create();
|
||||||
router.push("/admin/lingkungan/pengelolaan-sampah-bank-sampah/list-pengelolaan-sampah-bank-sampah")
|
resetForm();
|
||||||
}
|
router.push("/admin/lingkungan/pengelolaan-sampah-bank-sampah/list-pengelolaan-sampah-bank-sampah");
|
||||||
return (
|
} catch (error) {
|
||||||
<Box>
|
console.error('Error creating pengelolaan sampah:', error);
|
||||||
<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'}>
|
return (
|
||||||
<Stack gap={"xs"}>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Title order={3}>Create List Pengelolaan Sampah Bank Sampah</Title>
|
<Group mb="md">
|
||||||
|
<Tooltip label="Kembali ke halaman sebelumnya" color="blue" position="bottom">
|
||||||
|
<Button
|
||||||
|
variant="subtle"
|
||||||
|
onClick={() => router.back()}
|
||||||
|
p="xs"
|
||||||
|
radius="md"
|
||||||
|
>
|
||||||
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Title order={4} ml="sm" c="dark">
|
||||||
|
Tambah Pengelolaan Sampah Bank Sampah
|
||||||
|
</Title>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
<Paper
|
||||||
|
w={{ base: '100%', md: '50%' }}
|
||||||
|
bg={colors['white-1']}
|
||||||
|
p="lg"
|
||||||
|
radius="md"
|
||||||
|
shadow="sm"
|
||||||
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Nama Pengelolaan Sampah Bank Sampah</Text>}
|
label="Nama Pengelolaan Sampah"
|
||||||
placeholder="masukkan nama pengelolaan sampah bank sampah"
|
placeholder="Masukkan nama pengelolaan sampah"
|
||||||
onChange={(val) => stateCreate.create.form.name = val.target.value}
|
value={stateCreate.create.form.name || ''}
|
||||||
|
onChange={(e) => (stateCreate.create.form.name = e.target.value)}
|
||||||
|
required
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"sm"} fw={"bold"}>Ikon Pengelolaan Sampah Bank Sampah</Text>
|
<Text fw="bold" fz="sm" mb={6}>
|
||||||
<SelectIconProgram onChange={(value) => stateCreate.create.form.icon = value} />
|
Pilih Ikon
|
||||||
|
</Text>
|
||||||
|
<SelectIconProgram
|
||||||
|
onChange={(value) => (stateCreate.create.form.icon = value)}
|
||||||
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Group>
|
|
||||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Submit</Button>
|
<Group justify="right" mt="md">
|
||||||
|
<Button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
style={{
|
||||||
|
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||||
|
color: '#fff',
|
||||||
|
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
@@ -1,26 +1,24 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
|
import { Box, Button, Center, Group, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title, Tooltip } from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconChartLine, IconClipboardTextFilled, IconEdit, IconLeaf, IconRecycle, IconScale, IconSearch, IconTent, IconTrashFilled, IconTrophy, IconTruckFilled, IconX } from '@tabler/icons-react';
|
import { IconChartLine, IconClipboardTextFilled, IconEdit, IconLeaf, IconPlus, IconRecycle, IconScale, IconSearch, IconTent, IconTrashFilled, IconTrophy, IconTruckFilled } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import HeaderSearch from '../../../_com/header';
|
import HeaderSearch from '../../../_com/header';
|
||||||
import JudulList from '../../../_com/judulList';
|
|
||||||
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||||
import pengelolaanSampahState from '../../../_state/lingkungan/pengelolaan-sampah';
|
import pengelolaanSampahState from '../../../_state/lingkungan/pengelolaan-sampah';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
|
||||||
function PengelolaanSampahBankSampah() {
|
function PengelolaanSampahBankSampah() {
|
||||||
const [search, setSearch] = useState("")
|
const [search, setSearch] = useState("");
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<HeaderSearch
|
<HeaderSearch
|
||||||
title='List Pengelolaan Sampah Bank Sampah'
|
title='List Pengelolaan Sampah Bank Sampah'
|
||||||
placeholder='pencarian'
|
placeholder='Cari pengelolaan sampah...'
|
||||||
searchIcon={<IconSearch size={20} />}
|
searchIcon={<IconSearch size={20} />}
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
@@ -36,9 +34,18 @@ function ListPengelolaanSampahBankSampah({ search }: { search: string }) {
|
|||||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
page,
|
||||||
|
totalPages,
|
||||||
|
loading,
|
||||||
|
load,
|
||||||
|
} = stateList.findMany
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
stateList.findMany.load()
|
load(page, 10, search)
|
||||||
}, [])
|
}, [page, search])
|
||||||
|
|
||||||
|
|
||||||
const handleHapus = () => {
|
const handleHapus = () => {
|
||||||
if (selectedId) {
|
if (selectedId) {
|
||||||
@@ -48,15 +55,9 @@ function ListPengelolaanSampahBankSampah({ search }: { search: string }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const filteredData = (stateList.findMany.data || []).filter(item => {
|
const filteredData = data || []
|
||||||
const keyword = search.toLowerCase();
|
|
||||||
return (
|
|
||||||
item.name.toLowerCase().includes(keyword)
|
|
||||||
|| item.icon.toLowerCase().includes(keyword)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
const iconMap: Record<string, React.FC<any>> = {
|
const iconMap: Record<string, React.FC<{ size: number; style?: React.CSSProperties }>> = {
|
||||||
ekowisata: IconLeaf,
|
ekowisata: IconLeaf,
|
||||||
kompetisi: IconTrophy,
|
kompetisi: IconTrophy,
|
||||||
wisata: IconTent,
|
wisata: IconTent,
|
||||||
@@ -68,7 +69,7 @@ function ListPengelolaanSampahBankSampah({ search }: { search: string }) {
|
|||||||
trash: IconTrashFilled,
|
trash: IconTrashFilled,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!stateList.findMany.data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={10}>
|
||||||
<Skeleton h={500} />
|
<Skeleton h={500} />
|
||||||
@@ -78,49 +79,107 @@ function ListPengelolaanSampahBankSampah({ search }: { search: string }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py={10}>
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
<Paper bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
||||||
<JudulList
|
<Group justify="space-between" mb="md">
|
||||||
title='List Pengelolaan Sampah Bank Sampah'
|
<Title order={4}>Daftar Pengelolaan Sampah Bank Sampah</Title>
|
||||||
href='/admin/lingkungan/pengelolaan-sampah-bank-sampah/list-pengelolaan-sampah-bank-sampah/create'
|
<Tooltip label="Tambah Pengelolaan Sampah" withArrow>
|
||||||
/>
|
<Button
|
||||||
<Table striped withTableBorder withRowBorders>
|
leftSection={<IconPlus size={18} />}
|
||||||
<TableThead>
|
color="blue"
|
||||||
<TableTr>
|
variant="light"
|
||||||
<TableTh>Nama Pengelolaan Sampah</TableTh>
|
onClick={() => router.push('/admin/lingkungan/pengelolaan-sampah-bank-sampah/list-pengelolaan-sampah-bank-sampah/create')}
|
||||||
<TableTh>Icon</TableTh>
|
>
|
||||||
<TableTh>Edit</TableTh>
|
Tambah Baru
|
||||||
<TableTh>Delete</TableTh>
|
</Button>
|
||||||
</TableTr>
|
</Tooltip>
|
||||||
</TableThead>
|
</Group>
|
||||||
<TableTbody>
|
|
||||||
{filteredData.map((item) => (
|
<Box style={{ overflowX: 'auto' }}>
|
||||||
<TableTr key={item.id}>
|
<Table highlightOnHover>
|
||||||
<TableTd>{item.name}</TableTd>
|
<TableThead>
|
||||||
<TableTd style={{ width: '10%' }}>
|
<TableTr>
|
||||||
{iconMap[item.icon] && (
|
<TableTh>Nama Pengelolaan Sampah</TableTh>
|
||||||
<Box title={item.icon}>
|
<TableTh>Icon</TableTh>
|
||||||
{React.createElement(iconMap[item.icon], { size: 24 })}
|
<TableTh>Edit</TableTh>
|
||||||
</Box>
|
<TableTh>Delete</TableTh>
|
||||||
)}
|
|
||||||
</TableTd>
|
|
||||||
<TableTd>
|
|
||||||
<Button color="green" onClick={() => router.push(`/admin/lingkungan/pengelolaan-sampah-bank-sampah/list-pengelolaan-sampah-bank-sampah/${item.id}`)}>
|
|
||||||
<IconEdit size={20} />
|
|
||||||
</Button>
|
|
||||||
</TableTd>
|
|
||||||
<TableTd>
|
|
||||||
<Button color="red" onClick={() => {
|
|
||||||
setSelectedId(item.id)
|
|
||||||
setModalHapus(true)
|
|
||||||
}}>
|
|
||||||
<IconX size={20} />
|
|
||||||
</Button>
|
|
||||||
</TableTd>
|
|
||||||
</TableTr>
|
</TableTr>
|
||||||
))}
|
</TableThead>
|
||||||
</TableTbody>
|
<TableTbody>
|
||||||
</Table>
|
{filteredData.length > 0 ? (
|
||||||
|
filteredData.map((item) => (
|
||||||
|
<TableTr key={item.id}>
|
||||||
|
<TableTd>
|
||||||
|
<Text lineClamp={1} truncate="end" fw={500}>{item.name}</Text>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
{iconMap[item.icon] ? (
|
||||||
|
<Tooltip label={item.icon} withArrow>
|
||||||
|
<Box>
|
||||||
|
{React.createElement(iconMap[item.icon], {
|
||||||
|
size: 24,
|
||||||
|
style: { color: colors['blue-button'] }
|
||||||
|
})}
|
||||||
|
</Box>
|
||||||
|
</Tooltip>
|
||||||
|
) : (
|
||||||
|
<Text c="dimmed" fz="sm">-</Text>
|
||||||
|
)}
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Tooltip label="Edit" withArrow>
|
||||||
|
<Button
|
||||||
|
variant="light"
|
||||||
|
color="blue"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => router.push(`/admin/lingkungan/pengelolaan-sampah-bank-sampah/list-pengelolaan-sampah-bank-sampah/${item.id}`)}
|
||||||
|
>
|
||||||
|
<IconEdit size={18} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Tooltip label="Hapus" withArrow>
|
||||||
|
<Button
|
||||||
|
variant="light"
|
||||||
|
color="red"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedId(item.id);
|
||||||
|
setModalHapus(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconTrashFilled size={18} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<TableTr>
|
||||||
|
<TableTd colSpan={3} align="center" py="xl">
|
||||||
|
<Text c="dimmed">Tidak ada data pengelolaan sampah</Text>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
)}
|
||||||
|
</TableTbody>
|
||||||
|
</Table>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{totalPages > 1 && (
|
||||||
|
<Center mt="xl">
|
||||||
|
<Pagination
|
||||||
|
value={page}
|
||||||
|
onChange={(newPage) => load(newPage)}
|
||||||
|
total={totalPages}
|
||||||
|
siblings={1}
|
||||||
|
boundaries={1}
|
||||||
|
withEdges
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
{/* Modal Konfirmasi Hapus */}
|
{/* Modal Konfirmasi Hapus */}
|
||||||
<ModalKonfirmasiHapus
|
<ModalKonfirmasiHapus
|
||||||
opened={modalHapus}
|
opened={modalHapus}
|
||||||
|
|||||||
@@ -126,7 +126,9 @@ function ListProgramPenghijauan({ search }: { search: string }) {
|
|||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd style={{ width: '5%', textAlign: 'center' }}>{index + 1}</TableTd>
|
<TableTd style={{ width: '5%', textAlign: 'center' }}>{index + 1}</TableTd>
|
||||||
<TableTd style={{ width: '20%', wordWrap: 'break-word' }}>{item.name}</TableTd>
|
<TableTd style={{ width: '20%', wordWrap: 'break-word' }}>{item.name}</TableTd>
|
||||||
<TableTd style={{ width: '35%', wordWrap: 'break-word' }} dangerouslySetInnerHTML={{ __html: item.judul }}></TableTd>
|
<TableTd style={{ width: '35%', wordWrap: 'break-word' }}>
|
||||||
|
<Text lineClamp={1} fz={"sm"} dangerouslySetInnerHTML={{ __html: item.judul }}/>
|
||||||
|
</TableTd>
|
||||||
<TableTd style={{ width: '10%' }}>
|
<TableTd style={{ width: '10%' }}>
|
||||||
{iconMap[item.icon] && (
|
{iconMap[item.icon] && (
|
||||||
<Box title={item.icon}>
|
<Box title={item.icon}>
|
||||||
|
|||||||
@@ -0,0 +1,144 @@
|
|||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
'use client'
|
||||||
|
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
||||||
|
import beasiswaDesaState from '@/app/admin/(dashboard)/_state/pendidikan/beasiswa-desa';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title, Tooltip } 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 EditProgramKreatifDesa() {
|
||||||
|
const state = useProxy(beasiswaDesaState.keunggulanProgram)
|
||||||
|
const params = useParams()
|
||||||
|
const router = useRouter();
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
judul: '',
|
||||||
|
deskripsi: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const loadProgramKreatif = async () => {
|
||||||
|
const id = params?.id as string;
|
||||||
|
if (!id) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await state.update.load(id);
|
||||||
|
if (data) {
|
||||||
|
// ⬇️ FIX PENTING: tambahkan ini
|
||||||
|
state.update.id = id;
|
||||||
|
|
||||||
|
state.update.form = {
|
||||||
|
judul: data.judul,
|
||||||
|
deskripsi: data.deskripsi,
|
||||||
|
};
|
||||||
|
|
||||||
|
setFormData({
|
||||||
|
judul: data.judul,
|
||||||
|
deskripsi: data.deskripsi,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading pengelolaan sampah:", error);
|
||||||
|
toast.error("Gagal memuat data pengelolaan sampah");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadProgramKreatif();
|
||||||
|
}, [params?.id]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
try {
|
||||||
|
state.update.form = {
|
||||||
|
...state.update.form,
|
||||||
|
judul: formData.judul.trim(),
|
||||||
|
deskripsi: formData.deskripsi.trim(),
|
||||||
|
};
|
||||||
|
|
||||||
|
await state.update.update();
|
||||||
|
toast.success('Data keunggulan program berhasil diperbarui!');
|
||||||
|
router.push("/admin/pendidikan/beasiswa-desa/keunggulan-program");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error updating keunggulan program:", error);
|
||||||
|
toast.error(error instanceof Error ? error.message : "Gagal memperbarui data keunggulan program");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
|
<Group mb="md">
|
||||||
|
<Tooltip label="Kembali ke halaman sebelumnya" color="blue" withArrow>
|
||||||
|
<Button
|
||||||
|
variant="subtle"
|
||||||
|
onClick={() => router.back()}
|
||||||
|
p="xs"
|
||||||
|
radius="md"
|
||||||
|
>
|
||||||
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Title order={4} ml="sm" c="dark">
|
||||||
|
Edit Keunggulan Program
|
||||||
|
</Title>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
<Paper
|
||||||
|
w={{ base: '100%', md: '50%' }}
|
||||||
|
bg={colors['white-1']}
|
||||||
|
p="lg"
|
||||||
|
radius="md"
|
||||||
|
shadow="sm"
|
||||||
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
|
<TextInput
|
||||||
|
label="Judul"
|
||||||
|
placeholder="Masukkan judul"
|
||||||
|
value={formData.judul}
|
||||||
|
onChange={(e) => {
|
||||||
|
const value = e.target.value;
|
||||||
|
setFormData(prev => ({
|
||||||
|
...prev,
|
||||||
|
judul: value
|
||||||
|
}));
|
||||||
|
state.update.form.judul = value;
|
||||||
|
}}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fw="bold" fz="sm" mb={6}>
|
||||||
|
Deskripsi
|
||||||
|
</Text>
|
||||||
|
<EditEditor
|
||||||
|
value={state.update.form.deskripsi}
|
||||||
|
onChange={(htmlContent) => {
|
||||||
|
state.update.form.deskripsi = htmlContent;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Group justify="right" mt="md">
|
||||||
|
<Button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
style={{
|
||||||
|
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||||
|
color: '#fff',
|
||||||
|
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EditProgramKreatifDesa;
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
'use client';
|
||||||
|
import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
|
||||||
|
import beasiswaDesaState from '@/app/admin/(dashboard)/_state/pendidikan/beasiswa-desa';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title, Tooltip } from '@mantine/core';
|
||||||
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function CreateKeunggulanProgram() {
|
||||||
|
const stateCreate = useProxy(beasiswaDesaState.keunggulanProgram);
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
stateCreate.create.form = {
|
||||||
|
judul: "",
|
||||||
|
deskripsi: "",
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
try {
|
||||||
|
await stateCreate.create.create();
|
||||||
|
resetForm();
|
||||||
|
router.push("/admin/pendidikan/beasiswa-desa/keunggulan-program");
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error creating keunggulan program:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
|
<Group mb="md">
|
||||||
|
<Tooltip label="Kembali ke halaman sebelumnya" color="blue" position="bottom">
|
||||||
|
<Button
|
||||||
|
variant="subtle"
|
||||||
|
onClick={() => router.back()}
|
||||||
|
p="xs"
|
||||||
|
radius="md"
|
||||||
|
>
|
||||||
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Title order={4} ml="sm" c="dark">
|
||||||
|
Tambah Keunggulan Program
|
||||||
|
</Title>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
<Paper
|
||||||
|
w={{ base: '100%', md: '50%' }}
|
||||||
|
bg={colors['white-1']}
|
||||||
|
p="lg"
|
||||||
|
radius="md"
|
||||||
|
shadow="sm"
|
||||||
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
|
<TextInput
|
||||||
|
label="Judul"
|
||||||
|
placeholder="Masukkan judul"
|
||||||
|
value={stateCreate.create.form.judul || ''}
|
||||||
|
onChange={(e) => (stateCreate.create.form.judul = e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fw="bold" fz="sm" mb={6}>
|
||||||
|
Deskripsi
|
||||||
|
</Text>
|
||||||
|
<CreateEditor
|
||||||
|
value={stateCreate.create.form.deskripsi}
|
||||||
|
onChange={(htmlContent) => {
|
||||||
|
stateCreate.create.form.deskripsi = htmlContent;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Group justify="right" mt="md">
|
||||||
|
<Button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
style={{
|
||||||
|
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||||
|
color: '#fff',
|
||||||
|
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CreateKeunggulanProgram;
|
||||||
@@ -1,71 +1,168 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Paper, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
|
import { Box, Button, Center, Group, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title, Tooltip } from '@mantine/core';
|
||||||
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { IconEdit, IconPlus, IconSearch, IconTrashFilled } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
import HeaderSearch from '../../../_com/header';
|
import HeaderSearch from '../../../_com/header';
|
||||||
|
import beasiswaDesaState from '../../../_state/pendidikan/beasiswa-desa';
|
||||||
|
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||||
|
|
||||||
|
|
||||||
function BeasiswaDesa() {
|
function KeunggulanProgram() {
|
||||||
|
const [search, setSearch] = useState("");
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<HeaderSearch
|
<HeaderSearch
|
||||||
title='Keunggulan Program'
|
title='List Keunggulan Program'
|
||||||
placeholder='pencarian'
|
placeholder='Cari keunggulan program...'
|
||||||
searchIcon={<IconSearch size={20} />}
|
searchIcon={<IconSearch size={20} />}
|
||||||
|
value={search}
|
||||||
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
<ListBeasiswaDesa/>
|
<ListKeunggulanProgram search={search} />
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ListBeasiswaDesa() {
|
function ListKeunggulanProgram({ search }: { search: string }) {
|
||||||
const router = useRouter();
|
const stateList = useProxy(beasiswaDesaState.keunggulanProgram)
|
||||||
|
const router = useRouter()
|
||||||
|
const [modalHapus, setModalHapus] = useState(false)
|
||||||
|
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||||
|
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
page,
|
||||||
|
totalPages,
|
||||||
|
loading,
|
||||||
|
load,
|
||||||
|
} = stateList.findMany
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
load(page, 10, search)
|
||||||
|
}, [page, search])
|
||||||
|
|
||||||
|
const handleHapus = () => {
|
||||||
|
if (selectedId) {
|
||||||
|
stateList.delete.delete(selectedId)
|
||||||
|
setModalHapus(false)
|
||||||
|
setSelectedId(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const filteredData = data || []
|
||||||
|
|
||||||
|
if (loading || !data) {
|
||||||
|
return (
|
||||||
|
<Stack py={10}>
|
||||||
|
<Skeleton h={500} />
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py={10}>
|
||||||
<Paper bg={colors['white-1']} p="md">
|
<Paper bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
||||||
<Stack>
|
<Group justify="space-between" mb="md">
|
||||||
<Title order={4}>List Beasiswa Desa</Title>
|
<Title order={4}>Daftar Keunggulan Program</Title>
|
||||||
<Box style={{overflowX: 'auto'}}>
|
<Tooltip label="Tambah Keunggulan Program" withArrow>
|
||||||
<Table striped withRowBorders withTableBorder style={{minWidth: '700px'}}>
|
<Button
|
||||||
<TableThead>
|
leftSection={<IconPlus size={18} />}
|
||||||
|
color="blue"
|
||||||
|
variant="light"
|
||||||
|
onClick={() => router.push('/admin/pendidikan/beasiswa-desa/keunggulan-program/create')}
|
||||||
|
>
|
||||||
|
Tambah Baru
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
<Box style={{ overflowX: 'auto' }}>
|
||||||
|
<Table highlightOnHover>
|
||||||
|
<TableThead>
|
||||||
|
<TableTr>
|
||||||
|
<TableTh>Nama Keunggulan Program</TableTh>
|
||||||
|
<TableTh>Deskripsi</TableTh>
|
||||||
|
<TableTh>Edit</TableTh>
|
||||||
|
<TableTh>Delete</TableTh>
|
||||||
|
</TableTr>
|
||||||
|
</TableThead>
|
||||||
|
<TableTbody>
|
||||||
|
{filteredData.length > 0 ? (
|
||||||
|
filteredData.map((item) => (
|
||||||
|
<TableTr key={item.id}>
|
||||||
|
<TableTd>
|
||||||
|
<Text lineClamp={1} truncate="end" fw={500}>{item.judul}</Text>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Text lineClamp={1} truncate="end" fw={500} dangerouslySetInnerHTML={{ __html: item.deskripsi }}></Text>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Tooltip label="Edit" withArrow>
|
||||||
|
<Button
|
||||||
|
variant="light"
|
||||||
|
color="blue"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => router.push(`/admin/pendidikan/beasiswa-desa/keunggulan-program/${item.id}`)}
|
||||||
|
>
|
||||||
|
<IconEdit size={18} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Tooltip label="Hapus" withArrow>
|
||||||
|
<Button
|
||||||
|
variant="light"
|
||||||
|
color="red"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedId(item.id);
|
||||||
|
setModalHapus(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconTrashFilled size={18} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh>Nomor</TableTh>
|
<TableTd colSpan={3} align="center" py="xl">
|
||||||
<TableTh>Nama Lengkap</TableTh>
|
<Text c="dimmed">Tidak ada data pengelolaan sampah</Text>
|
||||||
<TableTh>Nomor Telepon</TableTh>
|
|
||||||
<TableTh>Email</TableTh>
|
|
||||||
<TableTh>Detail</TableTh>
|
|
||||||
</TableTr>
|
|
||||||
</TableThead>
|
|
||||||
<TableTbody>
|
|
||||||
<TableTr>
|
|
||||||
<TableTd>
|
|
||||||
<Box w={100}>
|
|
||||||
<Text truncate="end" fz={"sm"}>1</Text>
|
|
||||||
</Box>
|
|
||||||
</TableTd>
|
|
||||||
<TableTd>
|
|
||||||
<Text truncate="end" fz={"sm"}>Nama Lengkap</Text>
|
|
||||||
</TableTd>
|
|
||||||
<TableTd>
|
|
||||||
<Text truncate="end" fz={"sm"}>Nomor Telepon</Text>
|
|
||||||
</TableTd>
|
|
||||||
<TableTd>
|
|
||||||
<Text truncate="end" fz={"sm"}>Email</Text>
|
|
||||||
</TableTd>
|
|
||||||
<TableTd>
|
|
||||||
<Button onClick={() => router.push('/admin/pendidikan/beasiswa-desa/detail')}>
|
|
||||||
<IconDeviceImacCog size={25} />
|
|
||||||
</Button>
|
|
||||||
</TableTd>
|
</TableTd>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
</TableTbody>
|
)}
|
||||||
</Table>
|
</TableTbody>
|
||||||
</Box>
|
</Table>
|
||||||
</Stack>
|
</Box>
|
||||||
|
|
||||||
|
{totalPages > 1 && (
|
||||||
|
<Center mt="xl">
|
||||||
|
<Pagination
|
||||||
|
value={page}
|
||||||
|
onChange={(newPage) => load(newPage)}
|
||||||
|
total={totalPages}
|
||||||
|
siblings={1}
|
||||||
|
boundaries={1}
|
||||||
|
withEdges
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
</Paper>
|
</Paper>
|
||||||
|
{/* Modal Konfirmasi Hapus */}
|
||||||
|
<ModalKonfirmasiHapus
|
||||||
|
opened={modalHapus}
|
||||||
|
onClose={() => setModalHapus(false)}
|
||||||
|
onConfirm={handleHapus}
|
||||||
|
text='Apakah anda yakin ingin menghapus pengelolaan sampah bank sampah ini?'
|
||||||
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default BeasiswaDesa;
|
export default KeunggulanProgram;
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user