Compare commits
6 Commits
nico/28-ju
...
nico/1-aug
| Author | SHA1 | Date | |
|---|---|---|---|
| 54312e9486 | |||
| 024d5517fa | |||
| 4e61695649 | |||
| c11cc421a4 | |||
| 0109886e00 | |||
| 50e8999205 |
13
package.json
13
package.json
@@ -14,8 +14,10 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@cubejs-client/core": "^0.31.0",
|
||||
"@elysiajs/cookie": "^0.8.0",
|
||||
"@elysiajs/cors": "^1.2.0",
|
||||
"@elysiajs/eden": "^1.3.2",
|
||||
"@elysiajs/jwt": "^1.3.2",
|
||||
"@elysiajs/static": "^1.3.0",
|
||||
"@elysiajs/stream": "^1.1.0",
|
||||
"@elysiajs/swagger": "^1.2.0",
|
||||
@@ -44,8 +46,10 @@
|
||||
"@types/lodash": "^4.17.16",
|
||||
"add": "^2.0.6",
|
||||
"animate.css": "^4.1.1",
|
||||
"bcryptjs": "^3.0.2",
|
||||
"bun": "^1.2.2",
|
||||
"chart.js": "^4.4.8",
|
||||
"classnames": "^2.5.1",
|
||||
"dayjs": "^1.11.13",
|
||||
"elysia": "^1.3.5",
|
||||
"embla-carousel-autoplay": "^8.5.2",
|
||||
@@ -54,6 +58,7 @@
|
||||
"framer-motion": "^12.23.5",
|
||||
"get-port": "^7.1.0",
|
||||
"jotai": "^2.12.3",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"leaflet": "^1.9.4",
|
||||
"list": "^2.0.19",
|
||||
"lodash": "^4.17.21",
|
||||
@@ -63,12 +68,15 @@
|
||||
"next-view-transitions": "^0.3.4",
|
||||
"node-fetch": "^3.3.2",
|
||||
"p-limit": "^6.2.0",
|
||||
"primeicons": "^7.0.0",
|
||||
"primereact": "^10.9.6",
|
||||
"prisma": "^6.3.1",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-leaflet": "^5.0.0",
|
||||
"react-simple-toasts": "^6.1.0",
|
||||
"react-toastify": "^11.0.5",
|
||||
"react-transition-group": "^4.4.5",
|
||||
"readdirp": "^4.1.1",
|
||||
"recharts": "^2.15.3",
|
||||
"swr": "^2.3.2",
|
||||
@@ -78,15 +86,16 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3",
|
||||
"@types/jsonwebtoken": "^9.0.10",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "15.1.6",
|
||||
"parcel": "^2.6.2",
|
||||
"postcss": "^8.5.1",
|
||||
"postcss-preset-mantine": "^1.17.0",
|
||||
"postcss-simple-vars": "^7.0.1",
|
||||
"typescript": "^5",
|
||||
"parcel": "^2.6.2"
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
||||
|
||||
128
prisma/data/landing-page/desa-anti-korupsi/desaantiKorpusi.json
Normal file
128
prisma/data/landing-page/desa-anti-korupsi/desaantiKorpusi.json
Normal file
@@ -0,0 +1,128 @@
|
||||
[
|
||||
{
|
||||
"id": "cmds9h9ko000pvnberdjnx64b",
|
||||
"name": "1.1 ADANYA PERDES/KEPUTUSAN KEPALA DESA/SOP TENTANG PERENCANAAN, PELAKSANAAN, PENATAUSAHAAN DAN PERTANGGUNG JAWABAN APBDES BESERTA IMPLEMENTASINYA",
|
||||
"deskripsi": "<p>ADANYA PERDES/KEPUTUSAN KEPALA DESA/SOP TENTANG PERENCANAAN, PELAKSANAAN, PENATAUSAHAAN DAN PERTANGGUNG JAWABAN APBDES BESERTA IMPLEMENTASINYA</p>",
|
||||
"kategoriId": "cmds9es2o000ivnbe1o0swrvh",
|
||||
"fileId": ""
|
||||
},
|
||||
{
|
||||
"id": "cmds9sjmz000svnbesv2133of",
|
||||
"name": "1.2 ADANYA PERDES/KEPUTUSAN KEPALA DESA/SOP MENGENAI MEKANISME EVALUASI KINERJA PERANGKAT DESA",
|
||||
"deskripsi": "<p>ADANYA PERDES/KEPUTUSAN KEPALA DESA/SOP MENGENAI MEKANISME EVALUASI KINERJA PERANGKAT DESA</p>",
|
||||
"kategoriId": "cmds9es2o000ivnbe1o0swrvh",
|
||||
"fileId": ""
|
||||
},
|
||||
{
|
||||
"id": "cmds9tcpi000vvnbev3ebtlnt",
|
||||
"name": "1.3 ADANYA PERDES/KEPUTUSAN KEPALA DESA/SOP TENTANG PENGENDALIAN GRATIFIKASI, SUAP DAN KONFLIK KEPENTINGAN",
|
||||
"deskripsi": "<p>ADANYA PERDES/KEPUTUSAN KEPALA DESA/SOP TENTANG PENGENDALIAN GRATIFIKASI, SUAP DAN KONFLIK KEPENTINGAN</p>",
|
||||
"kategoriId": "cmds9es2o000ivnbe1o0swrvh",
|
||||
"fileId": ""
|
||||
},
|
||||
{
|
||||
"id": "cmds9twvj000yvnbep0pq8dzf",
|
||||
"name": "1.4 PERJANJIAN KERJA SAMA ANTARA PELAKSANA KEGIATAN ANGGARAN DENGAN PIHAK PENYEDIA, DAN TELAH MELALUI PROSES PENGADAAN BARANG/JASA DI DESA",
|
||||
"deskripsi": "<p>PERJANJIAN KERJA SAMA ANTARA PELAKSANA KEGIATAN ANGGARAN DENGAN PIHAK PENYEDIA, DAN TELAH MELALUI PROSES PENGADAAN BARANG/JASA DI DESA</p>",
|
||||
"kategoriId": "cmds9es2o000ivnbe1o0swrvh",
|
||||
"fileId": ""
|
||||
},
|
||||
{
|
||||
"id": "cmds9ugap0011vnbe118yv871",
|
||||
"name": "1.5 ADANYA PERDES/KEPUTUSAN KEPALA DESA/SOP TENTANG PAKTA INTEGRITAS DAN SEJENISNYA",
|
||||
"deskripsi": "<p>ADANYA PERDES/KEPUTUSAN KEPALA DESA/SOP TENTANG PAKTA INTEGRITAS DAN SEJENISNYA</p>",
|
||||
"kategoriId": "cmds9es2o000ivnbe1o0swrvh",
|
||||
"fileId": ""
|
||||
},
|
||||
{
|
||||
"id": "cmdsa35310014vnbe6qy6l1rz",
|
||||
"name": "2.1 ADANYA KEGIATAN PENGAWASAN DAN EVALUASI KINERJA PERANGKAT DESA",
|
||||
"deskripsi": "<p>ADANYA KEGIATAN PENGAWASAN DAN EVALUASI KINERJA PERANGKAT DESA</p>",
|
||||
"kategoriId": "cmds9f2ua000jvnbeksu1sfwm",
|
||||
"fileId": ""
|
||||
},
|
||||
{
|
||||
"id": "cmdsa46590017vnbepp3noso1",
|
||||
"name": "2.2 ADANYA TINDAK LANJUT HASIL PEMBINAAN, PETUNJUK, ARAH, PENGAWASAN, DAN PEMERIKSAAN DARI PEMERINTAH PUSAT/DAERAH",
|
||||
"deskripsi": "<p>ADANYA TINDAK LANJUT HASIL PEMBINAAN, PETUNJUK, ARAH, PENGAWASAN, DAN PEMERIKSAAN DARI PEMERINTAH PUSAT/DAERAH</p>",
|
||||
"kategoriId": "cmds9f2ua000jvnbeksu1sfwm",
|
||||
"fileId": ""
|
||||
},
|
||||
{
|
||||
"id": "cmdsa5m7z001avnbe4cvfrcz0",
|
||||
"name": "2.3 TIDAK ADANYA APARATUR DESA DALAM 3(TIGA) TAHUN TERAKHIR YANG TERJERAT TINDAKAN PIDANA KORUPSI",
|
||||
"deskripsi": "<p>TIDAK ADANYA APARATUR DESA DALAM 3(TIGA) TAHUN TERAKHIR YANG TERJERAT TINDAKAN PIDANA KORUPSI</p>",
|
||||
"kategoriId": "cmds9f2ua000jvnbeksu1sfwm",
|
||||
"fileId": ""
|
||||
},
|
||||
{
|
||||
"id": "cmdsa8q5q001dvnbemch8j24x",
|
||||
"name": "3.1 ADANYA LAYANAN PENGADUAN BAGI MASYARAKAT",
|
||||
"deskripsi": "<p>ADANYA LAYANAN PENGADUAN BAGI MASYARAKAT</p>",
|
||||
"kategoriId": "cmds9fr73000kvnbe6w281dcl",
|
||||
"fileId": ""
|
||||
},
|
||||
{
|
||||
"id": "cmdsa9lbi001gvnbequn2ba7m",
|
||||
"name": "3.2 ADANYA SURVEY KEPUASAN MASYARAKAT (SKM) TERHADAP LAYANAN PEMERINTAH DESA",
|
||||
"deskripsi": "<p>ADANYA SURVEY KEPUASAN MASYARAKAT (SKM) TERHADAP LAYANAN PEMERINTAH DESA</p>",
|
||||
"kategoriId": "cmds9fr73000kvnbe6w281dcl",
|
||||
"fileId": ""
|
||||
},
|
||||
{
|
||||
"id": "cmdsaa7aq001jvnbeizh04e67",
|
||||
"name": "3.3 ADANYA KETERBUKAAN AKSES MASYARAKAT TERHADAP INFORMASI LAYANAN PEMERINTAH DESA (KESEHATAN, PENDIDIKAN, SOSIAL, LINGKUNGAN, TRANTIBUMLINMAS, PEKERJAAN UMUM) PEMBANGUNAN, KEPENDUDUKAN, KEUANGAN, DAN PELAYANAN LAINNYA",
|
||||
"deskripsi": "<p>ADANYA KETERBUKAAN AKSES MASYARAKAT TERHADAP INFORMASI LAYANAN PEMERINTAH DESA (KESEHATAN, PENDIDIKAN, SOSIAL, LINGKUNGAN, TRANTIBUMLINMAS, PEKERJAAN UMUM) PEMBANGUNAN, KEPENDUDUKAN, KEUANGAN, DAN PELAYANAN LAINNYA</p>",
|
||||
"kategoriId": "cmds9fr73000kvnbe6w281dcl",
|
||||
"fileId": ""
|
||||
},
|
||||
{
|
||||
"id": "cmdsaaw8d001mvnbek3tfefrk",
|
||||
"name": "3.4 ADANYA MEDIA INFORMASI TENTANG APBDES DI BALAI DESA DAN/ATAU TEMPAT LAIN YANG MUDAH DIAKSES OLEH MASYARAKAT",
|
||||
"deskripsi": "<p>ADANYA MEDIA INFORMASI TENTANG APBDES DI BALAI DESA DAN/ATAU TEMPAT LAIN YANG MUDAH DIAKSES OLEH MASYARAKAT</p>",
|
||||
"kategoriId": "cmds9fr73000kvnbe6w281dcl",
|
||||
"fileId": ""
|
||||
},
|
||||
{
|
||||
"id": "cmdsabhif001pvnbepm06hry6",
|
||||
"name": "3.5 ADANYA MAKLUMAT PELAYANAN",
|
||||
"deskripsi": "<p>ADANYA MAKLUMAT PELAYANAN</p>",
|
||||
"kategoriId": "cmds9fr73000kvnbe6w281dcl",
|
||||
"fileId": ""
|
||||
},
|
||||
{
|
||||
"id": "cmdsag40b001svnbe7krq9khc",
|
||||
"name": "4.1 ADANYA PARTISIPASI DAN KETERLIBATAN MASYARAKAT DALAM PENYUSUNAN RKP DESA",
|
||||
"deskripsi": "<p>ADANYA PARTISIPASI DAN KETERLIBATAN MASYARAKAT DALAM PENYUSUNAN RKP DESA</p>",
|
||||
"kategoriId": "cmds9g5ow000lvnbel3rkkwrv",
|
||||
"fileId": ""
|
||||
},
|
||||
{
|
||||
"id": "cmdsagkaf001vvnbejo26w8sa",
|
||||
"name": "4.2 ADANYA KESADARAN MASYARAKAT DALAM MENCEGAH TERJADINYA PRAKTIK GRATIFIKASI, SUAP DAN KONFLIK KEPENTINGAN",
|
||||
"deskripsi": "<p>ADANYA KESADARAN MASYARAKAT DALAM MENCEGAH TERJADINYA PRAKTIK GRATIFIKASI, SUAP DAN KONFLIK KEPENTINGAN</p>",
|
||||
"kategoriId": "cmds9g5ow000lvnbel3rkkwrv",
|
||||
"fileId": ""
|
||||
},
|
||||
{
|
||||
"id": "cmdsah4qe001yvnbeiy3mwrvb",
|
||||
"name": "4.3 ADANYA KETERLIBATAN LEMBAGA KEMASYARAKATAN DALAM PELAKSANAAN PEMBANGUNAN DESA",
|
||||
"deskripsi": "<p>ADANYA KETERLIBATAN LEMBAGA KEMASYARAKATAN DALAM PELAKSANAAN PEMBANGUNAN DESA</p>",
|
||||
"kategoriId": "cmds9g5ow000lvnbel3rkkwrv",
|
||||
"fileId": ""
|
||||
},
|
||||
{
|
||||
"id": "cmdsak5vn0021vnbemg86aab4",
|
||||
"name": "5.1 ADANYA BUDAYA LOKAL/HUKUM ADAT YANG MENDORONG UPAYA PENCEGAHAN TINDAK PIDANA KORUPSI",
|
||||
"deskripsi": "<p>ADANYA BUDAYA LOKAL/HUKUM ADAT YANG MENDORONG UPAYA PENCEGAHAN TINDAK PIDANA KORUPSI</p>",
|
||||
"kategoriId": "cmds9govb000mvnbesq8b4y99",
|
||||
"fileId": ""
|
||||
},
|
||||
{
|
||||
"id": "cmdsalc800024vnbezgulhgrb",
|
||||
"name": "5.2 ADANYA TOKOH MASYARAKAT, TOKOH AGAMA, TOKOH ADAT, TOKOH PEMUDA, DAN KAUM PEREMPUAN YANG MENDORONG UPAYA PENCEGAHAN TINDAK PIDANA KORUPSI",
|
||||
"deskripsi": "<p>ADANYA TOKOH MASYARAKAT, TOKOH AGAMA, TOKOH ADAT, TOKOH PEMUDA, DAN KAUM PEREMPUAN YANG MENDORONG UPAYA PENCEGAHAN TINDAK PIDANA KORUPSI</p>",
|
||||
"kategoriId": "cmds9govb000mvnbesq8b4y99",
|
||||
"fileId": ""
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,22 @@
|
||||
[
|
||||
{
|
||||
"id": "cmds9es2o000ivnbe1o0swrvh",
|
||||
"name": "PENGUATAN TATA LAKSANA"
|
||||
},
|
||||
{
|
||||
"id": "cmds9f2ua000jvnbeksu1sfwm",
|
||||
"name": "PENGUATAN PENGAWASAN"
|
||||
},
|
||||
{
|
||||
"id": "cmds9fr73000kvnbe6w281dcl",
|
||||
"name": "PENGUATAN KUALITAS PELAYANAN PUBLIK"
|
||||
},
|
||||
{
|
||||
"id": "cmds9g5ow000lvnbel3rkkwrv",
|
||||
"name": "PENGUATAN PARTISIPASI MASYARAKAT"
|
||||
},
|
||||
{
|
||||
"id": "cmds9govb000mvnbesq8b4y99",
|
||||
"name": "KEARIFAN LOKAL"
|
||||
}
|
||||
]
|
||||
@@ -1,8 +0,0 @@
|
||||
[
|
||||
{
|
||||
"id": "edit",
|
||||
"name": "I.B Surya Prabhawa Manuaba, S.H., M.H.",
|
||||
"position": "Perbekel Darmasaba periode 2021-2027"
|
||||
}
|
||||
]
|
||||
|
||||
32
prisma/data/landing-page/profile/mediaSosial.json
Normal file
32
prisma/data/landing-page/profile/mediaSosial.json
Normal file
@@ -0,0 +1,32 @@
|
||||
[
|
||||
{
|
||||
"id": "cmds8w2q60002vnbe6i8qhkuo",
|
||||
"name": "Telephone Desa Darmasaba",
|
||||
"iconUrl": "081239580000"
|
||||
},
|
||||
{
|
||||
"id": "cmds8z7u20005vnbegyyvnbk0",
|
||||
"name": "Email Desa Darmasaba",
|
||||
"iconUrl": "desadarmasaba@badungkab.go.id"
|
||||
},
|
||||
{
|
||||
"id": "cmds9023u0008vnbe3oxmhwyf",
|
||||
"name": "Desa Darmasaba",
|
||||
"iconUrl": "https://www.youtube.com/channel/UCtPw9WOQO7d2HIKzKgel4Xg"
|
||||
},
|
||||
{
|
||||
"id": "cmds90oul000bvnbe2bqkptoi",
|
||||
"name": "Pemerintah Desa Darmasaba",
|
||||
"iconUrl": "https://www.facebook.com/DarmasabaDesaku"
|
||||
},
|
||||
{
|
||||
"id": "cmds91i4e000evnbe8gtf1gub",
|
||||
"name": "ddarmasaba",
|
||||
"iconUrl": "https://www.instagram.com/ddarmasaba/"
|
||||
},
|
||||
{
|
||||
"id": "cmds92de5000hvnbemlu6sq5x",
|
||||
"name": "desa.darmasaba",
|
||||
"iconUrl": "https://www.tiktok.com/@desa.darmasaba?is_from_webapp=1&sender_device=pc"
|
||||
}
|
||||
]
|
||||
7
prisma/data/landing-page/profile/profile.json
Normal file
7
prisma/data/landing-page/profile/profile.json
Normal file
@@ -0,0 +1,7 @@
|
||||
[
|
||||
{
|
||||
"id": "edit",
|
||||
"name": "I.B Surya Prabhawa Manuaba, S.H., M.H.",
|
||||
"position": "Perbekel Darmasaba periode 2021-2027"
|
||||
}
|
||||
]
|
||||
50
prisma/data/landing-page/profile/programInovasi.json
Normal file
50
prisma/data/landing-page/profile/programInovasi.json
Normal file
@@ -0,0 +1,50 @@
|
||||
[
|
||||
{
|
||||
"id": "cmdr7039z0002vn5rttctt9hn",
|
||||
"name": "Davest",
|
||||
"description": "Darmasaba Village Festval",
|
||||
"link": "https://darmasaba.desa.id/berita/55862-rakor-davest-2024"
|
||||
},
|
||||
{
|
||||
"id": "cmdr755pf0005vn5rp8tyuubw",
|
||||
"name": "Dmangan",
|
||||
"description": "Darmasaba Aman Pangan",
|
||||
"link": "https://darmasaba.desa.id/berita/61452-kader-d-mangan-berhasil-meraih-prestasi-dalam-ajang-lomba-banjar-bali-quis-bbq-tahun-2024"
|
||||
},
|
||||
{
|
||||
"id": "cmdr76nqk0008vn5rdddvcxnr",
|
||||
"name": "Bicara Darmasaba",
|
||||
"description": "Bicara Darmasaba",
|
||||
"link": "https://darmasaba.desa.id/berita/42506-bicara-darmasaba"
|
||||
},
|
||||
{
|
||||
"id": "cmdr77vbw000bvn5rvpmoq31s",
|
||||
"name": "Bares",
|
||||
"description": "Darmasaba Recycling Stock/Exchange",
|
||||
"link": "http://darmasaba.desa.id/berita/56722-bares"
|
||||
},
|
||||
{
|
||||
"id": "cmdr7bxtp000evn5rmy85wihx",
|
||||
"name": "Sajjana Dharma Raksaka",
|
||||
"description": "Sajjana Dharma Raksaka",
|
||||
"link": "https://ppid.badungkab.go.id/storage/dokumen/5RS9dldGkrgzMQq6bKdZsqsVRHI8gffWv4PGfb3r.pdf"
|
||||
},
|
||||
{
|
||||
"id": "cmdr7dlnk000hvn5r9lur3z35",
|
||||
"name": "PDKT",
|
||||
"description": "Perangkat Desa Kuat Teknologi",
|
||||
"link": "https://darmasaba.desa.id/berita/53752-p-d-k-t"
|
||||
},
|
||||
{
|
||||
"id": "cmdr7ftob000mvn5rfhgdtg8v",
|
||||
"name": "GM",
|
||||
"description": "Galah Melah",
|
||||
"link": "https://darmasaba.desa.id/berita/52880-galah-melah"
|
||||
},
|
||||
{
|
||||
"id": "cmdr7glue000pvn5r6onzslju",
|
||||
"name": "Inovasi Desa Darmasaba",
|
||||
"description": "Inovasi Desa Darmasaba",
|
||||
"link": "https://darmasaba.desa.id/produk-lokal-desa"
|
||||
}
|
||||
]
|
||||
91
prisma/data/ppid/struktur-ppid/pegawai-PPID.json
Normal file
91
prisma/data/ppid/struktur-ppid/pegawai-PPID.json
Normal file
@@ -0,0 +1,91 @@
|
||||
[
|
||||
{
|
||||
"id": "550e8400-e29b-41d4-a716-446655440001",
|
||||
"namaLengkap": "Ida Bagus Surya Prabhawa Manuaba, S.H.,M.H., NL.P.",
|
||||
"gelarAkademik": "S.H.,M.H.,NL.P.",
|
||||
"tanggalMasuk": "2020-01-01T00:00:00.000Z",
|
||||
"email": "bagus@desa.id",
|
||||
"telepon": "081234567891",
|
||||
"alamat": "Jl. Raya Desa No. 1",
|
||||
"posisiId": "kepala_desa",
|
||||
"isActive": true
|
||||
},
|
||||
{
|
||||
"id": "550e8400-e29b-41d4-a716-446655440002",
|
||||
"namaLengkap": "I Ketut Suwanta",
|
||||
"gelarAkademik": "S.Pt",
|
||||
"tanggalMasuk": "2020-02-01T00:00:00.000Z",
|
||||
"email": "suwanta@desa.id",
|
||||
"telepon": "081234567892",
|
||||
"alamat": "Jl. Raya Desa No. 2",
|
||||
"posisiId": "sekretaris_desa",
|
||||
"isActive": true
|
||||
},
|
||||
{
|
||||
"id": "550e8400-e29b-41d4-a716-446655440006",
|
||||
"namaLengkap": "Ni Wayan Supardiati",
|
||||
"gelarAkademik": "S.Pd",
|
||||
"tanggalMasuk": "2020-02-01T00:00:00.000Z",
|
||||
"email": "supardiati@desa.id",
|
||||
"telepon": "081234567892",
|
||||
"alamat": "Jl. Raya Desa No. 2",
|
||||
"posisiId": "kaur_keuangan",
|
||||
"isActive": true
|
||||
},
|
||||
{
|
||||
"id": "550e8400-e29b-41d4-a716-446655440011",
|
||||
"namaLengkap": "I Wayan Agus Juni Artha Saputra",
|
||||
"gelarAkademik": "S.T.",
|
||||
"tanggalMasuk": "2020-02-01T00:00:00.000Z",
|
||||
"email": "agus@desa.id",
|
||||
"telepon": "081234567892",
|
||||
"alamat": "Jl. Raya Desa No. 2",
|
||||
"posisiId": "kadus_banjar_dinas_menesa",
|
||||
"isActive": true
|
||||
},
|
||||
{
|
||||
"id": "550e8400-e29b-41d4-a716-446655440012",
|
||||
"namaLengkap": "I Wayan Sueca",
|
||||
"gelarAkademik": "S.H.",
|
||||
"tanggalMasuk": "2020-02-01T00:00:00.000Z",
|
||||
"email": "sueca@desa.id",
|
||||
"telepon": "081234567893",
|
||||
"alamat": "Jl. Raya Desa No. 2",
|
||||
"posisiId": "kadus_banjar_dinas_darmasaba",
|
||||
"isActive": true
|
||||
},
|
||||
{
|
||||
"id": "550e8400-e29b-41d4-a716-446655440017",
|
||||
"namaLengkap": "Si Gede Ketut Astawa",
|
||||
"gelarAkademik": "S.T.",
|
||||
"tanggalMasuk": "2020-02-01T00:00:00.000Z",
|
||||
"email": "astawa@desa.id",
|
||||
"telepon": "081234567893",
|
||||
"alamat": "Jl. Raya Desa No. 2",
|
||||
"posisiId": "kadus_banjar_dinas_bucu",
|
||||
"isActive": true
|
||||
},
|
||||
{
|
||||
"id": "550e8400-e29b-41d4-a716-446655440018",
|
||||
"namaLengkap": "I Kadek Arya Minarta",
|
||||
"gelarAkademik": "S.T.",
|
||||
"tanggalMasuk": "2020-02-01T00:00:00.000Z",
|
||||
"email": "minarta@desa.id",
|
||||
"telepon": "081234567893",
|
||||
"alamat": "Jl. Raya Desa No. 2",
|
||||
"posisiId": "kadus_banjar_dinas_gulingan",
|
||||
"isActive": true
|
||||
},
|
||||
{
|
||||
"id": "550e8400-e29b-41d4-a716-446655440021",
|
||||
"namaLengkap": "I Gede Andika Pradnya Diputra",
|
||||
"gelarAkademik": "S.E.",
|
||||
"tanggalMasuk": "2020-02-01T00:00:00.000Z",
|
||||
"email": "diputra@desa.id",
|
||||
"telepon": "081234567893",
|
||||
"alamat": "Jl. Raya Desa No. 2",
|
||||
"posisiId": "kadus_banjar_dinas_taman",
|
||||
"isActive": true
|
||||
}
|
||||
|
||||
]
|
||||
158
prisma/data/ppid/struktur-ppid/posisi-organisasi-PPID.json
Normal file
158
prisma/data/ppid/struktur-ppid/posisi-organisasi-PPID.json
Normal file
@@ -0,0 +1,158 @@
|
||||
[
|
||||
[
|
||||
{
|
||||
"id": "kepala_desa",
|
||||
"nama": "Kepala Desa",
|
||||
"deskripsi": "Pemimpin desa Darmasaba",
|
||||
"hierarki": 1,
|
||||
"parentId": null
|
||||
},
|
||||
{
|
||||
"id": "kepala_urusan",
|
||||
"nama": "Kepala Urusan",
|
||||
"deskripsi": "Pemimpin urusan desa Darmasaba",
|
||||
"hierarki": 2,
|
||||
"parentId": "kepala_desa"
|
||||
},
|
||||
{
|
||||
"id": "sekretaris_desa",
|
||||
"nama": "Sekretaris Desa",
|
||||
"deskripsi": "Pengelola administrasi desa",
|
||||
"hierarki": 2,
|
||||
"parentId": "kepala_desa"
|
||||
},
|
||||
{
|
||||
"id": "kaur_keuangan",
|
||||
"nama": "Kaur Keuangan",
|
||||
"deskripsi": "Pengelola keuangan desa",
|
||||
"hierarki": 3,
|
||||
"parentId": "kaur_umum"
|
||||
},
|
||||
{
|
||||
"id": "kaur_perencanaan",
|
||||
"nama": "Kaur Perencanaan",
|
||||
"deskripsi": "Penyusun program kerja desa",
|
||||
"hierarki": 3,
|
||||
"parentId": "kaur_umum"
|
||||
},
|
||||
{
|
||||
"id": "kaur_umum",
|
||||
"nama": "Kaur Umum & TU",
|
||||
"deskripsi": "Pelayanan umum dan administrasi",
|
||||
"hierarki": 2,
|
||||
"parentId": "kepala_desa"
|
||||
},
|
||||
{
|
||||
"id": "kasi_pemerintahan",
|
||||
"nama": "Kasi Pemerintahan",
|
||||
"deskripsi": "Urusan pemerintahan dan keamanan",
|
||||
"hierarki": 2,
|
||||
"parentId": "kepala_desa"
|
||||
},
|
||||
{
|
||||
"id": "kasi_pelayanan",
|
||||
"nama": "Kasi Pelayanan",
|
||||
"deskripsi": "Urusan pelayanan masyarakat",
|
||||
"hierarki": 2,
|
||||
"parentId": "kepala_desa"
|
||||
},
|
||||
{
|
||||
"id": "kasi_kesejahteraan",
|
||||
"nama": "Kasi Kesejahteraan",
|
||||
"deskripsi": "Urusan sosial dan kesejahteraan",
|
||||
"hierarki": 2,
|
||||
"parentId": "kepala_desa"
|
||||
},
|
||||
{
|
||||
"id": "kadus_banjar_dinas_cabe",
|
||||
"nama": "Kepala Dusun Banjar Dinas Cabe",
|
||||
"deskripsi": "Pimpinan wilayah Banjar Dinas Cabe",
|
||||
"hierarki": 3,
|
||||
"parentId": "sekretaris_desa"
|
||||
},
|
||||
{
|
||||
"id": "kadus_banjar_dinas_menesa",
|
||||
"nama": "Kepala Dusun Banjar Dinas Menesa",
|
||||
"deskripsi": "Pimpinan wilayah Banjar Menesa",
|
||||
"hierarki": 3,
|
||||
"parentId": "sekretaris_desa"
|
||||
},
|
||||
{
|
||||
"id": "kadus_banjar_dinas_penenjoan",
|
||||
"nama": "Kepala Dusun Banjar Dinas Penenjoan",
|
||||
"deskripsi": "Pimpinan wilayah Banjar Dinas Penenjoan",
|
||||
"hierarki": 3,
|
||||
"parentId": "sekretaris_desa"
|
||||
},
|
||||
{
|
||||
"id": "kadus_banjar_dinas_telanga",
|
||||
"nama": "Kepala Dusun Banjar Dinas Telanga",
|
||||
"deskripsi": "Pimpinan wilayah Banjar Dinas Telanga",
|
||||
"hierarki": 3,
|
||||
"parentId": "sekretaris_desa"
|
||||
},
|
||||
{
|
||||
"id": "kadus_banjar_dinas_tengah",
|
||||
"nama": "Kepala Dusun Banjar Dinas Tengah",
|
||||
"deskripsi": "Pimpinan wilayah Banjar Dinas Tengah",
|
||||
"hierarki": 3,
|
||||
"parentId": "sekretaris_desa"
|
||||
},
|
||||
{
|
||||
"id": "kadus_banjar_dinas_baler_pasar",
|
||||
"nama": "Kepala Dusun Banjar Dinas Baler Pasar",
|
||||
"deskripsi": "Pimpinan wilayah Banjar Dinas Baler Pasar",
|
||||
"hierarki": 3,
|
||||
"parentId": "sekretaris_desa"
|
||||
},
|
||||
{
|
||||
"id": "kadus_banjar_dinas_bucu",
|
||||
"nama": "Kepala Dusun Banjar Dinas Bucu",
|
||||
"deskripsi": "Pimpinan wilayah Banjar Dinas Bucu",
|
||||
"hierarki": 3,
|
||||
"parentId": "sekretaris_desa"
|
||||
},
|
||||
{
|
||||
"id": "kadus_banjar_dinas_gulingan",
|
||||
"nama": "Kepala Dusun Banjar Dinas Gulingan",
|
||||
"deskripsi": "Pimpinan wilayah Banjar Dinas Gulingan",
|
||||
"hierarki": 3,
|
||||
"parentId": "sekretaris_desa"
|
||||
},
|
||||
{
|
||||
"id": "kadus_banjar_dinas_bersih",
|
||||
"nama": "Kepala Dusun Banjar Dinas Bersih",
|
||||
"deskripsi": "Pimpinan wilayah Banjar Dinas Bersih",
|
||||
"hierarki": 3,
|
||||
"parentId": "sekretaris_desa"
|
||||
},
|
||||
{
|
||||
"id": "kadus_banjar_dinas_umahanyar",
|
||||
"nama": "Kepala Dusun Banjar Dinas Umahanyar",
|
||||
"deskripsi": "Pimpinan wilayah Banjar Dinas Umahanyar",
|
||||
"hierarki": 3,
|
||||
"parentId": "sekretaris_desa"
|
||||
},
|
||||
{
|
||||
"id": "kadus_banjar_dinas_taman",
|
||||
"nama": "Kepala Dusun Banjar Dinas Taman",
|
||||
"deskripsi": "Pimpinan wilayah Banjar Dinas Taman",
|
||||
"hierarki": 3,
|
||||
"parentId": "sekretaris_desa"
|
||||
},
|
||||
{
|
||||
"id": "kadus_banjar_dinas_darmasaba",
|
||||
"nama": "Kepala Dusun Banjar Dinas Darmasaba",
|
||||
"deskripsi": "Pimpinan wilayah Banjar Dinas Darmasaba",
|
||||
"hierarki": 3,
|
||||
"parentId": "sekretaris_desa"
|
||||
},
|
||||
{
|
||||
"id": "staf_desa",
|
||||
"nama": "Staf Desa",
|
||||
"deskripsi": "Staf Desa",
|
||||
"hierarki": 3,
|
||||
"parentId": "sekretaris_desa"
|
||||
}
|
||||
]
|
||||
]
|
||||
@@ -1,6 +0,0 @@
|
||||
[
|
||||
{
|
||||
"id" : "1",
|
||||
"name" : "Struktur PPID"
|
||||
}
|
||||
]
|
||||
6
prisma/data/user/role.json
Normal file
6
prisma/data/user/role.json
Normal file
@@ -0,0 +1,6 @@
|
||||
[
|
||||
{
|
||||
"id": "cmdpm429r0000vnndkcwslt0h",
|
||||
"name": "warga"
|
||||
}
|
||||
]
|
||||
@@ -96,6 +96,10 @@ model FileStorage {
|
||||
APBDesImage APBDes[] @relation("APBDesImage")
|
||||
APBDesFile APBDes[] @relation("APBDesFile")
|
||||
PrestasiDesa PrestasiDesa[]
|
||||
|
||||
DataPerpustakaan DataPerpustakaan[]
|
||||
|
||||
PegawaiPPID PegawaiPPID[]
|
||||
}
|
||||
|
||||
//========================================= MENU LANDING PAGE ========================================= //
|
||||
@@ -128,8 +132,8 @@ model ProgramInovasi {
|
||||
model MediaSosial {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
image FileStorage @relation(fields: [imageId], references: [id])
|
||||
imageId String
|
||||
image FileStorage? @relation(fields: [imageId], references: [id])
|
||||
imageId String?
|
||||
iconUrl String? @db.VarChar(255)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
@@ -144,8 +148,8 @@ model DesaAntiKorupsi {
|
||||
deskripsi String @db.Text
|
||||
kategori KategoriDesaAntiKorupsi @relation(fields: [kategoriId], references: [id])
|
||||
kategoriId String
|
||||
file FileStorage @relation(fields: [fileId], references: [id])
|
||||
fileId String
|
||||
file FileStorage? @relation(fields: [fileId], references: [id])
|
||||
fileId String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime @default(now())
|
||||
@@ -294,14 +298,61 @@ model ResponseStat {
|
||||
|
||||
//========================================= STRUKTUR PPID ========================================= //
|
||||
model StrukturPPID {
|
||||
id String @id @default(cuid())
|
||||
name String @db.Text
|
||||
image FileStorage? @relation(fields: [imageId], references: [id])
|
||||
imageId String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime @default(now())
|
||||
isActive Boolean @default(true)
|
||||
id String @id @default(cuid())
|
||||
name String @db.Text
|
||||
image FileStorage? @relation(fields: [imageId], references: [id])
|
||||
imageId String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime @default(now())
|
||||
isActive Boolean @default(true)
|
||||
PosisiOrganisasiPPID PosisiOrganisasiPPID? @relation(fields: [posisiOrganisasiPPIDId], references: [id])
|
||||
posisiOrganisasiPPIDId String?
|
||||
PegawaiPPID PegawaiPPID? @relation(fields: [pegawaiPPIDId], references: [id])
|
||||
pegawaiPPIDId String?
|
||||
}
|
||||
|
||||
model PosisiOrganisasiPPID {
|
||||
id String @id @default(cuid())
|
||||
nama String @db.VarChar(100)
|
||||
deskripsi String? @db.Text
|
||||
hierarki Int
|
||||
pegawai PegawaiPPID[]
|
||||
strukturOrganisasi StrukturPPID[] // Relasi balik
|
||||
parentId String?
|
||||
parent PosisiOrganisasiPPID? @relation("Parent", fields: [parentId], references: [id])
|
||||
children PosisiOrganisasiPPID[] @relation("Parent")
|
||||
}
|
||||
|
||||
model PegawaiPPID {
|
||||
id String @id @default(cuid())
|
||||
namaLengkap String @db.VarChar(255)
|
||||
gelarAkademik String? @db.VarChar(100)
|
||||
image FileStorage? @relation(fields: [imageId], references: [id])
|
||||
imageId String?
|
||||
tanggalMasuk DateTime? @db.Date
|
||||
email String? @unique @db.VarChar(255)
|
||||
telepon String? @db.VarChar(20)
|
||||
alamat String? @db.Text
|
||||
posisiId String @db.VarChar(50)
|
||||
isActive Boolean @default(true)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
posisi PosisiOrganisasiPPID @relation(fields: [posisiId], references: [id])
|
||||
strukturOrganisasi StrukturPPID[] // Relasi balik
|
||||
}
|
||||
|
||||
model StrukturOrganisasiPPID {
|
||||
id String @id @default(uuid())
|
||||
posisiOrganisasiId String @db.VarChar(50)
|
||||
pegawaiId String @db.Uuid
|
||||
hubunganOrganisasiId String @db.Uuid
|
||||
posisiOrganisasi PosisiOrganisasi @relation(fields: [posisiOrganisasiId], references: [id])
|
||||
pegawai Pegawai @relation(fields: [pegawaiId], references: [id])
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime?
|
||||
isActive Boolean @default(true)
|
||||
}
|
||||
|
||||
// ========================================= VISI MISI PPID ========================================= //
|
||||
@@ -1314,8 +1365,9 @@ model PosisiOrganisasi {
|
||||
deskripsi String? @db.Text
|
||||
hierarki Int
|
||||
|
||||
pegawai Pegawai[]
|
||||
strukturOrganisasi StrukturOrganisasi[] // Relasi balik
|
||||
pegawai Pegawai[]
|
||||
strukturOrganisasi StrukturOrganisasi[] // Relasi balik
|
||||
StrukturOrganisasiPPID StrukturOrganisasiPPID[]
|
||||
|
||||
@@map("posisi_organisasi")
|
||||
}
|
||||
@@ -1340,7 +1392,8 @@ model Pegawai {
|
||||
sebagaiAtasan HubunganOrganisasi[] @relation("AtasanToBawahan")
|
||||
sebagaiBawahan HubunganOrganisasi[] @relation("BawahanToAtasan")
|
||||
|
||||
strukturOrganisasi StrukturOrganisasi[] // Relasi balik
|
||||
strukturOrganisasi StrukturOrganisasi[] // Relasi balik
|
||||
StrukturOrganisasiPPID StrukturOrganisasiPPID[]
|
||||
|
||||
@@map("pegawai")
|
||||
}
|
||||
@@ -1839,10 +1892,71 @@ model Pengajar {
|
||||
isActive Boolean @default(true)
|
||||
}
|
||||
|
||||
// ========================================= BEASISWA DESA ========================================= //
|
||||
model KeunggulanProgram {
|
||||
id String @id @default(cuid())
|
||||
judul String
|
||||
deskripsi String @db.Text
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime @default(now())
|
||||
isActive Boolean @default(true)
|
||||
}
|
||||
|
||||
model BeasiswaPendaftar {
|
||||
id String @id @default(cuid())
|
||||
namaLengkap String
|
||||
nik String @unique
|
||||
tempatLahir String
|
||||
tanggalLahir DateTime
|
||||
jenisKelamin JenisKelamin
|
||||
kewarganegaraan String
|
||||
agama Agama
|
||||
alamatKTP String
|
||||
alamatDomisili String?
|
||||
noHp String
|
||||
email String @unique
|
||||
statusPernikahan StatusPernikahan
|
||||
ukuranBaju UkuranBaju?
|
||||
isActive Boolean @default(true)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
enum JenisKelamin {
|
||||
LAKI_LAKI
|
||||
PEREMPUAN
|
||||
}
|
||||
|
||||
enum Agama {
|
||||
ISLAM
|
||||
KRISTEN_PROTESTAN
|
||||
KRISTEN_KATOLIK
|
||||
HINDU
|
||||
BUDDHA
|
||||
KONGHUCU
|
||||
LAINNYA
|
||||
}
|
||||
|
||||
enum StatusPernikahan {
|
||||
BELUM_MENIKAH
|
||||
MENIKAH
|
||||
JANDA_DUDA
|
||||
}
|
||||
|
||||
enum UkuranBaju {
|
||||
S
|
||||
M
|
||||
L
|
||||
XL
|
||||
XXL
|
||||
LAINNYA
|
||||
}
|
||||
|
||||
// ========================================= PROGRAM PENDIDIKAN ANAK ========================================= //
|
||||
model TujuanProgram {
|
||||
id String @id @default(cuid())
|
||||
judul String
|
||||
judul String
|
||||
deskripsi String @db.Text
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
@@ -1852,7 +1966,7 @@ model TujuanProgram {
|
||||
|
||||
model ProgramUnggulan {
|
||||
id String @id @default(cuid())
|
||||
judul String
|
||||
judul String
|
||||
deskripsi String @db.Text
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
@@ -1863,7 +1977,7 @@ model ProgramUnggulan {
|
||||
// ========================================= BIMBINGAN BELAJAR DESA ========================================= //
|
||||
model TujuanBimbinganBelajarDesa {
|
||||
id String @id @default(cuid())
|
||||
judul String
|
||||
judul String
|
||||
deskripsi String @db.Text
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
@@ -1873,7 +1987,7 @@ model TujuanBimbinganBelajarDesa {
|
||||
|
||||
model LokasiJadwalBimbinganBelajarDesa {
|
||||
id String @id @default(cuid())
|
||||
judul String
|
||||
judul String
|
||||
deskripsi String @db.Text
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
@@ -1883,7 +1997,7 @@ model LokasiJadwalBimbinganBelajarDesa {
|
||||
|
||||
model FasilitasBimbinganBelajarDesa {
|
||||
id String @id @default(cuid())
|
||||
judul String
|
||||
judul String
|
||||
deskripsi String @db.Text
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
@@ -1894,7 +2008,7 @@ model FasilitasBimbinganBelajarDesa {
|
||||
// ========================================= PENDIDIKAN NON FORMAL ========================================= //
|
||||
model TujuanPendidikanNonFormal {
|
||||
id String @id @default(cuid())
|
||||
judul String
|
||||
judul String
|
||||
deskripsi String @db.Text
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
@@ -1904,7 +2018,7 @@ model TujuanPendidikanNonFormal {
|
||||
|
||||
model TempatKegiatan {
|
||||
id String @id @default(cuid())
|
||||
judul String
|
||||
judul String
|
||||
deskripsi String @db.Text
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
@@ -1914,10 +2028,68 @@ model TempatKegiatan {
|
||||
|
||||
model JenisProgramYangDiselenggarakan {
|
||||
id String @id @default(cuid())
|
||||
judul String
|
||||
judul String
|
||||
deskripsi String @db.Text
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime @default(now())
|
||||
isActive Boolean @default(true)
|
||||
}
|
||||
|
||||
// ========================================= PERPUSTAKAAN ========================================= //
|
||||
model DataPerpustakaan {
|
||||
id String @id @default(cuid())
|
||||
judul String
|
||||
deskripsi String @db.Text
|
||||
kategori KategoriBuku @relation(fields: [kategoriId], references: [id])
|
||||
kategoriId 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)
|
||||
}
|
||||
|
||||
model KategoriBuku {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime @default(now())
|
||||
isActive Boolean @default(true)
|
||||
DataPerpustakaan DataPerpustakaan[]
|
||||
}
|
||||
|
||||
model User {
|
||||
id String @id @default(cuid())
|
||||
nama String
|
||||
email String @unique
|
||||
password String
|
||||
role Role @relation(fields: [roleId], references: [id])
|
||||
roleId String
|
||||
isActive Boolean @default(true)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
model Role {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime @default(now())
|
||||
isActive Boolean @default(true)
|
||||
User User[]
|
||||
}
|
||||
|
||||
// ========================================= DATA PENDIDIKAN ========================================= //
|
||||
model DataPendidikan {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
jumlah String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime @default(now())
|
||||
isActive Boolean @default(true)
|
||||
}
|
||||
|
||||
216
prisma/seed.ts
216
prisma/seed.ts
@@ -1,4 +1,7 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import profilePejabatDesa from "./data/landing-page/profile/profile.json";
|
||||
import programInovasi from "./data/landing-page/profile/programInovasi.json";
|
||||
import mediaSosial from "./data/landing-page/profile/mediaSosial.json";
|
||||
import categoryPengumuman from "./data/category-pengumuman.json";
|
||||
import kategoriBerita from "./data/kategori-berita.json";
|
||||
import caraMemperolehInformasi from "./data/list-caraMemperolehInformasi.json";
|
||||
@@ -9,7 +12,6 @@ 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 visiMisiPPID from "./data/ppid/visi-misi-ppid/visimisiPPID.json";
|
||||
import strukturPPID from "./data/ppid/struktur-ppid/strukturPPID.json";
|
||||
import pelayananPerizinanBerusaha from "./data/desa/layanan/pelayananPerizinanBerusaha.json";
|
||||
import pelayananPendudukNonPermanen from "./data/desa/layanan/pelayanaPendudukNonPermanen.json";
|
||||
import sejarahDesa from "./data/desa/profile/sejarah_desa.json";
|
||||
@@ -21,25 +23,82 @@ import kategoriProduk from "./data/ekonomi/pasar-desa/kategori-produk.json";
|
||||
import hubunganOrganisasi from "./data/ekonomi/struktur-organisasi/hubungan-organisasi.json";
|
||||
import posisiOrganisasi from "./data/ekonomi/struktur-organisasi/posisi-organisasi.json";
|
||||
import pegawai from "./data/ekonomi/struktur-organisasi/pegawai.json";
|
||||
import detailDataPengangguran from './data/ekonomi/jumlah-pengangguran/detail-data-pengangguran.json';
|
||||
import tujuanEdukasiLingkungan from './data/lingkungan/edukasi-lingkungan/tujuan-edukasi-lingkungan.json';
|
||||
import materiEdukasiLingkungan from './data/lingkungan/edukasi-lingkungan/materi-edukasi-yang-diberikan.json';
|
||||
import contohEdukasiLingkungan from './data/lingkungan/edukasi-lingkungan/contoh-kegiatan-di-desa-darmasaba.json';
|
||||
import nilaiKonservasiAdat from './data/lingkungan/konservasi-adat-bali/nilai-konservasi-adat.json';
|
||||
import bentukKonservasiBerdasarkanAdat from './data/lingkungan/konservasi-adat-bali/bentuk-konservasi.json';
|
||||
import filosofiTriHita from './data/lingkungan/konservasi-adat-bali/filosofi-tri-hita.json';
|
||||
import profilePejabatDesa from './data/landing-page/profile.json';
|
||||
import tujuanProgram from './data/pendidikan/program-pendidikan-anak/tujuan-program.json';
|
||||
import tujuanProgram2 from './data/pendidikan/pendidikan-non-formal/tujuan-program2.json';
|
||||
import programUnggulan from './data/pendidikan/program-pendidikan-anak/program-unggulan.json';
|
||||
import tujuanBimbinganBelajarDesa from './data/pendidikan/bimbingan-belajar-desa/tujuan-bimbingan-belajar-desa.json';
|
||||
import lokasiJadwalBimbinganBelajarDesa from './data/pendidikan/bimbingan-belajar-desa/lokasi-dan-jadwal.json';
|
||||
import fasilitasBimbinganBelajarDesa from './data/pendidikan/bimbingan-belajar-desa/fasilitas-yang-disediakan.json';
|
||||
import tempatKegiatan from './data/pendidikan/pendidikan-non-formal/tempat-kegiatan.json';
|
||||
import jenisProgramYangDiselenggarakan from './data/pendidikan/pendidikan-non-formal/jenis-program-yang-diselenggarakan.json';
|
||||
|
||||
import detailDataPengangguran from "./data/ekonomi/jumlah-pengangguran/detail-data-pengangguran.json";
|
||||
import tujuanEdukasiLingkungan from "./data/lingkungan/edukasi-lingkungan/tujuan-edukasi-lingkungan.json";
|
||||
import materiEdukasiLingkungan from "./data/lingkungan/edukasi-lingkungan/materi-edukasi-yang-diberikan.json";
|
||||
import contohEdukasiLingkungan from "./data/lingkungan/edukasi-lingkungan/contoh-kegiatan-di-desa-darmasaba.json";
|
||||
import nilaiKonservasiAdat from "./data/lingkungan/konservasi-adat-bali/nilai-konservasi-adat.json";
|
||||
import bentukKonservasiBerdasarkanAdat from "./data/lingkungan/konservasi-adat-bali/bentuk-konservasi.json";
|
||||
import filosofiTriHita from "./data/lingkungan/konservasi-adat-bali/filosofi-tri-hita.json";
|
||||
import tujuanProgram from "./data/pendidikan/program-pendidikan-anak/tujuan-program.json";
|
||||
import tujuanProgram2 from "./data/pendidikan/pendidikan-non-formal/tujuan-program2.json";
|
||||
import programUnggulan from "./data/pendidikan/program-pendidikan-anak/program-unggulan.json";
|
||||
import tujuanBimbinganBelajarDesa from "./data/pendidikan/bimbingan-belajar-desa/tujuan-bimbingan-belajar-desa.json";
|
||||
import lokasiJadwalBimbinganBelajarDesa from "./data/pendidikan/bimbingan-belajar-desa/lokasi-dan-jadwal.json";
|
||||
import fasilitasBimbinganBelajarDesa from "./data/pendidikan/bimbingan-belajar-desa/fasilitas-yang-disediakan.json";
|
||||
import tempatKegiatan from "./data/pendidikan/pendidikan-non-formal/tempat-kegiatan.json";
|
||||
import jenisProgramYangDiselenggarakan from "./data/pendidikan/pendidikan-non-formal/jenis-program-yang-diselenggarakan.json";
|
||||
import posisiOrganisasiPPID from "./data/ppid/struktur-ppid/posisi-organisasi-PPID.json";
|
||||
import pegawaiPPID from "./data/ppid/struktur-ppid/pegawai-PPID.json";
|
||||
|
||||
(async () => {
|
||||
// =========== LANDING PAGE ===========
|
||||
// =========== PROFILE ===========
|
||||
for (const p of profilePejabatDesa) {
|
||||
await prisma.pejabatDesa.upsert({
|
||||
where: { id: p.id },
|
||||
update: {
|
||||
name: p.name,
|
||||
position: p.position,
|
||||
},
|
||||
create: {
|
||||
id: p.id,
|
||||
name: p.name,
|
||||
position: p.position,
|
||||
},
|
||||
});
|
||||
}
|
||||
console.log(
|
||||
"✅ profilePejabatDesa seeded without imageId (editable later via UI)"
|
||||
);
|
||||
|
||||
// =========== PROGRAM INOVASI ===========
|
||||
for (const p of programInovasi) {
|
||||
await prisma.programInovasi.upsert({
|
||||
where: { id: p.id },
|
||||
update: {
|
||||
name: p.name,
|
||||
description: p.description,
|
||||
link: p.link,
|
||||
},
|
||||
create: {
|
||||
id: p.id,
|
||||
name: p.name,
|
||||
description: p.description,
|
||||
link: p.link,
|
||||
},
|
||||
});
|
||||
}
|
||||
console.log("program inovasi success ...");
|
||||
|
||||
// =========== MEDIA SOSIAL ===========
|
||||
for (const p of mediaSosial) {
|
||||
await prisma.mediaSosial.upsert({
|
||||
where: { id: p.id },
|
||||
update: {
|
||||
name: p.name,
|
||||
iconUrl: p.iconUrl,
|
||||
},
|
||||
create: {
|
||||
id: p.id,
|
||||
name: p.name,
|
||||
iconUrl: p.iconUrl,
|
||||
},
|
||||
});
|
||||
}
|
||||
console.log("media sosial success ...");
|
||||
|
||||
// =========== LAYANAN ===========
|
||||
for (const l of layanan) {
|
||||
await prisma.layanan.upsert({
|
||||
where: {
|
||||
@@ -133,7 +192,9 @@ import jenisProgramYangDiselenggarakan from './data/pendidikan/pendidikan-non-fo
|
||||
},
|
||||
});
|
||||
}
|
||||
console.log("✅ profilePerbekel seeded without imageId (editable later via UI)");
|
||||
console.log(
|
||||
"✅ profilePerbekel seeded without imageId (editable later via UI)"
|
||||
);
|
||||
|
||||
for (const l of visiMisiDesa) {
|
||||
await prisma.visiMisiDesa.upsert({
|
||||
@@ -154,6 +215,64 @@ import jenisProgramYangDiselenggarakan from './data/pendidikan/pendidikan-non-fo
|
||||
|
||||
console.log("visi misi desa success ...");
|
||||
|
||||
// Flatten the nested array structure for posisiOrganisasiPPID
|
||||
const flattenedPosisiOrganisasiPPID = posisiOrganisasiPPID.flat();
|
||||
|
||||
for (const p of flattenedPosisiOrganisasiPPID) {
|
||||
await prisma.posisiOrganisasiPPID.upsert({
|
||||
where: {
|
||||
id: p.id,
|
||||
},
|
||||
update: {
|
||||
nama: p.nama,
|
||||
deskripsi: p.deskripsi,
|
||||
hierarki: p.hierarki,
|
||||
parentId: p.parentId,
|
||||
},
|
||||
create: {
|
||||
id: p.id,
|
||||
nama: p.nama,
|
||||
deskripsi: p.deskripsi,
|
||||
hierarki: p.hierarki,
|
||||
parentId: p.parentId,
|
||||
},
|
||||
});
|
||||
}
|
||||
console.log("posisi organisasi success ...");
|
||||
|
||||
// Flatten the nested array structure for pegawaiPPID
|
||||
const flattenedPegawaiPPID = pegawaiPPID.flat();
|
||||
|
||||
for (const p of flattenedPegawaiPPID) {
|
||||
await prisma.pegawaiPPID.upsert({
|
||||
where: {
|
||||
id: p.id,
|
||||
},
|
||||
update: {
|
||||
namaLengkap: p.namaLengkap,
|
||||
tanggalMasuk: new Date(p.tanggalMasuk),
|
||||
email: p.email,
|
||||
gelarAkademik: p.gelarAkademik,
|
||||
telepon: p.telepon,
|
||||
alamat: p.alamat,
|
||||
posisiId: p.posisiId,
|
||||
isActive: p.isActive,
|
||||
},
|
||||
create: {
|
||||
id: p.id,
|
||||
namaLengkap: p.namaLengkap,
|
||||
tanggalMasuk: new Date(p.tanggalMasuk),
|
||||
email: p.email,
|
||||
gelarAkademik: p.gelarAkademik,
|
||||
telepon: p.telepon,
|
||||
alamat: p.alamat,
|
||||
posisiId: p.posisiId,
|
||||
isActive: p.isActive,
|
||||
},
|
||||
});
|
||||
}
|
||||
console.log("pegawai success ...");
|
||||
|
||||
for (const l of pelayananPerizinanBerusaha) {
|
||||
await prisma.pelayananPerizinanBerusaha.upsert({
|
||||
where: {
|
||||
@@ -193,22 +312,6 @@ import jenisProgramYangDiselenggarakan from './data/pendidikan/pendidikan-non-fo
|
||||
}
|
||||
console.log("pelayanan penduduk non permanen success ...");
|
||||
|
||||
for (const s of strukturPPID) {
|
||||
await prisma.strukturPPID.upsert({
|
||||
where: {
|
||||
id: s.id,
|
||||
},
|
||||
update: {
|
||||
name: s.name,
|
||||
},
|
||||
create: {
|
||||
id: s.id,
|
||||
name: s.name,
|
||||
},
|
||||
});
|
||||
}
|
||||
console.log("struktur ppid success ...");
|
||||
|
||||
for (const p of potensi) {
|
||||
await prisma.potensi.upsert({
|
||||
where: {
|
||||
@@ -378,7 +481,6 @@ import jenisProgramYangDiselenggarakan from './data/pendidikan/pendidikan-non-fo
|
||||
}
|
||||
console.log("kategori produk success ...");
|
||||
|
||||
|
||||
for (const p of posisiOrganisasi) {
|
||||
await prisma.posisiOrganisasi.upsert({
|
||||
where: {
|
||||
@@ -567,7 +669,6 @@ import jenisProgramYangDiselenggarakan from './data/pendidikan/pendidikan-non-fo
|
||||
|
||||
console.log("bentuk konservasi berdasarkan adat success ...");
|
||||
|
||||
|
||||
for (const n of nilaiKonservasiAdat) {
|
||||
await prisma.nilaiKonservasiAdat.upsert({
|
||||
where: {
|
||||
@@ -587,22 +688,6 @@ import jenisProgramYangDiselenggarakan from './data/pendidikan/pendidikan-non-fo
|
||||
|
||||
console.log("nilai konservasi adat success ...");
|
||||
|
||||
for (const p of profilePejabatDesa) {
|
||||
await prisma.pejabatDesa.upsert({
|
||||
where: { id: p.id },
|
||||
update: {
|
||||
name: p.name,
|
||||
position: p.position,
|
||||
},
|
||||
create: {
|
||||
id: p.id,
|
||||
name: p.name,
|
||||
position: p.position,
|
||||
},
|
||||
});
|
||||
}
|
||||
console.log("✅ profilePejabatDesa seeded without imageId (editable later via UI)");
|
||||
|
||||
for (const t of tujuanProgram) {
|
||||
await prisma.tujuanProgram.upsert({
|
||||
where: { id: t.id },
|
||||
@@ -649,7 +734,9 @@ import jenisProgramYangDiselenggarakan from './data/pendidikan/pendidikan-non-fo
|
||||
},
|
||||
});
|
||||
}
|
||||
console.log("✅ tujuan bimbingan belajar desa seeded (editable later via UI)");
|
||||
console.log(
|
||||
"✅ tujuan bimbingan belajar desa seeded (editable later via UI)"
|
||||
);
|
||||
|
||||
for (const t of lokasiJadwalBimbinganBelajarDesa) {
|
||||
await prisma.lokasiJadwalBimbinganBelajarDesa.upsert({
|
||||
@@ -665,7 +752,9 @@ import jenisProgramYangDiselenggarakan from './data/pendidikan/pendidikan-non-fo
|
||||
},
|
||||
});
|
||||
}
|
||||
console.log("✅ lokasi jadwal bimbingan belajar desa seeded (editable later via UI)");
|
||||
console.log(
|
||||
"✅ lokasi jadwal bimbingan belajar desa seeded (editable later via UI)"
|
||||
);
|
||||
|
||||
for (const t of fasilitasBimbinganBelajarDesa) {
|
||||
await prisma.fasilitasBimbinganBelajarDesa.upsert({
|
||||
@@ -681,7 +770,9 @@ import jenisProgramYangDiselenggarakan from './data/pendidikan/pendidikan-non-fo
|
||||
},
|
||||
});
|
||||
}
|
||||
console.log("✅ fasilitas bimbingan belajar desa seeded (editable later via UI)");
|
||||
console.log(
|
||||
"✅ fasilitas bimbingan belajar desa seeded (editable later via UI)"
|
||||
);
|
||||
|
||||
for (const t of tujuanProgram2) {
|
||||
await prisma.tujuanPendidikanNonFormal.upsert({
|
||||
@@ -697,7 +788,9 @@ import jenisProgramYangDiselenggarakan from './data/pendidikan/pendidikan-non-fo
|
||||
},
|
||||
});
|
||||
}
|
||||
console.log("✅ fasilitas bimbingan belajar desa seeded (editable later via UI)");
|
||||
console.log(
|
||||
"✅ fasilitas bimbingan belajar desa seeded (editable later via UI)"
|
||||
);
|
||||
|
||||
for (const t of tempatKegiatan) {
|
||||
await prisma.tempatKegiatan.upsert({
|
||||
@@ -713,7 +806,9 @@ import jenisProgramYangDiselenggarakan from './data/pendidikan/pendidikan-non-fo
|
||||
},
|
||||
});
|
||||
}
|
||||
console.log("✅ fasilitas bimbingan belajar desa seeded (editable later via UI)");
|
||||
console.log(
|
||||
"✅ fasilitas bimbingan belajar desa seeded (editable later via UI)"
|
||||
);
|
||||
|
||||
for (const t of jenisProgramYangDiselenggarakan) {
|
||||
await prisma.jenisProgramYangDiselenggarakan.upsert({
|
||||
@@ -729,8 +824,9 @@ import jenisProgramYangDiselenggarakan from './data/pendidikan/pendidikan-non-fo
|
||||
},
|
||||
});
|
||||
}
|
||||
console.log("✅ fasilitas bimbingan belajar desa seeded (editable later via UI)");
|
||||
|
||||
console.log(
|
||||
"✅ fasilitas bimbingan belajar desa seeded (editable later via UI)"
|
||||
);
|
||||
})()
|
||||
.then(() => prisma.$disconnect())
|
||||
.catch((e) => {
|
||||
|
||||
@@ -5,176 +5,190 @@ import { proxy } from "valtio";
|
||||
import { z } from "zod";
|
||||
|
||||
const templateJumlahPendudukMiskin = z.object({
|
||||
year: z.number().min(1, "Data tahun harus diisi"),
|
||||
totalPoorPopulation: z.number().min(1, "Data total penduduk miskin harus diisi"),
|
||||
year: z.number().min(1, "Data tahun harus diisi"),
|
||||
totalPoorPopulation: z
|
||||
.number()
|
||||
.min(1, "Data total penduduk miskin harus diisi"),
|
||||
});
|
||||
|
||||
type JumlahPendudukMiskin = Prisma.GrafikJumlahPendudukMiskinGetPayload<{
|
||||
select: {
|
||||
id: true;
|
||||
year: true;
|
||||
totalPoorPopulation: true;
|
||||
};
|
||||
select: {
|
||||
id: true;
|
||||
year: true;
|
||||
totalPoorPopulation: true;
|
||||
};
|
||||
}>;
|
||||
|
||||
const defaultForm: Omit<JumlahPendudukMiskin, 'id'> & { id?: string } = {
|
||||
year: 0,
|
||||
totalPoorPopulation: 0,
|
||||
const defaultForm: Omit<JumlahPendudukMiskin, "id"> & { id?: string } = {
|
||||
year: 0,
|
||||
totalPoorPopulation: 0,
|
||||
};
|
||||
|
||||
const jumlahPendudukMiskin = proxy({
|
||||
create: {
|
||||
form: defaultForm,
|
||||
loading: false,
|
||||
async create() {
|
||||
const cek = templateJumlahPendudukMiskin.safeParse(
|
||||
jumlahPendudukMiskin.create.form
|
||||
);
|
||||
if (!cek.success) {
|
||||
const err = `[${cek.error.issues
|
||||
.map((v) => `${v.path.join(".")}`)
|
||||
.join("\n")}] required`;
|
||||
return toast.error(err);
|
||||
}
|
||||
try {
|
||||
jumlahPendudukMiskin.create.loading = true;
|
||||
const res = await ApiFetch.api.ekonomi.jumlahpendudukmiskin[
|
||||
"create"
|
||||
].post(jumlahPendudukMiskin.create.form);
|
||||
if (res.status === 200) {
|
||||
const id = res.data?.data?.id;
|
||||
if (id) {
|
||||
toast.success("Success create");
|
||||
jumlahPendudukMiskin.create.form = {
|
||||
year: 0,
|
||||
totalPoorPopulation: 0,
|
||||
};
|
||||
jumlahPendudukMiskin.findMany.load();
|
||||
return id;
|
||||
}
|
||||
}
|
||||
return toast.error("failed create");
|
||||
} catch (error) {
|
||||
console.log((error as Error).message);
|
||||
} finally {
|
||||
jumlahPendudukMiskin.create.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
findMany: {
|
||||
data: null as
|
||||
| Prisma.GrafikJumlahPendudukMiskinGetPayload<{
|
||||
select: { id: true; year: true; totalPoorPopulation: true; };
|
||||
}>[]
|
||||
| null,
|
||||
loading: false,
|
||||
async load() {
|
||||
const res = await ApiFetch.api.ekonomi.jumlahpendudukmiskin[
|
||||
"find-many"
|
||||
].get();
|
||||
if (res.status === 200) {
|
||||
jumlahPendudukMiskin.findMany.data = res.data?.data ?? [];
|
||||
}
|
||||
},
|
||||
},
|
||||
findUnique: {
|
||||
data: null as Prisma.GrafikJumlahPendudukMiskinGetPayload<{
|
||||
select: { id: true; year: true; totalPoorPopulation: true; };
|
||||
}> | null,
|
||||
async load(id: string) {
|
||||
try {
|
||||
const res = await fetch(
|
||||
`/api/ekonomi/jumlahpendudukmiskin/${id}`
|
||||
);
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
jumlahPendudukMiskin.findUnique.data = data.data ?? null;
|
||||
} else {
|
||||
console.error("Failed to fetch data", res.status, res.statusText);
|
||||
jumlahPendudukMiskin.findUnique.data = null;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading grafik jumlah penduduk miskin:", error);
|
||||
jumlahPendudukMiskin.findUnique.data = null;
|
||||
}
|
||||
},
|
||||
},
|
||||
update: {
|
||||
id: "",
|
||||
form: {...defaultForm},
|
||||
loading: false,
|
||||
async byId() {
|
||||
// Method implementation if needed
|
||||
},
|
||||
async submit() {
|
||||
const id = this.id;
|
||||
if (!id) {
|
||||
toast.warn("ID tidak valid");
|
||||
return null;
|
||||
}
|
||||
const cek = templateJumlahPendudukMiskin.safeParse(this.form);
|
||||
if (!cek.success) {
|
||||
const err = `[${cek.error.issues.map((v) => (v.path as string[]).join(".")).join("\n")}] required`;
|
||||
toast.error(err);
|
||||
return null;
|
||||
}
|
||||
this.loading = true;
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/api/ekonomi/jumlahpendudukmiskin/${id}`, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(this.form),
|
||||
});
|
||||
const result = await response.json();
|
||||
if (!response.ok || !result?.success) {
|
||||
throw new Error(result?.message || "Gagal update data");
|
||||
}
|
||||
toast.success("Berhasil update data!");
|
||||
await jumlahPendudukMiskin.findMany.load();
|
||||
return result.data;
|
||||
} catch (error) {
|
||||
console.error("Error update data grafik jumlah penduduk miskin:", error);
|
||||
toast.error("Gagal update data grafik jumlah penduduk miskin");
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
loading: false,
|
||||
async byId(id: string) {
|
||||
if (!id) return toast.warn("ID tidak valid");
|
||||
|
||||
try {
|
||||
jumlahPendudukMiskin.delete.loading = true;
|
||||
|
||||
const response = await fetch(`/api/ekonomi/jumlahpendudukmiskin/del/${id}`, {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (response.ok && result?.success) {
|
||||
toast.success(result.message || "Grafik jumlah penduduk miskin berhasil dihapus");
|
||||
await jumlahPendudukMiskin.findMany.load(); // refresh list
|
||||
} else {
|
||||
toast.error(result?.message || "Gagal menghapus grafik jumlah penduduk miskin");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Gagal delete grafik jumlah penduduk miskin:", error);
|
||||
toast.error("Terjadi kesalahan saat menghapus grafik jumlah penduduk miskin");
|
||||
} finally {
|
||||
jumlahPendudukMiskin.delete.loading = false;
|
||||
}
|
||||
},
|
||||
create: {
|
||||
form: defaultForm,
|
||||
loading: false,
|
||||
async create() {
|
||||
const cek = templateJumlahPendudukMiskin.safeParse(
|
||||
jumlahPendudukMiskin.create.form
|
||||
);
|
||||
if (!cek.success) {
|
||||
const err = `[${cek.error.issues
|
||||
.map((v) => `${v.path.join(".")}`)
|
||||
.join("\n")}] required`;
|
||||
return toast.error(err);
|
||||
}
|
||||
})
|
||||
export default jumlahPendudukMiskin
|
||||
|
||||
|
||||
try {
|
||||
jumlahPendudukMiskin.create.loading = true;
|
||||
const res = await ApiFetch.api.ekonomi.jumlahpendudukmiskin[
|
||||
"create"
|
||||
].post(jumlahPendudukMiskin.create.form);
|
||||
if (res.status === 200) {
|
||||
const id = res.data?.data?.id;
|
||||
if (id) {
|
||||
toast.success("Success create");
|
||||
jumlahPendudukMiskin.create.form = {
|
||||
year: 0,
|
||||
totalPoorPopulation: 0,
|
||||
};
|
||||
jumlahPendudukMiskin.findMany.load();
|
||||
return id;
|
||||
}
|
||||
}
|
||||
return toast.error("failed create");
|
||||
} catch (error) {
|
||||
console.log((error as Error).message);
|
||||
} finally {
|
||||
jumlahPendudukMiskin.create.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
findMany: {
|
||||
data: null as
|
||||
| Prisma.GrafikJumlahPendudukMiskinGetPayload<{
|
||||
select: { id: true; year: true; totalPoorPopulation: true };
|
||||
}>[]
|
||||
| null,
|
||||
loading: false,
|
||||
async load() {
|
||||
const res = await ApiFetch.api.ekonomi.jumlahpendudukmiskin[
|
||||
"find-many"
|
||||
].get();
|
||||
if (res.status === 200) {
|
||||
jumlahPendudukMiskin.findMany.data = res.data?.data ?? [];
|
||||
}
|
||||
},
|
||||
},
|
||||
findUnique: {
|
||||
data: null as Prisma.GrafikJumlahPendudukMiskinGetPayload<{
|
||||
select: { id: true; year: true; totalPoorPopulation: true };
|
||||
}> | null,
|
||||
async load(id: string) {
|
||||
try {
|
||||
const res = await fetch(`/api/ekonomi/jumlahpendudukmiskin/${id}`);
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
jumlahPendudukMiskin.findUnique.data = data.data ?? null;
|
||||
} else {
|
||||
console.error("Failed to fetch data", res.status, res.statusText);
|
||||
jumlahPendudukMiskin.findUnique.data = null;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading grafik jumlah penduduk miskin:", error);
|
||||
jumlahPendudukMiskin.findUnique.data = null;
|
||||
}
|
||||
},
|
||||
},
|
||||
update: {
|
||||
id: "",
|
||||
form: { ...defaultForm },
|
||||
loading: false,
|
||||
async byId() {
|
||||
// Method implementation if needed
|
||||
},
|
||||
async submit() {
|
||||
const id = this.id;
|
||||
if (!id) {
|
||||
toast.warn("ID tidak valid");
|
||||
return null;
|
||||
}
|
||||
const cek = templateJumlahPendudukMiskin.safeParse(this.form);
|
||||
if (!cek.success) {
|
||||
const err = `[${cek.error.issues
|
||||
.map((v) => (v.path as string[]).join("."))
|
||||
.join("\n")}] required`;
|
||||
toast.error(err);
|
||||
return null;
|
||||
}
|
||||
this.loading = true;
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/api/ekonomi/jumlahpendudukmiskin/${id}`,
|
||||
{
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(this.form),
|
||||
}
|
||||
);
|
||||
const result = await response.json();
|
||||
if (!response.ok || !result?.success) {
|
||||
throw new Error(result?.message || "Gagal update data");
|
||||
}
|
||||
toast.success("Berhasil update data!");
|
||||
await jumlahPendudukMiskin.findMany.load();
|
||||
return result.data;
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"Error update data grafik jumlah penduduk miskin:",
|
||||
error
|
||||
);
|
||||
toast.error("Gagal update data grafik jumlah penduduk miskin");
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
loading: false,
|
||||
async byId(id: string) {
|
||||
if (!id) return toast.warn("ID tidak valid");
|
||||
|
||||
try {
|
||||
jumlahPendudukMiskin.delete.loading = true;
|
||||
|
||||
const response = await fetch(
|
||||
`/api/ekonomi/jumlahpendudukmiskin/del/${id}`,
|
||||
{
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (response.ok && result?.success) {
|
||||
toast.success(
|
||||
result.message || "Grafik jumlah penduduk miskin berhasil dihapus"
|
||||
);
|
||||
await jumlahPendudukMiskin.findMany.load(); // refresh list
|
||||
} else {
|
||||
toast.error(
|
||||
result?.message || "Gagal menghapus grafik jumlah penduduk miskin"
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Gagal delete grafik jumlah penduduk miskin:", error);
|
||||
toast.error(
|
||||
"Terjadi kesalahan saat menghapus grafik jumlah penduduk miskin"
|
||||
);
|
||||
} finally {
|
||||
jumlahPendudukMiskin.delete.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
export default jumlahPendudukMiskin;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import ApiFetch from "@/lib/api-fetch";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { toast } from "react-toastify";
|
||||
@@ -54,20 +55,38 @@ const desaAntikorupsi = proxy({
|
||||
},
|
||||
},
|
||||
findMany: {
|
||||
data: null as Array<
|
||||
Prisma.DesaAntiKorupsiGetPayload<{
|
||||
include: {
|
||||
file: true;
|
||||
kategori: true;
|
||||
};
|
||||
}>
|
||||
> | null,
|
||||
async load() {
|
||||
const res = await ApiFetch.api.landingpage.desaantikorupsi[
|
||||
"find-many"
|
||||
].get();
|
||||
if (res.status === 200) {
|
||||
desaAntikorupsi.findMany.data = res.data?.data ?? [];
|
||||
data: null as any[] | null,
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
total: 0,
|
||||
loading: false,
|
||||
load: async (page = 1, limit = 10) => { // Change to arrow function
|
||||
desaAntikorupsi.findMany.loading = true; // Use the full path to access the property
|
||||
desaAntikorupsi.findMany.page = page;
|
||||
try {
|
||||
const res = await ApiFetch.api.landingpage.desaantikorupsi[
|
||||
"findMany"
|
||||
].get({
|
||||
query: { page, limit },
|
||||
});
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
desaAntikorupsi.findMany.data = res.data.data || [];
|
||||
desaAntikorupsi.findMany.total = res.data.total || 0;
|
||||
desaAntikorupsi.findMany.totalPages = res.data.totalPages || 1;
|
||||
} else {
|
||||
console.error("Failed to load media sosial:", res.data?.message);
|
||||
desaAntikorupsi.findMany.data = [];
|
||||
desaAntikorupsi.findMany.total = 0;
|
||||
desaAntikorupsi.findMany.totalPages = 1;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading media sosial:", error);
|
||||
desaAntikorupsi.findMany.data = [];
|
||||
desaAntikorupsi.findMany.total = 0;
|
||||
desaAntikorupsi.findMany.totalPages = 1;
|
||||
} finally {
|
||||
desaAntikorupsi.findMany.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -281,14 +300,38 @@ const kategoriDesaAntiKorupsi = proxy({
|
||||
},
|
||||
},
|
||||
findMany: {
|
||||
data: null as Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
}> | null,
|
||||
async load() {
|
||||
const res = await ApiFetch.api.landingpage.kategoridak["find-many"].get();
|
||||
if (res.status === 200) {
|
||||
kategoriDesaAntiKorupsi.findMany.data = res.data?.data ?? [];
|
||||
data: null as any[] | null,
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
total: 0,
|
||||
loading: false,
|
||||
load: async (page = 1, limit = 10) => { // Change to arrow function
|
||||
kategoriDesaAntiKorupsi.findMany.loading = true; // Use the full path to access the property
|
||||
kategoriDesaAntiKorupsi.findMany.page = page;
|
||||
try {
|
||||
const res = await ApiFetch.api.landingpage.kategoridak[
|
||||
"findMany"
|
||||
].get({
|
||||
query: { page, limit },
|
||||
});
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
kategoriDesaAntiKorupsi.findMany.data = res.data.data || [];
|
||||
kategoriDesaAntiKorupsi.findMany.total = res.data.total || 0;
|
||||
kategoriDesaAntiKorupsi.findMany.totalPages = res.data.totalPages || 1;
|
||||
} else {
|
||||
console.error("Failed to load media sosial:", res.data?.message);
|
||||
kategoriDesaAntiKorupsi.findMany.data = [];
|
||||
kategoriDesaAntiKorupsi.findMany.total = 0;
|
||||
kategoriDesaAntiKorupsi.findMany.totalPages = 1;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading media sosial:", error);
|
||||
kategoriDesaAntiKorupsi.findMany.data = [];
|
||||
kategoriDesaAntiKorupsi.findMany.total = 0;
|
||||
kategoriDesaAntiKorupsi.findMany.totalPages = 1;
|
||||
} finally {
|
||||
kategoriDesaAntiKorupsi.findMany.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import ApiFetch from "@/lib/api-fetch";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { toast } from "react-toastify";
|
||||
@@ -5,10 +6,10 @@ import { proxy } from "valtio";
|
||||
import { z } from "zod";
|
||||
|
||||
const templateProgramInovasi = z.object({
|
||||
name: z.string().min(3, "Nama minimal 3 karakter"),
|
||||
description: z.string().min(3, "Deskripsi minimal 3 karakter"),
|
||||
name: z.string().min(1, "Nama minimal 1 karakter"),
|
||||
description: z.string().min(1, "Deskripsi minimal 1 karakter"),
|
||||
imageId: z.string().min(1, "Gambar wajib dipilih"),
|
||||
link: z.string().min(3, "Link minimal 3 karakter"),
|
||||
link: z.string().min(1, "Link minimal 1 karakter"),
|
||||
});
|
||||
|
||||
type ProgramInovasiForm = Prisma.ProgramInovasiGetPayload<{
|
||||
@@ -59,15 +60,38 @@ const programInovasi = proxy({
|
||||
},
|
||||
},
|
||||
findMany: {
|
||||
data: null as
|
||||
| Prisma.ProgramInovasiGetPayload<{ include: { image: true } }>[]
|
||||
| null,
|
||||
async load() {
|
||||
const res = await ApiFetch.api.landingpage.programinovasi[
|
||||
"find-many"
|
||||
].get();
|
||||
if (res.status === 200) {
|
||||
programInovasi.findMany.data = res.data?.data ?? [];
|
||||
data: null as any[] | null,
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
total: 0,
|
||||
loading: false,
|
||||
load: async (page = 1, limit = 10) => { // Change to arrow function
|
||||
programInovasi.findMany.loading = true; // Use the full path to access the property
|
||||
programInovasi.findMany.page = page;
|
||||
try {
|
||||
const res = await ApiFetch.api.landingpage.programinovasi[
|
||||
"findMany"
|
||||
].get({
|
||||
query: { page, limit },
|
||||
});
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
programInovasi.findMany.data = res.data.data || [];
|
||||
programInovasi.findMany.total = res.data.total || 0;
|
||||
programInovasi.findMany.totalPages = res.data.totalPages || 1;
|
||||
} else {
|
||||
console.error("Failed to load pegawai:", res.data?.message);
|
||||
programInovasi.findMany.data = [];
|
||||
programInovasi.findMany.total = 0;
|
||||
programInovasi.findMany.totalPages = 1;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading pegawai:", error);
|
||||
programInovasi.findMany.data = [];
|
||||
programInovasi.findMany.total = 0;
|
||||
programInovasi.findMany.totalPages = 1;
|
||||
} finally {
|
||||
programInovasi.findMany.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -453,13 +477,38 @@ const mediaSosial = proxy({
|
||||
},
|
||||
},
|
||||
findMany: {
|
||||
data: null as
|
||||
| Prisma.MediaSosialGetPayload<{ include: { image: true } }>[]
|
||||
| null,
|
||||
async load() {
|
||||
const res = await ApiFetch.api.landingpage.mediasosial["find-many"].get();
|
||||
if (res.status === 200) {
|
||||
mediaSosial.findMany.data = res.data?.data ?? [];
|
||||
data: null as any[] | null,
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
total: 0,
|
||||
loading: false,
|
||||
load: async (page = 1, limit = 10) => { // Change to arrow function
|
||||
mediaSosial.findMany.loading = true; // Use the full path to access the property
|
||||
mediaSosial.findMany.page = page;
|
||||
try {
|
||||
const res = await ApiFetch.api.landingpage.mediasosial[
|
||||
"findMany"
|
||||
].get({
|
||||
query: { page, limit },
|
||||
});
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
mediaSosial.findMany.data = res.data.data || [];
|
||||
mediaSosial.findMany.total = res.data.total || 0;
|
||||
mediaSosial.findMany.totalPages = res.data.totalPages || 1;
|
||||
} else {
|
||||
console.error("Failed to load media sosial:", res.data?.message);
|
||||
mediaSosial.findMany.data = [];
|
||||
mediaSosial.findMany.total = 0;
|
||||
mediaSosial.findMany.totalPages = 1;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading media sosial:", error);
|
||||
mediaSosial.findMany.data = [];
|
||||
mediaSosial.findMany.total = 0;
|
||||
mediaSosial.findMany.totalPages = 1;
|
||||
} finally {
|
||||
mediaSosial.findMany.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
282
src/app/admin/(dashboard)/_state/pendidikan/beasiswa-desa.ts
Normal file
282
src/app/admin/(dashboard)/_state/pendidikan/beasiswa-desa.ts
Normal file
@@ -0,0 +1,282 @@
|
||||
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 templateBeasiswaPendaftar = z.object({
|
||||
namaLengkap: z.string().min(1, "Nama harus diisi"),
|
||||
nik: z.string().min(1, "NIK harus diisi"),
|
||||
tempatLahir: z.string().min(1, "Tempat lahir harus diisi"),
|
||||
tanggalLahir: z.string().min(1, "Tanggal lahir harus diisi"),
|
||||
jenisKelamin: z.string().min(1, "Jenis kelamin harus diisi"),
|
||||
kewarganegaraan: z.string().min(1, "Kewarganegaraan harus diisi"),
|
||||
agama: z.string().min(1, "Agama harus diisi"),
|
||||
alamatKTP: z.string().min(1, "Alamat KTP harus diisi"),
|
||||
alamatDomisili: z.string().min(1, "Alamat domisili harus diisi"),
|
||||
noHp: z.string().min(1, "No HP harus diisi"),
|
||||
email: z.string().min(1, "Email harus diisi"),
|
||||
statusPernikahan: z.string().min(1, "Status pernikahan harus diisi"),
|
||||
ukuranBaju: z.string().min(1, "Ukuran baju harus diisi"),
|
||||
});
|
||||
|
||||
const defaultBeasiswaPendaftar = {
|
||||
namaLengkap: "",
|
||||
nik: "",
|
||||
tempatLahir: "",
|
||||
tanggalLahir: "",
|
||||
jenisKelamin: "",
|
||||
kewarganegaraan: "",
|
||||
agama: "",
|
||||
alamatKTP: "",
|
||||
alamatDomisili: "",
|
||||
noHp: "",
|
||||
email: "",
|
||||
statusPernikahan: "",
|
||||
ukuranBaju: "",
|
||||
};
|
||||
|
||||
const beasiswaPendaftar = proxy({
|
||||
create: {
|
||||
form: { ...defaultBeasiswaPendaftar },
|
||||
loading: false,
|
||||
async create() {
|
||||
const cek = templateBeasiswaPendaftar.safeParse(
|
||||
beasiswaPendaftar.create.form
|
||||
);
|
||||
if (!cek.success) {
|
||||
const err = `[${cek.error.issues
|
||||
.map((v) => `${v.path.join(".")}`)
|
||||
.join("\n")}] required`;
|
||||
return toast.error(err);
|
||||
}
|
||||
|
||||
try {
|
||||
beasiswaPendaftar.create.loading = true;
|
||||
const res = await ApiFetch.api.pendidikan.beasiswa.beasiswapendaftar[
|
||||
"create"
|
||||
].post(beasiswaPendaftar.create.form);
|
||||
if (res.status === 200) {
|
||||
beasiswaPendaftar.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 {
|
||||
beasiswaPendaftar.create.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
findMany: {
|
||||
data: [] as Prisma.BeasiswaPendaftarGetPayload<{
|
||||
omit: {
|
||||
isActive: true;
|
||||
};
|
||||
}>[],
|
||||
loading: false,
|
||||
async load() {
|
||||
const res = await ApiFetch.api.pendidikan.beasiswa.beasiswapendaftar[
|
||||
"findMany"
|
||||
].get();
|
||||
if (res.status === 200) {
|
||||
beasiswaPendaftar.findMany.data = res.data?.data ?? [];
|
||||
}
|
||||
},
|
||||
},
|
||||
findUnique: {
|
||||
data: null as Prisma.BeasiswaPendaftarGetPayload<{
|
||||
omit: {
|
||||
isActive: true;
|
||||
};
|
||||
}> | null,
|
||||
loading: false,
|
||||
async load(id: string) {
|
||||
try {
|
||||
const res = await fetch(
|
||||
`/api/pendidikan/beasiswa/beasiswapendaftar/${id}`
|
||||
);
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
beasiswaPendaftar.findUnique.data = data.data ?? null;
|
||||
} else {
|
||||
console.error("Failed to fetch data", res.status, res.statusText);
|
||||
beasiswaPendaftar.findUnique.data = null;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching data:", error);
|
||||
beasiswaPendaftar.findUnique.data = null;
|
||||
}
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
loading: false,
|
||||
async delete(id: string) {
|
||||
if (!id) return toast.warn("ID tidak valid");
|
||||
|
||||
try {
|
||||
beasiswaPendaftar.delete.loading = true;
|
||||
|
||||
const response = await fetch(
|
||||
`/api/pendidikan/beasiswa/beasiswapendaftar/del/${id}`,
|
||||
{
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (response.ok && result?.success) {
|
||||
toast.success(result.message || "Beasiswa berhasil dihapus");
|
||||
await beasiswaPendaftar.findMany.load(); // refresh list
|
||||
} else {
|
||||
toast.error(result?.message || "Gagal menghapus beasiswa");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Gagal delete:", error);
|
||||
toast.error("Terjadi kesalahan saat menghapus beasiswa");
|
||||
} finally {
|
||||
beasiswaPendaftar.delete.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
update: {
|
||||
id: "",
|
||||
form: { ...defaultBeasiswaPendaftar },
|
||||
loading: false,
|
||||
async load(id: string) {
|
||||
if (!id) {
|
||||
toast.warn("ID tidak valid");
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/api/pendidikan/beasiswa/beasiswapendaftar/${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 = {
|
||||
namaLengkap: data.namaLengkap,
|
||||
nik: data.nik,
|
||||
tempatLahir: data.tempatLahir,
|
||||
tanggalLahir: data.tanggalLahir,
|
||||
jenisKelamin: data.jenisKelamin,
|
||||
kewarganegaraan: data.kewarganegaraan,
|
||||
agama: data.agama,
|
||||
alamatKTP: data.alamatKTP,
|
||||
alamatDomisili: data.alamatDomisili,
|
||||
noHp: data.noHp,
|
||||
email: data.email,
|
||||
statusPernikahan: data.statusPernikahan,
|
||||
ukuranBaju: data.ukuranBaju,
|
||||
};
|
||||
return data; // Return the loaded data
|
||||
} else {
|
||||
throw new Error(result?.message || "Gagal memuat data");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading beasiswa pendaftar:", error);
|
||||
toast.error(
|
||||
error instanceof Error ? error.message : "Gagal memuat data"
|
||||
);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
async update() {
|
||||
const cek = templateBeasiswaPendaftar.safeParse(
|
||||
beasiswaPendaftar.update.form
|
||||
);
|
||||
if (!cek.success) {
|
||||
const err = `[${cek.error.issues
|
||||
.map((v) => `${v.path.join(".")}`)
|
||||
.join("\n")}] required`;
|
||||
toast.error(err);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
beasiswaPendaftar.update.loading = true;
|
||||
|
||||
const response = await fetch(
|
||||
`/api/pendidikan/beasiswa/beasiswapendaftar/${this.id}`,
|
||||
{
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
namaLengkap: this.form.namaLengkap,
|
||||
nik: this.form.nik,
|
||||
tanggalLahir: this.form.tanggalLahir,
|
||||
jenisKelamin: this.form.jenisKelamin,
|
||||
kewarganegaraan: this.form.kewarganegaraan,
|
||||
agama: this.form.agama,
|
||||
alamatKTP: this.form.alamatKTP,
|
||||
alamatDomisili: this.form.alamatDomisili,
|
||||
noHp: this.form.noHp,
|
||||
email: this.form.email,
|
||||
statusPernikahan: this.form.statusPernikahan,
|
||||
ukuranBaju: this.form.ukuranBaju,
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
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 beasiswa pendaftar");
|
||||
await beasiswaPendaftar.findMany.load(); // refresh list
|
||||
return true;
|
||||
} else {
|
||||
throw new Error(result.message || "Gagal update beasiswa pendaftar");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error updating beasiswa pendaftar:", error);
|
||||
toast.error(
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Terjadi kesalahan saat update beasiswa pendaftar"
|
||||
);
|
||||
return false;
|
||||
} finally {
|
||||
beasiswaPendaftar.update.loading = false;
|
||||
}
|
||||
},
|
||||
reset() {
|
||||
beasiswaPendaftar.update.id = "";
|
||||
beasiswaPendaftar.update.form = { ...defaultBeasiswaPendaftar };
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const beasiswaDesaState = proxy({
|
||||
beasiswaPendaftar,
|
||||
});
|
||||
|
||||
export default beasiswaDesaState;
|
||||
178
src/app/admin/(dashboard)/_state/pendidikan/data-pendidikan.ts
Normal file
178
src/app/admin/(dashboard)/_state/pendidikan/data-pendidikan.ts
Normal file
@@ -0,0 +1,178 @@
|
||||
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 templateDataPendidikan = z.object({
|
||||
name: z.string().min(1, "Data nama harus diisi"),
|
||||
jumlah: z.string().min(1, "Data jumlah harus diisi"),
|
||||
});
|
||||
|
||||
type DataPendidikan = Prisma.DataPendidikanGetPayload<{
|
||||
select: {
|
||||
id: true;
|
||||
name: true;
|
||||
jumlah: true;
|
||||
};
|
||||
}>;
|
||||
|
||||
const defaultForm: Omit<DataPendidikan, "id"> & { id?: string } = {
|
||||
name: "",
|
||||
jumlah: "",
|
||||
};
|
||||
|
||||
const dataPendidikan = proxy({
|
||||
create: {
|
||||
form: defaultForm,
|
||||
loading: false,
|
||||
async create() {
|
||||
const cek = templateDataPendidikan.safeParse(dataPendidikan.create.form);
|
||||
if (!cek.success) {
|
||||
const err = `[${cek.error.issues
|
||||
.map((v) => `${v.path.join(".")}`)
|
||||
.join("\n")}] required`;
|
||||
return toast.error(err);
|
||||
}
|
||||
try {
|
||||
dataPendidikan.create.loading = true;
|
||||
const res = await ApiFetch.api.pendidikan.datapendidikan["create"].post(
|
||||
dataPendidikan.create.form
|
||||
);
|
||||
if (res.status === 200) {
|
||||
const id = res.data?.data?.id;
|
||||
if (id) {
|
||||
toast.success("Success create");
|
||||
dataPendidikan.create.form = {
|
||||
name: "",
|
||||
jumlah: "",
|
||||
};
|
||||
dataPendidikan.findMany.load();
|
||||
return id;
|
||||
}
|
||||
}
|
||||
return toast.error("failed create");
|
||||
} catch (error) {
|
||||
console.log((error as Error).message);
|
||||
} finally {
|
||||
dataPendidikan.create.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
findMany: {
|
||||
data: null as
|
||||
| Prisma.DataPendidikanGetPayload<{
|
||||
select: { id: true; name: true; jumlah: true };
|
||||
}>[]
|
||||
| null,
|
||||
loading: false,
|
||||
async load() {
|
||||
const res = await ApiFetch.api.pendidikan.datapendidikan[
|
||||
"findMany"
|
||||
].get();
|
||||
if (res.status === 200) {
|
||||
dataPendidikan.findMany.data = res.data?.data ?? [];
|
||||
}
|
||||
},
|
||||
},
|
||||
findUnique: {
|
||||
data: null as Prisma.DataPendidikanGetPayload<{
|
||||
select: { id: true; name: true; jumlah: true };
|
||||
}> | null,
|
||||
async load(id: string) {
|
||||
try {
|
||||
const res = await fetch(`/api/pendidikan/datapendidikan/${id}`);
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
dataPendidikan.findUnique.data = data.data ?? null;
|
||||
} else {
|
||||
console.error("Failed to fetch data", res.status, res.statusText);
|
||||
dataPendidikan.findUnique.data = null;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading data pendidikan:", error);
|
||||
dataPendidikan.findUnique.data = null;
|
||||
}
|
||||
},
|
||||
},
|
||||
update: {
|
||||
id: "",
|
||||
form: { ...defaultForm },
|
||||
loading: false,
|
||||
async byId() {
|
||||
// Method implementation if needed
|
||||
},
|
||||
async submit() {
|
||||
const id = this.id;
|
||||
if (!id) {
|
||||
toast.warn("ID tidak valid");
|
||||
return null;
|
||||
}
|
||||
const cek = templateDataPendidikan.safeParse(this.form);
|
||||
if (!cek.success) {
|
||||
const err = `[${cek.error.issues
|
||||
.map((v) => (v.path as string[]).join("."))
|
||||
.join("\n")}] required`;
|
||||
toast.error(err);
|
||||
return null;
|
||||
}
|
||||
this.loading = true;
|
||||
try {
|
||||
const response = await fetch(`/api/pendidikan/datapendidikan/${id}`, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(this.form),
|
||||
});
|
||||
const result = await response.json();
|
||||
if (!response.ok || !result?.success) {
|
||||
throw new Error(result?.message || "Gagal update data");
|
||||
}
|
||||
toast.success("Berhasil update data!");
|
||||
await dataPendidikan.findMany.load();
|
||||
return result.data;
|
||||
} catch (error) {
|
||||
console.error("Error update data data pendidikan:", error);
|
||||
toast.error("Gagal update data data pendidikan");
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
loading: false,
|
||||
async byId(id: string) {
|
||||
if (!id) return toast.warn("ID tidak valid");
|
||||
|
||||
try {
|
||||
dataPendidikan.delete.loading = true;
|
||||
|
||||
const response = await fetch(
|
||||
`/api/pendidikan/datapendidikan/del/${id}`,
|
||||
{
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (response.ok && result?.success) {
|
||||
toast.success(result.message || "Data berhasil dihapus");
|
||||
await dataPendidikan.findMany.load(); // refresh list
|
||||
} else {
|
||||
toast.error(result?.message || "Gagal menghapus data");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Gagal delete data pendidikan:", error);
|
||||
toast.error("Terjadi kesalahan saat menghapus data pendidikan");
|
||||
} finally {
|
||||
dataPendidikan.delete.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
export default dataPendidikan;
|
||||
@@ -0,0 +1,478 @@
|
||||
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 templateDataPerpustakaan = z.object({
|
||||
judul: z.string().min(1, "Judul harus diisi"),
|
||||
deskripsi: z.string().min(1, "Deskripsi harus diisi"),
|
||||
imageId: z.string().min(1, "Image ID harus diisi"),
|
||||
kategoriId: z.string().min(1, "Kategori ID harus diisi"),
|
||||
});
|
||||
|
||||
const defaultDataPerpustakaan = {
|
||||
judul: "",
|
||||
deskripsi: "",
|
||||
imageId: "",
|
||||
kategoriId: "",
|
||||
};
|
||||
|
||||
const dataPerpustakaan = proxy({
|
||||
create: {
|
||||
form: { ...defaultDataPerpustakaan },
|
||||
loading: false,
|
||||
async create() {
|
||||
const cek = templateDataPerpustakaan.safeParse(
|
||||
dataPerpustakaan.create.form
|
||||
);
|
||||
if (!cek.success) {
|
||||
const err = `[${cek.error.issues
|
||||
.map((v) => `${v.path.join(".")}`)
|
||||
.join("\n")}] required`;
|
||||
return toast.error(err);
|
||||
}
|
||||
|
||||
try {
|
||||
dataPerpustakaan.create.loading = true;
|
||||
const res =
|
||||
await ApiFetch.api.pendidikan.perpustakaandigital.dataperpustakaan[
|
||||
"create"
|
||||
].post(dataPerpustakaan.create.form);
|
||||
if (res.status === 200) {
|
||||
dataPerpustakaan.findMany.load();
|
||||
return toast.success("Data Data Perpustakaan Berhasil Dibuat");
|
||||
}
|
||||
console.log(res);
|
||||
return toast.error("failed create");
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return toast.error("failed create");
|
||||
} finally {
|
||||
dataPerpustakaan.create.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
findMany: {
|
||||
data: [] as Prisma.DataPerpustakaanGetPayload<{
|
||||
include: {
|
||||
kategori: true;
|
||||
image: true;
|
||||
};
|
||||
}>[],
|
||||
loading: false,
|
||||
async load() {
|
||||
const res =
|
||||
await ApiFetch.api.pendidikan.perpustakaandigital.dataperpustakaan[
|
||||
"findMany"
|
||||
].get();
|
||||
if (res.status === 200) {
|
||||
dataPerpustakaan.findMany.data = res.data?.data ?? [];
|
||||
}
|
||||
},
|
||||
},
|
||||
findUnique: {
|
||||
data: null as Prisma.DataPerpustakaanGetPayload<{
|
||||
include: {
|
||||
kategori: true;
|
||||
image: true;
|
||||
};
|
||||
}> | null,
|
||||
loading: false,
|
||||
async load(id: string) {
|
||||
try {
|
||||
const res = await fetch(
|
||||
`/api/pendidikan/perpustakaandigital/dataperpustakaan/${id}`
|
||||
);
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
dataPerpustakaan.findUnique.data = data.data ?? null;
|
||||
} else {
|
||||
console.error("Failed to fetch data", res.status, res.statusText);
|
||||
dataPerpustakaan.findUnique.data = null;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching data:", error);
|
||||
dataPerpustakaan.findUnique.data = null;
|
||||
}
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
loading: false,
|
||||
async delete(id: string) {
|
||||
if (!id) return toast.warn("ID tidak valid");
|
||||
|
||||
try {
|
||||
dataPerpustakaan.delete.loading = true;
|
||||
|
||||
const response = await fetch(
|
||||
`/api/pendidikan/perpustakaandigital/dataperpustakaan/del/${id}`,
|
||||
{
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (response.ok && result?.success) {
|
||||
toast.success(result.message || "Data Perpustakaan berhasil dihapus");
|
||||
await dataPerpustakaan.findMany.load(); // refresh list
|
||||
} else {
|
||||
toast.error(result?.message || "Gagal menghapus Data Perpustakaan");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Gagal delete:", error);
|
||||
toast.error("Terjadi kesalahan saat menghapus Data Perpustakaan");
|
||||
} finally {
|
||||
dataPerpustakaan.delete.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
update: {
|
||||
id: "",
|
||||
form: { ...defaultDataPerpustakaan },
|
||||
loading: false,
|
||||
async load(id: string) {
|
||||
if (!id) {
|
||||
toast.warn("ID tidak valid");
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/api/pendidikan/perpustakaandigital/dataperpustakaan/${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,
|
||||
imageId: data.imageId,
|
||||
kategoriId: data.kategoriId,
|
||||
};
|
||||
return data; // Return the loaded data
|
||||
} else {
|
||||
throw new Error(result?.message || "Gagal memuat data");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading perpustakaan digital:", error);
|
||||
toast.error(
|
||||
error instanceof Error ? error.message : "Gagal memuat data"
|
||||
);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
async update() {
|
||||
const cek = templateDataPerpustakaan.safeParse(
|
||||
dataPerpustakaan.update.form
|
||||
);
|
||||
if (!cek.success) {
|
||||
const err = `[${cek.error.issues
|
||||
.map((v) => `${v.path.join(".")}`)
|
||||
.join("\n")}] required`;
|
||||
toast.error(err);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
dataPerpustakaan.update.loading = true;
|
||||
|
||||
const response = await fetch(
|
||||
`/api/pendidikan/perpustakaandigital/dataperpustakaan/${this.id}`,
|
||||
{
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
judul: this.form.judul,
|
||||
deskripsi: this.form.deskripsi,
|
||||
imageId: this.form.imageId,
|
||||
kategoriId: this.form.kategoriId,
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
throw new Error(
|
||||
errorData.message || `HTTP error! status: ${response.status}`
|
||||
);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
toast.success("Berhasil update data perpustakaan digital");
|
||||
await dataPerpustakaan.findMany.load(); // refresh list
|
||||
return true;
|
||||
} else {
|
||||
throw new Error(
|
||||
result.message || "Gagal update data perpustakaan digital"
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error updating data perpustakaan digital:", error);
|
||||
toast.error(
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Terjadi kesalahan saat update data perpustakaan digital"
|
||||
);
|
||||
return false;
|
||||
} finally {
|
||||
dataPerpustakaan.update.loading = false;
|
||||
}
|
||||
},
|
||||
reset() {
|
||||
dataPerpustakaan.update.id = "";
|
||||
dataPerpustakaan.update.form = { ...defaultDataPerpustakaan };
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const templateKategoriBuku = z.object({
|
||||
name: z.string().min(1, "Nama harus diisi"),
|
||||
});
|
||||
|
||||
const defaultKategoriBuku = {
|
||||
name: "",
|
||||
};
|
||||
|
||||
const kategoriBuku = proxy({
|
||||
create: {
|
||||
form: { ...defaultKategoriBuku },
|
||||
loading: false,
|
||||
async create() {
|
||||
const cek = templateKategoriBuku.safeParse(kategoriBuku.create.form);
|
||||
if (!cek.success) {
|
||||
const err = `[${cek.error.issues
|
||||
.map((v) => `${v.path.join(".")}`)
|
||||
.join("\n")}] required`;
|
||||
return toast.error(err);
|
||||
}
|
||||
|
||||
try {
|
||||
kategoriBuku.create.loading = true;
|
||||
const res =
|
||||
await ApiFetch.api.pendidikan.perpustakaandigital.kategoribuku[
|
||||
"create"
|
||||
].post(kategoriBuku.create.form);
|
||||
if (res.status === 200) {
|
||||
kategoriBuku.findMany.load();
|
||||
return toast.success("Data Kategori Buku Berhasil Dibuat");
|
||||
}
|
||||
console.log(res);
|
||||
return toast.error("failed create");
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return toast.error("failed create");
|
||||
} finally {
|
||||
kategoriBuku.create.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
findMany: {
|
||||
data: [] as Prisma.KategoriBukuGetPayload<{
|
||||
omit: {
|
||||
isActive: true;
|
||||
};
|
||||
}>[],
|
||||
loading: false,
|
||||
async load() {
|
||||
const res =
|
||||
await ApiFetch.api.pendidikan.perpustakaandigital.kategoribuku[
|
||||
"findMany"
|
||||
].get();
|
||||
if (res.status === 200) {
|
||||
kategoriBuku.findMany.data = res.data?.data ?? [];
|
||||
}
|
||||
},
|
||||
},
|
||||
findUnique: {
|
||||
data: null as Prisma.KategoriBukuGetPayload<{
|
||||
omit: {
|
||||
isActive: true;
|
||||
};
|
||||
}> | null,
|
||||
loading: false,
|
||||
async load(id: string) {
|
||||
try {
|
||||
const res = await fetch(
|
||||
`/api/pendidikan/perpustakaandigital/kategoribuku/${id}`
|
||||
);
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
kategoriBuku.findUnique.data = data.data ?? null;
|
||||
} else {
|
||||
console.error("Failed to fetch data", res.status, res.statusText);
|
||||
kategoriBuku.findUnique.data = null;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching data:", error);
|
||||
kategoriBuku.findUnique.data = null;
|
||||
}
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
loading: false,
|
||||
async delete(id: string) {
|
||||
if (!id) return toast.warn("ID tidak valid");
|
||||
|
||||
try {
|
||||
kategoriBuku.delete.loading = true;
|
||||
|
||||
const response = await fetch(
|
||||
`/api/pendidikan/perpustakaandigital/kategoribuku/del/${id}`,
|
||||
{
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (response.ok && result?.success) {
|
||||
toast.success(
|
||||
result.message || "Data Kategori Buku berhasil dihapus"
|
||||
);
|
||||
await kategoriBuku.findMany.load(); // refresh list
|
||||
} else {
|
||||
toast.error(result?.message || "Gagal menghapus Data Kategori Buku");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Gagal delete:", error);
|
||||
toast.error("Terjadi kesalahan saat menghapus Data Kategori Buku");
|
||||
} finally {
|
||||
kategoriBuku.delete.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
update: {
|
||||
id: "",
|
||||
form: { ...defaultKategoriBuku },
|
||||
loading: false,
|
||||
async load(id: string) {
|
||||
if (!id) {
|
||||
toast.warn("ID tidak valid");
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/api/pendidikan/perpustakaandigital/kategoribuku/${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,
|
||||
};
|
||||
return data; // Return the loaded data
|
||||
} else {
|
||||
throw new Error(result?.message || "Gagal memuat data");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading kategori buku:", error);
|
||||
toast.error(
|
||||
error instanceof Error ? error.message : "Gagal memuat data"
|
||||
);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
async update() {
|
||||
const cek = templateKategoriBuku.safeParse(kategoriBuku.update.form);
|
||||
if (!cek.success) {
|
||||
const err = `[${cek.error.issues
|
||||
.map((v) => `${v.path.join(".")}`)
|
||||
.join("\n")}] required`;
|
||||
toast.error(err);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
kategoriBuku.update.loading = true;
|
||||
|
||||
const response = await fetch(
|
||||
`/api/pendidikan/perpustakaandigital/kategoribuku/${this.id}`,
|
||||
{
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: this.form.name,
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
throw new Error(
|
||||
errorData.message || `HTTP error! status: ${response.status}`
|
||||
);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
toast.success("Berhasil update data kategori buku");
|
||||
await kategoriBuku.findMany.load(); // refresh list
|
||||
return true;
|
||||
} else {
|
||||
throw new Error(result.message || "Gagal update data kategori buku");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error updating data kategori buku:", error);
|
||||
toast.error(
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Terjadi kesalahan saat update data kategori buku"
|
||||
);
|
||||
return false;
|
||||
} finally {
|
||||
kategoriBuku.update.loading = false;
|
||||
}
|
||||
},
|
||||
reset() {
|
||||
kategoriBuku.update.id = "";
|
||||
kategoriBuku.update.form = { ...defaultKategoriBuku };
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const perpustakaanDigitalState = proxy({
|
||||
dataPerpustakaan,
|
||||
kategoriBuku,
|
||||
});
|
||||
|
||||
export default perpustakaanDigitalState;
|
||||
@@ -1,169 +1,683 @@
|
||||
/* 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 templateForm = z.object({
|
||||
name: z.string().min(3, "Nama minimal 3 karakter"),
|
||||
imageId: z.string().min(1, "Gambar wajib dipilih"),
|
||||
})
|
||||
name: z.string().min(3, "Nama minimal 3 karakter"),
|
||||
imageId: z.string().min(1, "Gambar wajib dipilih"),
|
||||
});
|
||||
|
||||
const defaultForm = {
|
||||
name: "",
|
||||
imageId: "",
|
||||
name: "",
|
||||
imageId: "",
|
||||
};
|
||||
|
||||
type StrukturPPIDForm = Prisma.StrukturPPIDGetPayload<{
|
||||
select: {
|
||||
id: true;
|
||||
name: true;
|
||||
imageId: true;
|
||||
image?: {
|
||||
select: {
|
||||
link: true;
|
||||
};
|
||||
};
|
||||
select: {
|
||||
id: true;
|
||||
name: true;
|
||||
imageId: true;
|
||||
image?: {
|
||||
select: {
|
||||
link: true;
|
||||
};
|
||||
};
|
||||
};
|
||||
}>;
|
||||
|
||||
const stateStrukturPPID = proxy({
|
||||
struktur: {
|
||||
data: null as StrukturPPIDForm | null,
|
||||
loading: false,
|
||||
error: null as string | null,
|
||||
const stateStruktur = proxy({
|
||||
struktur: {
|
||||
data: null as StrukturPPIDForm | null,
|
||||
loading: false,
|
||||
error: null as string | null,
|
||||
|
||||
async load(id: string) {
|
||||
if(!id) {
|
||||
toast.warn("ID tidak valid")
|
||||
return null
|
||||
}
|
||||
async load(id: string) {
|
||||
if (!id) {
|
||||
toast.warn("ID tidak valid");
|
||||
return null;
|
||||
}
|
||||
|
||||
this.loading = true;
|
||||
this.error = null;
|
||||
this.loading = true;
|
||||
this.error = null;
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/ppid/strukturppid/${id}`);
|
||||
|
||||
if(!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`)
|
||||
}
|
||||
try {
|
||||
const response = await fetch(`/api/ppid/strukturppid/${id}`);
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if(result.success) {
|
||||
this.data = result.data;
|
||||
return result.data
|
||||
} else {
|
||||
throw new Error(result.message || "Gagal mengambil data struktur")
|
||||
}
|
||||
} catch (error) {
|
||||
const errorMessage = (error as Error).message;
|
||||
this.error = errorMessage;
|
||||
console.error("Load struktur error:", errorMessage);
|
||||
toast.error("Terjadi kesalahan saat mengambil data struktur");
|
||||
return null;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
reset() {
|
||||
this.data = null;
|
||||
this.error = null;
|
||||
this.loading = false;
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
},
|
||||
|
||||
editStruktur: {
|
||||
id: "",
|
||||
form: { ...defaultForm },
|
||||
loading: false,
|
||||
error: null as string | null,
|
||||
isReadOnly: false,
|
||||
const result = await response.json();
|
||||
|
||||
initialize(strukturData: StrukturPPIDForm) {
|
||||
this.id = strukturData.id;
|
||||
this.isReadOnly = false;
|
||||
this.form = {
|
||||
name: strukturData.name || "",
|
||||
imageId: strukturData.imageId || "",
|
||||
};
|
||||
},
|
||||
|
||||
updateField(field: keyof typeof defaultForm, value: string) {
|
||||
this.form[field] = value;
|
||||
},
|
||||
|
||||
async submit() {
|
||||
const validation = templateForm.safeParse(this.form);
|
||||
|
||||
if (!validation.success) {
|
||||
const errors = validation.error.issues
|
||||
.map((issue) => `${issue.path.join(".")}: ${issue.message}`)
|
||||
.join(", ");
|
||||
toast.error(`Form tidak valid: ${errors}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
this.loading = true;
|
||||
this.error = null;
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/ppid/strukturppid/${this.id}`, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(this.form),
|
||||
})
|
||||
|
||||
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 struktur");
|
||||
await stateStrukturPPID.struktur.load(this.id);
|
||||
return true;
|
||||
} else {
|
||||
throw new Error(result.message || "Gagal update struktur");
|
||||
}
|
||||
} catch (error) {
|
||||
const errorMessage = (error as Error).message;
|
||||
this.error = errorMessage;
|
||||
console.error("Update struktur error:", errorMessage);
|
||||
toast.error("Terjadi kesalahan saat update struktur");
|
||||
return false;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
reset() {
|
||||
this.id = "";
|
||||
this.form = { ...defaultForm };
|
||||
this.error = null;
|
||||
this.loading = false;
|
||||
this.isReadOnly = false;
|
||||
if (result.success) {
|
||||
this.data = result.data;
|
||||
return result.data;
|
||||
} else {
|
||||
throw new Error(result.message || "Gagal mengambil data struktur");
|
||||
}
|
||||
},
|
||||
|
||||
async loadForEdit(id: string) {
|
||||
const strukturData = await this.struktur.load(id);
|
||||
if (strukturData) {
|
||||
this.editStruktur.initialize(strukturData);
|
||||
}
|
||||
return strukturData;
|
||||
} catch (error) {
|
||||
const errorMessage = (error as Error).message;
|
||||
this.error = errorMessage;
|
||||
console.error("Load struktur error:", errorMessage);
|
||||
toast.error("Terjadi kesalahan saat mengambil data struktur");
|
||||
return null;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
reset() {
|
||||
this.struktur.reset();
|
||||
this.editStruktur.reset();
|
||||
this.data = null;
|
||||
this.error = null;
|
||||
this.loading = false;
|
||||
},
|
||||
},
|
||||
|
||||
editStruktur: {
|
||||
id: "",
|
||||
form: { ...defaultForm },
|
||||
loading: false,
|
||||
error: null as string | null,
|
||||
isReadOnly: false,
|
||||
|
||||
initialize(strukturData: StrukturPPIDForm) {
|
||||
this.id = strukturData.id;
|
||||
this.isReadOnly = false;
|
||||
this.form = {
|
||||
name: strukturData.name || "",
|
||||
imageId: strukturData.imageId || "",
|
||||
};
|
||||
},
|
||||
|
||||
updateField(field: keyof typeof defaultForm, value: string) {
|
||||
this.form[field] = value;
|
||||
},
|
||||
|
||||
async submit() {
|
||||
const validation = templateForm.safeParse(this.form);
|
||||
|
||||
if (!validation.success) {
|
||||
const errors = validation.error.issues
|
||||
.map((issue) => `${issue.path.join(".")}: ${issue.message}`)
|
||||
.join(", ");
|
||||
toast.error(`Form tidak valid: ${errors}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
this.loading = true;
|
||||
this.error = null;
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/ppid/strukturppid/${this.id}`, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(this.form),
|
||||
});
|
||||
|
||||
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 struktur");
|
||||
await stateStruktur.struktur.load(this.id);
|
||||
return true;
|
||||
} else {
|
||||
throw new Error(result.message || "Gagal update struktur");
|
||||
}
|
||||
} catch (error) {
|
||||
const errorMessage = (error as Error).message;
|
||||
this.error = errorMessage;
|
||||
console.error("Update struktur error:", errorMessage);
|
||||
toast.error("Terjadi kesalahan saat update struktur");
|
||||
return false;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
reset() {
|
||||
this.id = "";
|
||||
this.form = { ...defaultForm };
|
||||
this.error = null;
|
||||
this.loading = false;
|
||||
this.isReadOnly = false;
|
||||
},
|
||||
},
|
||||
|
||||
async loadForEdit(id: string) {
|
||||
const strukturData = await this.struktur.load(id);
|
||||
if (strukturData) {
|
||||
this.editStruktur.initialize(strukturData);
|
||||
}
|
||||
})
|
||||
return strukturData;
|
||||
},
|
||||
|
||||
reset() {
|
||||
this.struktur.reset();
|
||||
this.editStruktur.reset();
|
||||
},
|
||||
});
|
||||
|
||||
const templatePosisiOrganisasi = z.object({
|
||||
nama: z.string().min(1, "Nama harus diisi"),
|
||||
deskripsi: z.string().optional(),
|
||||
hierarki: z.number().int().positive("Hierarki harus angka positif"),
|
||||
});
|
||||
|
||||
const posisiOrganisasiDefaultForm = {
|
||||
nama: "",
|
||||
deskripsi: "",
|
||||
hierarki: 0,
|
||||
};
|
||||
|
||||
const posisiOrganisasi = proxy({
|
||||
create: {
|
||||
form: { ...posisiOrganisasiDefaultForm },
|
||||
loading: false,
|
||||
async submit() {
|
||||
const cek = templatePosisiOrganisasi.safeParse(this.form);
|
||||
if (!cek.success) {
|
||||
const err = cek.error.issues.map((v) => v.message).join("\n");
|
||||
return toast.error(err);
|
||||
}
|
||||
|
||||
try {
|
||||
this.loading = true;
|
||||
const res = await ApiFetch.api.ppid.strukturppid.posisiorganisasi[
|
||||
"create"
|
||||
].post(this.form);
|
||||
if (res.status === 200) {
|
||||
toast.success("Berhasil menambahkan posisi organisasi");
|
||||
posisiOrganisasi.findMany.load();
|
||||
this.reset();
|
||||
} else {
|
||||
toast.error(res.data?.message || "Gagal menambahkan posisi");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Create error:", error);
|
||||
toast.error("Terjadi kesalahan saat menambahkan posisi");
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
reset() {
|
||||
this.form = { ...posisiOrganisasiDefaultForm };
|
||||
},
|
||||
},
|
||||
|
||||
findUnique: {
|
||||
data: null as Prisma.StrukturOrganisasiPPIDGetPayload<{
|
||||
omit: { isActive: true };
|
||||
}> | null,
|
||||
async load(id: string) {
|
||||
try {
|
||||
const res = await fetch(
|
||||
`/api/ppid/strukturppid/posisiorganisasi/${id}`
|
||||
);
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
posisiOrganisasi.findUnique.data = data.data ?? null;
|
||||
} else {
|
||||
console.error("Failed to fetch posisiOrganisasi:", res.statusText);
|
||||
posisiOrganisasi.findUnique.data = null;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching posisiOrganisasi:", error);
|
||||
posisiOrganisasi.findUnique.data = null;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
edit: {
|
||||
id: "",
|
||||
form: { ...posisiOrganisasiDefaultForm },
|
||||
loading: false,
|
||||
async load(id: string) {
|
||||
if (!id) {
|
||||
toast.warn("ID tidak valid");
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/api/ppid/strukturppid/posisiorganisasi/${id}`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
const result = await response.json();
|
||||
if (result?.success) {
|
||||
const data = result.data;
|
||||
this.id = data.id;
|
||||
this.form = {
|
||||
nama: data.nama,
|
||||
deskripsi: data.deskripsi,
|
||||
hierarki: data.hierarki,
|
||||
};
|
||||
return data;
|
||||
} else {
|
||||
throw new Error(result?.message || "Gagal memuat data");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading posisi organisasi:", error);
|
||||
toast.error(
|
||||
error instanceof Error ? error.message : "Gagal memuat data"
|
||||
);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
async update() {
|
||||
const cek = templatePosisiOrganisasi.safeParse(this.form);
|
||||
if (!cek.success) {
|
||||
const err = `[${cek.error.issues
|
||||
.map((v) => `${v.path.join(".")}`)
|
||||
.join("\n")}] required`;
|
||||
return toast.error(err);
|
||||
}
|
||||
|
||||
try {
|
||||
this.loading = true;
|
||||
const response = await fetch(
|
||||
`/api/ppid/strukturppid/posisiorganisasi/${this.id}`,
|
||||
{
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
nama: this.form.nama,
|
||||
deskripsi: this.form.deskripsi,
|
||||
hierarki: this.form.hierarki,
|
||||
}),
|
||||
}
|
||||
);
|
||||
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 posisi organisasi");
|
||||
await posisiOrganisasi.findMany.load(); // refresh list
|
||||
return true;
|
||||
} else {
|
||||
throw new Error(
|
||||
result.message || "Gagal mengupdate posisi organisasi"
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error updating posisi organisasi:", error);
|
||||
toast.error(
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Gagal mengupdate posisi organisasi"
|
||||
);
|
||||
return false;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
reset() {
|
||||
this.id = "";
|
||||
this.form = { ...posisiOrganisasiDefaultForm };
|
||||
},
|
||||
},
|
||||
|
||||
findMany: {
|
||||
data: [] as Array<{
|
||||
id: string;
|
||||
nama: string;
|
||||
deskripsi: string | null;
|
||||
hierarki: number;
|
||||
}>,
|
||||
async load() {
|
||||
try {
|
||||
const res = await ApiFetch.api.ppid.strukturppid.posisiorganisasi[
|
||||
"find-many"
|
||||
].get();
|
||||
if (res.status === 200) {
|
||||
// The API now returns the id field, so we can use it directly
|
||||
this.data = res.data?.data ?? [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Find many error:", error);
|
||||
this.data = [];
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
delete: {
|
||||
loading: false,
|
||||
async byId(id: string) {
|
||||
if (!id) return toast.warn("ID tidak valid");
|
||||
|
||||
try {
|
||||
posisiOrganisasi.delete.loading = true;
|
||||
|
||||
const response = await fetch(
|
||||
`/api/ppid/strukturppid/posisiorganisasi/del/${id}`,
|
||||
{
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (response.ok && result?.success) {
|
||||
toast.success(result.message || "Posisi organisasi berhasil dihapus");
|
||||
await posisiOrganisasi.findMany.load(); // refresh list
|
||||
} else {
|
||||
toast.error(result?.message || "Gagal menghapus posisi organisasi");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Gagal delete:", error);
|
||||
toast.error("Terjadi kesalahan saat menghapus posisi organisasi");
|
||||
} finally {
|
||||
posisiOrganisasi.delete.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const templatePegawai = z.object({
|
||||
namaLengkap: z.string().min(1, "Nama wajib diisi"),
|
||||
gelarAkademik: z.string().min(1, "Gelar Akademik wajib diisi"),
|
||||
imageId: z.string().min(1, "Gambar wajib dipilih"),
|
||||
tanggalMasuk: z.string().min(1, "Tanggal masuk wajib diisi"), // ISO format
|
||||
email: z.string().email("Email tidak valid").optional(),
|
||||
telepon: z.string().min(1, "Telepom wajib diisi"),
|
||||
alamat: z.string().min(1, "Alamat wajib diisi"),
|
||||
posisiId: z.string().min(1, "Posisi wajib diisi"),
|
||||
isActive: z.boolean().default(true),
|
||||
});
|
||||
|
||||
const pegawaiDefaultForm = {
|
||||
namaLengkap: "",
|
||||
gelarAkademik: "",
|
||||
imageId: "",
|
||||
tanggalMasuk: "",
|
||||
email: "",
|
||||
telepon: "",
|
||||
alamat: "",
|
||||
posisiId: "",
|
||||
isActive: true,
|
||||
};
|
||||
|
||||
const pegawai = proxy({
|
||||
create: {
|
||||
form: { ...pegawaiDefaultForm },
|
||||
loading: false,
|
||||
async submit() {
|
||||
const cek = templatePegawai.safeParse(pegawai.create.form);
|
||||
if (!cek.success) {
|
||||
const err = cek.error.issues.map((i) => i.message).join("\n");
|
||||
toast.error(err);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
pegawai.create.loading = true;
|
||||
const res = await ApiFetch.api.ppid.strukturppid.pegawai[
|
||||
"create"
|
||||
].post(pegawai.create.form);
|
||||
if (res.status === 200) {
|
||||
toast.success("Pegawai berhasil ditambahkan");
|
||||
await pegawai.findMany.load();
|
||||
} else {
|
||||
toast.error(res.data?.message ?? "Gagal tambah pegawai");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Gagal create:", error);
|
||||
toast.error("Terjadi kesalahan saat menambahkan pegawai");
|
||||
} finally {
|
||||
pegawai.create.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
// In struktur-organisasi.ts
|
||||
findMany: {
|
||||
data: null as any[] | null,
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
total: 0,
|
||||
loading: false,
|
||||
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;
|
||||
try {
|
||||
const res = await ApiFetch.api.ppid.strukturppid.pegawai[
|
||||
"find-many"
|
||||
].get({
|
||||
query: { page, limit },
|
||||
});
|
||||
|
||||
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.total = 0;
|
||||
pegawai.findMany.totalPages = 1;
|
||||
} finally {
|
||||
pegawai.findMany.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
findUnique: {
|
||||
data: null as
|
||||
| (Prisma.PegawaiGetPayload<{
|
||||
include: { posisi: true; image: true };
|
||||
}> & { isActive: boolean })
|
||||
| null,
|
||||
async load(id: string) {
|
||||
const res = await fetch(`/api/ppid/strukturppid/pegawai/${id}`);
|
||||
if (res.ok) {
|
||||
const json = await res.json();
|
||||
pegawai.findUnique.data = json.data
|
||||
? {
|
||||
...json.data,
|
||||
isActive: json.data.isActive ?? json.data.aktif ?? true, // Fallback ke aktif:true jika tidak ada data
|
||||
}
|
||||
: null;
|
||||
} else {
|
||||
pegawai.findUnique.data = null;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
delete: {
|
||||
loading: false,
|
||||
async byId(id: string) {
|
||||
if (!id) return toast.warn("ID tidak valid");
|
||||
try {
|
||||
pegawai.delete.loading = true;
|
||||
const res = await fetch(
|
||||
`/api/ppid/strukturppid/pegawai/del/${id}`,
|
||||
{
|
||||
method: "DELETE",
|
||||
}
|
||||
);
|
||||
const json = await res.json();
|
||||
if (res.ok) {
|
||||
toast.success(json.message ?? "Berhasil hapus pegawai");
|
||||
await pegawai.findMany.load();
|
||||
} else {
|
||||
toast.error(json.message ?? "Gagal hapus pegawai");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Gagal delete:", error);
|
||||
toast.error("Terjadi kesalahan saat menghapus");
|
||||
} finally {
|
||||
pegawai.delete.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
edit: {
|
||||
id: "",
|
||||
form: { ...pegawaiDefaultForm },
|
||||
loading: false,
|
||||
|
||||
async load(id: string) {
|
||||
if (!id) {
|
||||
toast.warn("ID tidak valid");
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/api/ppid/strukturppid/pegawai/${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 = {
|
||||
namaLengkap: data.namaLengkap,
|
||||
gelarAkademik: data.gelarAkademik,
|
||||
imageId: data.imageId,
|
||||
tanggalMasuk: data.tanggalMasuk,
|
||||
email: data.email,
|
||||
telepon: data.telepon,
|
||||
alamat: data.alamat,
|
||||
posisiId: data.posisiId,
|
||||
isActive: data.isActive,
|
||||
};
|
||||
return data; // Return the loaded data
|
||||
} else {
|
||||
throw new Error(result?.message || "Gagal memuat data");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading berita:", error);
|
||||
toast.error(
|
||||
error instanceof Error ? error.message : "Gagal memuat data"
|
||||
);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
async submit() {
|
||||
const cek = templatePegawai.safeParse(pegawai.edit.form);
|
||||
if (!cek.success) {
|
||||
const err = `[${cek.error.issues
|
||||
.map((v) => `${v.path.join(".")}`)
|
||||
.join("\n")}] required`;
|
||||
toast.error(err);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
pegawai.edit.loading = true;
|
||||
|
||||
// Format tanggalMasuk to ISO string if it exists
|
||||
const formattedTanggalMasuk = this.form.tanggalMasuk
|
||||
? new Date(this.form.tanggalMasuk).toISOString()
|
||||
: undefined;
|
||||
|
||||
const response = await fetch(
|
||||
`/api/ppid/strukturppid/pegawai/${this.id}`,
|
||||
{
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
id: this.id,
|
||||
namaLengkap: this.form.namaLengkap,
|
||||
gelarAkademik: this.form.gelarAkademik,
|
||||
imageId: this.form.imageId || null,
|
||||
tanggalMasuk: formattedTanggalMasuk,
|
||||
email: this.form.email,
|
||||
telepon: this.form.telepon,
|
||||
alamat: this.form.alamat,
|
||||
posisiId: this.form.posisiId,
|
||||
isActive: this.form.isActive,
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
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 pegawai");
|
||||
await pegawai.findMany.load(); // refresh list
|
||||
return true;
|
||||
} else {
|
||||
throw new Error(result.message || "Gagal update pegawai");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error updating pegawai:", error);
|
||||
toast.error(
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Terjadi kesalahan saat update pegawai"
|
||||
);
|
||||
return false;
|
||||
} finally {
|
||||
pegawai.edit.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
reset() {
|
||||
pegawai.edit.id = "";
|
||||
pegawai.edit.form = { ...pegawaiDefaultForm };
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const stateStrukturPPID = proxy({
|
||||
stateStruktur,
|
||||
posisiOrganisasi,
|
||||
pegawai
|
||||
});
|
||||
|
||||
export default stateStrukturPPID;
|
||||
|
||||
456
src/app/admin/(dashboard)/_state/user/user-state.ts
Normal file
456
src/app/admin/(dashboard)/_state/user/user-state.ts
Normal file
@@ -0,0 +1,456 @@
|
||||
import { proxy } from 'valtio'
|
||||
import { toast } from 'react-toastify'
|
||||
import ApiFetch from '@/lib/api-fetch'
|
||||
import { Prisma } from '@prisma/client'
|
||||
import { z } from 'zod'
|
||||
|
||||
// 1. Validasi Zod
|
||||
const userSchema = z.object({
|
||||
nama: z.string().min(1, 'Nama harus diisi'),
|
||||
email: z.string().email('Email tidak valid'),
|
||||
password: z.string().min(6, 'Password minimal 6 karakter'),
|
||||
roleId: z.string().optional(),
|
||||
})
|
||||
|
||||
const defaultForm = { nama: '', email: '', password: '', roleId: '' }
|
||||
|
||||
// 2. State Valtio
|
||||
const userState = proxy({
|
||||
// Register
|
||||
register: {
|
||||
form: { ...defaultForm },
|
||||
loading: false,
|
||||
async submit() {
|
||||
const valid = userSchema.omit({ roleId: true }).safeParse(userState.register.form)
|
||||
if (!valid.success) {
|
||||
const err = valid.error.issues.map(i => i.message).join(', ')
|
||||
return toast.error(err)
|
||||
}
|
||||
try {
|
||||
userState.register.loading = true
|
||||
const res = await ApiFetch.api.user.register.post(userState.register.form)
|
||||
if (res.status === 200) {
|
||||
toast.success('Registrasi berhasil, silakan login')
|
||||
userState.register.form = { ...defaultForm } // reset
|
||||
} else {
|
||||
toast.error(res.data?.message || 'Gagal registrasi')
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
toast.error('Terjadi kesalahan saat registrasi')
|
||||
} finally {
|
||||
userState.register.loading = false
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
// Login
|
||||
login: {
|
||||
form: { email: '', password: '' },
|
||||
loading: false,
|
||||
async submit() {
|
||||
try {
|
||||
userState.login.loading = true
|
||||
const res = await ApiFetch.api.user.login.post(userState.login.form)
|
||||
if (res.status === 200) {
|
||||
toast.success('Login berhasil')
|
||||
const token = res.data?.data?.token
|
||||
if (typeof token === 'string') {
|
||||
localStorage.setItem('token', token)
|
||||
// Optional: simpan user role untuk otorisasi
|
||||
const user = res.data?.data?.user
|
||||
localStorage.setItem('user', JSON.stringify(user))
|
||||
}
|
||||
} else {
|
||||
toast.error(res.data?.message || 'Login gagal')
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
toast.error('Terjadi kesalahan saat login')
|
||||
} finally {
|
||||
userState.login.loading = false
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
// CRUD User (untuk admin)
|
||||
create: {
|
||||
form: { ...defaultForm },
|
||||
loading: false,
|
||||
async create(isAdmin = false) {
|
||||
const valid = userSchema.safeParse(userState.create.form)
|
||||
if (!valid.success) {
|
||||
const err = valid.error.issues.map(i => i.message).join(', ')
|
||||
return toast.error(err)
|
||||
}
|
||||
try {
|
||||
userState.create.loading = true
|
||||
const endpoint = isAdmin ? 'create' : 'register'
|
||||
const res = await ApiFetch.api.user[endpoint].post(userState.create.form)
|
||||
if (res.status === 200) {
|
||||
toast.success('User berhasil dibuat')
|
||||
userState.findMany.load() // refresh list
|
||||
userState.create.form = { ...defaultForm } // reset form
|
||||
} else {
|
||||
toast.error(res.data?.message || 'Gagal membuat user')
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
toast.error('Gagal membuat user')
|
||||
} finally {
|
||||
userState.create.loading = false
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
// Find Many
|
||||
findMany: {
|
||||
data: [] as Prisma.UserGetPayload<{ include: { role: true } }>[],
|
||||
loading: false,
|
||||
async load() {
|
||||
this.loading = true
|
||||
try {
|
||||
const res = await ApiFetch.api.user.findMany.get()
|
||||
if (res.status === 200) {
|
||||
this.data = res.data?.data || []
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
toast.error('Gagal muat data user')
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
// Find Unique
|
||||
findUnique: {
|
||||
data: null as Prisma.UserGetPayload<{ include: { role: true } }> | null,
|
||||
loading: false,
|
||||
async load(id: string) {
|
||||
this.loading = true
|
||||
try {
|
||||
const res = await fetch(`/api/user/findUnique/${id}`)
|
||||
const data = await res.json()
|
||||
if (res.status === 200) {
|
||||
this.data = data.data
|
||||
} else {
|
||||
toast.error(data.message)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
toast.error('Gagal ambil data user')
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
// Update
|
||||
update: {
|
||||
id: '',
|
||||
form: { ...defaultForm },
|
||||
loading: false,
|
||||
async load(id: string) {
|
||||
this.loading = true
|
||||
try {
|
||||
const res = await fetch(`/api/user/findUnique/${id}`)
|
||||
const data = await res.json()
|
||||
if (res.status === 200) {
|
||||
const user = data.data
|
||||
this.id = user.id
|
||||
this.form = {
|
||||
nama: user.nama,
|
||||
email: user.email,
|
||||
password: '', // jangan kirim password lama
|
||||
roleId: user.roleId,
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
toast.error('Gagal muat data')
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
async submit() {
|
||||
this.loading = true
|
||||
try {
|
||||
const res = await fetch(`/api/user/update/${this.id}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(this.form),
|
||||
})
|
||||
const data = await res.json()
|
||||
if (res.status === 200) {
|
||||
toast.success('Update berhasil')
|
||||
userState.findMany.load()
|
||||
} else {
|
||||
toast.error(data.message || 'Gagal update')
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
toast.error('Gagal update user')
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
// Delete (Soft Delete)
|
||||
delete: {
|
||||
loading: false,
|
||||
async submit(id: string) {
|
||||
this.loading = true
|
||||
try {
|
||||
const res = await fetch(`/api/user/del/${id}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
const data = await res.json()
|
||||
if (res.status === 200) {
|
||||
toast.success('User dinonaktifkan')
|
||||
userState.findMany.load()
|
||||
} else {
|
||||
toast.error(data.message || 'Gagal hapus')
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
toast.error('Gagal hapus user')
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const templateRole = z.object({
|
||||
name: z.string().min(1, "Nama harus diisi"),
|
||||
});
|
||||
|
||||
const defaultRole = {
|
||||
name: "",
|
||||
};
|
||||
|
||||
const roleState = proxy({
|
||||
create: {
|
||||
form: { ...defaultRole },
|
||||
loading: false,
|
||||
async create() {
|
||||
const cek = templateRole.safeParse(roleState.create.form);
|
||||
if (!cek.success) {
|
||||
const err = `[${cek.error.issues
|
||||
.map((v) => `${v.path.join(".")}`)
|
||||
.join("\n")}] required`;
|
||||
return toast.error(err);
|
||||
}
|
||||
|
||||
try {
|
||||
roleState.create.loading = true;
|
||||
const res =
|
||||
await ApiFetch.api.role[
|
||||
"create"
|
||||
].post(roleState.create.form);
|
||||
if (res.status === 200) {
|
||||
roleState.findMany.load();
|
||||
return toast.success("Data role Berhasil Dibuat");
|
||||
}
|
||||
console.log(res);
|
||||
return toast.error("failed create");
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return toast.error("failed create");
|
||||
} finally {
|
||||
roleState.create.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
findMany: {
|
||||
data: [] as Prisma.RoleGetPayload<{
|
||||
omit: {
|
||||
isActive: true;
|
||||
};
|
||||
}>[],
|
||||
loading: false,
|
||||
async load() {
|
||||
const res =
|
||||
await ApiFetch.api.role[
|
||||
"findMany"
|
||||
].get();
|
||||
if (res.status === 200) {
|
||||
roleState.findMany.data = res.data?.data ?? [];
|
||||
}
|
||||
},
|
||||
},
|
||||
findUnique: {
|
||||
data: null as Prisma.RoleGetPayload<{
|
||||
omit: {
|
||||
isActive: true;
|
||||
};
|
||||
}> | null,
|
||||
loading: false,
|
||||
async load(id: string) {
|
||||
try {
|
||||
const res = await fetch(
|
||||
`/api/role/${id}`
|
||||
);
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
roleState.findUnique.data = data.data ?? null;
|
||||
} else {
|
||||
console.error("Failed to fetch data", res.status, res.statusText);
|
||||
roleState.findUnique.data = null;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching data:", error);
|
||||
roleState.findUnique.data = null;
|
||||
}
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
loading: false,
|
||||
async delete(id: string) {
|
||||
if (!id) return toast.warn("ID tidak valid");
|
||||
|
||||
try {
|
||||
roleState.delete.loading = true;
|
||||
|
||||
const response = await fetch(
|
||||
`/api/role/del/${id}`,
|
||||
{
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (response.ok && result?.success) {
|
||||
toast.success(
|
||||
result.message || "Data role berhasil dihapus"
|
||||
);
|
||||
await roleState.findMany.load(); // refresh list
|
||||
} else {
|
||||
toast.error(result?.message || "Gagal menghapus Data role");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Gagal delete:", error);
|
||||
toast.error("Terjadi kesalahan saat menghapus Data role");
|
||||
} finally {
|
||||
roleState.delete.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
update: {
|
||||
id: "",
|
||||
form: { ...defaultRole },
|
||||
loading: false,
|
||||
async load(id: string) {
|
||||
if (!id) {
|
||||
toast.warn("ID tidak valid");
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/api/role/${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,
|
||||
};
|
||||
return data; // Return the loaded data
|
||||
} else {
|
||||
throw new Error(result?.message || "Gagal memuat data");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading role:", error);
|
||||
toast.error(
|
||||
error instanceof Error ? error.message : "Gagal memuat data"
|
||||
);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
async update() {
|
||||
const cek = templateRole.safeParse(roleState.update.form);
|
||||
if (!cek.success) {
|
||||
const err = `[${cek.error.issues
|
||||
.map((v) => `${v.path.join(".")}`)
|
||||
.join("\n")}] required`;
|
||||
toast.error(err);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
roleState.update.loading = true;
|
||||
|
||||
const response = await fetch(
|
||||
`/api/role/${this.id}`,
|
||||
{
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: this.form.name,
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
throw new Error(
|
||||
errorData.message || `HTTP error! status: ${response.status}`
|
||||
);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
toast.success("Berhasil update data role");
|
||||
await roleState.findMany.load(); // refresh list
|
||||
return true;
|
||||
} else {
|
||||
throw new Error(result.message || "Gagal update data role");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error updating data role:", error);
|
||||
toast.error(
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Terjadi kesalahan saat update data role"
|
||||
);
|
||||
return false;
|
||||
} finally {
|
||||
roleState.update.loading = false;
|
||||
}
|
||||
},
|
||||
reset() {
|
||||
roleState.update.id = "";
|
||||
roleState.update.form = { ...defaultRole };
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const user = proxy({
|
||||
userState,
|
||||
roleState,
|
||||
})
|
||||
|
||||
export default user
|
||||
@@ -148,7 +148,6 @@ function Page() {
|
||||
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
||||
<Box w={{ base: '100%', md: '50%' }}>
|
||||
<Dropzone
|
||||
|
||||
onDrop={(files) => {
|
||||
const newImages = files.map((file) => ({
|
||||
file,
|
||||
|
||||
@@ -12,17 +12,17 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
|
||||
{
|
||||
label: "Posisi Organisasi",
|
||||
value: "posisiorganisasi",
|
||||
href: "/admin/ekonomi/struktur-organisasi-dan-sk-pengurus-bumdesa/posisi-organisasi"
|
||||
href: "/admin/ekonomi/Struktur-Organisasi-dan-SK-Pengurus-BUMDesa/posisi-organisasi"
|
||||
},
|
||||
{
|
||||
label: "Pegawai",
|
||||
value: "pegawai",
|
||||
href: "/admin/ekonomi/struktur-organisasi-dan-sk-pengurus-bumdesa/pegawai"
|
||||
href: "/admin/ekonomi/Struktur-Organisasi-dan-SK-Pengurus-BUMDesa/pegawai"
|
||||
},
|
||||
{
|
||||
label: "Hubungan Organisasi",
|
||||
value: "hubunganorganisasi",
|
||||
href: "/admin/ekonomi/struktur-organisasi-dan-sk-pengurus-bumdesa/hubungan-organisasi"
|
||||
href: "/admin/ekonomi/Struktur-Organisasi-dan-SK-Pengurus-BUMDesa/hubungan-organisasi"
|
||||
},
|
||||
];
|
||||
const curentTab = tabs.find(tab => tab.href === pathname)
|
||||
|
||||
@@ -126,7 +126,7 @@ export default function EditPegawai() {
|
||||
const success = await stateOrganisasi.edit.submit();
|
||||
|
||||
if (success) {
|
||||
router.push("/admin/ekonomi/struktur-organisasi-dan-sk-pengurus-bumdesa/pegawai");
|
||||
router.push("/admin/ekonomi/Struktur-Organisasi-dan-SK-Pengurus-BUMDesa/pegawai");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error updating pegawai:", error);
|
||||
|
||||
@@ -25,7 +25,7 @@ function DetailPegawai() {
|
||||
statePegawai.delete.byId(selectedId)
|
||||
setModalHapus(false)
|
||||
setSelectedId(null)
|
||||
router.push("/admin/ekonomi/struktur-organisasi-dan-sk-pengurus-bumdesa/pegawai")
|
||||
router.push("/admin/ekonomi/Struktur-Organisasi-dan-SK-Pengurus-BUMDesa/pegawai")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,7 +120,7 @@ function DetailPegawai() {
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (statePegawai.findUnique.data) {
|
||||
router.push(`/admin/ekonomi/struktur-organisasi-dan-sk-pengurus-bumdesa/pegawai/${statePegawai.findUnique.data.id}/edit`);
|
||||
router.push(`/admin/ekonomi/Struktur-Organisasi-dan-SK-Pengurus-BUMDesa/pegawai/${statePegawai.findUnique.data.id}/edit`);
|
||||
}
|
||||
}}
|
||||
disabled={!statePegawai.findUnique.data}
|
||||
|
||||
@@ -64,7 +64,7 @@ function CreatePegawai() {
|
||||
// Reset form dan redirect
|
||||
resetForm();
|
||||
toast.success("Data pegawai berhasil ditambahkan");
|
||||
router.push("/admin/ekonomi/struktur-organisasi-dan-sk-pengurus-bumdesa/pegawai");
|
||||
router.push("/admin/ekonomi/Struktur-Organisasi-dan-SK-Pengurus-BUMDesa/pegawai");
|
||||
} catch (error) {
|
||||
console.error("Error creating pegawai:", error);
|
||||
toast.error("Terjadi kesalahan saat menambahkan pegawai");
|
||||
|
||||
@@ -78,7 +78,7 @@ function ListPegawai({ search }: { search: string }) {
|
||||
<Paper bg={colors['white-1']} p={'md'} h={{base: 770, md: 650}}>
|
||||
<JudulList
|
||||
title='List Pegawai'
|
||||
href='/admin/ekonomi/struktur-organisasi-dan-sk-pengurus-bumdesa/pegawai/create'
|
||||
href='/admin/ekonomi/Struktur-Organisasi-dan-SK-Pengurus-BUMDesa/pegawai/create'
|
||||
/>
|
||||
<Box style={{ overflowX: "auto" }}>
|
||||
<Table striped withTableBorder withRowBorders>
|
||||
@@ -131,7 +131,7 @@ function ListPegawai({ search }: { search: string }) {
|
||||
</Group>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Button bg={"green"} onClick={() => router.push(`/admin/ekonomi/struktur-organisasi-dan-sk-pengurus-bumdesa/pegawai/${item.id}`)}>
|
||||
<Button bg={"green"} onClick={() => router.push(`/admin/ekonomi/Struktur-Organisasi-dan-SK-Pengurus-BUMDesa/pegawai/${item.id}`)}>
|
||||
<IconDeviceImacCog size={25} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
|
||||
@@ -68,7 +68,7 @@ function EditPosisiOrganisasi() {
|
||||
const success = await stateOrganisasi.edit.update();
|
||||
|
||||
if (success) {
|
||||
router.push("/admin/ekonomi/struktur-organisasi-dan-sk-pengurus-bumdesa/posisi-organisasi");
|
||||
router.push("/admin/ekonomi/Struktur-Organisasi-dan-SK-Pengurus-BUMDesa/posisi-organisasi");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error updating posisi organisasi:", error);
|
||||
|
||||
@@ -27,7 +27,7 @@ function CreatePosisiOrganisasi() {
|
||||
const handleSubmit = async () => {
|
||||
await stateOrganisasi.create.submit();
|
||||
resetForm();
|
||||
router.push("/admin/ekonomi/struktur-organisasi-dan-sk-pengurus-bumdesa/posisi-organisasi")
|
||||
router.push("/admin/ekonomi/Struktur-Organisasi-dan-SK-Pengurus-BUMDesa/posisi-organisasi")
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -67,7 +67,7 @@ function ListPosisiOrganisasi({ search }: { search: string }) {
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
<JudulList
|
||||
title='List Posisi Organisasi'
|
||||
href='/admin/ekonomi/struktur-organisasi-dan-sk-pengurus-bumdesa/posisi-organisasi/create'
|
||||
href='/admin/ekonomi/Struktur-Organisasi-dan-SK-Pengurus-BUMDesa/posisi-organisasi/create'
|
||||
/>
|
||||
<Table striped withTableBorder withRowBorders>
|
||||
<TableThead>
|
||||
@@ -91,7 +91,7 @@ function ListPosisiOrganisasi({ search }: { search: string }) {
|
||||
<Button color="green"
|
||||
onClick={() => {
|
||||
if (item) {
|
||||
router.push(`/admin/ekonomi/struktur-organisasi-dan-sk-pengurus-bumdesa/posisi-organisasi/${item.id}`);
|
||||
router.push(`/admin/ekonomi/Struktur-Organisasi-dan-SK-Pengurus-BUMDesa/posisi-organisasi/${item.id}`);
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -21,7 +21,7 @@ function KeamananLingkungan() {
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||
/>
|
||||
<ListKeamananLingkungan search={search}/>
|
||||
<ListKeamananLingkungan search={search} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
|
||||
import { IconEdit, IconSearch, IconX } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import HeaderSearch from '../../../_com/header';
|
||||
import JudulList from '../../../_com/judulList';
|
||||
@@ -34,6 +34,14 @@ function ListKategoriKegiatan({ search }: { search: string }) {
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||
const router = useRouter()
|
||||
|
||||
const {
|
||||
data,
|
||||
page,
|
||||
totalPages,
|
||||
loading,
|
||||
load,
|
||||
} = stateKategori.findMany;
|
||||
|
||||
const handleHapus = () => {
|
||||
if (selectedId) {
|
||||
stateKategori.delete.byId(selectedId)
|
||||
@@ -42,23 +50,51 @@ function ListKategoriKegiatan({ search }: { search: string }) {
|
||||
}
|
||||
}
|
||||
|
||||
useShallowEffect(() => {
|
||||
stateKategori.findMany.load()
|
||||
}, [])
|
||||
useEffect(() => {
|
||||
load(page, 10)
|
||||
}, [page])
|
||||
|
||||
const filteredData = (stateKategori.findMany.data || []).filter(item => {
|
||||
const keyword = search.toLowerCase();
|
||||
return (
|
||||
item.name.toLowerCase().includes(keyword)
|
||||
);
|
||||
});
|
||||
const filteredData = useMemo(() => {
|
||||
if (!data) return [];
|
||||
return data.filter(item => {
|
||||
const keyword = search.toLowerCase();
|
||||
return (
|
||||
item.name?.toLowerCase().includes(keyword)
|
||||
);
|
||||
})
|
||||
}, [data, search]);
|
||||
|
||||
if (!stateKategori.findMany.data) {
|
||||
// Handle loading state
|
||||
if (loading || !data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Skeleton h={500} />
|
||||
<Skeleton height={300} />
|
||||
</Stack>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (data.length === 0) {
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
<JudulList
|
||||
title='List Kategori Kegiatan'
|
||||
href='/admin/landing-page/desa-anti-korupsi/kategori-desa-anti-korupsi/create'
|
||||
/>
|
||||
<Box style={{ overflowX: "auto" }}>
|
||||
<Table striped withTableBorder withRowBorders>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>Nama</TableTh>
|
||||
<TableTh>Edit</TableTh>
|
||||
<TableTh>Delete</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
</Table>
|
||||
</Box>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -68,36 +104,50 @@ function ListKategoriKegiatan({ search }: { search: string }) {
|
||||
title='List Kategori Kegiatan'
|
||||
href='/admin/landing-page/desa-anti-korupsi/kategori-desa-anti-korupsi/create'
|
||||
/>
|
||||
<Table striped withTableBorder withRowBorders>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>Nama Kategori</TableTh>
|
||||
<TableTh>Edit</TableTh>
|
||||
<TableTh>Delete</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>{item.name}</TableTd>
|
||||
<TableTd>
|
||||
<Button color="green" onClick={() => router.push(`/admin/landing-page/desa-anti-korupsi/kategori-desa-anti-korupsi/${item.id}`)}>
|
||||
<IconEdit size={20} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Button color="red" onClick={() => {
|
||||
setSelectedId(item.id)
|
||||
setModalHapus(true)
|
||||
}}>
|
||||
<IconX size={20} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
<Box style={{ overflowY: "auto" }}>
|
||||
<Table striped withTableBorder withRowBorders>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>Nama Kategori</TableTh>
|
||||
<TableTh>Edit</TableTh>
|
||||
<TableTh>Delete</TableTh>
|
||||
</TableTr>
|
||||
))}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>{item.name}</TableTd>
|
||||
<TableTd>
|
||||
<Button color="green" onClick={() => router.push(`/admin/landing-page/desa-anti-korupsi/kategori-desa-anti-korupsi/${item.id}`)}>
|
||||
<IconEdit size={20} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Button color="red" onClick={() => {
|
||||
setSelectedId(item.id)
|
||||
setModalHapus(true)
|
||||
}}>
|
||||
<IconX size={20} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Box>
|
||||
</Paper>
|
||||
<Center>
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={(newPage) => {
|
||||
load(newPage, 10);
|
||||
window.scrollTo(0, 0);
|
||||
}}
|
||||
total={totalPages}
|
||||
mt="md"
|
||||
mb="md"
|
||||
/>
|
||||
</Center>
|
||||
{/* Modal Konfirmasi Hapus */}
|
||||
<ModalKonfirmasiHapus
|
||||
opened={modalHapus}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import HeaderSearch from '../../../_com/header';
|
||||
import JudulList from '../../../_com/judulList';
|
||||
@@ -29,27 +29,65 @@ function DesaAntiKorupsi() {
|
||||
function ListDesaAntiKorupsi({ search }: { search: string }) {
|
||||
const listState = useProxy(korupsiState.desaAntikorupsi)
|
||||
const router = useRouter();
|
||||
|
||||
const {
|
||||
data,
|
||||
page,
|
||||
totalPages,
|
||||
loading,
|
||||
load,
|
||||
} = listState.findMany;
|
||||
|
||||
useEffect(() => {
|
||||
listState.findMany.load()
|
||||
}, [])
|
||||
load(page, 10);
|
||||
}, [page]);
|
||||
|
||||
const filteredData = (listState.findMany.data || []).filter(item => {
|
||||
const keyword = search.toLowerCase();
|
||||
return (
|
||||
item.name.toLowerCase().includes(keyword) ||
|
||||
item.deskripsi.toLowerCase().includes(keyword) ||
|
||||
item.kategori?.name?.toLowerCase().includes(keyword)
|
||||
);
|
||||
});
|
||||
const filteredData = useMemo(() => {
|
||||
if (!data) return [];
|
||||
return data.filter(item => {
|
||||
const keyword = search.toLowerCase();
|
||||
return (
|
||||
item.name?.toLowerCase().includes(keyword) ||
|
||||
item.deskripsi?.toLowerCase().includes(keyword) ||
|
||||
item.kategori?.name?.toLowerCase().includes(keyword)
|
||||
);
|
||||
})
|
||||
.sort((a, b) => b.createdAt - a.createdAt);
|
||||
}, [data, search]);
|
||||
|
||||
if (!listState.findMany.data) {
|
||||
// Handle loading state
|
||||
if (loading || !data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Skeleton h={500} />
|
||||
<Skeleton height={300} />
|
||||
</Stack>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (data.length === 0) {
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
<JudulList
|
||||
title='List Desa Anti Korupsi'
|
||||
href='/admin/landing-page/desa-anti-korupsi/list-desa-anti-korupsi/create'
|
||||
/>
|
||||
<Box style={{ overflowX: "auto" }}>
|
||||
<Table striped withTableBorder withRowBorders>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>Nama</TableTh>
|
||||
<TableTh>Deskripsi</TableTh>
|
||||
<TableTh>Kategori</TableTh>
|
||||
<TableTh>Detail</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
</Table>
|
||||
</Box>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
@@ -77,7 +115,9 @@ function ListDesaAntiKorupsi({ search }: { search: string }) {
|
||||
</Box>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Text truncate="end" fz={"sm"} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
||||
<Box w={200}>
|
||||
<Text truncate="end" fz={"sm"} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
||||
</Box>
|
||||
</TableTd>
|
||||
<TableTd>{item.kategori?.name}</TableTd>
|
||||
<TableTd>
|
||||
@@ -92,6 +132,18 @@ function ListDesaAntiKorupsi({ search }: { search: string }) {
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
<Center>
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={(newPage) => {
|
||||
load(newPage, 10);
|
||||
window.scrollTo(0, 0);
|
||||
}}
|
||||
total={totalPages}
|
||||
mt="md"
|
||||
mb="md"
|
||||
/>
|
||||
</Center>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -68,35 +68,37 @@ function ListKategoriPrestasi({ search }: { search: string }) {
|
||||
title='List Kategori Prestasi Desa'
|
||||
href='/admin/landing-page/prestasi-desa/kategori-prestasi-desa/create'
|
||||
/>
|
||||
<Table striped withTableBorder withRowBorders>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>Nama Kategori</TableTh>
|
||||
<TableTh>Edit</TableTh>
|
||||
<TableTh>Delete</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>{item.name}</TableTd>
|
||||
<TableTd>
|
||||
<Button color="green" onClick={() => router.push(`/admin/landing-page/prestasi-desa/kategori-prestasi-desa/${item.id}`)}>
|
||||
<IconEdit size={20} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Button color="red" onClick={() => {
|
||||
setSelectedId(item.id)
|
||||
setModalHapus(true)
|
||||
}}>
|
||||
<IconX size={20} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
<Box style={{ overflowY: "auto" }}>
|
||||
<Table striped withTableBorder withRowBorders>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>Nama Kategori</TableTh>
|
||||
<TableTh>Edit</TableTh>
|
||||
<TableTh>Delete</TableTh>
|
||||
</TableTr>
|
||||
))}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>{item.name}</TableTd>
|
||||
<TableTd>
|
||||
<Button color="green" onClick={() => router.push(`/admin/landing-page/prestasi-desa/kategori-prestasi-desa/${item.id}`)}>
|
||||
<IconEdit size={20} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Button color="red" onClick={() => {
|
||||
setSelectedId(item.id)
|
||||
setModalHapus(true)
|
||||
}}>
|
||||
<IconX size={20} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Box>
|
||||
</Paper>
|
||||
{/* Modal Konfirmasi Hapus */}
|
||||
<ModalKonfirmasiHapus
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Image, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { Box, Button, Center, Image, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import HeaderSearch from '../../../_com/header';
|
||||
import JudulList from '../../../_com/judulList';
|
||||
@@ -30,24 +30,61 @@ function ListMediaSosial({ search }: { search: string }) {
|
||||
const stateMediaSosial = useProxy(profileLandingPageState.mediaSosial)
|
||||
const router = useRouter();
|
||||
|
||||
useShallowEffect(() => {
|
||||
stateMediaSosial.findMany.load()
|
||||
}, [])
|
||||
const {
|
||||
data,
|
||||
page,
|
||||
totalPages,
|
||||
loading,
|
||||
load,
|
||||
} = stateMediaSosial.findMany;
|
||||
|
||||
const filteredData = (stateMediaSosial.findMany.data || []).filter(item => {
|
||||
const keyword = search.toLowerCase();
|
||||
return (
|
||||
item.name.toLowerCase().includes(keyword) ||
|
||||
item.iconUrl?.toLowerCase().includes(keyword)
|
||||
);
|
||||
});
|
||||
useEffect(() => {
|
||||
load(page, 10)
|
||||
}, [page])
|
||||
|
||||
if (!stateMediaSosial.findMany.data) {
|
||||
const filteredData = useMemo(() => {
|
||||
if (!data) return [];
|
||||
return data.filter(item => {
|
||||
const keyword = search.toLowerCase();
|
||||
return (
|
||||
item.name?.toLowerCase().includes(keyword) ||
|
||||
item.iconUrl?.toLowerCase().includes(keyword)
|
||||
);
|
||||
})
|
||||
}, [data, search]);
|
||||
|
||||
// Handle loading state
|
||||
if (loading || !data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Skeleton h={500} />
|
||||
<Skeleton height={300} />
|
||||
</Stack>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (data.length === 0) {
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
<JudulList
|
||||
title='List Media Sosial'
|
||||
href='/admin/landing-page/profile/media-sosial/create'
|
||||
/>
|
||||
<Box style={{ overflowX: "auto" }}>
|
||||
<Table striped withTableBorder withRowBorders>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>Nama Media Sosial / Nama Kontak</TableTh>
|
||||
<TableTh>Image</TableTh>
|
||||
<TableTh>Icon URL / No Telephone</TableTh>
|
||||
<TableTh>Detail</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
</Table>
|
||||
</Box>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -57,35 +94,55 @@ function ListMediaSosial({ search }: { search: string }) {
|
||||
title='List Media Sosial'
|
||||
href='/admin/landing-page/profile/media-sosial/create'
|
||||
/>
|
||||
<Table striped withTableBorder withRowBorders>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>Nama Media Sosial / Nama Kontak</TableTh>
|
||||
<TableTh>Image</TableTh>
|
||||
<TableTh>Icon URL / No Telephone</TableTh>
|
||||
<TableTh>Detail</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>{item.name}</TableTd>
|
||||
<TableTd>
|
||||
<Box w={50} h={50}>
|
||||
<Image src={item.image?.link} alt={item.name} />
|
||||
</Box>
|
||||
</TableTd>
|
||||
<TableTd>{item.iconUrl}</TableTd>
|
||||
<TableTd>
|
||||
<Button onClick={() => router.push(`/admin/landing-page/profile/media-sosial/${item.id}`)}>
|
||||
<IconDeviceImac size={20} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
<Box style={{ overflowY: "auto" }}>
|
||||
<Table striped withTableBorder withRowBorders>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>Nama Media Sosial / Nama Kontak</TableTh>
|
||||
<TableTh>Image</TableTh>
|
||||
<TableTh>Icon URL / No Telephone</TableTh>
|
||||
<TableTh>Detail</TableTh>
|
||||
</TableTr>
|
||||
))}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>{item.name}</TableTd>
|
||||
<TableTd>
|
||||
<Box w={50} h={50}>
|
||||
<Image src={item.image?.link} alt={item.name} />
|
||||
</Box>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Box w={250}>
|
||||
<a style={{color: "black"}} href={item.iconUrl} target="_blank" rel="noopener noreferrer">
|
||||
<Text truncate fz={'sm'}>{item.iconUrl}</Text>
|
||||
</a>
|
||||
</Box>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Button onClick={() => router.push(`/admin/landing-page/profile/media-sosial/${item.id}`)}>
|
||||
<IconDeviceImac size={20} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Box>
|
||||
</Paper>
|
||||
<Center>
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={(newPage) => {
|
||||
load(newPage, 10);
|
||||
window.scrollTo(0, 0);
|
||||
}}
|
||||
total={totalPages}
|
||||
mt="md"
|
||||
mb="md"
|
||||
/>
|
||||
</Center>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -59,7 +59,13 @@ function DetailProgramInovasi() {
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fz={"lg"} fw={"bold"}>Link</Text>
|
||||
<Text fz={"lg"}>{stateProgramInovasi.findUnique.data?.link}</Text>
|
||||
<a
|
||||
href={stateProgramInovasi.findUnique.data?.link || "#"}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{stateProgramInovasi.findUnique.data?.link || "Tidak ada link"}
|
||||
</a>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fz={"lg"} fw={"bold"}>Gambar</Text>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import HeaderSearch from '../../../_com/header';
|
||||
import JudulList from '../../../_com/judulList';
|
||||
@@ -30,27 +30,62 @@ function ListProgramInovasi({ search }: { search: string }) {
|
||||
const stateProgramInovasi = useProxy(profileLandingPageState.programInovasi)
|
||||
const router = useRouter();
|
||||
|
||||
useShallowEffect(() => {
|
||||
stateProgramInovasi.findMany.load()
|
||||
}, [])
|
||||
const {
|
||||
data,
|
||||
page,
|
||||
totalPages,
|
||||
loading,
|
||||
load,
|
||||
} = stateProgramInovasi.findMany;
|
||||
|
||||
const filteredData = (stateProgramInovasi.findMany.data || []).filter(item => {
|
||||
const keyword = search.toLowerCase();
|
||||
return (
|
||||
item.name.toLowerCase().includes(keyword) ||
|
||||
item.description?.toLowerCase().includes(keyword) ||
|
||||
item.link?.toLowerCase().includes(keyword)
|
||||
);
|
||||
});
|
||||
useEffect(() => {
|
||||
load(page, 10);
|
||||
}, [page]);
|
||||
|
||||
if (!stateProgramInovasi.findMany.data) {
|
||||
const filteredData = useMemo(() => {
|
||||
if (!data) return [];
|
||||
return data.filter(item => {
|
||||
const keyword = search.toLowerCase();
|
||||
return (
|
||||
item.name?.toLowerCase().includes(keyword) ||
|
||||
item.description?.toLowerCase().includes(keyword) ||
|
||||
item.link?.toLowerCase().includes(keyword)
|
||||
);
|
||||
})
|
||||
}, [data, search]);
|
||||
|
||||
if (loading || !data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Skeleton h={500} />
|
||||
<Skeleton height={300} />
|
||||
</Stack>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (data.length === 0) {
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
<JudulList
|
||||
title='List Program Inovasi'
|
||||
href='/admin/landing-page/profile/program-inovasi/create'
|
||||
/>
|
||||
<Box style={{ overflowX: "auto" }}>
|
||||
<Table striped withTableBorder withRowBorders>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>Nama Program</TableTh>
|
||||
<TableTh>Deskripsi</TableTh>
|
||||
<TableTh>Link</TableTh>
|
||||
<TableTh>Detail</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
</Table>
|
||||
</Box>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
@@ -58,31 +93,51 @@ function ListProgramInovasi({ search }: { search: string }) {
|
||||
title='List Program Inovasi'
|
||||
href='/admin/landing-page/profile/program-inovasi/create'
|
||||
/>
|
||||
<Table striped withTableBorder withRowBorders>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>Nama Program</TableTh>
|
||||
<TableTh>Deskripsi</TableTh>
|
||||
<TableTh>Link</TableTh>
|
||||
<TableTh>Detail</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>{item.name}</TableTd>
|
||||
<TableTd>{item.description}</TableTd>
|
||||
<TableTd>{item.link}</TableTd>
|
||||
<TableTd>
|
||||
<Button onClick={() => router.push(`/admin/landing-page/profile/program-inovasi/${item.id}`)}>
|
||||
<IconDeviceImac size={20} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
<Box style={{ overflowY: "auto" }}>
|
||||
<Table striped withTableBorder withRowBorders>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>Nama Program</TableTh>
|
||||
<TableTh>Deskripsi</TableTh>
|
||||
<TableTh>Link</TableTh>
|
||||
<TableTh>Detail</TableTh>
|
||||
</TableTr>
|
||||
))}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>{item.name}</TableTd>
|
||||
<TableTd w={200}>{item.description}</TableTd>
|
||||
<TableTd>
|
||||
<Box w={250}>
|
||||
<a style={{ color: "black" }} href={item.link} target="_blank" rel="noopener noreferrer">
|
||||
<Text truncate fz={'sm'}>{item.link}</Text>
|
||||
</a>
|
||||
</Box>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Button onClick={() => router.push(`/admin/landing-page/profile/program-inovasi/${item.id}`)}>
|
||||
<IconDeviceImac size={20} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Box>
|
||||
</Paper>
|
||||
<Center>
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={(newPage) => {
|
||||
load(newPage, 10);
|
||||
window.scrollTo(0, 0);
|
||||
}}
|
||||
total={totalPages}
|
||||
mt="md"
|
||||
mb="md"
|
||||
/>
|
||||
</Center>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 LayoutTabs({ children }: { children: React.ReactNode }) {
|
||||
const router = useRouter()
|
||||
const pathname = usePathname()
|
||||
const tabs = [
|
||||
{
|
||||
label: "Beasiswa Pendaftar",
|
||||
value: "beasiswa-pendaftar",
|
||||
href: "/admin/pendidikan/beasiswa-desa/beasiswa-pendaftar"
|
||||
},
|
||||
{
|
||||
label: "Keunggulan Program",
|
||||
value: "keunggulan-program",
|
||||
href: "/admin/pendidikan/beasiswa-desa/keunggulan-program"
|
||||
},
|
||||
|
||||
];
|
||||
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}>Beasiswa Desa</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 LayoutTabs;
|
||||
@@ -0,0 +1,137 @@
|
||||
'use client'
|
||||
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
||||
import beasiswaDesaState from '@/app/admin/(dashboard)/_state/pendidikan/beasiswa-desa';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Flex, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconArrowBack, IconX } from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
|
||||
function DetailBeasiswaPendaftar() {
|
||||
const state = useProxy(beasiswaDesaState.beasiswaPendaftar)
|
||||
const [modalHapus, setModalHapus] = useState(false);
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||
const router = useRouter()
|
||||
const params = useParams()
|
||||
|
||||
useShallowEffect(() => {
|
||||
state.findUnique.load(params?.id as string)
|
||||
}, [])
|
||||
|
||||
const handleHapus = () => {
|
||||
if (selectedId) {
|
||||
state.delete.delete(selectedId)
|
||||
setModalHapus(false)
|
||||
setSelectedId(null)
|
||||
router.push("/admin/pendidikan/beasiswa-desa/beasiswa-pendaftar")
|
||||
}
|
||||
}
|
||||
|
||||
if (!state.findUnique.data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<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 bg={colors['white-1']} w={{ base: "100%", md: "100%", lg: "50%" }} p={'md'}>
|
||||
<Stack>
|
||||
<Flex justify="space-between" gap={"xs"}>
|
||||
<Text fz={"xl"} fw={"bold"}>Detail Beasiswa Pendaftar</Text>
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (state.findUnique.data) {
|
||||
setSelectedId(state.findUnique.data.id);
|
||||
setModalHapus(true);
|
||||
}
|
||||
}}
|
||||
disabled={state.delete.loading || !state.findUnique.data}
|
||||
color={"red"}
|
||||
>
|
||||
<IconX size={20} />
|
||||
</Button>
|
||||
</Flex>
|
||||
{state.findUnique.data ? (
|
||||
<Paper key={state.findUnique.data.id} bg={colors['BG-trans']} p={'md'}>
|
||||
<Stack gap={"xs"}>
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"lg"}>Nama Lengkap</Text>
|
||||
<Text fz={"lg"}>{state.findUnique.data?.namaLengkap}</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"lg"}>NIK</Text>
|
||||
<Text fz={"lg"}>{state.findUnique.data?.nik}</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"lg"}>Tempat Lahir</Text>
|
||||
<Text fz={"lg"}>{state.findUnique.data?.tempatLahir}</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"lg"}>Tanggal Lahir</Text>
|
||||
<Text fz={"lg"}>{state.findUnique.data?.tanggalLahir ? new Date(state.findUnique.data.tanggalLahir).toLocaleDateString() : '-'}</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"lg"}>Jenis Kelamin</Text>
|
||||
<Text fz={"lg"}>{state.findUnique.data?.jenisKelamin}</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"lg"}>Kewarganegaraan</Text>
|
||||
<Text fz={"lg"}>{state.findUnique.data?.kewarganegaraan}</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"lg"}>Agama</Text>
|
||||
<Text fz={"lg"}>{state.findUnique.data?.agama}</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"lg"}>Alamat KTP</Text>
|
||||
<Text fz={"lg"}>{state.findUnique.data?.alamatKTP}</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"lg"}>Alamat Domisili</Text>
|
||||
<Text fz={"lg"}>{state.findUnique.data?.alamatDomisili}</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"lg"}>No HP</Text>
|
||||
<Text fz={"lg"}>{state.findUnique.data?.noHp}</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"lg"}>Email</Text>
|
||||
<Text fz={"lg"}>{state.findUnique.data?.email}</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"lg"}>Status Pernikahan</Text>
|
||||
<Text fz={"lg"}>{state.findUnique.data?.statusPernikahan}</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"lg"}>Ukuran Baju</Text>
|
||||
<Text fz={"lg"}>{state.findUnique.data?.ukuranBaju}</Text>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
) : null}
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
{/* Modal Konfirmasi Hapus */}
|
||||
<ModalKonfirmasiHapus
|
||||
opened={modalHapus}
|
||||
onClose={() => setModalHapus(false)}
|
||||
onConfirm={handleHapus}
|
||||
text='Apakah anda yakin ingin menghapus beasiswa desa ini?'
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default DetailBeasiswaPendaftar;
|
||||
@@ -0,0 +1,107 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
|
||||
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import HeaderSearch from '../../../_com/header';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import beasiswaDesaState from '../../../_state/pendidikan/beasiswa-desa';
|
||||
|
||||
|
||||
function BeasiswaPendaftar() {
|
||||
const [search, setSearch] = useState('');
|
||||
return (
|
||||
<Box>
|
||||
<HeaderSearch
|
||||
title='Beasiswa Pendaftar'
|
||||
placeholder='pencarian'
|
||||
searchIcon={<IconSearch size={20} />}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||
/>
|
||||
<ListBeasiswaPendaftar search={search} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function ListBeasiswaPendaftar({ search }: { search: string }) {
|
||||
const listDataState = useProxy(beasiswaDesaState.beasiswaPendaftar)
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
listDataState.findMany.load()
|
||||
}, [])
|
||||
|
||||
const filteredData = (listDataState.findMany.data || []).filter(item => {
|
||||
const keyword = search.toLowerCase();
|
||||
return (
|
||||
item.namaLengkap.toLowerCase().includes(keyword) ||
|
||||
item.alamatKTP.toLowerCase().includes(keyword) ||
|
||||
(item.tanggalLahir ? new Date(item.tanggalLahir).toLocaleDateString() : '').toLowerCase().includes(keyword) ||
|
||||
item.jenisKelamin.toLowerCase().includes(keyword)
|
||||
);
|
||||
});
|
||||
|
||||
if (!listDataState.findMany.data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Skeleton h={500} />
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Paper bg={colors['white-1']} p="md">
|
||||
<Stack>
|
||||
<Title order={4}>List Beasiswa Pendaftar</Title>
|
||||
<Box style={{ overflowX: 'auto' }}>
|
||||
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>No</TableTh>
|
||||
<TableTh>Nama Lengkap</TableTh>
|
||||
<TableTh>Alamat</TableTh>
|
||||
<TableTh>Tanggal Lahir</TableTh>
|
||||
<TableTh>Jenis Kelamin</TableTh>
|
||||
<TableTh>Detail</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.map((item, index) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>
|
||||
<Box w={100}>
|
||||
<Text truncate="end" fz={"sm"}>{index + 1}</Text>
|
||||
</Box>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Text truncate="end" fz={"sm"}>{item.namaLengkap}</Text>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Text truncate="end" fz={"sm"}>{item.alamatKTP}</Text>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Text truncate="end" fz={"sm"}>{item.tanggalLahir ? new Date(item.tanggalLahir).toLocaleDateString() : '-'}</Text>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Text truncate="end" fz={"sm"}>{item.jenisKelamin}</Text>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Button onClick={() => router.push(`/admin/pendidikan/beasiswa-desa/beasiswa-pendaftar/${item.id}`)}>
|
||||
<IconDeviceImacCog size={25} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export default BeasiswaPendaftar;
|
||||
@@ -3,13 +3,14 @@ import colors from '@/con/colors';
|
||||
import { Box, Button, Paper, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
|
||||
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import HeaderSearch from '../../_com/header';
|
||||
import HeaderSearch from '../../../_com/header';
|
||||
|
||||
|
||||
function BeasiswaDesa() {
|
||||
return (
|
||||
<Box>
|
||||
<HeaderSearch
|
||||
title='Beasiswa Desa'
|
||||
title='Keunggulan Program'
|
||||
placeholder='pencarian'
|
||||
searchIcon={<IconSearch size={20} />}
|
||||
/>
|
||||
@@ -0,0 +1,13 @@
|
||||
'use client'
|
||||
import React from 'react';
|
||||
import LayoutTabs from './_lib/layoutTabs';
|
||||
|
||||
function Layout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<LayoutTabs>
|
||||
{children}
|
||||
</LayoutTabs>
|
||||
);
|
||||
}
|
||||
|
||||
export default Layout;
|
||||
@@ -0,0 +1,80 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Paper, Stack, TextInput, Title } from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useEffect } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import dataPendidikan from '../../../_state/pendidikan/data-pendidikan';
|
||||
|
||||
function EditDataPendidikan() {
|
||||
const router = useRouter()
|
||||
const params = useParams() as { id: string }
|
||||
const stateDPM = useProxy(dataPendidikan)
|
||||
|
||||
const id = params.id
|
||||
|
||||
// Load data saat komponen mount
|
||||
useEffect(() => {
|
||||
if (id) {
|
||||
stateDPM.findUnique.load(id).then(() => {
|
||||
const data = stateDPM.findUnique.data
|
||||
if (data) {
|
||||
stateDPM.update.form = {
|
||||
name: data.name || "",
|
||||
jumlah: data.jumlah || "",
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}, [id])
|
||||
|
||||
const handleSubmit = async () => {
|
||||
// Set the ID before submitting
|
||||
stateDPM.update.id = id;
|
||||
await stateDPM.update.submit();
|
||||
router.push('/admin/pendidikan/data-pendidikan')
|
||||
}
|
||||
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 Data Pendidikan</Title>
|
||||
<TextInput
|
||||
label="Nama"
|
||||
placeholder="masukkan nama"
|
||||
value={stateDPM.update.form.name}
|
||||
onChange={(val) => {
|
||||
stateDPM.update.form.name = val.currentTarget.value;
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label="Jumlah"
|
||||
type="number"
|
||||
placeholder="masukkan jumlah"
|
||||
value={stateDPM.update.form.jumlah}
|
||||
onChange={(val) => {
|
||||
stateDPM.update.form.jumlah = val.currentTarget.value;
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
mt={10}
|
||||
bg={colors['blue-button']}
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
Simpan
|
||||
</Button>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export default EditDataPendidikan;
|
||||
@@ -0,0 +1,81 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Group, Paper, Stack, TextInput, Title } from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import dataPendidikan from '../../../_state/pendidikan/data-pendidikan';
|
||||
|
||||
function CreateDataPendidikan() {
|
||||
const stateDPM = useProxy(dataPendidikan);
|
||||
const [chartData, setChartData] = useState<any[]>([]);
|
||||
const router = useRouter()
|
||||
|
||||
const resetForm = () => {
|
||||
stateDPM.create.form = {
|
||||
name: "",
|
||||
jumlah: "",
|
||||
}
|
||||
}
|
||||
|
||||
const handleSubmit = async () => {
|
||||
const id = await stateDPM.create.create();
|
||||
if (id) {
|
||||
const idStr = String(id);
|
||||
await stateDPM.findUnique.load(idStr);
|
||||
if (stateDPM.findUnique.data) {
|
||||
setChartData([stateDPM.findUnique.data]);
|
||||
}
|
||||
}
|
||||
resetForm();
|
||||
router.push("/admin/pendidikan/data-pendidikan");
|
||||
}
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button variant="subtle" onClick={() => router.back()}>
|
||||
<IconArrowBack size={20} />
|
||||
</Button>
|
||||
</Box>
|
||||
<Box>
|
||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
||||
<Title order={4}>Tambah Data Pendidikan</Title>
|
||||
<Stack gap={"xs"}>
|
||||
<TextInput
|
||||
label="Nama"
|
||||
type="text"
|
||||
value={stateDPM.create.form.name}
|
||||
placeholder="Masukkan nama"
|
||||
onChange={(val) => {
|
||||
stateDPM.create.form.name = val.currentTarget.value;
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label="Jumlah"
|
||||
type="number"
|
||||
value={stateDPM.create.form.jumlah}
|
||||
placeholder="Masukkan jumlah"
|
||||
onChange={(val) => {
|
||||
stateDPM.create.form.jumlah = val.currentTarget.value;
|
||||
}}
|
||||
/>
|
||||
<Group>
|
||||
<Button
|
||||
bg={colors['blue-button']}
|
||||
mt={10}
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default CreateDataPendidikan;
|
||||
@@ -1,74 +1,169 @@
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Paper, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
||||
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
|
||||
import { useMediaQuery, useShallowEffect } from '@mantine/hooks';
|
||||
import { IconEdit, IconSearch, IconTrash } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Bar, BarChart, Legend, Tooltip, XAxis, YAxis } from 'recharts';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import HeaderSearch from '../../_com/header';
|
||||
import JudulList from '../../_com/judulList';
|
||||
import { ModalKonfirmasiHapus } from '../../_com/modalKonfirmasiHapus';
|
||||
import dataPendidikan from '../../_state/pendidikan/data-pendidikan';
|
||||
|
||||
function DataPendidikan() {
|
||||
const [search, setSearch] = useState("");
|
||||
return (
|
||||
<Box>
|
||||
<HeaderSearch
|
||||
title='Data Pendidikan'
|
||||
placeholder='pencarian'
|
||||
searchIcon={<IconSearch size={20} />}
|
||||
title='Data Pendidikan'
|
||||
placeholder='pencarian'
|
||||
searchIcon={<IconSearch size={20} />}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||
/>
|
||||
<ListDataPendidikan/>
|
||||
<ListDataPendidikan search={search} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function ListDataPendidikan() {
|
||||
function ListDataPendidikan({ search }: { search: string }) {
|
||||
type DPMrafik = {
|
||||
id: string;
|
||||
name: string;
|
||||
jumlah: number;
|
||||
}
|
||||
const stateDPM = useProxy(dataPendidikan);
|
||||
const [chartData, setChartData] = useState<DPMrafik[]>([]);
|
||||
const [mounted, setMounted] = useState(false); // untuk memastikan DOM sudah ready
|
||||
const isTablet = useMediaQuery('(max-width: 1024px)')
|
||||
const isMobile = useMediaQuery('(max-width: 768px)')
|
||||
const [modalHapus, setModalHapus] = useState(false)
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||
const router = useRouter();
|
||||
|
||||
const handleDelete = () => {
|
||||
if (selectedId) {
|
||||
stateDPM.delete.byId(selectedId)
|
||||
setModalHapus(false)
|
||||
setSelectedId(null)
|
||||
|
||||
stateDPM.findMany.load()
|
||||
}
|
||||
}
|
||||
|
||||
useShallowEffect(() => {
|
||||
setMounted(true)
|
||||
stateDPM.findMany.load()
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
if (stateDPM.findMany.data) {
|
||||
setChartData(stateDPM.findMany.data.map((item) => ({
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
jumlah: Number(item.jumlah)
|
||||
})));
|
||||
}
|
||||
}, [stateDPM.findMany.data]);
|
||||
|
||||
const filteredData = (stateDPM.findMany.data || []).filter(item => {
|
||||
const keyword = search.toLowerCase();
|
||||
return (
|
||||
item.name.toLowerCase().includes(keyword) ||
|
||||
item.jumlah.toLowerCase().includes(keyword)
|
||||
);
|
||||
});
|
||||
|
||||
if (!stateDPM.findMany.data) {
|
||||
return (
|
||||
<Stack>
|
||||
<Skeleton h={500} />
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Paper bg={colors['white-1']} p="md">
|
||||
<Stack>
|
||||
<JudulList
|
||||
title='List Data Pendidikan'
|
||||
href='/admin/pendidikan/data-pendidikan/create'
|
||||
<Stack gap={'xs'}>
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
<JudulList
|
||||
title='List Data Pendidikan'
|
||||
href='/admin/pendidikan/data-pendidikan/create'
|
||||
/>
|
||||
<Box style={{overflowX: 'auto'}}>
|
||||
<Table striped withRowBorders withTableBorder style={{minWidth: '700px'}}>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>Nomor</TableTh>
|
||||
<TableTh>Nama Lengkap</TableTh>
|
||||
<TableTh>Nomor Telepon</TableTh>
|
||||
<TableTh>Email</TableTh>
|
||||
<TableTh>Detail</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
<TableTr>
|
||||
<Table striped withTableBorder withRowBorders>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>Nama</TableTh>
|
||||
<TableTh>Jumlah</TableTh>
|
||||
<TableTh>Edit</TableTh>
|
||||
<TableTh>Delete</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>{item.name}</TableTd>
|
||||
<TableTd>{item.jumlah}</TableTd>
|
||||
<TableTd>
|
||||
<Box w={100}>
|
||||
<Text truncate="end" fz={"sm"}>1</Text>
|
||||
</Box>
|
||||
<Button color='green' onClick={() => router.push(`/admin/pendidikan/data-pendidikan/${item.id}`)}>
|
||||
<IconEdit size={20} />
|
||||
</Button>
|
||||
</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/data-pendidikan/detail')}>
|
||||
<IconDeviceImacCog size={25} />
|
||||
<Button
|
||||
color='red'
|
||||
disabled={stateDPM.delete.loading}
|
||||
onClick={() => {
|
||||
setSelectedId(item.id)
|
||||
setModalHapus(true)
|
||||
}}>
|
||||
<IconTrash size={20} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
</TableTbody>
|
||||
</Table>
|
||||
))}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Paper>
|
||||
|
||||
{/* Chart */}
|
||||
{!mounted && !chartData ? (
|
||||
<Box style={{ width: '100%', minWidth: 300, height: 400, minHeight: 300 }}>
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
<Title pb={10} order={3}>Grafik Data Pendidikan</Title>
|
||||
<Text c='dimmed'>Belum ada data untuk ditampilkan dalam grafik</Text>
|
||||
</Paper>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
) : (
|
||||
<Box style={{ width: '100%', minWidth: 300, height: 400, minHeight: 300 }}>
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
<Title pb={10} order={4}>Grafik Data Pendidikan</Title>
|
||||
{mounted && chartData.length > 0 && (
|
||||
<BarChart width={isMobile ? 450 : isTablet ? 500 : 550} height={350} data={chartData} >
|
||||
<XAxis dataKey="name" />
|
||||
<YAxis />
|
||||
<Tooltip />
|
||||
<Legend />
|
||||
<Bar dataKey="jumlah" fill={colors['blue-button']} name="Data Pendidikan" />
|
||||
</BarChart>
|
||||
)}
|
||||
</Paper>
|
||||
</Box>
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
{/* Modal Konfirmasi Hapus */}
|
||||
<ModalKonfirmasiHapus
|
||||
opened={modalHapus}
|
||||
onClose={() => setModalHapus(false)}
|
||||
onConfirm={handleDelete}
|
||||
text='Apakah anda yakin ingin menghapus grafik data pendidikan ini?'
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export default DataPendidikan;
|
||||
|
||||
@@ -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 LayoutTabs({ children }: { children: React.ReactNode }) {
|
||||
const router = useRouter()
|
||||
const pathname = usePathname()
|
||||
const tabs = [
|
||||
{
|
||||
label: "Data Perpustakaan",
|
||||
value: "data-perpustakaan",
|
||||
href: "/admin/pendidikan/perpustakaan-digital/data-perpustakaan"
|
||||
},
|
||||
{
|
||||
label: "Kategori Buku",
|
||||
value: "kategori-buku",
|
||||
href: "/admin/pendidikan/perpustakaan-digital/kategori-buku"
|
||||
},
|
||||
|
||||
];
|
||||
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}>Perpustakaan Digital</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 LayoutTabs;
|
||||
@@ -0,0 +1,198 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
||||
import perpustakaanDigitalState from '@/app/admin/(dashboard)/_state/pendidikan/perpustakaan-digital';
|
||||
import colors from '@/con/colors';
|
||||
import ApiFetch from '@/lib/api-fetch';
|
||||
import { Box, Button, Group, Image, Paper, Select, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import { Dropzone } from '@mantine/dropzone';
|
||||
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
function EditPasarDesa() {
|
||||
const editState = useProxy(perpustakaanDigitalState.dataPerpustakaan)
|
||||
const router = useRouter();
|
||||
const params = useParams();
|
||||
|
||||
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
||||
const [file, setFile] = useState<File | null>(null);
|
||||
const [formData, setFormData] = useState({
|
||||
judul: editState.update.form.judul || "",
|
||||
deskripsi: editState.update.form.deskripsi || "",
|
||||
imageId: editState.update.form.imageId || "",
|
||||
kategoriId: editState.update.form.kategoriId || "",
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
perpustakaanDigitalState.kategoriBuku.findMany.load();
|
||||
const loadDataPerpustakaan = async () => {
|
||||
const id = params?.id as string;
|
||||
if (!id) return;
|
||||
|
||||
try {
|
||||
const data = await editState.update.load(id);
|
||||
if (data) {
|
||||
setFormData({
|
||||
judul: data.judul || "",
|
||||
deskripsi: data.deskripsi || "",
|
||||
imageId: data.imageId || "",
|
||||
kategoriId: data.kategoriId || "",
|
||||
});
|
||||
// Tampilkan preview gambar
|
||||
if (data.image?.link) {
|
||||
setPreviewImage(data.image.link);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading data perpustakaan:", error);
|
||||
toast.error(
|
||||
error instanceof Error ? error.message : "Gagal mengambil data perpustakaan"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
loadDataPerpustakaan();
|
||||
}, [params?.id]);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
editState.update.form = {
|
||||
...editState.update.form,
|
||||
judul: formData.judul,
|
||||
deskripsi: formData.deskripsi,
|
||||
kategoriId: formData.kategoriId,
|
||||
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");
|
||||
}
|
||||
|
||||
// Update imageId in global state
|
||||
editState.update.form.imageId = uploaded.id;
|
||||
}
|
||||
|
||||
await editState.update.update();
|
||||
toast.success("Data perpustakaan berhasil diperbarui!");
|
||||
router.push("/admin/pendidikan/perpustakaan-digital/data-perpustakaan");
|
||||
} catch (error) {
|
||||
console.error("Error updating data perpustakaan:", error);
|
||||
toast.error("Terjadi kesalahan saat memperbarui data perpustakaan");
|
||||
}
|
||||
};
|
||||
|
||||
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 Data Perpustakaan</Title>
|
||||
<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>
|
||||
<TextInput
|
||||
value={formData.judul}
|
||||
onChange={(e) => setFormData({ ...formData, judul: e.target.value })}
|
||||
label={<Text fw={"bold"} fz={"sm"}>Judul</Text>}
|
||||
placeholder='Masukkan judul'
|
||||
/>
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"sm"}>Deskripsi</Text>
|
||||
<EditEditor
|
||||
value={formData.deskripsi}
|
||||
onChange={(val) => {
|
||||
setFormData({ ...formData, deskripsi: val });
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Select
|
||||
value={formData.kategoriId}
|
||||
onChange={(val) => setFormData({ ...formData, kategoriId: val || "" })}
|
||||
label={<Text fw={"bold"} fz={"sm"}>Kategori</Text>}
|
||||
placeholder='Pilih kategori'
|
||||
data={
|
||||
perpustakaanDigitalState.kategoriBuku.findMany.data?.map((v) => ({
|
||||
value: v.id,
|
||||
label: v.name
|
||||
})) || []
|
||||
}
|
||||
clearable
|
||||
searchable
|
||||
required
|
||||
error={!formData.kategoriId ? "Pilih kategori" : undefined}
|
||||
/>
|
||||
|
||||
<Group>
|
||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Submit</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default EditPasarDesa;
|
||||
@@ -0,0 +1,109 @@
|
||||
'use client'
|
||||
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
||||
import perpustakaanDigitalState from '@/app/admin/(dashboard)/_state/pendidikan/perpustakaan-digital';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Flex, Image, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
function DetailDataPerpustakaan() {
|
||||
const stateDetail = useProxy(perpustakaanDigitalState.dataPerpustakaan)
|
||||
const [modalHapus, setModalHapus] = useState(false)
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||
const params = useParams()
|
||||
const router = useRouter();
|
||||
|
||||
useShallowEffect(() => {
|
||||
stateDetail.findUnique.load(params?.id as string)
|
||||
}, [])
|
||||
|
||||
const handleHapus = () => {
|
||||
if (selectedId) {
|
||||
stateDetail.delete.delete(selectedId)
|
||||
setModalHapus(false)
|
||||
setSelectedId(null)
|
||||
router.push("/admin/pendidikan/perpustakaan-digital/data-perpustakaan")
|
||||
}
|
||||
}
|
||||
|
||||
if (!stateDetail.findUnique.data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<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 Data Perpustakaan</Text>
|
||||
<Paper bg={colors['BG-trans']} p={'md'}>
|
||||
<Stack gap={"xs"}>
|
||||
<Box>
|
||||
<Text fz={"lg"} fw={"bold"}>Judul</Text>
|
||||
<Text fz={"lg"}>{stateDetail.findUnique.data?.judul}</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fz={"lg"} fw={"bold"}>Deskripsi</Text>
|
||||
<Text fz={"lg"} dangerouslySetInnerHTML={{ __html: stateDetail.findUnique.data?.deskripsi }} />
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fz={"lg"} fw={"bold"}>Kategori</Text>
|
||||
<Text fz={"lg"}>{stateDetail.findUnique.data?.kategori.name}</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fz={"lg"} fw={"bold"}>Gambar</Text>
|
||||
<Image src={stateDetail.findUnique.data?.image.link} alt="gambar" />
|
||||
</Box>
|
||||
<Box>
|
||||
<Flex gap={"xs"}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (stateDetail.findUnique.data) {
|
||||
setSelectedId(stateDetail.findUnique.data.id);
|
||||
setModalHapus(true);
|
||||
}
|
||||
}}
|
||||
disabled={!stateDetail.findUnique.data}
|
||||
color="red">
|
||||
<IconX size={20} />
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (stateDetail.findUnique.data) {
|
||||
router.push(`/admin/pendidikan/perpustakaan-digital/data-perpustakaan/${stateDetail.findUnique.data.id}/edit`);
|
||||
}
|
||||
}}
|
||||
disabled={!stateDetail.findUnique.data}
|
||||
color="green">
|
||||
<IconEdit size={20} />
|
||||
</Button>
|
||||
</Flex>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
{/* Modal Hapus */}
|
||||
<ModalKonfirmasiHapus
|
||||
opened={modalHapus}
|
||||
onClose={() => setModalHapus(false)}
|
||||
onConfirm={handleHapus}
|
||||
text="Apakah anda yakin ingin menghapus data ini?"
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default DetailDataPerpustakaan;
|
||||
@@ -0,0 +1,163 @@
|
||||
'use client'
|
||||
import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
|
||||
import perpustakaanDigitalState from '@/app/admin/(dashboard)/_state/pendidikan/perpustakaan-digital';
|
||||
import colors from '@/con/colors';
|
||||
import ApiFetch from '@/lib/api-fetch';
|
||||
import { Box, Button, Group, Image, Paper, Select, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import { Dropzone } from '@mantine/dropzone';
|
||||
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
|
||||
function CreateDataPerpustakaan() {
|
||||
const createState = useProxy(perpustakaanDigitalState.dataPerpustakaan)
|
||||
const router = useRouter();
|
||||
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
||||
const [file, setFile] = useState<File | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
perpustakaanDigitalState.kategoriBuku.findMany.load();
|
||||
}, []);
|
||||
|
||||
const resetForm = () => {
|
||||
createState.create.form = {
|
||||
judul: "",
|
||||
deskripsi: "",
|
||||
imageId: "",
|
||||
kategoriId: "",
|
||||
};
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
createState.create.form.imageId = uploaded.id;
|
||||
await createState.create.create();
|
||||
resetForm();
|
||||
router.push("/admin/pendidikan/perpustakaan-digital/data-perpustakaan")
|
||||
};
|
||||
|
||||
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 Data Perpustakaan</Title>
|
||||
<TextInput
|
||||
label={<Text fw={"bold"} fz={"sm"}>Judul</Text>}
|
||||
placeholder='Masukkan judul'
|
||||
value={createState.create.form.judul}
|
||||
onChange={(val) => {
|
||||
createState.create.form.judul = val.target.value;
|
||||
}}
|
||||
/>
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"sm"}>Deskripsi</Text>
|
||||
<CreateEditor
|
||||
value={createState.create.form.deskripsi}
|
||||
onChange={(val) => {
|
||||
createState.create.form.deskripsi = val;
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Select
|
||||
label={<Text fw={"bold"} fz={"sm"}>Kategori</Text>}
|
||||
placeholder='Pilih kategori'
|
||||
value={createState.create.form.kategoriId || ""}
|
||||
onChange={(val) => {
|
||||
createState.create.form.kategoriId = val ?? "";
|
||||
}}
|
||||
data={perpustakaanDigitalState.kategoriBuku.findMany.data?.map((item) => ({
|
||||
value: item.id,
|
||||
label: item.name,
|
||||
}))}
|
||||
/>
|
||||
<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 CreateDataPerpustakaan;
|
||||
@@ -0,0 +1,104 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import HeaderSearch from '../../../_com/header';
|
||||
import JudulList from '../../../_com/judulList';
|
||||
import perpustakaanDigitalState from '../../../_state/pendidikan/perpustakaan-digital';
|
||||
|
||||
|
||||
|
||||
function DataPerpustakaan() {
|
||||
const [search, setSearch] = useState('');
|
||||
return (
|
||||
<Box>
|
||||
<HeaderSearch
|
||||
title='Data Perpustakaan'
|
||||
placeholder='pencarian'
|
||||
searchIcon={<IconSearch size={20} />}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||
/>
|
||||
<ListDataPerpustakaan search={search} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function ListDataPerpustakaan({ search }: { search: string }) {
|
||||
const listDataState = useProxy(perpustakaanDigitalState.dataPerpustakaan)
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
perpustakaanDigitalState.dataPerpustakaan.findMany.load()
|
||||
listDataState.findMany.load()
|
||||
}, [])
|
||||
|
||||
const filteredData = (listDataState.findMany.data || []).filter(item => {
|
||||
const keyword = search.toLowerCase();
|
||||
return (
|
||||
item.judul.toLowerCase().includes(keyword) ||
|
||||
item.deskripsi.toLowerCase().includes(keyword) ||
|
||||
item.kategori.name.toLowerCase().includes(keyword)
|
||||
);
|
||||
});
|
||||
|
||||
if (!listDataState.findMany.data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Skeleton h={500} />
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Paper bg={colors['white-1']} p="md">
|
||||
<Stack>
|
||||
<JudulList
|
||||
title='List Data Perpustakaan'
|
||||
href='/admin/pendidikan/perpustakaan-digital/data-perpustakaan/create'
|
||||
/>
|
||||
<Box style={{ overflowX: 'auto' }}>
|
||||
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>No</TableTh>
|
||||
<TableTh>Judul</TableTh>
|
||||
<TableTh>Kategori</TableTh>
|
||||
<TableTh>Detail</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.map((item, index) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>
|
||||
<Box w={100}>
|
||||
<Text truncate="end" fz={"sm"}>{index + 1}</Text>
|
||||
</Box>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Text truncate="end" fz={"sm"}>{item.judul}</Text>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Text truncate="end" fz={"sm"}>{item.kategori.name}</Text>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Button onClick={() => router.push(`/admin/pendidikan/perpustakaan-digital/data-perpustakaan/${item.id}`)}>
|
||||
<IconDeviceImacCog size={25} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export default DataPerpustakaan;
|
||||
@@ -0,0 +1,80 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import perpustakaanDigitalState from '@/app/admin/(dashboard)/_state/pendidikan/perpustakaan-digital';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
function EditKategoriBuku() {
|
||||
const editState = useProxy(perpustakaanDigitalState.kategoriBuku)
|
||||
const router = useRouter();
|
||||
const params = useParams();
|
||||
const [formData, setFormData] = useState({
|
||||
name: editState.update.form.name || '',
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const loadKategori = async () => {
|
||||
const id = params?.id as string;
|
||||
if (!id) return;
|
||||
|
||||
try {
|
||||
const data = await editState.update.load(id); // akses langsung, bukan dari proxy
|
||||
if (data) {
|
||||
setFormData({
|
||||
name: data.name || '',
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading kategori buku:", error);
|
||||
toast.error("Gagal memuat data kategori buku");
|
||||
}
|
||||
};
|
||||
|
||||
loadKategori();
|
||||
}, [params?.id]);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
editState.update.form = {
|
||||
...editState.update.form,
|
||||
name: formData.name,
|
||||
};
|
||||
await editState.update.update();
|
||||
toast.success('Kategori Buku berhasil diperbarui!');
|
||||
router.push('/admin/pendidikan/perpustakaan-digital/kategori-buku');
|
||||
} catch (error) {
|
||||
console.error('Error updating kategori buku:', error);
|
||||
toast.error('Terjadi kesalahan saat memperbarui kategori buku');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button variant="subtle" onClick={() => router.back()}>
|
||||
<IconArrowBack color={colors["blue-button"]} size={30} />
|
||||
</Button>
|
||||
</Box>
|
||||
<Paper bg={"white"} p={"md"} w={{ base: "100%", md: "50%" }}>
|
||||
<Stack gap={"xs"}>
|
||||
<Title order={3}>Edit Kategori Buku</Title>
|
||||
<TextInput
|
||||
value={formData.name}
|
||||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||
label={<Text fz={"sm"} fw={"bold"}>Nama Kategori Buku</Text>}
|
||||
placeholder="masukkan nama kategori buku"
|
||||
/>
|
||||
|
||||
<Button onClick={handleSubmit}>Simpan</Button>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default EditKategoriBuku;
|
||||
@@ -0,0 +1,55 @@
|
||||
'use client'
|
||||
import perpustakaanDigitalState from '@/app/admin/(dashboard)/_state/pendidikan/perpustakaan-digital';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
|
||||
|
||||
function CreateKategoriBuku() {
|
||||
const createState = useProxy(perpustakaanDigitalState.kategoriBuku)
|
||||
const router = useRouter();
|
||||
|
||||
const resetForm = () => {
|
||||
createState.create.form = {
|
||||
name: "",
|
||||
};
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
await createState.create.create();
|
||||
resetForm();
|
||||
router.push("/admin/pendidikan/perpustakaan-digital/kategori-buku")
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
||||
<Stack gap={"xs"}>
|
||||
<Title order={4}>Create Kategori Buku</Title>
|
||||
<TextInput
|
||||
label={<Text fw={"bold"} fz={"sm"}>Nama Kategori Buku</Text>}
|
||||
placeholder='Masukkan nama kategori buku'
|
||||
value={createState.create.form.name}
|
||||
onChange={(val) => {
|
||||
createState.create.form.name = val.target.value;
|
||||
}}
|
||||
/>
|
||||
<Group>
|
||||
<Button onClick={handleSubmit} bg={colors['blue-button']}>Submit</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default CreateKategoriBuku;
|
||||
@@ -0,0 +1,128 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||
import { IconEdit, IconSearch, IconTrash } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import HeaderSearch from '../../../_com/header';
|
||||
import JudulList from '../../../_com/judulList';
|
||||
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||
import perpustakaanDigitalState from '../../../_state/pendidikan/perpustakaan-digital';
|
||||
|
||||
|
||||
|
||||
function KategoriBuku() {
|
||||
const [search, setSearch] = useState('');
|
||||
return (
|
||||
<Box>
|
||||
<HeaderSearch
|
||||
title='Kategori Buku'
|
||||
placeholder='pencarian'
|
||||
searchIcon={<IconSearch size={20} />}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||
/>
|
||||
<ListKategoriBuku search={search} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function ListKategoriBuku({ search }: { search: string }) {
|
||||
const listDataState = useProxy(perpustakaanDigitalState.kategoriBuku)
|
||||
const router = useRouter();
|
||||
const [modalHapus, setModalHapus] = useState(false)
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
listDataState.findMany.load()
|
||||
}, [])
|
||||
|
||||
const handleDelete = () => {
|
||||
if (selectedId) {
|
||||
perpustakaanDigitalState.kategoriBuku.delete.delete(selectedId)
|
||||
setModalHapus(false)
|
||||
setSelectedId(null)
|
||||
|
||||
perpustakaanDigitalState.kategoriBuku.findMany.load()
|
||||
}
|
||||
}
|
||||
|
||||
const filteredData = (listDataState.findMany.data || []).filter(item => {
|
||||
const keyword = search.toLowerCase();
|
||||
return (
|
||||
item.name.toLowerCase().includes(keyword)
|
||||
);
|
||||
});
|
||||
|
||||
if (!listDataState.findMany.data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Skeleton h={500} />
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Paper bg={colors['white-1']} p="md">
|
||||
<Stack>
|
||||
<JudulList
|
||||
title='List Kategori Buku'
|
||||
href='/admin/pendidikan/perpustakaan-digital/kategori-buku/create'
|
||||
/>
|
||||
<Box style={{ overflowX: 'auto' }}>
|
||||
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>No</TableTh>
|
||||
<TableTh>Nama</TableTh>
|
||||
<TableTh>Edit</TableTh>
|
||||
<TableTh>Hapus</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.map((item, index) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>
|
||||
<Box w={100}>
|
||||
<Text truncate="end" fz={"sm"}>{index + 1}</Text>
|
||||
</Box>
|
||||
</TableTd>
|
||||
<TableTd>{item.name}</TableTd>
|
||||
<TableTd>
|
||||
<Button color='green' onClick={() => router.push(`/admin/pendidikan/perpustakaan-digital/kategori-buku/${item.id}`)}>
|
||||
<IconEdit size={20} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Button
|
||||
color='red'
|
||||
disabled={perpustakaanDigitalState.kategoriBuku.delete.loading}
|
||||
onClick={() => {
|
||||
setSelectedId(item.id)
|
||||
setModalHapus(true)
|
||||
}}>
|
||||
<IconTrash size={20} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
{/* Modal Konfirmasi Hapus */}
|
||||
<ModalKonfirmasiHapus
|
||||
opened={modalHapus}
|
||||
onClose={() => setModalHapus(false)}
|
||||
onConfirm={handleDelete}
|
||||
text='Apakah anda yakin ingin menghapus kategori buku ini?'
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export default KategoriBuku;
|
||||
@@ -0,0 +1,13 @@
|
||||
'use client'
|
||||
import React from 'react';
|
||||
import LayoutTabs from './_lib/layoutTabs';
|
||||
|
||||
function Layout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<LayoutTabs>
|
||||
{children}
|
||||
</LayoutTabs>
|
||||
);
|
||||
}
|
||||
|
||||
export default Layout;
|
||||
@@ -1,74 +0,0 @@
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Paper, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import HeaderSearch from '../../_com/header';
|
||||
import JudulList from '../../_com/judulList';
|
||||
|
||||
function PerpustakaanDigital() {
|
||||
return (
|
||||
<Box>
|
||||
<HeaderSearch
|
||||
title='Perpustakaan Digital'
|
||||
placeholder='pencarian'
|
||||
searchIcon={<IconSearch size={20} />}
|
||||
/>
|
||||
<ListPerpustakaanDigital/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function ListPerpustakaanDigital() {
|
||||
const router = useRouter();
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Paper bg={colors['white-1']} p="md">
|
||||
<Stack>
|
||||
<JudulList
|
||||
title='List Perpustakaan Digital'
|
||||
href='/admin/pendidikan/perpustakaan-digital/create'
|
||||
/>
|
||||
<Box style={{overflowX: 'auto'}}>
|
||||
<Table striped withRowBorders withTableBorder style={{minWidth: '700px'}}>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>Nomor</TableTh>
|
||||
<TableTh>Nama Lengkap</TableTh>
|
||||
<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/perpustakaan-digital/detail')}>
|
||||
<IconDeviceImacCog size={25} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export default PerpustakaanDigital;
|
||||
@@ -1,225 +0,0 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import {
|
||||
Alert,
|
||||
Box,
|
||||
Button, Center, FileInput, Group, Image, Paper,
|
||||
Stack,
|
||||
Text,
|
||||
TextInput,
|
||||
Title
|
||||
} from '@mantine/core';
|
||||
import { IconAlertCircle, IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import stateStrukturPPID from '../../../_state/ppid/struktur_ppid/struktur_PPID';
|
||||
import { toast } from 'react-toastify';
|
||||
import ApiFetch from '@/lib/api-fetch';
|
||||
|
||||
|
||||
function EditStrukturPPID() {
|
||||
const strukturPPID = useProxy(stateStrukturPPID);
|
||||
const params = useParams();
|
||||
const router = useRouter()
|
||||
|
||||
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
||||
const [file, setFile] = useState<File | null>(null);
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const loadData = async () => {
|
||||
const id = params?.id as string;
|
||||
if (!id) {
|
||||
toast.error("ID tidak valid");
|
||||
router.push("/admin/ppid/struktur-ppid");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const profileData = await strukturPPID.loadForEdit(id);
|
||||
|
||||
if (profileData && profileData.image?.link) {
|
||||
setPreviewImage(profileData.image.link);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading profile:", error);
|
||||
toast.error("Gagal memuat data profile");
|
||||
}
|
||||
};
|
||||
|
||||
loadData();
|
||||
|
||||
return () => {
|
||||
strukturPPID.editStruktur.reset(); // cleanup form
|
||||
};
|
||||
}, [params?.id, router]);
|
||||
|
||||
const handleFieldChange = (field: string, value: string) => {
|
||||
strukturPPID.editStruktur.updateField(field as any, value);
|
||||
};
|
||||
|
||||
const handleFileChange = (newFile: File | null) => {
|
||||
if (!newFile) {
|
||||
setFile(null);
|
||||
return;
|
||||
}
|
||||
|
||||
setFile(newFile);
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = (event) => {
|
||||
setPreviewImage(event.target?.result as string);
|
||||
};
|
||||
reader.readAsDataURL(newFile);
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (isSubmitting || !strukturPPID.editStruktur.form.name.trim()) {
|
||||
toast.error("Nama wajib diisi");
|
||||
return;
|
||||
}
|
||||
|
||||
setIsSubmitting(true);
|
||||
|
||||
try {
|
||||
// Upload file jika ada
|
||||
if (file) {
|
||||
const uploadResponse = await ApiFetch.api.fileStorage.create.post({ file, name: file.name });
|
||||
const uploaded = uploadResponse.data?.data;
|
||||
|
||||
if (!uploaded?.id) {
|
||||
toast.error("Gagal upload gambar");
|
||||
return;
|
||||
}
|
||||
|
||||
strukturPPID.editStruktur.form.imageId = uploaded.id;
|
||||
}
|
||||
|
||||
// Submit form
|
||||
const success = await strukturPPID.editStruktur.submit();
|
||||
|
||||
if (success) {
|
||||
toast.success("Berhasil menyimpan perubahan");
|
||||
router.push("/admin/ppid/struktur-ppid");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error submitting form:", error);
|
||||
toast.error("Gagal menyimpan profile");
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleBack = () => {
|
||||
router.back();
|
||||
};
|
||||
|
||||
if (strukturPPID.struktur.loading) {
|
||||
return (
|
||||
<Box>
|
||||
<Center h={400}>
|
||||
<Text>Memuat data struktur...</Text>
|
||||
</Center>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
// Error state
|
||||
if (strukturPPID.struktur.error) {
|
||||
return (
|
||||
<Box>
|
||||
<Stack gap="md">
|
||||
<Button variant="subtle" onClick={handleBack}>
|
||||
<IconArrowBack color={colors['blue-button']} size={20} />
|
||||
</Button>
|
||||
<Alert icon={<IconAlertCircle size={16} />} color="red">
|
||||
<Text fw="bold">Error</Text>
|
||||
<Text>{strukturPPID.struktur.error}</Text>
|
||||
</Alert>
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
// No data state
|
||||
if (!strukturPPID.struktur.data) {
|
||||
return (
|
||||
<Box>
|
||||
<Stack gap="md">
|
||||
<Button variant="subtle" onClick={handleBack}>
|
||||
<IconArrowBack color={colors['blue-button']} size={20} />
|
||||
</Button>
|
||||
<Alert icon={<IconAlertCircle size={16} />} color="yellow">
|
||||
<Text fw="bold">Data tidak ditemukan</Text>
|
||||
<Text>Profile PPID tidak dapat ditemukan</Text>
|
||||
</Alert>
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Stack gap={'xs'}>
|
||||
<Box>
|
||||
<Button
|
||||
variant={'subtle'}
|
||||
onClick={handleBack}
|
||||
>
|
||||
<IconArrowBack color={colors['blue-button']} size={20} />
|
||||
</Button>
|
||||
</Box>
|
||||
<Box>
|
||||
<Paper bg={colors['white-1']} p={'md'} radius={10} w={{ base: '100%', md: '50%' }}>
|
||||
<Stack gap={'xs'}>
|
||||
<Title order={3}>Edit Struktur PPID</Title>
|
||||
<TextInput
|
||||
value={strukturPPID.editStruktur.form.name}
|
||||
onChange={(e) => handleFieldChange('name', e.currentTarget.value)}
|
||||
label={<Text fz="sm" fw="bold">Judul</Text>}
|
||||
placeholder="Masukkan judul"
|
||||
/>
|
||||
{/* File Upload */}
|
||||
<FileInput
|
||||
label={<Text fz="sm" fw="bold">Upload Gambar Baru (Opsional)</Text>}
|
||||
value={file}
|
||||
onChange={handleFileChange}
|
||||
accept="image/*"
|
||||
/>
|
||||
|
||||
{/* 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>
|
||||
|
||||
<Group>
|
||||
<Button
|
||||
bg={colors['blue-button']}
|
||||
onClick={handleSubmit}
|
||||
disabled={isSubmitting || strukturPPID.editStruktur.loading}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default EditStrukturPPID;
|
||||
@@ -0,0 +1,67 @@
|
||||
/* 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 LayoutTabs({ children }: { children: React.ReactNode }) {
|
||||
const router = useRouter()
|
||||
const pathname = usePathname()
|
||||
const tabs = [
|
||||
{
|
||||
label: "Pegawai",
|
||||
value: "pegawai",
|
||||
href: "/admin/ppid/struktur-ppid/pegawai"
|
||||
},
|
||||
{
|
||||
label: "Posisi Organisasi",
|
||||
value: "posisiorganisasi",
|
||||
href: "/admin/ppid/struktur-ppid/posisi-organisasi"
|
||||
},
|
||||
{
|
||||
label: "Struktur Organisasi",
|
||||
value: "strukturorganisasi",
|
||||
href: "/admin/ppid/struktur-ppid/struktur-organisasi"
|
||||
}
|
||||
];
|
||||
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}>Struktur PPID Desa Darmasaba</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 LayoutTabs;
|
||||
12
src/app/admin/(dashboard)/ppid/struktur-ppid/layout.tsx
Normal file
12
src/app/admin/(dashboard)/ppid/struktur-ppid/layout.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
'use client'
|
||||
|
||||
import LayoutTabs from "./_lib/layoutTabs"
|
||||
|
||||
|
||||
export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<LayoutTabs>
|
||||
{children}
|
||||
</LayoutTabs>
|
||||
)
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Grid, GridCol, Image, Paper, Skeleton, Stack, Text, Title } from '@mantine/core';
|
||||
import { IconEdit } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import stateStrukturPPID from '../../_state/ppid/struktur_ppid/struktur_PPID';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
|
||||
function Page() {
|
||||
const router = useRouter()
|
||||
const strukturPPID = useProxy(stateStrukturPPID)
|
||||
useShallowEffect(() => {
|
||||
strukturPPID.struktur.load("1")
|
||||
}, [])
|
||||
|
||||
if (!strukturPPID.struktur.data) {
|
||||
return <Stack>
|
||||
<Skeleton radius={10} h={800} />
|
||||
</Stack>
|
||||
}
|
||||
|
||||
const dataArray = Array.isArray(strukturPPID.struktur.data)
|
||||
? strukturPPID.struktur.data
|
||||
: [strukturPPID.struktur.data];
|
||||
|
||||
return (
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
<Stack gap={"xs"}>
|
||||
<Grid>
|
||||
<GridCol span={{ base: 12, md: 11 }}>
|
||||
<Title order={3}>Preview Struktur PPID</Title>
|
||||
</GridCol>
|
||||
<GridCol span={{ base: 12, md: 1 }}>
|
||||
<Button onClick={() => router.push(`/admin/ppid/struktur-ppid/${strukturPPID.struktur.data?.id}`)} bg={colors['blue-button']}>
|
||||
<IconEdit size={16} />
|
||||
</Button>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
{dataArray.map((item) => (
|
||||
<Paper key={item.id} p={"xl"} bg={colors['BG-trans']}>
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"h4"}>{item.name}</Text>
|
||||
<Image
|
||||
w={{ base: "100%", md: "100%" }}
|
||||
src={item.image?.link}
|
||||
alt=''
|
||||
onError={(e) => {
|
||||
e.currentTarget.src = "/struktur_ppid.png";
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Paper>
|
||||
))}
|
||||
</Stack>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
||||
export default Page;
|
||||
@@ -0,0 +1,275 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import stateStrukturPPID from '@/app/admin/(dashboard)/_state/ppid/struktur_ppid/struktur_PPID';
|
||||
import colors from '@/con/colors';
|
||||
import ApiFetch from '@/lib/api-fetch';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Group,
|
||||
Image,
|
||||
Paper,
|
||||
Select,
|
||||
Stack,
|
||||
Text,
|
||||
TextInput,
|
||||
Title
|
||||
} from '@mantine/core';
|
||||
import { Dropzone } from '@mantine/dropzone';
|
||||
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
|
||||
export default function EditPegawaiPPID() {
|
||||
const router = useRouter();
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
||||
const [file, setFile] = useState<File | null>(null);
|
||||
const stateOrganisasi = useProxy(stateStrukturPPID.pegawai);
|
||||
const [formData, setFormData] = useState({
|
||||
namaLengkap: stateOrganisasi.edit.form.namaLengkap,
|
||||
gelarAkademik: stateOrganisasi.edit.form.gelarAkademik,
|
||||
imageId: stateOrganisasi.edit.form.imageId,
|
||||
tanggalMasuk: stateOrganisasi.edit.form.tanggalMasuk,
|
||||
email: stateOrganisasi.edit.form.email,
|
||||
telepon: stateOrganisasi.edit.form.telepon,
|
||||
alamat: stateOrganisasi.edit.form.alamat,
|
||||
posisiId: stateOrganisasi.edit.form.posisiId,
|
||||
isActive: stateOrganisasi.edit.form.isActive,
|
||||
});
|
||||
|
||||
|
||||
// Format date to YYYY-MM-DD for date input
|
||||
const formatDateForInput = (dateString: string) => {
|
||||
if (!dateString) return '';
|
||||
const date = new Date(dateString);
|
||||
return date.toISOString().split('T')[0];
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
stateStrukturPPID.posisiOrganisasi.findMany.load();
|
||||
const loadPegawai = async () => {
|
||||
try {
|
||||
const data = await stateOrganisasi.edit.load(id);
|
||||
if (data) {
|
||||
setFormData({
|
||||
namaLengkap: data.namaLengkap || "",
|
||||
gelarAkademik: data.gelarAkademik || "",
|
||||
imageId: data.imageId || "",
|
||||
tanggalMasuk: data.tanggalMasuk || "",
|
||||
email: data.email || "",
|
||||
telepon: data.telepon || "",
|
||||
alamat: data.alamat || "",
|
||||
posisiId: data.posisiId || "",
|
||||
isActive: data.isActive ?? true, // pakai nullish coalescing
|
||||
});
|
||||
|
||||
if (data.image?.link) {
|
||||
setPreviewImage(data.image.link);
|
||||
} else {
|
||||
setPreviewImage(null);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading pegawai:", error);
|
||||
toast.error(
|
||||
error instanceof Error ? error.message : "Gagal mengambil data pegawai"
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
loadPegawai();
|
||||
}, [id]);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
if (!formData.namaLengkap.trim()) {
|
||||
toast.error('Nama lengkap tidak boleh kosong');
|
||||
return;
|
||||
}
|
||||
|
||||
stateOrganisasi.edit.form = {
|
||||
namaLengkap: formData.namaLengkap.trim(),
|
||||
gelarAkademik: formData.gelarAkademik.trim(),
|
||||
imageId: formData.imageId || "",
|
||||
tanggalMasuk: formData.tanggalMasuk.trim(),
|
||||
email: formData.email.trim(),
|
||||
telepon: formData.telepon.trim(),
|
||||
alamat: formData.alamat.trim(),
|
||||
posisiId: formData.posisiId.trim(),
|
||||
isActive: formData.isActive,
|
||||
};
|
||||
|
||||
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
|
||||
stateOrganisasi.edit.form.imageId = uploaded.id;
|
||||
}
|
||||
|
||||
if (id && !stateOrganisasi.edit.id) {
|
||||
stateOrganisasi.edit.id = id;
|
||||
}
|
||||
|
||||
const success = await stateOrganisasi.edit.submit();
|
||||
|
||||
if (success) {
|
||||
router.push("/admin/ppid/struktur-ppid/pegawai");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error updating pegawai:", error);
|
||||
toast.error(error instanceof Error ? error.message : "Gagal memperbarui data pegawai");
|
||||
}
|
||||
|
||||
|
||||
|
||||
};
|
||||
|
||||
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 Data Pegawai PPID</Title>
|
||||
<TextInput
|
||||
label="Nama Lengkap"
|
||||
placeholder="Masukkan nama lengkap"
|
||||
value={formData.namaLengkap}
|
||||
onChange={(e) => setFormData({ ...formData, namaLengkap: e.target.value })}
|
||||
/>
|
||||
<TextInput
|
||||
label="Gelar Akademik"
|
||||
placeholder="Contoh: S.Kom"
|
||||
value={formData.gelarAkademik}
|
||||
onChange={(e) => setFormData({ ...formData, gelarAkademik: e.target.value })}
|
||||
/>
|
||||
<Box>
|
||||
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
||||
<Box >
|
||||
<Dropzone
|
||||
onDrop={(files) => {
|
||||
const file = files[0]; // Hanya ambil file pertama
|
||||
if (file) {
|
||||
setFile(file);
|
||||
setPreviewImage(URL.createObjectURL(file)); // Buat preview
|
||||
}
|
||||
}}
|
||||
maxSize={5 * 1024 ** 2} // 5MB
|
||||
accept={{
|
||||
'image/*': ['.jpeg', '.jpg', '.png', '.webp']
|
||||
}}
|
||||
>
|
||||
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
||||
<Dropzone.Accept>
|
||||
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||
</Dropzone.Accept>
|
||||
<Dropzone.Reject>
|
||||
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||
</Dropzone.Reject>
|
||||
<Dropzone.Idle>
|
||||
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||
</Dropzone.Idle>
|
||||
|
||||
<div>
|
||||
<Text size="xl" inline>
|
||||
Drag images here or click to select files
|
||||
</Text>
|
||||
<Text size="sm" c="dimmed" inline mt={7}>
|
||||
Attach as many files as you like, each file should not exceed 5mb
|
||||
</Text>
|
||||
</div>
|
||||
</Group>
|
||||
</Dropzone>
|
||||
{previewImage && (
|
||||
<Image
|
||||
src={previewImage}
|
||||
alt="Preview"
|
||||
width={280}
|
||||
height={180}
|
||||
fit="cover"
|
||||
radius="sm"
|
||||
mt="md"
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
<TextInput
|
||||
label="Tanggal Masuk"
|
||||
type="date"
|
||||
placeholder="Contoh: 2022-01-01"
|
||||
value={formatDateForInput(formData.tanggalMasuk)}
|
||||
onChange={(e) => setFormData({ ...formData, tanggalMasuk: e.target.value })}
|
||||
/>
|
||||
<TextInput
|
||||
label="Email"
|
||||
placeholder="Contoh: email@example.com"
|
||||
value={formData.email}
|
||||
onChange={(e) => (formData.email = e.currentTarget.value)}
|
||||
/>
|
||||
<TextInput
|
||||
label="Telepon"
|
||||
placeholder="Contoh: 08123456789"
|
||||
value={formData.telepon}
|
||||
onChange={(e) => (formData.telepon = e.currentTarget.value)}
|
||||
/>
|
||||
<TextInput
|
||||
label="Alamat"
|
||||
placeholder="Contoh: Jl. Contoh No. 1"
|
||||
value={formData.alamat}
|
||||
onChange={(e) => (formData.alamat = e.currentTarget.value)}
|
||||
/>
|
||||
<Select
|
||||
label="Posisi"
|
||||
placeholder="Pilih posisi"
|
||||
data={
|
||||
stateStrukturPPID.posisiOrganisasi.findMany.data?.map((p) => ({
|
||||
value: p.id, // harus string
|
||||
label: p.nama,
|
||||
})) || []
|
||||
}
|
||||
value={formData.posisiId}
|
||||
onChange={(value) => {
|
||||
if (value !== null) {
|
||||
setFormData({ ...formData, posisiId: value }); // value harus string
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Select
|
||||
label="Status Pegawai"
|
||||
data={[
|
||||
{ value: 'true', label: 'Aktif' },
|
||||
{ value: 'false', label: 'Tidak Aktif' },
|
||||
]}
|
||||
value={String(formData.isActive)} // 'true' atau 'false'
|
||||
onChange={(val) => {
|
||||
setFormData({ ...formData, isActive: val === 'true' });
|
||||
}}
|
||||
/>
|
||||
|
||||
|
||||
<Group>
|
||||
<Button
|
||||
onClick={handleSubmit}
|
||||
color="blue"
|
||||
>
|
||||
Simpan
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box >
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
'use client'
|
||||
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
||||
import stateStrukturPPID from '@/app/admin/(dashboard)/_state/ppid/struktur_ppid/struktur_PPID';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Flex, Image, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
function DetailPegawai() {
|
||||
const statePegawai = useProxy(stateStrukturPPID.pegawai)
|
||||
const [modalHapus, setModalHapus] = useState(false)
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||
const params = useParams()
|
||||
const router = useRouter();
|
||||
|
||||
useShallowEffect(() => {
|
||||
statePegawai.findUnique.load(params?.id as string)
|
||||
}, [])
|
||||
|
||||
const handleHapus = () => {
|
||||
if (selectedId) {
|
||||
statePegawai.delete.byId(selectedId)
|
||||
setModalHapus(false)
|
||||
setSelectedId(null)
|
||||
router.push("/admin/ppid/struktur-ppid/pegawai")
|
||||
}
|
||||
}
|
||||
|
||||
if (!statePegawai.findUnique.data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<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 Pegawai PPID</Text>
|
||||
<Paper bg={colors['BG-trans']} p={'md'}>
|
||||
<Stack gap={"xs"}>
|
||||
<Box>
|
||||
<Text fz={"lg"} fw={"bold"}>Nama Lengkap</Text>
|
||||
<Text fz={"lg"}>{statePegawai.findUnique.data?.namaLengkap}</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fz={"lg"} fw={"bold"}>Gelar Akademik</Text>
|
||||
<Text fz={"lg"}>{statePegawai.findUnique.data?.gelarAkademik}</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fz={"lg"} fw={"bold"}>Image</Text>
|
||||
{statePegawai.findUnique.data?.image?.link ? (
|
||||
<Image src={statePegawai.findUnique.data?.image?.link} alt='' />
|
||||
) : (
|
||||
<Text fz={"md"} c="dimmed">Tidak ada gambar</Text>
|
||||
)}
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fz={"lg"} fw={"bold"}>Tanggal Masuk</Text>
|
||||
<Text fz={"lg"}>
|
||||
{statePegawai.findUnique.data?.tanggalMasuk
|
||||
? new Date(statePegawai.findUnique.data.tanggalMasuk).toLocaleDateString()
|
||||
: "-"}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fz={"lg"} fw={"bold"}>Email</Text>
|
||||
<Text fz={"lg"}>{statePegawai.findUnique.data?.email}</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fz={"lg"} fw={"bold"}>Telepon</Text>
|
||||
<Text fz={"lg"}>{statePegawai.findUnique.data?.telepon}</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fz={"lg"} fw={"bold"}>Alamat</Text>
|
||||
<Text fz={"lg"}>{statePegawai.findUnique.data?.alamat}</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fz={"lg"} fw={"bold"}>Posisi</Text>
|
||||
<Stack gap={4}>
|
||||
{statePegawai.findUnique.data?.posisi ? (
|
||||
<Text fz={"lg"}>
|
||||
{statePegawai.findUnique.data.posisi.nama}
|
||||
</Text>
|
||||
) : (
|
||||
<Text fz={"lg"} c="dimmed">
|
||||
Tidak ada posisi
|
||||
</Text>
|
||||
)}
|
||||
</Stack>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fz={"lg"} fw={"bold"}>Aktif</Text>
|
||||
<Text fz={"lg"}>{statePegawai.findUnique.data?.isActive ? "Ya" : "Tidak"}</Text>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Flex gap={"xs"}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (statePegawai.findUnique.data) {
|
||||
setSelectedId(statePegawai.findUnique.data.id);
|
||||
setModalHapus(true);
|
||||
}
|
||||
}}
|
||||
disabled={!statePegawai.findUnique.data}
|
||||
color="red">
|
||||
<IconX size={20} />
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (statePegawai.findUnique.data) {
|
||||
router.push(`/admin/ppid/struktur-ppid/pegawai/${statePegawai.findUnique.data.id}/edit`);
|
||||
}
|
||||
}}
|
||||
disabled={!statePegawai.findUnique.data}
|
||||
color="green">
|
||||
<IconEdit size={20} />
|
||||
</Button>
|
||||
</Flex>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
{/* Modal Hapus */}
|
||||
<ModalKonfirmasiHapus
|
||||
opened={modalHapus}
|
||||
onClose={() => setModalHapus(false)}
|
||||
onConfirm={handleHapus}
|
||||
text="Apakah anda yakin ingin menghapus pegawai ini?"
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default DetailPegawai;
|
||||
@@ -0,0 +1,200 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import stateStrukturPPID from '@/app/admin/(dashboard)/_state/ppid/struktur_ppid/struktur_PPID';
|
||||
import colors from '@/con/colors';
|
||||
import ApiFetch from '@/lib/api-fetch';
|
||||
import { Box, Button, Group, Image, Paper, Select, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import { Dropzone } from '@mantine/dropzone';
|
||||
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
function CreatePegawaiPPID() {
|
||||
const router = useRouter();
|
||||
const [previewImage, setPreviewImage] = useState<{ preview: string; file: File } | null>(null);
|
||||
const stateOrganisasi = useProxy(stateStrukturPPID.pegawai)
|
||||
useEffect(() => {
|
||||
stateStrukturPPID.posisiOrganisasi.findMany.load();
|
||||
resetForm();
|
||||
}, []);
|
||||
|
||||
const resetForm = () => {
|
||||
stateOrganisasi.create.form = {
|
||||
namaLengkap: "",
|
||||
gelarAkademik: "",
|
||||
imageId: "",
|
||||
tanggalMasuk: "",
|
||||
email: "",
|
||||
telepon: "",
|
||||
alamat: "",
|
||||
posisiId: "",
|
||||
isActive: true,
|
||||
};
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!previewImage) {
|
||||
return toast.warn("Pilih file gambar terlebih dahulu");
|
||||
}
|
||||
|
||||
try {
|
||||
// Upload gambar dulu
|
||||
const res = await ApiFetch.api.fileStorage.create.post({
|
||||
file: previewImage.file,
|
||||
name: previewImage.file.name,
|
||||
});
|
||||
|
||||
const uploaded = res.data?.data;
|
||||
if (!uploaded?.id) {
|
||||
return toast.error("Gagal upload gambar");
|
||||
}
|
||||
|
||||
// Set status aktif secara otomatis
|
||||
stateOrganisasi.create.form.isActive = true;
|
||||
|
||||
// Simpan ID gambar ke form
|
||||
stateOrganisasi.create.form.imageId = uploaded.id;
|
||||
|
||||
// Submit form
|
||||
await stateOrganisasi.create.submit();
|
||||
|
||||
|
||||
// Reset form dan redirect
|
||||
resetForm();
|
||||
toast.success("Data pegawai berhasil ditambahkan");
|
||||
router.push("/admin/ppid/struktur-ppid/pegawai");
|
||||
} catch (error) {
|
||||
console.error("Error creating pegawai:", error);
|
||||
toast.error("Terjadi kesalahan saat menambahkan pegawai");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button variant="subtle" onClick={() => router.back()}>
|
||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||
</Button>
|
||||
</Box>
|
||||
<Paper bg={colors["white-1"]} p={"md"} w={{ base: "100%", md: "50%" }}>
|
||||
<Stack gap={"xs"}>
|
||||
<Title order={3}>Create Pegawai</Title>
|
||||
<TextInput
|
||||
label="Nama Lengkap"
|
||||
placeholder="Masukkan nama lengkap"
|
||||
value={stateOrganisasi.create.form.namaLengkap}
|
||||
onChange={(e) => (stateOrganisasi.create.form.namaLengkap = e.currentTarget.value)}
|
||||
/>
|
||||
<TextInput
|
||||
label="Gelar Akademik"
|
||||
placeholder="Contoh: S.Kom"
|
||||
value={stateOrganisasi.create.form.gelarAkademik}
|
||||
onChange={(e) => (stateOrganisasi.create.form.gelarAkademik = e.currentTarget.value)}
|
||||
/>
|
||||
<Box>
|
||||
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
||||
<Box >
|
||||
<Dropzone
|
||||
onDrop={(files) => {
|
||||
const file = files[0]; // Hanya ambil file pertama
|
||||
if (file) {
|
||||
setPreviewImage({
|
||||
file,
|
||||
preview: URL.createObjectURL(file)
|
||||
});
|
||||
}
|
||||
}}
|
||||
maxSize={5 * 1024 ** 2} // 5MB
|
||||
accept={{
|
||||
'image/*': ['.jpeg', '.jpg', '.png', '.webp']
|
||||
}}
|
||||
>
|
||||
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
||||
<Dropzone.Accept>
|
||||
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||
</Dropzone.Accept>
|
||||
<Dropzone.Reject>
|
||||
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||
</Dropzone.Reject>
|
||||
<Dropzone.Idle>
|
||||
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||
</Dropzone.Idle>
|
||||
|
||||
<div>
|
||||
<Text size="xl" inline>
|
||||
Drag images here or click to select files
|
||||
</Text>
|
||||
<Text size="sm" c="dimmed" inline mt={7}>
|
||||
Attach as many files as you like, each file should not exceed 5mb
|
||||
</Text>
|
||||
</div>
|
||||
</Group>
|
||||
</Dropzone>
|
||||
{previewImage && (
|
||||
<Image
|
||||
src={previewImage.preview}
|
||||
alt="Preview"
|
||||
width={280}
|
||||
height={180}
|
||||
fit="cover"
|
||||
radius="sm"
|
||||
mt="md"
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
<TextInput
|
||||
label="Tanggal Masuk"
|
||||
type="date"
|
||||
placeholder="Contoh: 2022-01-01"
|
||||
value={stateOrganisasi.create.form.tanggalMasuk}
|
||||
onChange={(e) => (stateOrganisasi.create.form.tanggalMasuk = e.currentTarget.value)}
|
||||
/>
|
||||
<TextInput
|
||||
label="Email"
|
||||
placeholder="Contoh: email@example.com"
|
||||
value={stateOrganisasi.create.form.email}
|
||||
onChange={(e) => (stateOrganisasi.create.form.email = e.currentTarget.value)}
|
||||
/>
|
||||
<TextInput
|
||||
label="Telepon"
|
||||
placeholder="Contoh: 08123456789"
|
||||
value={stateOrganisasi.create.form.telepon}
|
||||
onChange={(e) => (stateOrganisasi.create.form.telepon = e.currentTarget.value)}
|
||||
/>
|
||||
<TextInput
|
||||
label="Alamat"
|
||||
placeholder="Contoh: Jl. Contoh No. 1"
|
||||
value={stateOrganisasi.create.form.alamat}
|
||||
onChange={(e) => (stateOrganisasi.create.form.alamat = e.currentTarget.value)}
|
||||
/>
|
||||
<Select
|
||||
label="Posisi"
|
||||
placeholder="Pilih posisi"
|
||||
data={stateStrukturPPID.posisiOrganisasi.findMany.data?.map(p => ({
|
||||
value: p.id,
|
||||
label: p.nama
|
||||
})) || []}
|
||||
value={stateOrganisasi.create.form.posisiId}
|
||||
onChange={(value) => {
|
||||
if (value) stateOrganisasi.create.form.posisiId = value;
|
||||
}}
|
||||
searchable
|
||||
/>
|
||||
|
||||
|
||||
<Button
|
||||
onClick={handleSubmit}
|
||||
color="blue"
|
||||
>
|
||||
Simpan
|
||||
</Button>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default CreatePegawaiPPID;
|
||||
178
src/app/admin/(dashboard)/ppid/struktur-ppid/pegawai/page.tsx
Normal file
178
src/app/admin/(dashboard)/ppid/struktur-ppid/pegawai/page.tsx
Normal file
@@ -0,0 +1,178 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Badge, Box, Button, Center, Group, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, ThemeIcon } from '@mantine/core';
|
||||
import { IconCheck, IconDeviceImacCog, IconSearch, IconX } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import HeaderSearch from '../../../_com/header';
|
||||
import JudulList from '../../../_com/judulList';
|
||||
import stateStrukturPPID from '../../../_state/ppid/struktur_ppid/struktur_PPID';
|
||||
|
||||
function PegawaiPPID() {
|
||||
const [search, setSearch] = useState("");
|
||||
return (
|
||||
<Box>
|
||||
<HeaderSearch
|
||||
title='Pegawai PPID'
|
||||
placeholder='pencarian'
|
||||
searchIcon={<IconSearch size={20} />}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||
/>
|
||||
<ListPegawaiPPID search={search} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function ListPegawaiPPID({ search }: { search: string }) {
|
||||
const stateOrganisasi = useProxy(stateStrukturPPID.pegawai);
|
||||
const router = useRouter();
|
||||
|
||||
const {
|
||||
data,
|
||||
page,
|
||||
totalPages,
|
||||
loading,
|
||||
load,
|
||||
} = stateOrganisasi.findMany;
|
||||
|
||||
useEffect(() => {
|
||||
load(page, 10);
|
||||
}, [page]);
|
||||
|
||||
const filteredData = useMemo(() => {
|
||||
if (!data) return [];
|
||||
return data.filter(item => {
|
||||
const keyword = search.toLowerCase();
|
||||
return (
|
||||
item.namaLengkap?.toLowerCase().includes(keyword) ||
|
||||
item.gelarAkademik?.toLowerCase().includes(keyword) ||
|
||||
item.telepon?.toLowerCase().includes(keyword) ||
|
||||
item.posisi?.nama?.toLowerCase().includes(keyword)
|
||||
);
|
||||
})
|
||||
.sort((a, b) => a.posisi?.hierarki - b.posisi?.hierarki);
|
||||
}, [data, search]);
|
||||
|
||||
// Handle loading state
|
||||
if (loading || !data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Skeleton height={300} />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
if (data.length === 0) {
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
<JudulList
|
||||
title='List Pegawai'
|
||||
href='/admin/ppid/struktur-ppid/pegawai/create'
|
||||
/>
|
||||
<Box style={{ overflowX: "auto" }}>
|
||||
<Table striped withTableBorder withRowBorders>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>Nama</TableTh>
|
||||
<TableTh>Gelar Akademik</TableTh>
|
||||
<TableTh>Telepon</TableTh>
|
||||
<TableTh>Posisi</TableTh>
|
||||
<TableTh>Aktif</TableTh>
|
||||
<TableTh>Detail</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
</Table>
|
||||
</Box>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Paper bg={colors['white-1']} p={'md'} h={{base: 770, md: 650}}>
|
||||
<JudulList
|
||||
title='List Pegawai'
|
||||
href='/admin/ppid/struktur-ppid/pegawai/create'
|
||||
/>
|
||||
<Box style={{ overflowX: "auto" }}>
|
||||
<Table striped withTableBorder withRowBorders>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>Nama</TableTh>
|
||||
<TableTh>Gelar Akademik</TableTh>
|
||||
<TableTh>Telepon</TableTh>
|
||||
<TableTh>Posisi</TableTh>
|
||||
<TableTh>Aktif</TableTh>
|
||||
<TableTh>Detail</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{(() => {
|
||||
console.log('Rendering table with items:', stateOrganisasi.findMany.data);
|
||||
return null;
|
||||
})()}
|
||||
{([...filteredData]
|
||||
.sort((a, b) => {
|
||||
if (a.isActive === b.isActive) {
|
||||
return a.namaLengkap.localeCompare(b.namaLengkap); // kalau status sama, urut nama
|
||||
}
|
||||
return Number(b.isActive) - Number(a.isActive); // aktif duluan
|
||||
}) // Aktif di atas
|
||||
).map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>{item.namaLengkap}</TableTd>
|
||||
<TableTd>{item.gelarAkademik}</TableTd>
|
||||
<TableTd>{item.telepon}</TableTd>
|
||||
<TableTd>{item.posisi?.nama}</TableTd>
|
||||
<TableTd>
|
||||
<Group gap="xs" wrap="nowrap">
|
||||
<Box visibleFrom="sm">
|
||||
<Badge color={item.isActive ? "green" : "red"}>
|
||||
{item.isActive ? "Aktif" : "Tidak Aktif"}
|
||||
</Badge>
|
||||
</Box>
|
||||
<Box hiddenFrom="sm">
|
||||
{item.isActive ? (
|
||||
<ThemeIcon color="green" variant="light" size="sm">
|
||||
<IconCheck size={16} />
|
||||
</ThemeIcon>
|
||||
) : (
|
||||
<ThemeIcon color="red" variant="light" size="sm">
|
||||
<IconX size={16} />
|
||||
</ThemeIcon>
|
||||
)}
|
||||
</Box>
|
||||
</Group>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Button bg={"green"} onClick={() => router.push(`/admin/ppid/struktur-ppid/pegawai/${item.id}`)}>
|
||||
<IconDeviceImacCog size={25} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Box>
|
||||
</Paper>
|
||||
<Center>
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={(newPage) => {
|
||||
load(newPage, 10);
|
||||
window.scrollTo(0, 0);
|
||||
}}
|
||||
total={totalPages}
|
||||
mt="md"
|
||||
mb="md"
|
||||
/>
|
||||
</Center>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default PegawaiPPID;
|
||||
@@ -0,0 +1,117 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
||||
import stateStrukturPPID from '@/app/admin/(dashboard)/_state/ppid/struktur_ppid/struktur_PPID';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
function EditPosisiOrganisasiPPID() {
|
||||
const router = useRouter();
|
||||
const params = useParams();
|
||||
const id = params?.id as string;
|
||||
const stateOrganisasi = useProxy(stateStrukturPPID.posisiOrganisasi);
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
nama: "",
|
||||
deskripsi: "",
|
||||
hierarki: 0,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const loadPosisiOrganisasi = async () => {
|
||||
if (!id) return;
|
||||
|
||||
try {
|
||||
const data = await stateOrganisasi.edit.load(id);
|
||||
|
||||
if (data) {
|
||||
// pastikan id-nya masuk ke state edit
|
||||
stateOrganisasi.edit.id = id;
|
||||
setFormData({
|
||||
nama: data.nama || '',
|
||||
deskripsi: data.deskripsi || '',
|
||||
hierarki: data.hierarki || 0,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading posisi organisasi:", error);
|
||||
toast.error("Gagal memuat data posisi organisasi");
|
||||
}
|
||||
};
|
||||
|
||||
loadPosisiOrganisasi();
|
||||
}, [id]);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
if (!formData.nama.trim()) {
|
||||
toast.error('Nama posisi organisasi tidak boleh kosong');
|
||||
return;
|
||||
}
|
||||
|
||||
stateOrganisasi.edit.form = {
|
||||
nama: formData.nama.trim(),
|
||||
deskripsi: formData.deskripsi.trim(),
|
||||
hierarki: formData.hierarki,
|
||||
};
|
||||
|
||||
// Safety check tambahan: pastikan ID tidak kosong
|
||||
if (!stateOrganisasi.edit.id) {
|
||||
stateOrganisasi.edit.id = id; // fallback
|
||||
}
|
||||
|
||||
const success = await stateOrganisasi.edit.update();
|
||||
|
||||
if (success) {
|
||||
router.push("/admin/ppid/struktur-ppid/posisi-organisasi");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error updating posisi organisasi:", error);
|
||||
// toast akan ditampilkan dari fungsi update
|
||||
}
|
||||
};
|
||||
|
||||
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 Posisi Organisasi PPID</Title>
|
||||
<TextInput
|
||||
value={formData.nama}
|
||||
onChange={(e) => setFormData({ ...formData, nama: e.target.value })}
|
||||
label={<Text fw={"bold"} fz={"sm"}>Nama Posisi Organisasi</Text>}
|
||||
placeholder='Masukkan nama posisi organisasi'
|
||||
/>
|
||||
<EditEditor
|
||||
value={formData.deskripsi}
|
||||
onChange={(htmlContent) => {
|
||||
setFormData({ ...formData, deskripsi: htmlContent });
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
value={formData.hierarki}
|
||||
onChange={(e) => setFormData({ ...formData, hierarki: parseInt(e.target.value) })}
|
||||
label={<Text fw={"bold"} fz={"sm"}>Hierarki</Text>}
|
||||
placeholder='Masukkan hierarki'
|
||||
/>
|
||||
<Group>
|
||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Submit</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default EditPosisiOrganisasiPPID;
|
||||
@@ -0,0 +1,79 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
|
||||
import stateStrukturPPID from '@/app/admin/(dashboard)/_state/ppid/struktur_ppid/struktur_PPID';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Paper, Stack, TextInput, Title } from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useEffect } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
function CreatePosisiOrganisasiPPID() {
|
||||
const router = useRouter();
|
||||
const stateOrganisasi = useProxy(stateStrukturPPID.posisiOrganisasi)
|
||||
useEffect(() => {
|
||||
stateOrganisasi.findMany.load();
|
||||
}, []);
|
||||
|
||||
const resetForm = () => {
|
||||
stateOrganisasi.create.form = {
|
||||
nama: "",
|
||||
deskripsi: "",
|
||||
hierarki: 0, // Initialize as 0 to allow any number input
|
||||
};
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
await stateOrganisasi.create.submit();
|
||||
resetForm();
|
||||
router.push("/admin/ppid/struktur-ppid/posisi-organisasi")
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button variant="subtle" onClick={() => router.back()}>
|
||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||
</Button>
|
||||
</Box>
|
||||
<Paper bg={colors["white-1"]} p={"md"} w={{ base: "100%", md: "50%" }}>
|
||||
<Stack gap={"xs"}>
|
||||
<Title order={3}>Create Posisi Organisasi PPID</Title>
|
||||
<TextInput
|
||||
label="Nama Posisi"
|
||||
placeholder="Contoh: Kepala Desa"
|
||||
value={stateOrganisasi.create.form.nama}
|
||||
onChange={(e) => (stateOrganisasi.create.form.nama = e.currentTarget.value)}
|
||||
/>
|
||||
<CreateEditor
|
||||
value={stateOrganisasi.create.form.deskripsi}
|
||||
onChange={(htmlContent) => {
|
||||
stateOrganisasi.create.form.deskripsi = htmlContent;
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label="Hierarki"
|
||||
type="number"
|
||||
placeholder="Contoh: 1"
|
||||
value={stateOrganisasi.create.form.hierarki}
|
||||
onChange={(e) => {
|
||||
const value = parseInt(e.currentTarget.value, 10);
|
||||
if (!isNaN(value)) {
|
||||
stateOrganisasi.create.form.hierarki = value;
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
onClick={handleSubmit}
|
||||
color="blue"
|
||||
>
|
||||
Simpan
|
||||
</Button>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default CreatePosisiOrganisasiPPID;
|
||||
@@ -0,0 +1,134 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||
import { IconEdit, IconSearch, IconTrash } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import HeaderSearch from '../../../_com/header';
|
||||
import JudulList from '../../../_com/judulList';
|
||||
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||
import stateStrukturPPID from '../../../_state/ppid/struktur_ppid/struktur_PPID';
|
||||
|
||||
function PosisiOrganisasiPPID() {
|
||||
const [search, setSearch] = useState("");
|
||||
return (
|
||||
<Box>
|
||||
<HeaderSearch
|
||||
title='Posisi Organisasi PPID'
|
||||
placeholder='pencarian'
|
||||
searchIcon={<IconSearch size={20} />}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||
/>
|
||||
<ListPosisiOrganisasiPPID search={search} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function ListPosisiOrganisasiPPID({ search }: { search: string }) {
|
||||
const stateOrganisasi = useProxy(stateStrukturPPID.posisiOrganisasi)
|
||||
const router = useRouter();
|
||||
const [modalHapus, setModalHapus] = useState(false)
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
stateOrganisasi.findMany.load()
|
||||
}, [])
|
||||
|
||||
const handleHapus = async () => {
|
||||
if (selectedId) {
|
||||
await stateOrganisasi.delete.byId(selectedId);
|
||||
setModalHapus(false)
|
||||
setSelectedId(null)
|
||||
}
|
||||
}
|
||||
|
||||
const filteredData = (stateOrganisasi.findMany.data || []).filter(item => {
|
||||
const keyword = search.toLowerCase();
|
||||
return (
|
||||
item.nama?.toLowerCase().includes(keyword) ||
|
||||
item.deskripsi?.toLowerCase().includes(keyword) ||
|
||||
item.hierarki?.toString().toLowerCase().includes(keyword)
|
||||
);
|
||||
})
|
||||
.sort((a, b) => a.hierarki - b.hierarki);
|
||||
|
||||
if (!stateOrganisasi.findMany.data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Skeleton h={500} />
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
<JudulList
|
||||
title='List Posisi Organisasi PPID'
|
||||
href='/admin/ppid/struktur-ppid/posisi-organisasi/create'
|
||||
/>
|
||||
<Box style={{ overflowY: 'auto', maxHeight: '400px' }}>
|
||||
<Table striped withTableBorder withRowBorders>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>Nama Posisi</TableTh>
|
||||
<TableTh>Deskripsi</TableTh>
|
||||
<TableTh>Hierarki</TableTh>
|
||||
<TableTh>Edit</TableTh>
|
||||
<TableTh>Hapus</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody >
|
||||
{filteredData.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>{item.nama}</TableTd>
|
||||
<TableTd>
|
||||
<Text truncate dangerouslySetInnerHTML={{ __html: item.deskripsi ?? "" }} />
|
||||
</TableTd>
|
||||
<TableTd>{item.hierarki}</TableTd>
|
||||
<TableTd>
|
||||
<Button color="green"
|
||||
onClick={() => {
|
||||
if (item) {
|
||||
router.push(`/admin/ppid/struktur-ppid/posisi-organisasi/${item.id}`);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<IconEdit size={20} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Button color="red"
|
||||
onClick={() => {
|
||||
if (item) {
|
||||
setSelectedId(item.id);
|
||||
setModalHapus(true);
|
||||
}
|
||||
}}
|
||||
disabled={!item}
|
||||
|
||||
>
|
||||
<IconTrash size={20} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Box>
|
||||
</Paper>
|
||||
{/* Modal Hapus */}
|
||||
<ModalKonfirmasiHapus
|
||||
opened={modalHapus}
|
||||
onClose={() => setModalHapus(false)}
|
||||
onConfirm={handleHapus}
|
||||
text="Apakah anda yakin ingin menghapus posisi organisasi PPID ini?"
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default PosisiOrganisasiPPID;
|
||||
@@ -0,0 +1,117 @@
|
||||
/* eslint-disable prefer-const */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Image, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
||||
import { OrganizationChart } from 'primereact/organizationchart';
|
||||
import { useEffect } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import stateStrukturPPID from '../../../_state/ppid/struktur_ppid/struktur_PPID';
|
||||
|
||||
function StrukturOrganisasiPPID() {
|
||||
return (
|
||||
<Box >
|
||||
<ListStrukturOrganisasiPPID />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function ListStrukturOrganisasiPPID() {
|
||||
const stateOrganisasi = useProxy(stateStrukturPPID.pegawai)
|
||||
|
||||
useEffect(() => {
|
||||
stateOrganisasi.findMany.load()
|
||||
}, [])
|
||||
|
||||
if (!stateOrganisasi.findMany.data || stateOrganisasi.findMany.data.length === 0) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Skeleton h={500} />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
// Step 1: Group pegawai berdasarkan posisiId
|
||||
const posisiMap = new Map<string, any>();
|
||||
|
||||
for (const pegawai of stateOrganisasi.findMany.data) {
|
||||
const posisiId = pegawai.posisi.id;
|
||||
if (!posisiMap.has(posisiId)) {
|
||||
posisiMap.set(posisiId, {
|
||||
...pegawai.posisi,
|
||||
pegawaiList: [],
|
||||
children: []
|
||||
});
|
||||
}
|
||||
posisiMap.get(posisiId)!.pegawaiList.push(pegawai);
|
||||
}
|
||||
|
||||
|
||||
// Step 2: Buat struktur pohon berdasarkan parentId
|
||||
let root: any[] = [];
|
||||
|
||||
posisiMap.forEach((posisi) => {
|
||||
if (posisi.parentId) {
|
||||
const parent = posisiMap.get(posisi.parentId);
|
||||
if (parent) {
|
||||
parent.children.push(posisi);
|
||||
}
|
||||
} else {
|
||||
root.push(posisi);
|
||||
}
|
||||
});
|
||||
|
||||
// Step 3: Ubah struktur ke format OrganizationChart
|
||||
function toOrgChartFormat(node: any): any {
|
||||
return {
|
||||
expanded: true,
|
||||
type: 'person',
|
||||
styleClass: 'p-person',
|
||||
data: {
|
||||
name: node.pegawaiList?.[0]?.namaLengkap || 'Tidak ada pegawai',
|
||||
status: node.nama,
|
||||
image: node.pegawaiList?.[0]?.image?.link || '/img/default.png'
|
||||
},
|
||||
children: node.children.map(toOrgChartFormat)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
const chartData = root.map(toOrgChartFormat);
|
||||
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Paper bg={colors.grey} p="md" style={{overflowX: 'auto'}}>
|
||||
<OrganizationChart value={chartData} nodeTemplate={nodeTemplate} />
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
function nodeTemplate(node: any) {
|
||||
const imageSrc = node?.data?.image || '/img/default.png';
|
||||
const name = node?.data?.name || 'Tanpa Nama';
|
||||
const status = node?.data?.status || 'Tidak ada deskripsi';
|
||||
|
||||
return (
|
||||
<Stack align="center" gap={4}>
|
||||
<Image
|
||||
src={imageSrc}
|
||||
alt={name}
|
||||
radius="xl"
|
||||
w={120}
|
||||
h={120}
|
||||
fit="cover"
|
||||
/>
|
||||
<Text fw={600} ta="center">{name}</Text>
|
||||
<Text size="sm" c="dimmed" ta="center">{status}</Text>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
export default StrukturOrganisasiPPID;
|
||||
@@ -49,7 +49,7 @@ export const navBar = [
|
||||
{
|
||||
id: "PPID_2",
|
||||
name: "Struktur PPID",
|
||||
path: "/admin/ppid/struktur-ppid"
|
||||
path: "/admin/ppid/struktur-ppid/pegawai"
|
||||
},
|
||||
{
|
||||
id: "PPID_3",
|
||||
@@ -225,7 +225,7 @@ export const navBar = [
|
||||
{
|
||||
id: "Ekonomi_3",
|
||||
name: "Struktur Organisasi dan SK Pengurus BUMDesa",
|
||||
path: "/admin/ekonomi/struktur-organisasi-dan-sk-pengurus-bumdesa/posisi-organisasi"
|
||||
path: "/admin/ekonomi/Struktur-Organisasi-dan-SK-Pengurus-BUMDesa/posisi-organisasi"
|
||||
},
|
||||
{
|
||||
id: "Ekonomi_4",
|
||||
@@ -349,7 +349,7 @@ export const navBar = [
|
||||
{
|
||||
id: "Pendidikan_2",
|
||||
name: "Beasiswa Desa",
|
||||
path: "/admin/pendidikan/beasiswa-desa"
|
||||
path: "/admin/pendidikan/beasiswa-desa/beasiswa-pendaftar"
|
||||
},
|
||||
{
|
||||
id: "Pendidikan_3",
|
||||
@@ -369,7 +369,7 @@ export const navBar = [
|
||||
{
|
||||
id: "Pendidikan_6",
|
||||
name: "Perpustakaan Digital",
|
||||
path: "/admin/pendidikan/perpustakaan-digital"
|
||||
path: "/admin/pendidikan/perpustakaan-digital/data-perpustakaan"
|
||||
},
|
||||
{
|
||||
id: "Pendidikan_7",
|
||||
|
||||
@@ -6,33 +6,35 @@ import grafikJumlahPendudukMiskinCreate from "./create";
|
||||
import grafikJumlahPendudukMiskinDelete from "./del";
|
||||
|
||||
const JumlahPendudukMiskin = new Elysia({
|
||||
prefix: "/jumlahpendudukmiskin",
|
||||
tags: ["Ekonomi/Jumlah Penduduk Miskin"],
|
||||
prefix: "/jumlahpendudukmiskin",
|
||||
tags: ["Ekonomi/Jumlah Penduduk Miskin"],
|
||||
})
|
||||
.get("/find-many", grafikJumlahPendudukMiskinFindMany)
|
||||
.get("/:id", async (context) => {
|
||||
const response = await grafikJumlahPendudukMiskinFindById(new Request(context.request))
|
||||
return response
|
||||
})
|
||||
.put("/:id", grafikJumlahPendudukMiskinUpdate, {
|
||||
params: t.Object({
|
||||
id: t.String(),
|
||||
}),
|
||||
body: t.Object({
|
||||
year: t.Number(),
|
||||
totalPoorPopulation: t.Number(),
|
||||
}),
|
||||
})
|
||||
.post("/create", grafikJumlahPendudukMiskinCreate, {
|
||||
body: t.Object({
|
||||
year: t.Number(),
|
||||
totalPoorPopulation: t.Number(),
|
||||
}),
|
||||
})
|
||||
.delete("/del/:id", grafikJumlahPendudukMiskinDelete, {
|
||||
params: t.Object({
|
||||
id: t.String(),
|
||||
}),
|
||||
});
|
||||
.get("/find-many", grafikJumlahPendudukMiskinFindMany)
|
||||
.get("/:id", async (context) => {
|
||||
const response = await grafikJumlahPendudukMiskinFindById(
|
||||
new Request(context.request)
|
||||
);
|
||||
return response;
|
||||
})
|
||||
.put("/:id", grafikJumlahPendudukMiskinUpdate, {
|
||||
params: t.Object({
|
||||
id: t.String(),
|
||||
}),
|
||||
body: t.Object({
|
||||
year: t.Number(),
|
||||
totalPoorPopulation: t.Number(),
|
||||
}),
|
||||
})
|
||||
.post("/create", grafikJumlahPendudukMiskinCreate, {
|
||||
body: t.Object({
|
||||
year: t.Number(),
|
||||
totalPoorPopulation: t.Number(),
|
||||
}),
|
||||
})
|
||||
.delete("/del/:id", grafikJumlahPendudukMiskinDelete, {
|
||||
params: t.Object({
|
||||
id: t.String(),
|
||||
}),
|
||||
});
|
||||
|
||||
export default JumlahPendudukMiskin;
|
||||
export default JumlahPendudukMiskin;
|
||||
|
||||
@@ -45,7 +45,7 @@ const Pegawai = new Elysia({
|
||||
id: t.String(),
|
||||
namaLengkap: t.Optional(t.String()),
|
||||
gelarAkademik: t.Optional(t.String()),
|
||||
imageId: t.String(),
|
||||
imageId: t.Optional(t.String()),
|
||||
tanggalMasuk: t.Optional(t.String()),
|
||||
email: t.Optional(t.String()),
|
||||
telepon: t.Optional(t.String()),
|
||||
|
||||
@@ -6,7 +6,7 @@ type FormUpdatePegawai = {
|
||||
id: string;
|
||||
namaLengkap?: string;
|
||||
gelarAkademik?: string;
|
||||
imageId: string;
|
||||
imageId?: string;
|
||||
tanggalMasuk?: string;
|
||||
email?: string;
|
||||
telepon?: string;
|
||||
|
||||
@@ -17,7 +17,7 @@ async function desaAntiKorupsiFindMany(context: Context) {
|
||||
},
|
||||
skip,
|
||||
take: limit,
|
||||
orderBy: { createdAt: 'desc' }, // opsional, kalau mau urut berdasarkan waktu
|
||||
orderBy: { name: 'asc' }, // opsional, kalau mau urut berdasarkan waktu
|
||||
}),
|
||||
prisma.desaAntiKorupsi.count({
|
||||
where: { isActive: true }
|
||||
|
||||
@@ -11,7 +11,7 @@ const DesaAntiKorupsi = new Elysia({
|
||||
})
|
||||
|
||||
// ✅ Find all
|
||||
.get("/find-many", desaAntiKorupsiFindMany)
|
||||
.get("/findMany", desaAntiKorupsiFindMany)
|
||||
|
||||
// ✅ Find by ID
|
||||
.get("/:id", desaAntiKorupsiFindUnique)
|
||||
|
||||
@@ -1,15 +1,40 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
// /api/berita/findManyPaginated.ts
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
export default async function kategoriDesaAntiKorupsiFindMany() {
|
||||
const data = await prisma.kategoriDesaAntiKorupsi.findMany();
|
||||
return {
|
||||
success: true,
|
||||
data: data.map((item: any) => {
|
||||
return {
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
}
|
||||
}),
|
||||
async function kategoriDesaAntiKorupsiFindMany(context: Context) {
|
||||
const page = Number(context.query.page) || 1;
|
||||
const limit = Number(context.query.limit) || 10;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
try {
|
||||
const [data, total] = await Promise.all([
|
||||
prisma.kategoriDesaAntiKorupsi.findMany({
|
||||
where: { isActive: true },
|
||||
skip,
|
||||
take: limit,
|
||||
orderBy: { name: 'asc' }, // opsional, kalau mau urut berdasarkan waktu
|
||||
}),
|
||||
prisma.kategoriDesaAntiKorupsi.count({
|
||||
where: { isActive: true }
|
||||
})
|
||||
]);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Success fetch kategori desa anti korupsi with pagination",
|
||||
data,
|
||||
page,
|
||||
totalPages: Math.ceil(total / limit),
|
||||
total,
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Find many paginated error:", e);
|
||||
return {
|
||||
success: false,
|
||||
message: "Failed fetch kategori desa anti korupsi with pagination",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default kategoriDesaAntiKorupsiFindMany;
|
||||
|
||||
@@ -9,7 +9,7 @@ const KategoriDesaAntiKorupsi = new Elysia({
|
||||
prefix: "/kategoridak",
|
||||
tags: ["Landing Page/Desa Anti Korupsi"],
|
||||
})
|
||||
.get("/find-many", kategoriDesaAntiKorupsiFindMany)
|
||||
.get("/findMany", kategoriDesaAntiKorupsiFindMany)
|
||||
.get("/:id", async (context) => {
|
||||
const response = await kategoriDesaAntiKorupsiFindUnique(context);
|
||||
return response;
|
||||
|
||||
@@ -16,7 +16,7 @@ async function mediaSosialFindMany(context: Context) {
|
||||
},
|
||||
skip,
|
||||
take: limit,
|
||||
orderBy: { createdAt: "desc" }, // opsional, kalau mau urut berdasarkan waktu
|
||||
orderBy: { name: "asc" }, // opsional, kalau mau urut berdasarkan waktu
|
||||
}),
|
||||
prisma.mediaSosial.count({
|
||||
where: { isActive: true },
|
||||
|
||||
@@ -11,7 +11,7 @@ const MediaSosial = new Elysia({
|
||||
})
|
||||
|
||||
// ✅ Find all
|
||||
.get("/find-many", MediaSosialFindMany)
|
||||
.get("/findMany", MediaSosialFindMany)
|
||||
|
||||
// ✅ Find by ID
|
||||
.get("/:id", MediaSosialFindUnique)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// /api/berita/findManyPaginated.ts
|
||||
// // /api/berita/findManyPaginated.ts
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
@@ -16,7 +16,7 @@ async function programInovasiFindMany(context: Context) {
|
||||
},
|
||||
skip,
|
||||
take: limit,
|
||||
orderBy: { createdAt: "desc" }, // opsional, kalau mau urut berdasarkan waktu
|
||||
orderBy: { name: "asc" }, // opsional, kalau mau urut berdasarkan waktu
|
||||
}),
|
||||
prisma.programInovasi.count({
|
||||
where: { isActive: true },
|
||||
@@ -41,3 +41,4 @@ async function programInovasiFindMany(context: Context) {
|
||||
}
|
||||
|
||||
export default programInovasiFindMany;
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ const ProgramInovasi = new Elysia({
|
||||
})
|
||||
|
||||
// ✅ Find all
|
||||
.get("/find-many", ProgramInovasiFindMany)
|
||||
.get("/findMany", ProgramInovasiFindMany)
|
||||
|
||||
// ✅ Find by ID
|
||||
.get("/:id", ProgramInovasiFindUnique)
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
type FormCreate = {
|
||||
namaLengkap: string;
|
||||
nik: string;
|
||||
tempatLahir: string;
|
||||
tanggalLahir: string; // ISO date string
|
||||
jenisKelamin: "LAKI_LAKI" | "PEREMPUAN";
|
||||
kewarganegaraan: string;
|
||||
agama: "ISLAM" | "KRISTEN_PROTESTAN" | "KRISTEN_KATOLIK" | "HINDU" | "BUDDHA" | "KONGHUCU" | "LAINNYA";
|
||||
alamatKTP: string;
|
||||
alamatDomisili?: string;
|
||||
noHp: string;
|
||||
email: string;
|
||||
statusPernikahan: "BELUM_MENIKAH" | "MENIKAH" | "JANDA_DUDA";
|
||||
ukuranBaju?: "S" | "M" | "L" | "XL" | "XXL" | "LAINNYA";
|
||||
};
|
||||
|
||||
export default async function beasiswaPendaftarCreate(context: Context) {
|
||||
const body = (await context.body) as FormCreate;
|
||||
|
||||
try {
|
||||
const result = await prisma.beasiswaPendaftar.create({
|
||||
data: {
|
||||
namaLengkap: body.namaLengkap,
|
||||
nik: body.nik,
|
||||
tempatLahir: body.tempatLahir,
|
||||
tanggalLahir: new Date(body.tanggalLahir),
|
||||
jenisKelamin: body.jenisKelamin,
|
||||
kewarganegaraan: body.kewarganegaraan,
|
||||
agama: body.agama,
|
||||
alamatKTP: body.alamatKTP,
|
||||
alamatDomisili: body.alamatDomisili,
|
||||
noHp: body.noHp,
|
||||
email: body.email,
|
||||
statusPernikahan: body.statusPernikahan,
|
||||
ukuranBaju: body.ukuranBaju,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Berhasil membuat data pendaftar beasiswa",
|
||||
data: result,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Gagal membuat data beasiswa pendaftar:", error);
|
||||
throw new Error("Gagal membuat data beasiswa pendaftar: " + (error as Error).message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
export default async function beasiswaPendaftarDelete(context: Context) {
|
||||
const id = context.params.id as string;
|
||||
|
||||
await prisma.beasiswaPendaftar.delete({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
success: true,
|
||||
message: "Success delete beasiswa pendaftar",
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export default async function beasiswaPendaftarFindMany() {
|
||||
const data = await prisma.beasiswaPendaftar.findMany();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Success get all beasiswa pendaftar",
|
||||
data,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export default async function beasiswaPendaftarFindUnique(request: Request) {
|
||||
const url = new URL(request.url);
|
||||
const pathSegments = url.pathname.split('/');
|
||||
const id = pathSegments[pathSegments.length - 1];
|
||||
|
||||
if (!id) {
|
||||
return {
|
||||
success: false,
|
||||
message: "ID is required",
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (typeof id !== 'string') {
|
||||
return {
|
||||
success: false,
|
||||
message: "ID is required",
|
||||
}
|
||||
}
|
||||
|
||||
const data = await prisma.beasiswaPendaftar.findUnique({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
if (!data) {
|
||||
return {
|
||||
success: false,
|
||||
message: "Data not found",
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Success get beasiswa pendaftar",
|
||||
data,
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Find by ID error:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: "Gagal mengambil data: " + (error instanceof Error ? error.message : 'Unknown error'),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import Elysia, { t } from "elysia";
|
||||
import beasiswaPendaftarCreate from "./create";
|
||||
import beasiswaPendaftarFindMany from "./findMany";
|
||||
import beasiswaPendaftarFindUnique from "./findUnique";
|
||||
import beasiswaPendaftarUpdate from "./updt";
|
||||
import beasiswaPendaftarDelete from "./del";
|
||||
|
||||
const BeasiswaPendaftar = new Elysia({
|
||||
prefix: "/beasiswapendaftar",
|
||||
tags: ["Pendidikan / Beasiswa Desa / Beasiswa Pendaftar"],
|
||||
})
|
||||
|
||||
.post("/create", beasiswaPendaftarCreate, {
|
||||
body: t.Object({
|
||||
namaLengkap: t.String(),
|
||||
nik: t.String(),
|
||||
tempatLahir: t.String(),
|
||||
tanggalLahir: t.String(),
|
||||
jenisKelamin: t.String(),
|
||||
kewarganegaraan: t.String(),
|
||||
agama: t.String(),
|
||||
alamatKTP: t.String(),
|
||||
alamatDomisili: t.String(),
|
||||
noHp: t.String(),
|
||||
email: t.String(),
|
||||
statusPernikahan: t.String(),
|
||||
ukuranBaju: t.String(),
|
||||
}),
|
||||
})
|
||||
.get("/findMany", beasiswaPendaftarFindMany)
|
||||
.get("/:id", async (context) => {
|
||||
const response = await beasiswaPendaftarFindUnique(
|
||||
new Request(context.request)
|
||||
);
|
||||
return response;
|
||||
})
|
||||
.put(
|
||||
"/:id",
|
||||
async (context) => {
|
||||
const response = await beasiswaPendaftarUpdate(context);
|
||||
return response;
|
||||
},
|
||||
{
|
||||
body: t.Object({
|
||||
namaLengkap: t.String(),
|
||||
nik: t.String(),
|
||||
tempatLahir: t.String(),
|
||||
tanggalLahir: t.String(),
|
||||
jenisKelamin: t.String(),
|
||||
kewarganegaraan: t.String(),
|
||||
agama: t.String(),
|
||||
alamatKTP: t.String(),
|
||||
alamatDomisili: t.String(),
|
||||
noHp: t.String(),
|
||||
email: t.String(),
|
||||
statusPernikahan: t.String(),
|
||||
ukuranBaju: t.String(),
|
||||
}),
|
||||
}
|
||||
)
|
||||
.delete("/del/:id", beasiswaPendaftarDelete);
|
||||
|
||||
export default BeasiswaPendaftar;
|
||||
@@ -0,0 +1,79 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
type FormUpdate = {
|
||||
namaLengkap: string;
|
||||
nik: string;
|
||||
tempatLahir: string;
|
||||
tanggalLahir: string; // ISO date string
|
||||
jenisKelamin: "LAKI_LAKI" | "PEREMPUAN";
|
||||
kewarganegaraan: string;
|
||||
agama:
|
||||
| "ISLAM"
|
||||
| "KRISTEN_PROTESTAN"
|
||||
| "KRISTEN_KATOLIK"
|
||||
| "HINDU"
|
||||
| "BUDDHA"
|
||||
| "KONGHUCU"
|
||||
| "LAINNYA";
|
||||
alamatKTP: string;
|
||||
alamatDomisili?: string;
|
||||
noHp: string;
|
||||
email: string;
|
||||
statusPernikahan: "BELUM_MENIKAH" | "MENIKAH" | "JANDA_DUDA";
|
||||
ukuranBaju?: "S" | "M" | "L" | "XL" | "XXL" | "LAINNYA";
|
||||
};
|
||||
|
||||
export default async function beasiswaPendaftarUpdate(context: Context) {
|
||||
const id = context.params.id as string;
|
||||
const body = context.body as FormUpdate;
|
||||
|
||||
try {
|
||||
if (typeof id !== "string") {
|
||||
return {
|
||||
success: false,
|
||||
message: "ID is required",
|
||||
};
|
||||
}
|
||||
|
||||
const data = await prisma.beasiswaPendaftar.update({
|
||||
where: { id },
|
||||
data: {
|
||||
namaLengkap: body.namaLengkap,
|
||||
nik: body.nik,
|
||||
tempatLahir: body.tempatLahir,
|
||||
tanggalLahir: new Date(body.tanggalLahir),
|
||||
jenisKelamin: body.jenisKelamin,
|
||||
kewarganegaraan: body.kewarganegaraan,
|
||||
agama: body.agama,
|
||||
alamatKTP: body.alamatKTP,
|
||||
alamatDomisili: body.alamatDomisili,
|
||||
noHp: body.noHp,
|
||||
email: body.email,
|
||||
statusPernikahan: body.statusPernikahan,
|
||||
ukuranBaju: body.ukuranBaju,
|
||||
},
|
||||
});
|
||||
|
||||
if (!data) {
|
||||
return {
|
||||
success: false,
|
||||
message: "Data not found",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Success update beasiswa pendaftar",
|
||||
data,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Update by ID error:", error);
|
||||
return {
|
||||
success: false,
|
||||
message:
|
||||
"Gagal mengupdate data: " +
|
||||
(error instanceof Error ? error.message : "Unknown error"),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import Elysia from "elysia";
|
||||
import BeasiswaPendaftar from "./beasiswa-pendaftar";
|
||||
|
||||
const Beasiswa = new Elysia({
|
||||
prefix: "/beasiswa",
|
||||
tags: ["Pendidikan/Beasiswa Desa"]
|
||||
})
|
||||
.use(BeasiswaPendaftar)
|
||||
|
||||
export default Beasiswa
|
||||
@@ -0,0 +1,31 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { Context } from "elysia";
|
||||
|
||||
type FormCreate = Prisma.DataPendidikanGetPayload<{
|
||||
select: {
|
||||
name: true;
|
||||
jumlah: true;
|
||||
};
|
||||
}>;
|
||||
export default async function dataPendidikanCreate(context: Context) {
|
||||
const body = context.body as FormCreate;
|
||||
|
||||
const created = await prisma.dataPendidikan.create({
|
||||
data: {
|
||||
name: body.name,
|
||||
jumlah: body.jumlah,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
jumlah: true,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Data pendidikan berhasil dibuat",
|
||||
data: created,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import prisma from "@/lib/prisma"
|
||||
import { Context } from "elysia"
|
||||
|
||||
export default async function dataPendidikanDelete(context: Context) {
|
||||
const {id} = context.params as {id: string}
|
||||
|
||||
try {
|
||||
const existingData = await prisma.dataPendidikan.findUnique({
|
||||
where: {
|
||||
id: id,
|
||||
}
|
||||
})
|
||||
|
||||
if (!existingData) {
|
||||
return {
|
||||
success: false,
|
||||
message: "Data tidak ditemukan",
|
||||
data: null,
|
||||
}
|
||||
}
|
||||
|
||||
await prisma.dataPendidikan.delete({
|
||||
where: {
|
||||
id: id
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Data berhasil dihapus",
|
||||
data: {
|
||||
id: id,
|
||||
deleted: true,
|
||||
},
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
message: error instanceof Error ? error.message : "Unknown error",
|
||||
data: null,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export default async function dataPendidikanFindMany() {
|
||||
const res = await prisma.dataPendidikan.findMany();
|
||||
return {
|
||||
data: res,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export default async function dataPendidikanFindUnique(
|
||||
request: Request
|
||||
) {
|
||||
const url = new URL(request.url);
|
||||
const pathSegments = url.pathname.split("/");
|
||||
const id = pathSegments[pathSegments.length - 1];
|
||||
|
||||
if (!id) {
|
||||
return Response.json(
|
||||
{
|
||||
success: false,
|
||||
message: "ID tidak boleh kosong",
|
||||
},
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
if (typeof id !== "string") {
|
||||
return Response.json(
|
||||
{
|
||||
success: false,
|
||||
message: "ID tidak valid",
|
||||
},
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
const data = await prisma.dataPendidikan.findUnique({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
if (!data) {
|
||||
return Response.json(
|
||||
{
|
||||
success: false,
|
||||
message: "Data tidak ditemukan",
|
||||
},
|
||||
{ status: 404 }
|
||||
);
|
||||
}
|
||||
|
||||
return Response.json(
|
||||
{
|
||||
success: true,
|
||||
message: "Berhasil mengambil data pendidikan berdasarkan ID",
|
||||
data,
|
||||
},
|
||||
{ status: 200 }
|
||||
);
|
||||
} catch (e) {
|
||||
console.error("Find by ID error:", e);
|
||||
return Response.json(
|
||||
{
|
||||
success: false,
|
||||
message:
|
||||
"Gagal mengambil data pendidikan: " +
|
||||
(e instanceof Error ? e.message : "Unknown error"),
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import Elysia, { t } from "elysia";
|
||||
import dataPendidikanFindMany from "./findMany";
|
||||
import dataPendidikanFindUnique from "./findUnique";
|
||||
import dataPendidikanUpdate from "./updt";
|
||||
import dataPendidikanCreate from "./create";
|
||||
import dataPendidikanDelete from "./del";
|
||||
|
||||
const DataPendidikan = new Elysia({
|
||||
prefix: "/datapendidikan",
|
||||
tags: ["Pendidikan/Data Pendidikan"],
|
||||
})
|
||||
.get("/findMany", dataPendidikanFindMany)
|
||||
.get("/:id", async (context) => {
|
||||
const response = await dataPendidikanFindUnique(
|
||||
new Request(context.request)
|
||||
);
|
||||
return response;
|
||||
})
|
||||
.put("/:id", dataPendidikanUpdate, {
|
||||
params: t.Object({
|
||||
id: t.String(),
|
||||
}),
|
||||
body: t.Object({
|
||||
name: t.String(),
|
||||
jumlah: t.String(),
|
||||
}),
|
||||
})
|
||||
.post("/create", dataPendidikanCreate, {
|
||||
body: t.Object({
|
||||
name: t.String(),
|
||||
jumlah: t.String(),
|
||||
}),
|
||||
})
|
||||
.delete("/del/:id", dataPendidikanDelete, {
|
||||
params: t.Object({
|
||||
id: t.String(),
|
||||
}),
|
||||
});
|
||||
export default DataPendidikan;
|
||||
@@ -0,0 +1,48 @@
|
||||
import { Context } from "elysia";
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export default async function dataPendidikanUpdate(context: Context) {
|
||||
const id = context.params?.id;
|
||||
|
||||
if (!id) {
|
||||
return {
|
||||
success: false,
|
||||
message: "Id tidak ditemukan",
|
||||
}
|
||||
}
|
||||
|
||||
const existingData = await prisma.dataPendidikan.findUnique({
|
||||
where: {
|
||||
id: id,
|
||||
}
|
||||
})
|
||||
|
||||
const {name, jumlah} = context.body as {
|
||||
name: string;
|
||||
jumlah: string;
|
||||
}
|
||||
|
||||
if (!existingData) {
|
||||
return {
|
||||
success: false,
|
||||
message: "Data tidak ditemukan",
|
||||
data: null,
|
||||
}
|
||||
}
|
||||
|
||||
const updated = await prisma.dataPendidikan.update({
|
||||
where: {
|
||||
id: id,
|
||||
},
|
||||
data: {
|
||||
name: name,
|
||||
jumlah: jumlah,
|
||||
},
|
||||
})
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Data pendidikan berhasil diupdate",
|
||||
data: updated,
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,9 @@ import InfoSekolahPAUD from "./info-sekolah-paud";
|
||||
import ProgramPendidikanAnak from "./program-pendidikan-anak";
|
||||
import BimbinganBelajarDesa from "./bimbingan-belajar-desa";
|
||||
import PendidikanNonFormal from "./pendidikan-non-formal";
|
||||
import DataPendidikan from "./data-pendidikan";
|
||||
import Beasiswa from "./beasiswa-desa";
|
||||
import PerpustakaanDigital from "./perpustakaan-digital";
|
||||
|
||||
const Pendidikan = new Elysia({
|
||||
prefix: "/api/pendidikan",
|
||||
@@ -13,5 +16,8 @@ const Pendidikan = new Elysia({
|
||||
.use(ProgramPendidikanAnak)
|
||||
.use(BimbinganBelajarDesa)
|
||||
.use(PendidikanNonFormal)
|
||||
.use(DataPendidikan)
|
||||
.use(Beasiswa)
|
||||
.use(PerpustakaanDigital)
|
||||
|
||||
export default Pendidikan;
|
||||
@@ -0,0 +1,32 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
type FormCreate = {
|
||||
judul: string;
|
||||
deskripsi: string;
|
||||
imageId: string;
|
||||
kategoriId: string;
|
||||
}
|
||||
|
||||
export default async function dataPerpustakaanCreate(context: Context) {
|
||||
const body = (await context.body) as FormCreate;
|
||||
|
||||
try {
|
||||
const result = await prisma.dataPerpustakaan.create({
|
||||
data: {
|
||||
judul: body.judul,
|
||||
deskripsi: body.deskripsi,
|
||||
imageId: body.imageId,
|
||||
kategoriId: body.kategoriId,
|
||||
},
|
||||
});
|
||||
return {
|
||||
success: true,
|
||||
message: "Berhasil membuat data perpustakaan",
|
||||
data: result,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error creating data perpustakaan:", error);
|
||||
throw new Error("Gagal membuat data perpustakaan: " + (error as Error).message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
export default async function dataPerpustakaanDelete(context: Context) {
|
||||
const id = context.params.id as string;
|
||||
|
||||
await prisma.dataPerpustakaan.delete({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
success: true,
|
||||
message: "Success delete data perpustakaan",
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export default async function dataPerpustakaanFindMany() {
|
||||
const data = await prisma.dataPerpustakaan.findMany({
|
||||
include: {
|
||||
kategori: true,
|
||||
image: true,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Success get all data perpustakaan",
|
||||
data,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export default async function dataPerpustakaanFindUnique(request: Request) {
|
||||
const url = new URL(request.url);
|
||||
const pathSegments = url.pathname.split('/');
|
||||
const id = pathSegments[pathSegments.length - 1];
|
||||
|
||||
if (!id) {
|
||||
return {
|
||||
success: false,
|
||||
message: "ID is required",
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (typeof id !== 'string') {
|
||||
return {
|
||||
success: false,
|
||||
message: "ID is required",
|
||||
}
|
||||
}
|
||||
|
||||
const data = await prisma.dataPerpustakaan.findUnique({
|
||||
where: { id },
|
||||
include: {
|
||||
kategori: true,
|
||||
image: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!data) {
|
||||
return {
|
||||
success: false,
|
||||
message: "Data not found",
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Success get data perpustakaan",
|
||||
data,
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Find by ID error:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: "Gagal mengambil data: " + (error instanceof Error ? error.message : 'Unknown error'),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import Elysia, { t } from "elysia";
|
||||
import dataPerpustakaanCreate from "./create";
|
||||
import dataPerpustakaanDelete from "./del";
|
||||
import dataPerpustakaanFindMany from "./findMany";
|
||||
import dataPerpustakaanFindUnique from "./findUnique";
|
||||
import dataPerpustakaanUpdate from "./updt";
|
||||
|
||||
const DataPerpustakaan = new Elysia({
|
||||
prefix: "/dataperpustakaan",
|
||||
tags: ["Pendidikan / Perpustakaan Digital / Data Perpustakaan"],
|
||||
})
|
||||
|
||||
.post("/create", dataPerpustakaanCreate, {
|
||||
body: t.Object({
|
||||
judul: t.String(),
|
||||
deskripsi: t.String(),
|
||||
imageId: t.String(),
|
||||
kategoriId: t.String(),
|
||||
}),
|
||||
})
|
||||
|
||||
.get("/findMany", dataPerpustakaanFindMany)
|
||||
.get("/:id", async (context) => {
|
||||
const response = await dataPerpustakaanFindUnique(
|
||||
new Request(context.request)
|
||||
);
|
||||
return response;
|
||||
})
|
||||
.put("/:id", dataPerpustakaanUpdate, {
|
||||
body: t.Object({
|
||||
judul: t.String(),
|
||||
deskripsi: t.String(),
|
||||
imageId: t.String(),
|
||||
kategoriId: t.String(),
|
||||
}),
|
||||
})
|
||||
.delete("/del/:id", dataPerpustakaanDelete);
|
||||
|
||||
export default DataPerpustakaan;
|
||||
@@ -0,0 +1,35 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
type FormUpdate = {
|
||||
judul: string;
|
||||
deskripsi: string;
|
||||
imageId: string;
|
||||
kategoriId: string;
|
||||
}
|
||||
|
||||
export default async function dataPerpustakaanUpdate(context: Context) {
|
||||
const body = (await context.body) as FormUpdate;
|
||||
const id = context.params.id as string;
|
||||
|
||||
try {
|
||||
const result = await prisma.dataPerpustakaan.update({
|
||||
where: { id },
|
||||
data: {
|
||||
judul: body.judul,
|
||||
deskripsi: body.deskripsi,
|
||||
imageId: body.imageId,
|
||||
kategoriId: body.kategoriId,
|
||||
},
|
||||
});
|
||||
return {
|
||||
success: true,
|
||||
message: "Berhasil mengupdate data perpustakaan",
|
||||
data: result,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error updating data perpustakaan:", error);
|
||||
throw new Error("Gagal mengupdate data perpustakaan: " + (error as Error).message);
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user