Compare commits

...

70 Commits

Author SHA1 Message Date
54312e9486 Fix UI Menu Landing Page, Submenu Profile & Desa Anti Korupsi 2025-08-01 15:21:01 +08:00
024d5517fa UI & API Menu PPID, Submenu Struktur PPID 2025-07-31 16:22:02 +08:00
4e61695649 Test AUTH - 30 Jul 2025-07-30 12:11:17 +08:00
c11cc421a4 UI & API Menu Pendidikan, Submenu Perpustakaan Digital 2025-07-29 17:58:17 +08:00
0109886e00 UI & API Menu Pendidikan, Submenu Beasiswa tab beasiswa pendaftar 2025-07-29 12:29:25 +08:00
50e8999205 UI & API Menu Pendidikan, Submenu Data Pendidikan 2025-07-29 11:05:01 +08:00
e2e1672c80 UI & API Pendidikan Non Formal 2025-07-28 17:51:42 +08:00
ac0eb926eb UI & API Menu Pendidikan & Submenu Bimbingan Belajar Desa 2025-07-28 16:45:36 +08:00
b24bcd8019 UI & API Menu Pendidikan, Submenu Info Sekolah 2025-07-28 14:17:42 +08:00
5601e59922 UI & API Menu Landing Page 2025-07-28 10:18:24 +08:00
a25cfe8b8a API & UI Menu Landing Page, Submenu Prestasi Desa 2025-07-24 14:49:53 +08:00
b745bd4623 API & UI Menu Landing Page, Submenu SDGs Desa & APBDes 2025-07-24 11:53:58 +08:00
bdf751ec3d UI & API Menu Landing Page, Submenu Desa Anti Korupsi 2025-07-23 17:12:33 +08:00
1bc6dd8dbf UI & API Menu LandingPage, Submenu Profile 2025-07-23 12:19:10 +08:00
88a10538a7 API & UI Menu Landing Page, Submenu Profile, Tabs 1 & 2 sisa tabs 3 2025-07-23 10:08:02 +08:00
d4efcacf1b API & UI Menu Landing Page, Submenu Profile 2025-07-22 17:13:56 +08:00
9b2201ea57 FIX UI & API Menu Lingkungan Submenu Gotong Royong 2025-07-22 11:53:15 +08:00
80a7df663e API & UI Menu Lingkungan, Submenu Gotong Royong 2025-07-22 11:24:19 +08:00
9dfcda7687 Pertama 2025-07-21 15:23:50 +08:00
e2f75ff3ad API & UI Menu Lingkungan, Submenu Edukas Lingkungan 2025-07-18 21:24:51 +08:00
f05a096633 pertama 2025-07-18 17:42:10 +08:00
9c55869aa6 pertama 2025-07-18 17:40:57 +08:00
6e5d45fa20 pertama 2025-07-18 17:31:31 +08:00
e5373b4823 pertama 2025-07-18 17:28:55 +08:00
928cd048c0 pertama 2025-07-18 17:27:54 +08:00
41f54772e9 API & UI Menu Lingkungan, Submenu Data Lingkungan Desa 2025-07-18 17:11:13 +08:00
cd343badb2 API & UI Menu Lingkungan, Submenu Program Penghijauan 2025-07-18 16:27:38 +08:00
4025771a4d API & UI Menu Lingkungan Submenu Pengelolaan Sampah 2025-07-18 15:01:43 +08:00
7439eb7687 API & UI Menu Lingkungan, Submenu Penglolaan Sampah 2025-07-17 17:09:28 +08:00
49a1084099 API & UI Menu Inovasi, SubMenu Layanan Online Desa 2025-07-17 15:31:10 +08:00
cde6c91cd4 API & UI Menu Inovasi, Submenu Layanan Online Desa: Tab 1-4 2025-07-17 11:44:53 +08:00
55433128a9 API & UI Menu Inovasi & Submenu Layan Online Desa 2 tabs 2025-07-16 00:23:15 +08:00
e8ad74d118 Merge branch 'nico/push-stg' into nico/15-jul-25 2025-07-15 11:51:13 +08:00
99c1fd1004 UI & API Menu Inovasi, SubMenu : Kolaborasi Inovasi & Info Teknologi 2025-07-15 11:48:56 +08:00
03c0523194 UI & API Menu Inovasi, SubMenu Program Kreatif Desa 2025-07-14 17:35:38 +08:00
ae328f40a0 UI & API Menu Inovasi, SubMenu Program Kreatif Desa 2025-07-14 17:14:02 +08:00
6e109ffe00 FIX UI Admin Menu PPID 2025-07-14 14:33:08 +08:00
c4aea568e9 UI & API Smart DIgital Village 2025-07-14 10:24:59 +08:00
1c8104ee69 API & UI Admin Menu Ekonomi, Submenu PADesa 2025-07-11 18:50:20 +08:00
4baffe95f3 UI & API Menu Ekonomi, SubMenu PADesa : Tabs Pendapatan, Pembiayaan, dan Belanja 2025-07-11 17:51:07 +08:00
cb52701f47 API & UI Tabs SubMenu Pendapatan, Menu PADesa 2025-07-10 16:52:26 +08:00
2bc9b2f3c6 Menerapkan pagination di submenu pegawai & berita 2025-07-10 10:46:58 +08:00
7b2b306849 API & UI Menu Ekonomi, Submenu Jumlah Pengangguran 2025-07-10 00:21:33 +08:00
d328f64d86 UI & API Tabs Detail Data Pengangguran 2025-07-09 15:25:48 +08:00
119275b95c API & State Jumlah Pengangguran 2025-07-09 12:00:37 +08:00
c823462a47 Merge pull request #39 from bipproduction/nico/4-jul-25
Nico/4 jul 25:
Fix UI & API Admin Menu Ekonomi Pasar Desa
2025-07-04 11:20:15 +08:00
32a75bcb01 Merge pull request #38 from bipproduction/nico/1-jul-25
UI & API Menu Keamanan baru 3 Menu : Keamanan Lingkungan, Polsek Terd
2025-07-01 11:19:03 +08:00
9f39eb41ab Merge pull request #36 from bipproduction/nico/30-jun-2025
Nico/30 jun 2025
2025-06-30 11:15:28 +08:00
81ea18cb07 Merge branch 'nico/push-stg' into nico/30-jun-2025 2025-06-30 11:15:06 +08:00
21085ce342 Merge branch 'staging' into nico/push-stg 2025-06-26 11:19:54 +08:00
88784f00f6 Merge pull request #34 from bipproduction/nico/25-jun-25
Nico/25 jun 25
2025-06-26 11:01:15 +08:00
456342851b Merge pull request #33 from bipproduction/nico/25-jun-25
Nico/25 jun 25
staging versi 0.1.3
2025-06-25 16:32:28 +08:00
fa922c7127 Merge pull request #31 from bipproduction/nico/push-stg
Nico/push stg
2025-06-18 16:38:45 +08:00
45acdba93f Merge pull request #30 from bipproduction/nico/18-jun-25
Nico/18 jun 25
2025-06-18 16:38:10 +08:00
cb8d561467 Merge pull request #29 from bipproduction/nico/push-stg
Nico/push stg 18 Juni 2025:
UI & API Menu PPID & Desa Clear
2025-06-18 15:35:49 +08:00
85a0cb6d56 Merge pull request #28 from bipproduction/nico/18-jun-25
Nico/18 jun 25:
UI & API Menu PPID & Desa Clear
2025-06-18 15:34:27 +08:00
4f2c565b2e Merge pull request #27 from bipproduction/nico/push-stg
Nico/push stg

Senin, 2 Juni 2025 : 
Yang Sudah Di Kerjakan
* Tampilan UI Admin di menu desa
Yang Lagi Dikerjakan:
* Tampilan UI Admin di menu kesehatan

Yang Akan Dikerjakan:
* API Create, edit dan delete pengumuman
* Tampilan UI Admin di menu keamanan
2025-06-02 22:30:15 +08:00
c4adc9bb22 Merge pull request #26 from bipproduction/nico/1-jun-25
Senin, 2 Juni 2025 : 
Yang Sudah Di Kerjakan
* Tampilan UI Admin di menu desa
Yang Lagi Dikerjakan:
* Tampilan UI Admin di menu kesehatan

Yang Akan Dikerjakan:
* API Create, edit dan delete pengumuman
* Tampilan UI Admin di menu keamanan
2025-06-02 22:28:58 +08:00
9d572f82c3 Merge pull request #25 from bipproduction/nico/push-stg
Jum'at, 30 May 2025 : 
Yang Sudah Di Kerjakan
- Tampilan UI Admin di menu inovasi
- API Create, edit dan delete potensi
- Tampilan UI Landing Page sudah sesuai di mobile

Yang Lagi Dikerjakan:
- Progress Tampilan UI Admin Di Menu lingkungan
- Progress API Create, edit dan delete potensi

Yang Akan Dikerjakan:
- API Create, edit dan delete pengumuman
- Tampilan UI Admin Di Menu Pendidikan
2025-05-30 21:16:15 +08:00
452692f314 Merge pull request #24 from bipproduction/nico/30-may-25
Jum'at, 30 May 2025 : 
Yang Sudah Di Kerjakan
- Tampilan UI Admin di menu inovasi
- API Create, edit dan delete potensi
- Tampilan UI Landing Page sudah sesuai di mobile

Yang Lagi Dikerjakan:
- Progress Tampilan UI Admin Di Menu lingkungan
- Progress API Create, edit dan delete potensi

Yang Akan Dikerjakan:
- API Create, edit dan delete pengumuman
- Tampilan UI Admin Di Menu Pendidikan
2025-05-30 21:15:27 +08:00
5010677bc8 Merge pull request #23 from bipproduction/nico/27-may-25-01
Selasa, 27 May 2025:
Yang Sudah Di Kerjakan
* Tampilan UI Admin di menu ekonomi
* API Create, edit dan delete berita

Yang Lagi Dikerjakan:
* Progress Tampilan UI Admin Di Menu Inovasi
* Progress API ProfilePPID

Yang Akan Dikerjakan:
* API Menu Lain
* Tampilan UI Admin Di Menu Lingkungan
* Tampilan UI Admin Di Menu Pendidikan
2025-05-27 11:24:24 +08:00
7af3fbff2d Merge pull request #22 from bipproduction/nico/26-may-25
Senin, 26 May 2025 :
Yang Sudah Di Kerjakan
* Tampilan UI Admin di menu ekonomi
* API Create, edit dan delete berita

Yang Akan Dikerjakan:
* API ProfilePPID
* Tampilan UI Admin Di Menu Inovasi
2025-05-26 17:16:07 +08:00
34ca736dda Merge pull request #21 from bipproduction/nico/26-may-25
Nico/26 may 25
Senin, 26 May 2025 : 
Yang Sudah Di Kerjakan
* Tampilan UI Admin di menu ekonomi
* API Create, dan delete berita

Yang Akan Dikerjakan:
* API Di Menu Desa : Edit Berita
* Tampilan UI Admin Di Menu Inovasi
2025-05-26 14:30:07 +08:00
3b61f54509 Merge pull request #20 from bipproduction/nico/8-may-25
Nico/8 may 25
2025-05-09 10:36:45 +08:00
fbe0c19d22 Merge pull request #19 from bipproduction/nico/2-may-25
Fix Eror Admin Persentase & Grafik
2025-05-02 16:11:38 +08:00
f8914ab78f Merge pull request #18 from bipproduction/nico/30-apr-25
Admin Dashboard Bagian Data Kesehatan
2025-04-30 16:42:45 +08:00
8f3ee2f831 Push STG 2025-04-30 16:32:29 +08:00
ddf0ca62c4 Merge pull request #17 from bipproduction/nico/28-apr-25
Dashboard Admin
2025-04-30 16:16:48 +08:00
f8cdd3abdd Merge pull request #13 from bipproduction/nico/24-mar-25
tamabah versi
2025-03-24 15:35:55 +08:00
64bc739496 Merge pull request #12 from bipproduction/nico/24-mar-25
Nico/24 mar 25
2025-03-24 15:33:27 +08:00
770 changed files with 51723 additions and 5104 deletions

BIN
bun.lockb

Binary file not shown.

View File

@@ -1,6 +1,6 @@
{
"name": "desa-darmasaba",
"version": "0.1.3",
"version": "0.1.5",
"private": true,
"scripts": {
"dev": "next dev --turbopack",
@@ -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",
@@ -40,19 +42,25 @@
"@tiptap/react": "^2.11.7",
"@tiptap/starter-kit": "^2.11.7",
"@types/bun": "^1.2.2",
"@types/leaflet": "^1.9.20",
"@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",
"embla-carousel-react": "^7.1.0",
"form-data": "^4.0.2",
"framer-motion": "^12.4.1",
"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",
"motion": "^12.4.1",
"nanoid": "^5.1.5",
@@ -60,11 +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",
@@ -74,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"
}
}

View File

@@ -0,0 +1,51 @@
[
{
"month": "Jan",
"year": 2025,
"totalUnemployment": 160,
"educatedUnemployment": 95,
"uneducatedUnemployment": 65,
"percentageChange": null
},
{
"month": "Feb",
"year": 2025,
"totalUnemployment": 155,
"educatedUnemployment": 90,
"uneducatedUnemployment": 65,
"percentageChange": -3.1
},
{
"month": "Mar",
"year": 2025,
"totalUnemployment": 150,
"educatedUnemployment": 88,
"uneducatedUnemployment": 62,
"percentageChange": -3.2
},
{
"month": "Apr",
"year": 2025,
"totalUnemployment": 148,
"educatedUnemployment": 85,
"uneducatedUnemployment": 63,
"percentageChange": -1.3
},
{
"month": "Mei",
"year": 2025,
"totalUnemployment": 145,
"educatedUnemployment": 82,
"uneducatedUnemployment": 63,
"percentageChange": -2.0
},
{
"month": "Jun",
"year": 2025,
"totalUnemployment": 140,
"educatedUnemployment": 80,
"uneducatedUnemployment": 60,
"percentageChange": -3.4
}
]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,7 @@
[
{
"id": "edit",
"judul": "Contoh Kegiatan di Desa Darmasaba",
"deskripsi": "<ul><li><p>Pelatihan membuat kompos dari sampah rumah tangga</p></li><li><p>Gerakan Jumat Bersih rutin</p></li><li><p>Workshop membuat ecobrick</p></li><li><p>Lomba kebersihan antar banjar</p></li><li><p>Sosialisasi lingkungan di sekolah dan posyandu</p></li></ul>"
}
]

View File

@@ -0,0 +1,7 @@
[
{
"id": "edit",
"judul": "Materi Edukasi yang Diberikan",
"deskripsi": "<ul><li><p>Pengelolaan Sampah (Pilah sampah organik dan anorganik)</p></li><li><p>Pencegahan pencemaran lingkungan (air, udara, dan tanah)</p></li><li><p>Pemanfaatan lahan hijau dan penghijauan desa</p></li><li><p>Daur ulang dan kreatifitas dari sampah</p></li><li><p>Bahaya pembakaran sampah sembarangan</p></li></ul>"
}
]

View File

@@ -0,0 +1,7 @@
[
{
"id": "edit",
"judul": "Tujuan Edukasi Lingkungan",
"deskripsi": "<ul><li><p>Meningkatkan kesadaran masyarakat tentang pentingnya lingkungan bersih dan sehat</p></li><li><p>Mendorong partisipasi warga dalam kegiatan pengelolaan sampah, penghijauan, dan konservasi</p></li><li><p>Mengurangi dampak negatif terhadap lingkungan dari kegiatan manusia</p></li><li><p>Membentuk generasi muda yang peduli terhadap isu-isu lingkungan</p></li></ul>"
}
]

View File

@@ -0,0 +1,7 @@
[
{
"id": "edit",
"judul": "Bentuk Konservasi Berdasarkan Adat",
"deskripsi": "<ul><li><p>Pelestarian Hutan Adat seperti Alas Pala Sangeh atau Wana Kerthi</p></li><li><p>Subak: Sistem pengelolaan irigasi tradisional yang menjunjung kebersamaan dan keberlanjutan</p></li><li><p>Hari Raya Tumpek Uduh: Perayaan khusus untuk menghormati pohon dan tumbuhan</p></li><li><p>Perarem dan Awig-Awig: Aturan adat desa yang mengatur larangan menebang pohon sembarangan, membuang limbah ke sungai, dll.</p></li><li><p>Ritual penyucian alam seperti Melasti, Piodalan Segara, dan lainnya</p></li></ul>"
}
]

View File

@@ -0,0 +1,7 @@
[
{
"id": "edit",
"judul": "Filosofi Tri Hita Karuna",
"deskripsi": "<ul><li><p>Parahyangan: Hubungan manusia dengan Tuhan</p></li><li><p>Pawongan: Hubungan antar manusia</p></li><li><p>Palemahan: Hubungan manusia dengan alam</p></li></ul>"
}
]

View File

@@ -0,0 +1,7 @@
[
{
"id": "edit",
"judul": "Nilai Konservasi Adat",
"deskripsi": "<ul><li><p>Menjaga keseimbangan ekosistem</p></li><li><p>Melestarikan spiritualitas lokal dan kesucian alam</p></li><li><p>Menumbuhkan kesadaran kolektif untuk hidup selaras dengan lingkungan</p></li><li><p>Menjaga keberlangsungan sumber daya alam untuk generasi mendatang</p></li></ul>"
}
]

View File

@@ -0,0 +1,7 @@
[
{
"id": "edit",
"judul": "Fasilitas yang Disediakan",
"deskripsi": "<ul><li><p>Buku-buku pelajaran dan alat tulis</p></li><li><p>Ruang belajar nyaman dan kondusif</p></li><li><p>Modul latihan dan pendampingan tugas</p></li><li><p>Minuman ringan dan dukungan motivasi belajar</p></li></ul>"
}
]

View File

@@ -0,0 +1,7 @@
[
{
"id": "edit",
"judul": "Lokasi dan Jadwal",
"deskripsi": "<ul><li><p>Lokasi: Balai Banjar / Balai Desa Darmasaba / Perpustakaan Desa</p></li><li><p>Jadwal: Setiap hari Senin, Rabu, dan Jumat pukul 16.0018.00 WITA</p></li><li><p>Peserta: Terbuka untuk semua siswa SDSMP di wilayah desa</p></li></ul>"
}
]

View File

@@ -0,0 +1,7 @@
[
{
"id": "edit",
"judul": "Tujuan Program",
"deskripsi": "<ul><li><p>Memberikan pendampingan belajar secara gratis bagi siswa SD hingga SMP</p></li><li><p>Membantu siswa dalam menghadapi ujian dan menyelesaikan tugas sekolah</p></li><li><p>Menumbuhkan kepercayaan diri dan kemandirian dalam belajar</p></li><li><p>Meningkatkan kesetaraan pendidikan untuk seluruh anak desa</p></li></ul>"
}
]

View File

@@ -0,0 +1,7 @@
[
{
"id": "edit",
"judul": "Tempat Kegiatan",
"deskripsi": "<p>Program Pendidikan Non Formal yang diselenggarakan di Desa Darmasaba meliputi:</p><p>1) Keaksaraan Fungsional</p><ul><li><p>Untuk warga yang belum bisa membaca dan menulis</p></li></ul><p>2) Pendidikan Kesetaraan (Paket A, B, C)</p><ul><li><p>Setara SD, SMP, dan SMA bagi yang tidak menyelesaikan pendidikan formal</p></li></ul><p>3) Pelatihan Keterampilan</p><ul><li><p>Menjahit, memasak, sablon, pertanian, peternakan, hingga teknologi digital</p></li></ul><p>4) Kursus &amp; Pelatihan Soft Skill</p><ul><li><p>Public speaking, pengelolaan keuangan, kepemimpinan pemuda</p></li></ul><p>5) Pendidikan Keluarga &amp; Parenting</p><ul><li><p>Untuk membekali orang tua dalam mendampingi tumbuh kembang anak</p></li></ul>"
}
]

View File

@@ -0,0 +1,7 @@
[
{
"id": "edit",
"judul": "Tempat Kegiatan",
"deskripsi": "<ul><li><p>Balai Desa Darmasaba</p></li><li><p>TPK, Perpustakaan Desa, atau Posyandu</p></li><li><p>Bisa juga dilakukan secara mobile atau door to door</p></li></ul>"
}
]

View File

@@ -0,0 +1,7 @@
[
{
"id": "edit",
"judul": "Tujuan Program",
"deskripsi": "<ul><li><p>Memberikan kesempatan belajar yang fleksibel bagi warga desa</p></li><li><p>Meningkatkan keterampilan hidup dan kemandirian ekonomi</p></li><li><p>Mendorong partisipasi masyarakat dalam pembangunan desa</p></li><li><p>Mengurangi angka putus sekolah dan meningkatkan kualitas SDM</p></li></ul>"
}
]

View File

@@ -0,0 +1,7 @@
[
{
"id": "edit",
"judul": "Program Unggulan",
"deskripsi": "<ul><li><p>Bimbingan Belajar Gratis: Untuk siswa kurang mampu</p></li><li><p>Gerakan Literasi Desa: Meningkatkan minat baca sejak dini</p></li><li><p>Pelatihan Digital untuk Anak dan Remaja</p></li><li><p>Beasiswa Anak Berprestasi &amp; Kurang Mampu</p></li></ul>"
}
]

View File

@@ -0,0 +1,7 @@
[
{
"id": "edit",
"judul": "Tujuan Program",
"deskripsi": "<ul><li><p>Meningkatkan akses pendidikan yang merata dan berkualitas</p></li><li><p>Menumbuhkan semangat belajar sejak dini</p></li><li><p>Membentuk karakter anak yang berakhlak dan berwawasan lingkungan</p></li><li><p>Mendukung tumbuh kembang anak melalui pendekatan pendidikan yang holistik</p></li></ul>"
}
]

View 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
}
]

View 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"
}
]
]

View File

@@ -1,6 +0,0 @@
[
{
"id" : "1",
"name" : "Struktur PPID"
}
]

View File

@@ -0,0 +1,6 @@
[
{
"id": "cmdpm429r0000vnndkcwslt0h",
"name": "warga"
}
]

View File

@@ -0,0 +1,19 @@
-- CreateTable
CREATE TABLE "DetailDataPengangguran" (
"id" UUID NOT NULL,
"month" VARCHAR(20) NOT NULL,
"year" INTEGER NOT NULL,
"totalUnemployment" INTEGER NOT NULL,
"educatedUnemployment" INTEGER NOT NULL,
"uneducatedUnemployment" INTEGER NOT NULL,
"percentageChange" DOUBLE PRECISION,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "DetailDataPengangguran_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "DetailDataPengangguran_month_year_key" ON "DetailDataPengangguran"("month", "year");

View File

@@ -0,0 +1,133 @@
-- CreateTable
CREATE TABLE "ApbDesa" (
"id" TEXT NOT NULL,
"tahun" INTEGER NOT NULL,
"pendapatanId" TEXT NOT NULL,
"belanjaId" TEXT NOT NULL,
"pembiayaanId" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "ApbDesa_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Pendapatan" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"value" INTEGER NOT NULL,
CONSTRAINT "Pendapatan_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Belanja" (
"id" TEXT NOT NULL,
"penyelenggaraan" INTEGER NOT NULL,
"pelaksanaanPembangunan" INTEGER NOT NULL,
"pembinaanMasyarakat" INTEGER NOT NULL,
"pemberdayaanMasyarakat" INTEGER NOT NULL,
"penanggulanganBencana" INTEGER NOT NULL,
"total" INTEGER NOT NULL,
CONSTRAINT "Belanja_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Pembiayaan" (
"id" TEXT NOT NULL,
"silpa" INTEGER NOT NULL,
CONSTRAINT "Pembiayaan_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "KlasifikasiBelanja" (
"id" TEXT NOT NULL,
"jenis" TEXT NOT NULL,
"persen" DOUBLE PRECISION NOT NULL,
"total" INTEGER NOT NULL,
"apbDesaId" TEXT NOT NULL,
CONSTRAINT "KlasifikasiBelanja_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "RincianBelanja" (
"id" TEXT NOT NULL,
"nama" TEXT NOT NULL,
"jumlah" INTEGER NOT NULL,
"klasifikasiBelanjaId" TEXT NOT NULL,
CONSTRAINT "RincianBelanja_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "KegiatanSubak" (
"id" TEXT NOT NULL,
"nama" TEXT NOT NULL,
"jumlah" INTEGER NOT NULL,
"apbDesaId" TEXT NOT NULL,
CONSTRAINT "KegiatanSubak_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "_ApbDesaToKegiatanSubak" (
"A" TEXT NOT NULL,
"B" TEXT NOT NULL,
CONSTRAINT "_ApbDesaToKegiatanSubak_AB_pkey" PRIMARY KEY ("A","B")
);
-- CreateTable
CREATE TABLE "_BelanjaToKlasifikasiBelanja" (
"A" TEXT NOT NULL,
"B" TEXT NOT NULL,
CONSTRAINT "_BelanjaToKlasifikasiBelanja_AB_pkey" PRIMARY KEY ("A","B")
);
-- CreateTable
CREATE TABLE "_KlasifikasiBelanjaToRincianBelanja" (
"A" TEXT NOT NULL,
"B" TEXT NOT NULL,
CONSTRAINT "_KlasifikasiBelanjaToRincianBelanja_AB_pkey" PRIMARY KEY ("A","B")
);
-- CreateIndex
CREATE INDEX "_ApbDesaToKegiatanSubak_B_index" ON "_ApbDesaToKegiatanSubak"("B");
-- CreateIndex
CREATE INDEX "_BelanjaToKlasifikasiBelanja_B_index" ON "_BelanjaToKlasifikasiBelanja"("B");
-- CreateIndex
CREATE INDEX "_KlasifikasiBelanjaToRincianBelanja_B_index" ON "_KlasifikasiBelanjaToRincianBelanja"("B");
-- AddForeignKey
ALTER TABLE "ApbDesa" ADD CONSTRAINT "ApbDesa_pendapatanId_fkey" FOREIGN KEY ("pendapatanId") REFERENCES "Pendapatan"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ApbDesa" ADD CONSTRAINT "ApbDesa_belanjaId_fkey" FOREIGN KEY ("belanjaId") REFERENCES "Belanja"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ApbDesa" ADD CONSTRAINT "ApbDesa_pembiayaanId_fkey" FOREIGN KEY ("pembiayaanId") REFERENCES "Pembiayaan"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_ApbDesaToKegiatanSubak" ADD CONSTRAINT "_ApbDesaToKegiatanSubak_A_fkey" FOREIGN KEY ("A") REFERENCES "ApbDesa"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_ApbDesaToKegiatanSubak" ADD CONSTRAINT "_ApbDesaToKegiatanSubak_B_fkey" FOREIGN KEY ("B") REFERENCES "KegiatanSubak"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_BelanjaToKlasifikasiBelanja" ADD CONSTRAINT "_BelanjaToKlasifikasiBelanja_A_fkey" FOREIGN KEY ("A") REFERENCES "Belanja"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_BelanjaToKlasifikasiBelanja" ADD CONSTRAINT "_BelanjaToKlasifikasiBelanja_B_fkey" FOREIGN KEY ("B") REFERENCES "KlasifikasiBelanja"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_KlasifikasiBelanjaToRincianBelanja" ADD CONSTRAINT "_KlasifikasiBelanjaToRincianBelanja_A_fkey" FOREIGN KEY ("A") REFERENCES "KlasifikasiBelanja"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_KlasifikasiBelanjaToRincianBelanja" ADD CONSTRAINT "_KlasifikasiBelanjaToRincianBelanja_B_fkey" FOREIGN KEY ("B") REFERENCES "RincianBelanja"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@@ -0,0 +1,34 @@
/*
Warnings:
- You are about to drop the column `pelaksanaanPembangunan` on the `Belanja` table. All the data in the column will be lost.
- You are about to drop the column `pemberdayaanMasyarakat` on the `Belanja` table. All the data in the column will be lost.
- You are about to drop the column `pembinaanMasyarakat` on the `Belanja` table. All the data in the column will be lost.
- You are about to drop the column `penanggulanganBencana` on the `Belanja` table. All the data in the column will be lost.
- You are about to drop the column `penyelenggaraan` on the `Belanja` table. All the data in the column will be lost.
- Added the required column `name` to the `Belanja` table without a default value. This is not possible if the table is not empty.
- Added the required column `updatedAt` to the `Belanja` table without a default value. This is not possible if the table is not empty.
- Added the required column `value` to the `Belanja` table without a default value. This is not possible if the table is not empty.
- Added the required column `total` to the `Pendapatan` table without a default value. This is not possible if the table is not empty.
- Added the required column `updatedAt` to the `Pendapatan` table without a default value. This is not possible if the table is not empty.
*/
-- AlterTable
ALTER TABLE "Belanja" DROP COLUMN "pelaksanaanPembangunan",
DROP COLUMN "pemberdayaanMasyarakat",
DROP COLUMN "pembinaanMasyarakat",
DROP COLUMN "penanggulanganBencana",
DROP COLUMN "penyelenggaraan",
ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
ADD COLUMN "deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
ADD COLUMN "isActive" BOOLEAN NOT NULL DEFAULT true,
ADD COLUMN "name" TEXT NOT NULL,
ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL,
ADD COLUMN "value" INTEGER NOT NULL;
-- AlterTable
ALTER TABLE "Pendapatan" ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
ADD COLUMN "deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
ADD COLUMN "isActive" BOOLEAN NOT NULL DEFAULT true,
ADD COLUMN "total" INTEGER NOT NULL,
ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL;

View File

@@ -0,0 +1,12 @@
/*
Warnings:
- You are about to drop the column `total` on the `Belanja` table. All the data in the column will be lost.
- You are about to drop the column `total` on the `Pendapatan` table. All the data in the column will be lost.
*/
-- AlterTable
ALTER TABLE "Belanja" DROP COLUMN "total";
-- AlterTable
ALTER TABLE "Pendapatan" DROP COLUMN "total";

View File

@@ -0,0 +1,12 @@
/*
Warnings:
- You are about to drop the column `silpa` on the `Pembiayaan` table. All the data in the column will be lost.
- Added the required column `name` to the `Pembiayaan` table without a default value. This is not possible if the table is not empty.
- Added the required column `value` to the `Pembiayaan` table without a default value. This is not possible if the table is not empty.
*/
-- AlterTable
ALTER TABLE "Pembiayaan" DROP COLUMN "silpa",
ADD COLUMN "name" TEXT NOT NULL,
ADD COLUMN "value" INTEGER NOT NULL;

View File

@@ -0,0 +1,245 @@
/*
Warnings:
- You are about to drop the column `belanjaId` on the `ApbDesa` table. All the data in the column will be lost.
- You are about to drop the column `pembiayaanId` on the `ApbDesa` table. All the data in the column will be lost.
- You are about to drop the column `pendapatanId` on the `ApbDesa` table. All the data in the column will be lost.
- You are about to drop the `KegiatanSubak` table. If the table is not empty, all the data it contains will be lost.
- You are about to drop the `KlasifikasiBelanja` table. If the table is not empty, all the data it contains will be lost.
- You are about to drop the `RincianBelanja` table. If the table is not empty, all the data it contains will be lost.
- You are about to drop the `_ApbDesaToKegiatanSubak` table. If the table is not empty, all the data it contains will be lost.
- You are about to drop the `_BelanjaToKlasifikasiBelanja` table. If the table is not empty, all the data it contains will be lost.
- You are about to drop the `_KlasifikasiBelanjaToRincianBelanja` table. If the table is not empty, all the data it contains will be lost.
- Changed the type of `tanggal` on the `DaftarInformasiPublik` table. No cast exists, the column would be dropped and recreated, which cannot be done if there is data, since the column is required.
- Added the required column `updatedAt` to the `Pembiayaan` table without a default value. This is not possible if the table is not empty.
*/
-- DropForeignKey
ALTER TABLE "ApbDesa" DROP CONSTRAINT "ApbDesa_belanjaId_fkey";
-- DropForeignKey
ALTER TABLE "ApbDesa" DROP CONSTRAINT "ApbDesa_pembiayaanId_fkey";
-- DropForeignKey
ALTER TABLE "ApbDesa" DROP CONSTRAINT "ApbDesa_pendapatanId_fkey";
-- DropForeignKey
ALTER TABLE "_ApbDesaToKegiatanSubak" DROP CONSTRAINT "_ApbDesaToKegiatanSubak_A_fkey";
-- DropForeignKey
ALTER TABLE "_ApbDesaToKegiatanSubak" DROP CONSTRAINT "_ApbDesaToKegiatanSubak_B_fkey";
-- DropForeignKey
ALTER TABLE "_BelanjaToKlasifikasiBelanja" DROP CONSTRAINT "_BelanjaToKlasifikasiBelanja_A_fkey";
-- DropForeignKey
ALTER TABLE "_BelanjaToKlasifikasiBelanja" DROP CONSTRAINT "_BelanjaToKlasifikasiBelanja_B_fkey";
-- DropForeignKey
ALTER TABLE "_KlasifikasiBelanjaToRincianBelanja" DROP CONSTRAINT "_KlasifikasiBelanjaToRincianBelanja_A_fkey";
-- DropForeignKey
ALTER TABLE "_KlasifikasiBelanjaToRincianBelanja" DROP CONSTRAINT "_KlasifikasiBelanjaToRincianBelanja_B_fkey";
-- AlterTable
ALTER TABLE "ApbDesa" DROP COLUMN "belanjaId",
DROP COLUMN "pembiayaanId",
DROP COLUMN "pendapatanId",
ADD COLUMN "deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
ADD COLUMN "isActive" BOOLEAN NOT NULL DEFAULT true;
-- AlterTable
ALTER TABLE "DaftarInformasiPublik" DROP COLUMN "tanggal",
ADD COLUMN "tanggal" DATE NOT NULL;
-- AlterTable
ALTER TABLE "Pembiayaan" ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
ADD COLUMN "deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
ADD COLUMN "isActive" BOOLEAN NOT NULL DEFAULT true,
ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL;
-- DropTable
DROP TABLE "KegiatanSubak";
-- DropTable
DROP TABLE "KlasifikasiBelanja";
-- DropTable
DROP TABLE "RincianBelanja";
-- DropTable
DROP TABLE "_ApbDesaToKegiatanSubak";
-- DropTable
DROP TABLE "_BelanjaToKlasifikasiBelanja";
-- DropTable
DROP TABLE "_KlasifikasiBelanjaToRincianBelanja";
-- CreateTable
CREATE TABLE "DesaDigital" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"deskripsi" TEXT NOT NULL,
"imageId" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "DesaDigital_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ProgramKreatif" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"slug" TEXT NOT NULL,
"deskripsi" TEXT NOT NULL,
"icon" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "ProgramKreatif_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "KolaborasiInovasi" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"tahun" INTEGER NOT NULL,
"slug" TEXT NOT NULL,
"deskripsi" TEXT NOT NULL,
"kolaborator" TEXT NOT NULL,
"imageId" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "KolaborasiInovasi_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "InfoTekno" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"deskripsi" TEXT NOT NULL,
"imageId" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "InfoTekno_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "AjukanIdeInovatif" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"alamat" TEXT NOT NULL,
"namaIde" TEXT NOT NULL,
"deskripsi" TEXT NOT NULL,
"masalah" TEXT NOT NULL,
"benefit" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "AjukanIdeInovatif_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "AdministrasiOnline" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"alamat" TEXT NOT NULL,
"nomorTelepon" TEXT NOT NULL,
"jenisLayananId" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "AdministrasiOnline_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "JenisLayanan" (
"id" TEXT NOT NULL,
"nama" TEXT NOT NULL,
"deskripsi" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "JenisLayanan_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "_ApbDesaPembiayaan" (
"A" TEXT NOT NULL,
"B" TEXT NOT NULL,
CONSTRAINT "_ApbDesaPembiayaan_AB_pkey" PRIMARY KEY ("A","B")
);
-- CreateTable
CREATE TABLE "_ApbDesaBelanja" (
"A" TEXT NOT NULL,
"B" TEXT NOT NULL,
CONSTRAINT "_ApbDesaBelanja_AB_pkey" PRIMARY KEY ("A","B")
);
-- CreateTable
CREATE TABLE "_ApbDesaPendapatan" (
"A" TEXT NOT NULL,
"B" TEXT NOT NULL,
CONSTRAINT "_ApbDesaPendapatan_AB_pkey" PRIMARY KEY ("A","B")
);
-- CreateIndex
CREATE INDEX "_ApbDesaPembiayaan_B_index" ON "_ApbDesaPembiayaan"("B");
-- CreateIndex
CREATE INDEX "_ApbDesaBelanja_B_index" ON "_ApbDesaBelanja"("B");
-- CreateIndex
CREATE INDEX "_ApbDesaPendapatan_B_index" ON "_ApbDesaPendapatan"("B");
-- AddForeignKey
ALTER TABLE "DesaDigital" ADD CONSTRAINT "DesaDigital_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "KolaborasiInovasi" ADD CONSTRAINT "KolaborasiInovasi_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "InfoTekno" ADD CONSTRAINT "InfoTekno_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "AdministrasiOnline" ADD CONSTRAINT "AdministrasiOnline_jenisLayananId_fkey" FOREIGN KEY ("jenisLayananId") REFERENCES "JenisLayanan"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_ApbDesaPembiayaan" ADD CONSTRAINT "_ApbDesaPembiayaan_A_fkey" FOREIGN KEY ("A") REFERENCES "ApbDesa"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_ApbDesaPembiayaan" ADD CONSTRAINT "_ApbDesaPembiayaan_B_fkey" FOREIGN KEY ("B") REFERENCES "Pembiayaan"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_ApbDesaBelanja" ADD CONSTRAINT "_ApbDesaBelanja_A_fkey" FOREIGN KEY ("A") REFERENCES "ApbDesa"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_ApbDesaBelanja" ADD CONSTRAINT "_ApbDesaBelanja_B_fkey" FOREIGN KEY ("B") REFERENCES "Belanja"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_ApbDesaPendapatan" ADD CONSTRAINT "_ApbDesaPendapatan_A_fkey" FOREIGN KEY ("A") REFERENCES "ApbDesa"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_ApbDesaPendapatan" ADD CONSTRAINT "_ApbDesaPendapatan_B_fkey" FOREIGN KEY ("B") REFERENCES "Pendapatan"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@@ -0,0 +1,67 @@
-- CreateTable
CREATE TABLE "PengaduanMasyarakat" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"email" TEXT NOT NULL,
"nomorTelepon" TEXT NOT NULL,
"nik" TEXT NOT NULL,
"judulPengaduan" TEXT NOT NULL,
"lokasiKejadian" TEXT NOT NULL,
"imageId" TEXT NOT NULL,
"deskripsiPengaduan" TEXT NOT NULL,
"jenisPengaduanId" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "PengaduanMasyarakat_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "JenisPengaduan" (
"id" TEXT NOT NULL,
"nama" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "JenisPengaduan_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "PengelolaanSampah" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"icon" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "PengelolaanSampah_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "KeteranganBankSampahTerdekat" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"alamat" TEXT NOT NULL,
"namaTempatMaps" TEXT NOT NULL,
"linkPetunjukArah" TEXT NOT NULL,
"lat" DOUBLE PRECISION NOT NULL,
"lng" DOUBLE PRECISION NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "KeteranganBankSampahTerdekat_pkey" PRIMARY KEY ("id")
);
-- AddForeignKey
ALTER TABLE "PengaduanMasyarakat" ADD CONSTRAINT "PengaduanMasyarakat_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PengaduanMasyarakat" ADD CONSTRAINT "PengaduanMasyarakat_jenisPengaduanId_fkey" FOREIGN KEY ("jenisPengaduanId") REFERENCES "JenisPengaduan"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@@ -0,0 +1,144 @@
-- CreateTable
CREATE TABLE "ProgramPenghijauan" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"judul" TEXT NOT NULL,
"deskripsi" TEXT NOT NULL,
"icon" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "ProgramPenghijauan_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "DataLingkunganDesa" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"jumlah" TEXT NOT NULL,
"deskripsi" TEXT NOT NULL,
"icon" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "DataLingkunganDesa_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "KegiatanDesa" (
"id" TEXT NOT NULL,
"judul" TEXT NOT NULL,
"deskripsiSingkat" TEXT NOT NULL,
"deskripsiLengkap" TEXT NOT NULL,
"tanggal" TIMESTAMP(3) NOT NULL,
"lokasi" TEXT NOT NULL,
"partisipan" INTEGER NOT NULL,
"imageId" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"kategoriKegiatanId" TEXT NOT NULL,
CONSTRAINT "KegiatanDesa_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "KategoriKegiatan" (
"id" TEXT NOT NULL,
"nama" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "KategoriKegiatan_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "TujuanEdukasiLingkungan" (
"id" TEXT NOT NULL,
"judul" TEXT NOT NULL,
"deskripsi" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "TujuanEdukasiLingkungan_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "MateriEdukasiLingkungan" (
"id" TEXT NOT NULL,
"judul" TEXT NOT NULL,
"deskripsi" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "MateriEdukasiLingkungan_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ContohEdukasiLingkungan" (
"id" TEXT NOT NULL,
"judul" TEXT NOT NULL,
"deskripsi" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "ContohEdukasiLingkungan_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "FilosofiTriHita" (
"id" TEXT NOT NULL,
"judul" TEXT NOT NULL,
"deskripsi" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "FilosofiTriHita_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "BentukKonservasiBerdasarkanAdat" (
"id" TEXT NOT NULL,
"judul" TEXT NOT NULL,
"deskripsi" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "BentukKonservasiBerdasarkanAdat_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "NilaiKonservasiAdat" (
"id" TEXT NOT NULL,
"judul" TEXT NOT NULL,
"deskripsi" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "NilaiKonservasiAdat_pkey" PRIMARY KEY ("id")
);
-- AddForeignKey
ALTER TABLE "KegiatanDesa" ADD CONSTRAINT "KegiatanDesa_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "KegiatanDesa" ADD CONSTRAINT "KegiatanDesa_kategoriKegiatanId_fkey" FOREIGN KEY ("kategoriKegiatanId") REFERENCES "KategoriKegiatan"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@@ -0,0 +1,56 @@
-- CreateTable
CREATE TABLE "PejabatDesa" (
"id" TEXT NOT NULL,
"name" VARCHAR(255) NOT NULL,
"position" TEXT NOT NULL,
"imageId" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "PejabatDesa_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ProgramInovasi" (
"id" TEXT NOT NULL,
"name" VARCHAR(255) NOT NULL,
"description" TEXT,
"imageId" TEXT,
"link" VARCHAR(255),
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "ProgramInovasi_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "MediaSosial" (
"id" TEXT NOT NULL,
"imageId" TEXT NOT NULL,
"iconUrl" VARCHAR(255),
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3),
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "MediaSosial_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "PejabatDesa_name_key" ON "PejabatDesa"("name");
-- CreateIndex
CREATE UNIQUE INDEX "ProgramInovasi_name_key" ON "ProgramInovasi"("name");
-- AddForeignKey
ALTER TABLE "PejabatDesa" ADD CONSTRAINT "PejabatDesa_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ProgramInovasi" ADD CONSTRAINT "ProgramInovasi_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "MediaSosial" ADD CONSTRAINT "MediaSosial_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@@ -60,6 +60,7 @@ model FileStorage {
deletedAt DateTime?
isActive Boolean @default(true)
link String
category String // "image" / "document" / "other"
Berita Berita[]
PotensiDesa PotensiDesa[]
Posyandu Posyandu[]
@@ -74,30 +75,39 @@ model FileStorage {
ProgramKesehatan ProgramKesehatan[]
PenangananDarurat PenangananDarurat[]
KontakDarurat KontakDarurat[]
InfoWabahPenyakit InfoWabahPenyakit[]
KeamananLingkungan KeamananLingkungan[]
MenuTipsKeamanan MenuTipsKeamanan[]
Pelapor Pelapor[]
PasarDesa PasarDesa[]
KontakDaruratKeamanan KontakDaruratKeamanan[]
KontakItem KontakItem[]
Pegawai Pegawai[]
DesaDigital DesaDigital[]
KolaborasiInovasi KolaborasiInovasi[]
InfoTekno InfoTekno[]
PengaduanMasyarakat PengaduanMasyarakat[]
KegiatanDesa KegiatanDesa[]
ProgramInovasi ProgramInovasi[]
PejabatDesa PejabatDesa[]
MediaSosial MediaSosial[]
DesaAntiKorupsi DesaAntiKorupsi[]
SDGSDesa SDGSDesa[]
APBDesImage APBDes[] @relation("APBDesImage")
APBDesFile APBDes[] @relation("APBDesFile")
PrestasiDesa PrestasiDesa[]
InfoWabahPenyakit InfoWabahPenyakit[]
DataPerpustakaan DataPerpustakaan[]
KeamananLingkungan KeamananLingkungan[]
MenuTipsKeamanan MenuTipsKeamanan[]
Pelapor Pelapor[]
PasarDesa PasarDesa[]
KontakDaruratKeamanan KontakDaruratKeamanan[]
KontakItem KontakItem[]
Pegawai Pegawai[]
PegawaiPPID PegawaiPPID[]
}
//========================================= MENU PPID ========================================= //
//========================================= STRUKTUR PPID ========================================= //
model StrukturPPID {
//========================================= MENU LANDING PAGE ========================================= //
//========================================= PROFILE ========================================= //
model PejabatDesa {
id String @id @default(cuid())
name String @db.Text
name String @unique @db.VarChar(255)
position String
image FileStorage? @relation(fields: [imageId], references: [id])
imageId String?
createdAt DateTime @default(now())
@@ -106,6 +116,245 @@ model StrukturPPID {
isActive Boolean @default(true)
}
model ProgramInovasi {
id String @id @default(cuid())
name String @unique @db.VarChar(255)
description String? @db.Text
image FileStorage? @relation(fields: [imageId], references: [id])
imageId String?
link String? @db.VarChar(255)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime? @default(now())
isActive Boolean @default(true)
}
model MediaSosial {
id String @id @default(cuid())
name String
image FileStorage? @relation(fields: [imageId], references: [id])
imageId String?
iconUrl String? @db.VarChar(255)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
isActive Boolean @default(true)
}
//========================================= PROFILE ========================================= //
model DesaAntiKorupsi {
id String @id @default(cuid())
name String @unique
deskripsi String @db.Text
kategori KategoriDesaAntiKorupsi @relation(fields: [kategoriId], references: [id])
kategoriId String
file FileStorage? @relation(fields: [fileId], references: [id])
fileId String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
model KategoriDesaAntiKorupsi {
id String @id @default(cuid())
name String @unique
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
DesaAntiKorupsi DesaAntiKorupsi[]
}
//========================================= SDGS Desa ========================================= //
model SDGSDesa {
id String @id @default(cuid())
name String @unique
jumlah String
image FileStorage @relation(fields: [imageId], references: [id])
imageId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
//========================================= APBDes ========================================= //
model APBDes {
id String @id @default(cuid())
name String @unique
jumlah String
image FileStorage @relation("APBDesImage", fields: [imageId], references: [id])
imageId String
file FileStorage @relation("APBDesFile", fields: [fileId], references: [id])
fileId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
//========================================= PRESTASI DESA ========================================= //
model PrestasiDesa {
id String @id @default(cuid())
name String @unique
deskripsi String @db.Text
kategori KategoriPrestasiDesa @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 KategoriPrestasiDesa {
id String @id @default(cuid())
name String @unique
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
PrestasiDesa PrestasiDesa[]
}
//========================================= INDEKS KEPUASAN MASYARAKAT ========================================= //
// Entitas Survey
model Survey {
id String @id @default(cuid())
title String // Judul survei
totalRespondents Int // Total jumlah responden
averageScore Float // Rata-rata skor
monthlyStats MonthlyStat[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
// Entitas Statistik Bulanan
model MonthlyStat {
id String @id @default(cuid())
month String // Nama bulan (e.g., "Januari", "Februari")
respondentsCount Int // Jumlah responden per bulan
surveyId String @unique(map: "monthly_stat_survey_id_month_key")
survey Survey @relation(fields: [surveyId], references: [id])
AgeStat AgeStat[]
ResponseStat ResponseStat[]
genderStat genderStat[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
// Entitas Gender
model genderStat {
id String @id @default(cuid())
laki Int
perempuan Int
percentLaki Float
percentPerempuan Float
total Int
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
MonthlyStat MonthlyStat? @relation(fields: [monthlyStatId], references: [id])
monthlyStatId String?
}
// Entitas Age
model AgeStat {
id String @id @default(cuid())
group String // "18-44", "45+" dll
count Int
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
MonthlyStat MonthlyStat? @relation(fields: [monthlyStatId], references: [id])
monthlyStatId String?
}
// Entitas Response
model ResponseStat {
id String @id @default(cuid())
label String // BAIK / BURUK
count Int
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
MonthlyStat MonthlyStat? @relation(fields: [monthlyStatId], references: [id])
monthlyStatId String?
}
//========================================= MENU PPID ========================================= //
//========================================= 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)
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 ========================================= //
model VisiMisiPPID {
id String @id @default(cuid())
@@ -150,7 +399,7 @@ model DaftarInformasiPublik {
id String @id @default(cuid())
jenisInformasi String
deskripsi String
tanggal String
tanggal DateTime @db.Date
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
@@ -1116,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")
}
@@ -1142,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")
}
@@ -1262,3 +1513,583 @@ model DataDemografiPekerjaan {
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
// ========================================= JUMLAH PENGANGGURAN ========================================= //
model DetailDataPengangguran {
id String @id @default(uuid()) @db.Uuid
month String @db.VarChar(20)
year Int
totalUnemployment Int
educatedUnemployment Int
uneducatedUnemployment Int
percentageChange Float?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
@@unique([month, year])
}
// ========================================= PADESA PENDAPATAN ASLI DESA ========================================= //
model ApbDesa {
id String @id @default(uuid())
tahun Int
pembiayaan Pembiayaan[] @relation("ApbDesaPembiayaan")
belanja Belanja[] @relation("ApbDesaBelanja")
pendapatan Pendapatan[] @relation("ApbDesaPendapatan")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
model Pendapatan {
id String @id @default(uuid())
name String
value Int
ApbDesa ApbDesa[] @relation("ApbDesaPendapatan")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
model Belanja {
id String @id @default(uuid())
name String
value Int
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
ApbDesa ApbDesa[] @relation("ApbDesaBelanja")
}
model Pembiayaan {
id String @id @default(uuid())
name String
value Int
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
ApbDesa ApbDesa[] @relation("ApbDesaPembiayaan")
}
// ========================================= INOVASI ========================================= //
// ========================================= DESA DIGITAL / SMART VILLAGE ========================================= //
model DesaDigital {
id String @id @default(cuid())
name String
deskripsi String @db.Text
image FileStorage @relation(fields: [imageId], references: [id])
imageId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
// ========================================= PROGRAM KREATIF ========================================= //
model ProgramKreatif {
id String @id @default(cuid())
name String
slug String @db.Text //deskripsi singkat
deskripsi String @db.Text //deskripsi panjang
icon String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
// ========================================= KOLABORASI INOVASI ========================================= //
model KolaborasiInovasi {
id String @id @default(cuid())
name String
tahun Int
slug String @db.Text //deskripsi singkat
deskripsi String @db.Text //deskripsi panjang
kolaborator String
image FileStorage @relation(fields: [imageId], references: [id])
imageId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
// ========================================= INFO TEKHNOLOGI TEPAT GUNA ========================================= //
model InfoTekno {
id String @id @default(cuid())
name String
deskripsi String @db.Text
image FileStorage @relation(fields: [imageId], references: [id])
imageId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
// ========================================= AJUKAN IDE INOVATIF ========================================= //
model AjukanIdeInovatif {
id String @id @default(cuid())
name String
alamat String
namaIde String
deskripsi String @db.Text
masalah String @db.Text
benefit String @db.Text
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
// ========================================= LAYANAN ONLINE DESA ========================================= //
model AdministrasiOnline {
id String @id @default(cuid())
name String
alamat String
nomorTelepon String
jenisLayanan JenisLayanan @relation(fields: [jenisLayananId], references: [id])
jenisLayananId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
model JenisLayanan {
id String @id @default(uuid())
nama String
deskripsi String @db.Text
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
AdministrasiOnline AdministrasiOnline[]
}
model PengaduanMasyarakat {
id String @id @default(cuid())
name String
email String
nomorTelepon String
nik String
judulPengaduan String
lokasiKejadian String
image FileStorage @relation(fields: [imageId], references: [id])
imageId String
deskripsiPengaduan String @db.Text
jenisPengaduan JenisPengaduan @relation(fields: [jenisPengaduanId], references: [id])
jenisPengaduanId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
model JenisPengaduan {
id String @id @default(uuid())
nama String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
PengaduanMasyarakat PengaduanMasyarakat[]
}
// ========================================= LINGKUNGAN ========================================= //
// ========================================= PENGELOLAAN SAMPAH ========================================= //
model PengelolaanSampah {
id String @id @default(cuid())
name String
icon String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
model KeteranganBankSampahTerdekat {
id String @id @default(cuid())
name String
alamat String
namaTempatMaps String
linkPetunjukArah String
lat Float
lng Float
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
// ========================================= PORGRAM PENGHIJAUAN ========================================= //
model ProgramPenghijauan {
id String @id @default(cuid())
name String
judul String @db.Text //deskripsi singkat
deskripsi String @db.Text //deskripsi panjang
icon String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
// ========================================= DATA LINGKUNGAN DESA ========================================= //
model DataLingkunganDesa {
id String @id @default(cuid())
name String
jumlah String
deskripsi String @db.Text //deskripsi panjang
icon String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
// ========================================= GOTONG ROYONG ========================================= //
model KegiatanDesa {
id String @id @default(uuid())
judul String
deskripsiSingkat String
deskripsiLengkap String
tanggal DateTime
lokasi String
partisipan Int
image FileStorage @relation(fields: [imageId], references: [id])
imageId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
kategoriKegiatan KategoriKegiatan @relation(fields: [kategoriKegiatanId], references: [id])
kategoriKegiatanId String
}
model KategoriKegiatan {
id String @id @default(cuid())
nama String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
KegiatanDesa KegiatanDesa[]
}
// ========================================= EDUKASI LINGKUNGAN ========================================= //
model TujuanEdukasiLingkungan {
id String @id @default(cuid())
judul String @db.Text
deskripsi String @db.Text
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
model MateriEdukasiLingkungan {
id String @id @default(cuid())
judul String @db.Text
deskripsi String @db.Text
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
model ContohEdukasiLingkungan {
id String @id @default(cuid())
judul String @db.Text
deskripsi String @db.Text
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
// ========================================= KONSERVASI ADAT BALI ========================================= //
model FilosofiTriHita {
id String @id @default(cuid())
judul String @db.Text
deskripsi String @db.Text
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
model BentukKonservasiBerdasarkanAdat {
id String @id @default(cuid())
judul String @db.Text
deskripsi String @db.Text
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
model NilaiKonservasiAdat {
id String @id @default(cuid())
judul String @db.Text
deskripsi String @db.Text
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
// ========================================= MENU PENDIDIKAN ========================================= //
// ========================================= INFO SEKOLAH & PAUD ========================================= //
model JenjangPendidikan {
id String @id @default(cuid())
nama String // TK/PAUD, SD, SMP, SMA/SMK
lembagas Lembaga[] // Relasi ke lembaga
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
model Lembaga {
id String @id @default(cuid())
nama String
jenjangPendidikan JenjangPendidikan @relation(fields: [jenjangId], references: [id])
jenjangId String
siswa Siswa[]
pengajar Pengajar[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
model Siswa {
id String @id @default(cuid())
nama String
lembaga Lembaga @relation(fields: [lembagaId], references: [id])
lembagaId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
model Pengajar {
id String @id @default(cuid())
nama String
lembaga Lembaga @relation(fields: [lembagaId], references: [id])
lembagaId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
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
deskripsi String @db.Text
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
model ProgramUnggulan {
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)
}
// ========================================= BIMBINGAN BELAJAR DESA ========================================= //
model TujuanBimbinganBelajarDesa {
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 LokasiJadwalBimbinganBelajarDesa {
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 FasilitasBimbinganBelajarDesa {
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)
}
// ========================================= PENDIDIKAN NON FORMAL ========================================= //
model TujuanPendidikanNonFormal {
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 TempatKegiatan {
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 JenisProgramYangDiselenggarakan {
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)
}
// ========================================= 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)
}

View File

@@ -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,8 +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 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: {
@@ -116,7 +192,9 @@ import pegawai from "./data/ekonomi/struktur-organisasi/pegawai.json";
},
});
}
console.log("✅ profilePerbekel seeded without imageId (editable later via UI)");
console.log(
"✅ profilePerbekel seeded without imageId (editable later via UI)"
);
for (const l of visiMisiDesa) {
await prisma.visiMisiDesa.upsert({
@@ -137,6 +215,64 @@ import pegawai from "./data/ekonomi/struktur-organisasi/pegawai.json";
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: {
@@ -176,22 +312,6 @@ import pegawai from "./data/ekonomi/struktur-organisasi/pegawai.json";
}
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: {
@@ -361,7 +481,6 @@ import pegawai from "./data/ekonomi/struktur-organisasi/pegawai.json";
}
console.log("kategori produk success ...");
for (const p of posisiOrganisasi) {
await prisma.posisiOrganisasi.upsert({
where: {
@@ -431,6 +550,283 @@ import pegawai from "./data/ekonomi/struktur-organisasi/pegawai.json";
});
}
console.log("hubungan organisasi success ...");
for (const d of detailDataPengangguran) {
await prisma.detailDataPengangguran.upsert({
where: {
month_year: { month: d.month, year: d.year },
},
update: {
totalUnemployment: d.totalUnemployment,
educatedUnemployment: d.educatedUnemployment,
uneducatedUnemployment: d.uneducatedUnemployment,
percentageChange: d.percentageChange,
},
create: {
month: d.month,
year: d.year,
totalUnemployment: d.totalUnemployment,
educatedUnemployment: d.educatedUnemployment,
uneducatedUnemployment: d.uneducatedUnemployment,
percentageChange: d.percentageChange,
},
});
}
console.log("📊 detailDataPengangguran success ...");
for (const e of tujuanEdukasiLingkungan) {
await prisma.tujuanEdukasiLingkungan.upsert({
where: {
id: e.id,
},
update: {
judul: e.judul,
deskripsi: e.deskripsi,
},
create: {
id: e.id,
judul: e.judul,
deskripsi: e.deskripsi,
},
});
}
console.log("tujuan edukasi lingkungan success ...");
for (const m of materiEdukasiLingkungan) {
await prisma.materiEdukasiLingkungan.upsert({
where: {
id: m.id,
},
update: {
judul: m.judul,
deskripsi: m.deskripsi,
},
create: {
id: m.id,
judul: m.judul,
deskripsi: m.deskripsi,
},
});
}
console.log("materi edukasi lingkungan success ...");
for (const c of contohEdukasiLingkungan) {
await prisma.contohEdukasiLingkungan.upsert({
where: {
id: c.id,
},
update: {
judul: c.judul,
deskripsi: c.deskripsi,
},
create: {
id: c.id,
judul: c.judul,
deskripsi: c.deskripsi,
},
});
}
console.log("contoh edukasi lingkungan success ...");
for (const f of filosofiTriHita) {
await prisma.filosofiTriHita.upsert({
where: {
id: f.id,
},
update: {
judul: f.judul,
deskripsi: f.deskripsi,
},
create: {
id: f.id,
judul: f.judul,
deskripsi: f.deskripsi,
},
});
}
console.log("filosofi tri hita success ...");
for (const b of bentukKonservasiBerdasarkanAdat) {
await prisma.bentukKonservasiBerdasarkanAdat.upsert({
where: {
id: b.id,
},
update: {
judul: b.judul,
deskripsi: b.deskripsi,
},
create: {
id: b.id,
judul: b.judul,
deskripsi: b.deskripsi,
},
});
}
console.log("bentuk konservasi berdasarkan adat success ...");
for (const n of nilaiKonservasiAdat) {
await prisma.nilaiKonservasiAdat.upsert({
where: {
id: n.id,
},
update: {
judul: n.judul,
deskripsi: n.deskripsi,
},
create: {
id: n.id,
judul: n.judul,
deskripsi: n.deskripsi,
},
});
}
console.log("nilai konservasi adat success ...");
for (const t of tujuanProgram) {
await prisma.tujuanProgram.upsert({
where: { id: t.id },
update: {
judul: t.judul,
deskripsi: t.deskripsi,
},
create: {
id: t.id,
judul: t.judul,
deskripsi: t.deskripsi,
},
});
}
console.log("✅ tujuan program seeded (editable later via UI)");
for (const t of programUnggulan) {
await prisma.programUnggulan.upsert({
where: { id: t.id },
update: {
judul: t.judul,
deskripsi: t.deskripsi,
},
create: {
id: t.id,
judul: t.judul,
deskripsi: t.deskripsi,
},
});
}
console.log("✅ program unggulan seeded (editable later via UI)");
for (const t of tujuanBimbinganBelajarDesa) {
await prisma.tujuanBimbinganBelajarDesa.upsert({
where: { id: t.id },
update: {
judul: t.judul,
deskripsi: t.deskripsi,
},
create: {
id: t.id,
judul: t.judul,
deskripsi: t.deskripsi,
},
});
}
console.log(
"✅ tujuan bimbingan belajar desa seeded (editable later via UI)"
);
for (const t of lokasiJadwalBimbinganBelajarDesa) {
await prisma.lokasiJadwalBimbinganBelajarDesa.upsert({
where: { id: t.id },
update: {
judul: t.judul,
deskripsi: t.deskripsi,
},
create: {
id: t.id,
judul: t.judul,
deskripsi: t.deskripsi,
},
});
}
console.log(
"✅ lokasi jadwal bimbingan belajar desa seeded (editable later via UI)"
);
for (const t of fasilitasBimbinganBelajarDesa) {
await prisma.fasilitasBimbinganBelajarDesa.upsert({
where: { id: t.id },
update: {
judul: t.judul,
deskripsi: t.deskripsi,
},
create: {
id: t.id,
judul: t.judul,
deskripsi: t.deskripsi,
},
});
}
console.log(
"✅ fasilitas bimbingan belajar desa seeded (editable later via UI)"
);
for (const t of tujuanProgram2) {
await prisma.tujuanPendidikanNonFormal.upsert({
where: { id: t.id },
update: {
judul: t.judul,
deskripsi: t.deskripsi,
},
create: {
id: t.id,
judul: t.judul,
deskripsi: t.deskripsi,
},
});
}
console.log(
"✅ fasilitas bimbingan belajar desa seeded (editable later via UI)"
);
for (const t of tempatKegiatan) {
await prisma.tempatKegiatan.upsert({
where: { id: t.id },
update: {
judul: t.judul,
deskripsi: t.deskripsi,
},
create: {
id: t.id,
judul: t.judul,
deskripsi: t.deskripsi,
},
});
}
console.log(
"✅ fasilitas bimbingan belajar desa seeded (editable later via UI)"
);
for (const t of jenisProgramYangDiselenggarakan) {
await prisma.jenisProgramYangDiselenggarakan.upsert({
where: { id: t.id },
update: {
judul: t.judul,
deskripsi: t.deskripsi,
},
create: {
id: t.id,
judul: t.judul,
deskripsi: t.deskripsi,
},
});
}
console.log(
"✅ fasilitas bimbingan belajar desa seeded (editable later via UI)"
);
})()
.then(() => prisma.$disconnect())
.catch((e) => {

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
public/pa-desa.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

View File

@@ -19,7 +19,7 @@ const HeaderSearch = ({
onChange,
}: HeaderSearchProps) => {
return (
<Grid>
<Grid mb={10}>
<GridCol span={{ base: 12, md: 9 }}>
<Title order={3}>{title}</Title>
</GridCol>

View File

@@ -15,6 +15,8 @@ type JudulListTabProps = {
}
const JudulListTab = ({
title = "",
href = "#",

View File

@@ -0,0 +1,59 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
'use client';
import { MapContainer, TileLayer, Marker, useMapEvents } from 'react-leaflet';
import { useEffect, useState } from 'react';
import 'leaflet/dist/leaflet.css';
import L, { LeafletMouseEvent } from 'leaflet';
type Props = {
defaultCenter: { lat: number; lng: number };
onSelect?: (pos: { lat: number; lng: number }) => void;
readOnly?: boolean;
};
export default function LeafletMap({ defaultCenter, onSelect, readOnly = false }: Props) {
const [markerPos, setMarkerPos] = useState(defaultCenter);
useEffect(() => {
// Aman di sini, karena ini hanya jalan di client
delete (L.Icon.Default.prototype as any)._getIconUrl;
L.Icon.Default.mergeOptions({
iconRetinaUrl:
'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-icon-2x.png',
iconUrl:
'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-icon.png',
shadowUrl:
'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-shadow.png',
});
}, []);
function LocationMarker() {
useMapEvents({
click(e: LeafletMouseEvent) {
if (readOnly) return;
const { lat, lng } = e.latlng;
setMarkerPos({ lat, lng });
onSelect?.({ lat, lng });
},
});
return <Marker position={markerPos} />;
}
return (
<MapContainer
center={defaultCenter}
zoom={16}
scrollWheelZoom
style={{ height: '100%', width: '100%', zIndex: 0 }}
>
<TileLayer
attribution='&copy; <a href="https://osm.org/copyright">OpenStreetMap</a>'
url='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
/>
<LocationMarker />
</MapContainer>
);
}

View File

@@ -0,0 +1,62 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
'use client';
import { MapContainer, TileLayer, Marker, useMapEvents } from 'react-leaflet';
import { useState, useEffect } from 'react';
import 'leaflet/dist/leaflet.css';
import L, { LeafletMouseEvent } from 'leaflet';
type Props = {
initialPosition: { lat: number; lng: number };
onChange: (pos: { lat: number; lng: number }) => void;
};
export default function LeafletMapEdit({ initialPosition, onChange }: Props) {
const [markerPos, setMarkerPos] = useState(initialPosition);
// ✅ Pastikan icon config cuma jalan di client
useEffect(() => {
if (typeof window !== 'undefined') {
delete (L.Icon.Default.prototype as any)._getIconUrl;
L.Icon.Default.mergeOptions({
iconRetinaUrl:
'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-icon-2x.png',
iconUrl:
'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-icon.png',
shadowUrl:
'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-shadow.png',
});
}
}, []);
useEffect(() => {
setMarkerPos(initialPosition);
}, [initialPosition]);
function LocationMarker() {
useMapEvents({
click(e: LeafletMouseEvent) {
const { lat, lng } = e.latlng;
setMarkerPos({ lat, lng });
onChange({ lat, lng });
},
});
return <Marker position={markerPos} />;
}
return (
<MapContainer
center={markerPos}
zoom={16}
scrollWheelZoom
style={{ height: '100%', width: '100%', zIndex: 0 }}
>
<TileLayer
attribution='&copy; <a href="https://osm.org/copyright">OpenStreetMap</a>'
url='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
/>
<LocationMarker />
</MapContainer>
);
}

View File

@@ -0,0 +1,98 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import { Box, rem, Select } from '@mantine/core';
import {
IconChartLine,
IconChristmasTreeFilled,
IconClipboardTextFilled,
IconDroplet,
IconHome,
IconHomeEco,
IconLeaf,
IconRecycle,
IconScale,
IconShieldFilled,
IconTent,
IconTrashFilled,
IconTree,
IconTrendingUp,
IconTrophy,
IconTruckFilled,
} from '@tabler/icons-react';
import { useEffect, useState } from 'react';
const iconMap = {
ekowisata: { label: 'Ekowisata', icon: IconLeaf },
kompetisi: { label: 'Kompetisi', icon: IconTrophy },
wisata: { label: 'Wisata', icon: IconTent },
ekonomi: { label: 'Ekonomi', icon: IconChartLine },
sampah: { label: 'Sampah', icon: IconRecycle },
truck: { label: 'Truck', icon: IconTruckFilled },
scale: { label: 'Scale', icon: IconScale },
clipboard: { label: 'Clipboard', icon: IconClipboardTextFilled },
trash: { label: 'Trash', icon: IconTrashFilled },
lingkunganSehat: {label: 'Lingkungan Sehat', icon: IconHomeEco},
sumberOksigen: {label: 'Sumber Oksigen', icon: IconChristmasTreeFilled},
ekonomiBerkelanjutan: {label: 'Ekonomi Berkelanjutan', icon: IconTrendingUp},
mencegahBencana: {label: 'Mencegah Bencana', icon: IconShieldFilled},
rumah: {label: 'Rumah', icon: IconHome},
pohon: {label: 'Pohon', icon: IconTree},
air: {label: 'Air', icon: IconDroplet}
};
type IconKey = keyof typeof iconMap;
const iconList = Object.entries(iconMap).map(([value, data]) => ({
value,
label: data.label,
}));
export default function SelectIconProgram(
{ onChange }: { onChange: (value: IconKey) => void }) {
const [selectedIcon, setSelectedIcon] = useState<IconKey>('ekowisata');
const IconComponent = iconMap[selectedIcon]?.icon || null;
// Push default icon ke state saat render awal
useEffect(() => {
onChange(selectedIcon);
}, []);
return (
<Box maw={300}>
<Select
placeholder="Pilih ikon"
value={selectedIcon}
onChange={(value) => {
if (value) {
setSelectedIcon(value as IconKey);
onChange(value as IconKey);
}
}}
data={iconList}
leftSection={
IconComponent && (
<Box>
<IconComponent size={24} stroke={1.5} />
</Box>
)
}
withCheckIcon={false}
searchable={false}
rightSectionWidth={0}
styles={{
input: {
textAlign: 'left',
fontSize: rem(16),
paddingLeft: 40,
},
section: {
left: 10,
right: 'auto',
},
}}
/>
</Box>
);
}

View File

@@ -0,0 +1,92 @@
'use client'
import { Box, rem, Select } from '@mantine/core';
import {
IconChartLine,
IconChristmasTreeFilled,
IconClipboardTextFilled,
IconDroplet,
IconHome,
IconHomeEco,
IconLeaf,
IconRecycle,
IconScale,
IconShieldFilled,
IconTent,
IconTrashFilled,
IconTree,
IconTrendingUp,
IconTrophy,
IconTruckFilled,
} from '@tabler/icons-react';
const iconMap = {
ekowisata: { label: 'Ekowisata', icon: IconLeaf },
kompetisi: { label: 'Kompetisi', icon: IconTrophy },
wisata: { label: 'Wisata', icon: IconTent },
ekonomi: { label: 'Ekonomi', icon: IconChartLine },
sampah: { label: 'Sampah', icon: IconRecycle },
truck: { label: 'Truck', icon: IconTruckFilled },
scale: { label: 'Scale', icon: IconScale },
clipboard: { label: 'Clipboard', icon: IconClipboardTextFilled },
trash: { label: 'Trash', icon: IconTrashFilled },
lingkunganSehat: {label: 'Lingkungan Sehat', icon: IconHomeEco},
sumberOksigen: {label: 'Sumber Oksigen', icon: IconChristmasTreeFilled},
ekonomiBerkelanjutan: {label: 'Ekonomi Berkelanjutan', icon: IconTrendingUp},
mencegahBencana: {label: 'Mencegah Bencana', icon: IconShieldFilled},
rumah: {label: 'Rumah', icon: IconHome},
pohon: {label: 'Pohon', icon: IconTree},
air: {label: 'Air', icon: IconDroplet}
};
type IconKey = keyof typeof iconMap;
const iconList = Object.entries(iconMap).map(([value, data]) => ({
value,
label: data.label,
}));
export default function SelectIconProgramEdit({
onChange,
value,
}: {
onChange: (value: IconKey) => void;
value: IconKey;
}) {
const IconComponent = iconMap[value]?.icon || null;
return (
<Box maw={300}>
<Select
placeholder="Pilih ikon"
value={value}
onChange={(value) => {
if (value) onChange(value as IconKey);
}}
data={iconList}
leftSection={
IconComponent && (
<Box>
<IconComponent size={24} stroke={1.5} />
</Box>
)
}
withCheckIcon={false}
searchable={false}
rightSectionWidth={0}
styles={{
input: {
textAlign: 'left',
fontSize: rem(16),
paddingLeft: 40,
},
section: {
left: 10,
right: 'auto',
},
}}
/>
</Box>
);
}

View File

@@ -80,13 +80,33 @@ const berita = proxy({
};
}>[]
| null,
async load() {
const res = await ApiFetch.api.desa.berita["find-many"].get();
if (res.status === 200) {
berita.findMany.data = (res.data?.data ) ?? [];
page: 1,
totalPages: 1,
loading: false,
async load(page = 1, limit = 10) {
berita.findMany.loading = true;
berita.findMany.page = page;
try {
const res = await ApiFetch.api.desa.berita["find-many"].get({
query: {
page,
limit,
},
});
if (res.status === 200 && res.data?.success) {
berita.findMany.data = res.data.data ?? [];
berita.findMany.totalPages = res.data.totalPages ?? 1;
}
} catch (err) {
console.error("Gagal fetch berita paginated:", err);
} finally {
berita.findMany.loading = false;
}
},
},
findUnique: {
data: null as
| Prisma.BeritaGetPayload<{
@@ -243,6 +263,59 @@ const berita = proxy({
berita.edit.form = { ...defaultForm };
},
},
findFirst: {
data: null as Prisma.BeritaGetPayload<{
include: {
image: true;
kategoriBerita: true;
};
}> | null,
loading: false,
async load() {
this.loading = true;
try {
const res = await ApiFetch.api.desa.berita["find-first"].get();
if (res.status === 200 && res.data?.success) {
// Add type assertion to ensure type safety
berita.findFirst.data = res.data.data as Prisma.BeritaGetPayload<{
include: {
image: true;
kategoriBerita: true;
};
}> | null;
}
} catch (err) {
console.error("Gagal fetch berita terbaru:", err);
} finally {
this.loading = false;
}
},
},
findRecent: {
data: [] as Prisma.BeritaGetPayload<{
include: {
image: true;
kategoriBerita: true;
};
}>[],
loading: false,
async load() {
try {
this.loading = true;
const res = await ApiFetch.api.desa.berita["find-recent"].get();
if (res.status === 200 && res.data?.success) {
this.data = res.data.data ?? [];
}
} catch (error) {
console.error("Gagal fetch berita recent:", error);
} finally {
this.loading = false;
}
},
}
});

View File

@@ -68,11 +68,11 @@ const pengumuman = proxy({
},
findMany: {
data: null as
| Prisma.PengumumanGetPayload<{
include: {
| Prisma.PengumumanGetPayload<{
include: {
CategoryPengumuman: true;
}
}>[]
};
}>[]
| null,
async load() {
const res = await ApiFetch.api.desa.pengumuman["find-many"].get();
@@ -82,30 +82,28 @@ const pengumuman = proxy({
}
},
},
// findUnique: {
// data: null as
// | Prisma.PengumumanGetPayload<{
// include: {
// CategoryPengumuman: true;
// }
// }>
// | null,
// async load(id: string) {
// try {
// const res = await fetch(`/api/desa/pengumuman/${id}`);
// if (res.ok) {
// const data = await res.json();
// pengumuman.findUnique.data = data.data ?? null;
// } else {
// console.error('Failed to fetch pengumuman:', res.statusText);
// pengumuman.findUnique.data = null;
// }
// } catch (error) {
// console.error('Error fetching pengumuman:', error);
// pengumuman.findUnique.data = null;
// }
// },
// },
findUnique: {
data: null as Prisma.PengumumanGetPayload<{
include: {
CategoryPengumuman: true;
};
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/desa/pengumuman/${id}`);
if (res.ok) {
const data = await res.json();
pengumuman.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch pengumuman:", res.statusText);
pengumuman.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching pengumuman:", error);
pengumuman.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
@@ -237,6 +235,55 @@ const pengumuman = proxy({
}
},
},
findFirst: {
data: null as Prisma.PengumumanGetPayload<{
include: {
CategoryPengumuman: true;
};
}> | null,
loading: false,
async load() {
this.loading = true;
try {
const res = await ApiFetch.api.desa.pengumuman["find-first"].get();
if (res.status === 200 && res.data?.success) {
// Add type assertion to ensure type safety
pengumuman.findFirst.data = res.data
.data as Prisma.PengumumanGetPayload<{
include: {
CategoryPengumuman: true;
};
}> | null;
}
} catch (err) {
console.error("Gagal fetch pengumuman terbaru:", err);
} finally {
this.loading = false;
}
},
},
findRecent: {
data: [] as Prisma.PengumumanGetPayload<{
include: {
CategoryPengumuman: true;
};
}>[],
loading: false,
async load() {
try {
this.loading = true;
const res = await ApiFetch.api.desa.pengumuman["find-recent"].get();
if (res.status === 200 && res.data?.success) {
this.data = res.data.data ?? [];
}
} catch (error) {
console.error("Gagal fetch pengumuman recent:", error);
} finally {
this.loading = false;
}
},
},
});
const stateDesaPengumuman = proxy({

View File

@@ -0,0 +1,887 @@
/* 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 templateApbDesa = z.object({
tahun: z.number().min(4, "Tahun minimal 4 karakter"),
pembiayaanIds: z.array(z.string().uuid()).nonempty("Pilih minimal 1 pembiayaan"),
belanjaIds: z.array(z.string().uuid()).nonempty("Pilih minimal 1 belanja"),
pendapatanIds: z.array(z.string().uuid()).nonempty("Pilih minimal 1 pendapatan"),
});
const ApbDesaDefaultForm = {
tahun: 0,
pendapatanIds: [] as string[],
belanjaIds: [] as string[],
pembiayaanIds: [] as string[],
};
const ApbDesa = proxy({
create: {
form: { ...ApbDesaDefaultForm },
loading: false,
async submit() {
const cek = templateApbDesa.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.ekonomi.pendapatanaslidesa.apbdesa[
"create"
].post(this.form);
if (res.status === 200) {
toast.success("Berhasil menambahkan APB Desa");
ApbDesa.findMany.load();
this.reset();
} else {
toast.error(res.data?.message || "Gagal menambahkan APB Desa");
}
} catch (error) {
console.error("Create error:", error);
toast.error("Gagal menambahkan APB Desa");
} finally {
this.loading = false;
}
},
reset() {
this.form = { ...ApbDesaDefaultForm };
},
},
findMany: {
data: null as
| Prisma.ApbDesaGetPayload<{
include: {
pendapatan: true;
belanja: true;
pembiayaan: true;
};
}>[]
| null,
loading: false,
async load() {
try {
this.loading = true;
const res = await ApiFetch.api.ekonomi.pendapatanaslidesa.apbdesa[
"find-many"
].get();
if (res.status === 200) {
this.data = res.data?.data ?? [];
} else {
toast.error(res.data?.message || "Gagal mengambil APB Desa");
}
} catch (error) {
console.error("Find many error:", error);
toast.error("Gagal mengambil APB Desa");
} finally {
this.loading = false;
}
},
},
update: {
id: "",
form: { ...ApbDesaDefaultForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(
`/api/ekonomi/pendapatanaslidesa/apbdesa/${id}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
}
);
if (!response.ok) {
throw new Error("Gagal mengambil APB Desa");
}
const result = await response.json();
if (!result.success) {
throw new Error(result.message || "Gagal memuat APB Desa");
}
const data = result.data;
this.id = id;
this.form = {
tahun: data.tahun || 0,
pendapatanIds: data.pendapatan?.map((p: any) => p.id) || [],
belanjaIds: data.belanja?.map((b: any) => b.id) || [],
pembiayaanIds: data.pembiayaan?.map((p: any) => p.id) || [],
};
return data;
} catch (error) {
console.error("Error loading APB Desa:", error);
toast.error("Gagal memuat data APB Desa");
return null;
}
},
async update() {
try {
this.loading = true;
const response = await fetch(
`/api/ekonomi/pendapatanaslidesa/apbdesa/${this.id}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(this.form),
}
);
if (!response.ok) {
throw new Error("Gagal memperbarui APB Desa");
}
const data = await response.json();
toast.success("APB Desa berhasil diperbarui");
return data;
} catch (error) {
console.error("Error updating APB Desa:", error);
toast.error("Gagal memperbarui APB Desa");
throw error;
} finally {
this.loading = false;
}
},
reset() {
this.id = "";
this.form = { ...ApbDesaDefaultForm };
},
},
delete: {
loading: false,
async byId(id: string) {
try {
this.loading = true;
const response = await fetch(
`/api/ekonomi/pendapatanaslidesa/apbdesa/del/${id}`,
{
method: "DELETE",
}
);
if (!response.ok) {
throw new Error("Gagal menghapus APB Desa");
}
toast.success("APB Desa berhasil dihapus");
return true;
} catch (error) {
console.error("Error deleting APB Desa:", error);
toast.error("Gagal menghapus APB Desa");
return false;
} finally {
this.loading = false;
}
},
},
findUnique: {
data: null as Prisma.ApbDesaGetPayload<{
include: { pendapatan: true; belanja: true; pembiayaan: true };
}> | null,
async load(id: string) {
try {
const response = await fetch(
`/api/ekonomi/pendapatanaslidesa/apbdesa/${id}`
);
if (!response.ok) {
throw new Error("Gagal mengambil detail APB Desa");
}
const result = await response.json();
if (!result.success) {
throw new Error(result.message || "Gagal mengambil data");
}
this.data = result.data; // ✅ fix utama di sini
return result.data;
} catch (error) {
console.error("Error loading APB Desa detail:", error);
toast.error("Gagal memuat detail APB Desa");
return null;
}
},
},
});
const templatePendapatan = z.object({
name: z.string().min(2, "Nama harus diisi"),
value: z.number().int().positive("Nilai harus angka positif"),
});
const PendapatanDefaultForm = {
name: "",
value: 0,
};
const pendapatan = proxy({
create: {
form: { ...PendapatanDefaultForm },
loading: false,
async submit() {
const cek = templatePendapatan.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.ekonomi.pendapatanaslidesa.pendapatanasli[
"create"
].post(this.form);
if (res.status === 200) {
toast.success("Berhasil menambahkan Pendapatan Asli");
pendapatan.findMany.load();
this.reset();
} else {
toast.error(res.data?.message || "Gagal menambahkan Pendapatan Asli");
}
} catch (error) {
console.error("Create error:", error);
toast.error("Gagal menambahkan Pendapatan Asli");
} finally {
this.loading = false;
}
},
reset() {
this.form = { ...PendapatanDefaultForm };
},
},
findMany: {
data: null as any[] | null,
page: 1,
totalPages: 1,
total: 0,
loading: false,
load: async (page = 1, limit = 10) => {
// Change to arrow function
pendapatan.findMany.loading = true; // Use the full path to access the property
pendapatan.findMany.page = page;
try {
const res =
await ApiFetch.api.ekonomi.pendapatanaslidesa.pendapatanasli[
"find-many"
].get({
query: { page, limit },
});
if (res.status === 200 && res.data?.success) {
pendapatan.findMany.data = res.data.data || [];
pendapatan.findMany.total = res.data.total || 0;
pendapatan.findMany.totalPages = res.data.totalPages || 1;
} else {
console.error("Failed to load pendapatan:", res.data?.message);
pendapatan.findMany.data = [];
pendapatan.findMany.total = 0;
pendapatan.findMany.totalPages = 1;
}
} catch (error) {
console.error("Error loading pendapatan:", error);
pendapatan.findMany.data = [];
pendapatan.findMany.total = 0;
pendapatan.findMany.totalPages = 1;
} finally {
pendapatan.findMany.loading = false;
}
},
},
update: {
id: "",
form: { ...PendapatanDefaultForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(`/api/ekonomi/pendapatanaslidesa/pendapatanasli/${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,
value: data.value,
};
return data;
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error loading pendapatan:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async update() {
const cek = templatePendapatan.safeParse(pendapatan.update.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
pendapatan.update.loading = true;
const response = await fetch(`/api/ekonomi/pendapatanaslidesa/pendapatanasli/${this.id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: this.form.name,
value: this.form.value,
}),
});
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 pendapatan");
await pendapatan.findMany.load(); // refresh list
return true;
} else {
throw new Error(result.message || "Gagal mengupdate pendapatan");
}
} catch (error) {
console.error("Error updating pendapatan:", error);
toast.error(
error instanceof Error ? error.message : "Gagal mengupdate pendapatan"
);
return false;
} finally {
pendapatan.update.loading = false;
}
},
reset() {
pendapatan.update.id = "";
pendapatan.update.form = { ...PendapatanDefaultForm };
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
pendapatan.delete.loading = true;
const response = await fetch(
`/api/ekonomi/pendapatanaslidesa/pendapatanasli/del/${id}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
);
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Pendapatan Asli berhasil dihapus");
await pendapatan.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus Pendapatan Asli");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus Pendapatan Asli");
} finally {
pendapatan.delete.loading = false;
}
},
},
findUnique: {
data: null as Prisma.PendapatanGetPayload<{
select: { isActive: boolean };
}> | null,
async load(id: string) {
const res = await fetch(
`/api/ekonomi/pendapatanaslidesa/pendapatanasli/${id}`
);
if (res.ok) {
const json = await res.json();
pendapatan.findUnique.data = json.data
? {
...json.data,
isActive: json.data.isActive ?? true, // Fallback ke aktif:true jika tidak ada data
}
: null;
} else {
pendapatan.findUnique.data = null;
}
},
},
});
const templateBelanja = z.object({
name: z.string().min(2, "Nama harus diisi"),
value: z.number().int().positive("Nilai harus angka positif"),
});
const BelanjaDefaultForm = {
name: "",
value: 0,
};
const belanja = proxy({
create: {
form: { ...BelanjaDefaultForm },
loading: false,
async submit() {
const cek = templateBelanja.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.ekonomi.pendapatanaslidesa.belanja[
"create"
].post(this.form);
if (res.status === 200) {
toast.success("Berhasil menambahkan Belanja");
belanja.findMany.load();
this.reset();
} else {
toast.error(res.data?.message || "Gagal menambahkan Belanja");
}
} catch (error) {
console.error("Create error:", error);
toast.error("Gagal menambahkan Belanja");
} finally {
this.loading = false;
}
},
reset() {
this.form = { ...BelanjaDefaultForm };
},
},
findMany: {
data: [] as Array<{
id: string;
name: string;
value: number;
}>,
loading: false,
async load() {
try {
this.loading = true;
const res = await ApiFetch.api.ekonomi.pendapatanaslidesa.belanja[
"find-many"
].get();
if (res.status === 200) {
this.data = res.data?.data ?? [];
} else {
toast.error(res.data?.message || "Gagal mengambil Belanja");
}
} catch (error) {
console.error("Find many error:", error);
toast.error("Gagal mengambil Belanja");
} finally {
this.loading = false;
}
},
},
update: {
id: "",
form: { ...BelanjaDefaultForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(`/api/ekonomi/pendapatanaslidesa/belanja/${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,
value: data.value,
};
return data;
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error loading belanja:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async update() {
const cek = templateBelanja.safeParse(belanja.update.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
belanja.update.loading = true;
const response = await fetch(`/api/ekonomi/pendapatanaslidesa/belanja/${this.id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: this.form.name,
value: this.form.value,
}),
});
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 belanja");
await belanja.findMany.load(); // refresh list
return true;
} else {
throw new Error(result.message || "Gagal mengupdate belanja");
}
} catch (error) {
console.error("Error updating belanja:", error);
toast.error(
error instanceof Error ? error.message : "Gagal mengupdate belanja"
);
return false;
} finally {
belanja.update.loading = false;
}
},
reset() {
belanja.update.id = "";
belanja.update.form = { ...BelanjaDefaultForm };
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
belanja.delete.loading = true;
const response = await fetch(
`/api/ekonomi/pendapatanaslidesa/belanja/del/${id}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
);
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Belanja berhasil dihapus");
await belanja.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus Belanja");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus Belanja");
} finally {
belanja.delete.loading = false;
}
},
},
findUnique: {
data: null as Prisma.BelanjaGetPayload<{
select: { isActive: boolean };
}> | null,
async load(id: string) {
const res = await fetch(`/api/ekonomi/pendapatanaslidesa/belanja/${id}`);
if (res.ok) {
const json = await res.json();
belanja.findUnique.data = json.data
? {
...json.data,
isActive: json.data.isActive ?? true, // Fallback ke aktif:true jika tidak ada data
}
: null;
} else {
belanja.findUnique.data = null;
}
},
},
});
const templatePembiayaan = z.object({
name: z.string().min(2, "Nama harus diisi"),
value: z.number().int().positive("Nilai harus angka positif"),
});
const PembiayaanDefaultForm = {
name: "",
value: 0,
};
const pembiayaan = proxy({
create: {
form: { ...PembiayaanDefaultForm },
loading: false,
async submit() {
const cek = templatePembiayaan.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.ekonomi.pendapatanaslidesa.pembiayaan[
"create"
].post(this.form);
if (res.status === 200) {
toast.success("Berhasil menambahkan Pembiayaan");
pembiayaan.findMany.load();
this.reset();
} else {
toast.error(res.data?.message || "Gagal menambahkan Pembiayaan");
}
} catch (error) {
console.error("Create error:", error);
toast.error("Gagal menambahkan Pembiayaan");
} finally {
this.loading = false;
}
},
reset() {
this.form = { ...PembiayaanDefaultForm };
},
},
findMany: {
data: [] as Array<{
id: string;
name: string;
value: number;
}>,
loading: false,
async load() {
try {
this.loading = true;
const res = await ApiFetch.api.ekonomi.pendapatanaslidesa.pembiayaan[
"find-many"
].get();
if (res.status === 200) {
this.data = res.data?.data ?? [];
} else {
toast.error(res.data?.message || "Gagal mengambil Pembiayaan");
}
} catch (error) {
console.error("Find many error:", error);
toast.error("Gagal mengambil Pembiayaan");
} finally {
this.loading = false;
}
},
},
update: {
id: "",
form: { ...PembiayaanDefaultForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(`/api/ekonomi/pendapatanaslidesa/pembiayaan/${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,
value: data.value,
};
return data;
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error loading pembiayaan:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async update() {
const cek = templatePembiayaan.safeParse(pembiayaan.update.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
pembiayaan.update.loading = true;
const response = await fetch(`/api/ekonomi/pendapatanaslidesa/pembiayaan/${this.id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: this.form.name,
value: this.form.value,
}),
});
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 pembiayaan");
await pembiayaan.findMany.load(); // refresh list
return true;
} else {
throw new Error(result.message || "Gagal mengupdate pembiayaan");
}
} catch (error) {
console.error("Error updating pembiayaan:", error);
toast.error(
error instanceof Error ? error.message : "Gagal mengupdate pembiayaan"
);
return false;
} finally {
pembiayaan.update.loading = false;
}
},
reset() {
pembiayaan.update.id = "";
pembiayaan.update.form = { ...PembiayaanDefaultForm };
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
pembiayaan.delete.loading = true;
const response = await fetch(
`/api/ekonomi/pendapatanaslidesa/pembiayaan/del/${id}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
);
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Pembiayaan berhasil dihapus");
await pembiayaan.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus Pembiayaan");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus Pembiayaan");
} finally {
pembiayaan.delete.loading = false;
}
},
},
findUnique: {
data: null as Prisma.PembiayaanGetPayload<{
select: { isActive: boolean };
}> | null,
async load(id: string) {
const res = await fetch(
`/api/ekonomi/pendapatanaslidesa/pembiayaan/${id}`
);
if (res.ok) {
const json = await res.json();
pembiayaan.findUnique.data = json.data
? {
...json.data,
isActive: json.data.isActive ?? true, // Fallback ke aktif:true jika tidak ada data
}
: null;
} else {
pembiayaan.findUnique.data = null;
}
},
},
});
const PendapatanAsliDesa = proxy({
ApbDesa,
belanja,
pembiayaan,
pendapatan,
});
export default PendapatanAsliDesa;

View File

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

View File

@@ -0,0 +1,243 @@
/* 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 templateJumlahPengngguran = z.object({
month: z.string().min(1, "Bulan harus diisi"),
year: z.number().min(1, "Tahun harus diisi"),
totalUnemployment: z.number().min(1, "Total pengangguran harus diisi"),
educatedUnemployment: z
.number()
.min(1, "Pengangguran pendidikan harus diisi"),
uneducatedUnemployment: z
.number()
.min(1, "Pengangguran tidak pendidikan harus diisi"),
percentageChange: z.number().min(0, "Persentase perubahan harus diisi"),
});
type JumlahPengangguran = {
month: string;
year: number;
totalUnemployment: number;
educatedUnemployment: number;
uneducatedUnemployment: number;
percentageChange: number;
};
const jumlahPengangguranForm: JumlahPengangguran = {
month: "",
year: 0,
totalUnemployment: 0,
educatedUnemployment: 0,
uneducatedUnemployment: 0,
percentageChange: 0,
};
const jumlahPengangguran = proxy({
findByMonthYear: {
data: null as any,
loading: false,
load: async ({ month, year }: { month: string; year: number }) => {
jumlahPengangguran.findByMonthYear.loading = true;
try {
const res = await fetch(
`/api/ekonomi/jumlahpengangguran/detaildatapengangguran/month/${month}/year/${year}`
);
const json = await res.json();
jumlahPengangguran.findByMonthYear.data = json.data;
return json.data;
} catch (err) {
console.error("Gagal ambil data bulan/tahun:", err);
} finally {
jumlahPengangguran.findByMonthYear.loading = false;
}
},
},
create: {
form: jumlahPengangguranForm,
loading: false,
async create() {
const cek = templateJumlahPengngguran.safeParse(
jumlahPengangguran.create.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return null;
}
try {
jumlahPengangguran.create.loading = true;
const res =
await ApiFetch.api.ekonomi.jumlahpengangguran.detaildatapengangguran[
"create"
].post(jumlahPengangguran.create.form);
if (res.status === 200) {
const id = res.data?.data?.id;
if (id) {
toast.success("Success create");
jumlahPengangguran.create.form = { ...jumlahPengangguranForm };
jumlahPengangguran.findMany.load();
return id;
}
}
toast.error("failed create");
return null;
} catch (error) {
console.log((error as Error).message);
return null;
} finally {
jumlahPengangguran.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.DetailDataPengangguranGetPayload<{
omit: { isActive: true };
}>[]
| null,
async load() {
const res =
await ApiFetch.api.ekonomi.jumlahpengangguran.detaildatapengangguran[
"find-many"
].get();
if (res.status === 200) {
jumlahPengangguran.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.DetailDataPengangguranGetPayload<{
omit: { isActive: true };
}> | null,
async load(id: string) {
try {
const res = await fetch(
`/api/ekonomi/jumlahpengangguran/detaildatapengangguran/${id}`
);
if (res.ok) {
const data = await res.json();
jumlahPengangguran.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch jumlahPengangguran:", res.statusText);
jumlahPengangguran.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching jumlahPengangguran:", error);
jumlahPengangguran.findUnique.data = null;
}
},
},
update: {
id: "",
form: { ...jumlahPengangguranForm },
loading: false,
async submit() {
const id = this.id;
if (!id) {
toast.warn("ID tidak valid");
return null;
}
const formData = {
month: this.form.month,
year: this.form.year,
totalUnemployment: this.form.totalUnemployment,
educatedUnemployment: this.form.educatedUnemployment,
uneducatedUnemployment: this.form.uneducatedUnemployment,
percentageChange: this.form.percentageChange,
};
const cek = templateJumlahPengngguran.safeParse(formData);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return null;
}
try {
this.loading = true;
const res = await fetch(
`/api/ekonomi/jumlahpengangguran/detaildatapengangguran/${id}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(formData),
}
);
const result = await res.json();
if (!res.ok || !result?.success) {
throw new Error(result?.message || "Gagal update data");
}
toast.success("Berhasil update data!");
await jumlahPengangguran.findMany.load();
return result.data;
} catch (error) {
console.error("Update error:", error);
toast.error("Gagal update data jumlah pengangguran");
throw error;
} finally {
this.loading = false;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
jumlahPengangguran.delete.loading = true;
const response = await fetch(
`/api/ekonomi/jumlahpengangguran/detaildatapengangguran/del/${id}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
);
const result = await response.json();
if (response.ok && result?.success) {
toast.success(
result.message || "Jumlah pengangguran berhasil dihapus"
);
await jumlahPengangguran.findMany.load();
} else {
toast.error(result?.message || "Gagal menghapus jumlah pengangguran");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus jumlah pengangguran");
} finally {
jumlahPengangguran.delete.loading = false;
}
},
},
});
const jumlahPengangguranState = proxy({
jumlahPengangguran,
});
export default jumlahPengangguranState;

View File

@@ -275,8 +275,7 @@ const kategoriProduk = proxy({
data: null as Array<{
id: string;
nama: string;
}>
| null,
}> | null,
async load() {
const res = await ApiFetch.api.ekonomi.kategoriproduk["find-many"].get();
if (res.status === 200) {
@@ -335,125 +334,135 @@ const kategoriProduk = proxy({
}
},
},
edit: {
id: "",
form: { ...kategoriProdukDefaultForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
edit: {
id: "",
form: { ...kategoriProdukDefaultForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(`/api/ekonomi/kategoriproduk/${id}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
try {
const response = await fetch(`/api/ekonomi/kategoriproduk/${id}`, {
method: "GET",
const result = await response.json();
if (result?.success) {
const data = result.data;
this.id = data.id;
this.form = {
nama: data.nama,
};
return data;
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error loading kategori produk:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async update() {
const cek = kategoriProdukForm.safeParse(kategoriProduk.edit.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return false;
}
try {
kategoriProduk.edit.loading = true;
const response = await fetch(
`/api/ekonomi/kategoriproduk/${kategoriProduk.edit.id}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
body: JSON.stringify({
nama: kategoriProduk.edit.form.nama,
}),
}
const result = await response.json();
if (result?.success) {
const data = result.data;
this.id = data.id;
this.form = {
nama: data.nama,
};
return data;
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error loading kategori produk:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async update() {
const cek = kategoriProdukForm.safeParse(kategoriProduk.edit.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return false;
}
);
// Clone the response to avoid 'body already read' error
const responseClone = response.clone();
try {
kategoriProduk.edit.loading = true;
const response = await fetch(
`/api/ekonomi/kategoriproduk/${kategoriProduk.edit.id}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
nama: kategoriProduk.edit.form.nama,
}),
}
);
// Clone the response to avoid 'body already read' error
const responseClone = response.clone();
try {
const result = await response.json();
if (!response.ok) {
console.error('Update failed with status:', response.status, 'Response:', result);
throw new Error(
result?.message || `Gagal mengupdate kategori produk (${response.status})`
);
}
if (result.success) {
toast.success(result.message || "Berhasil memperbarui kategori produk");
await kategoriProduk.findMany.load(); // refresh list
return true;
} else {
throw new Error(result.message || "Gagal mengupdate kategori produk");
}
} catch (error) {
// If JSON parsing fails, try to get the response text for better error messages
try {
const text = await responseClone.text();
console.error('Error response text:', text);
throw new Error(`Gagal memproses respons dari server: ${text}`);
} catch (textError) {
console.error('Error parsing response as text:', textError);
console.error('Original error:', error);
throw new Error('Gagal memproses respons dari server');
}
const result = await response.json();
if (!response.ok) {
console.error(
"Update failed with status:",
response.status,
"Response:",
result
);
throw new Error(
result?.message ||
`Gagal mengupdate kategori produk (${response.status})`
);
}
if (result.success) {
toast.success(
result.message || "Berhasil memperbarui kategori produk"
);
await kategoriProduk.findMany.load(); // refresh list
return true;
} else {
throw new Error(
result.message || "Gagal mengupdate kategori produk"
);
}
} catch (error) {
console.error("Error updating kategori produk:", error);
toast.error(
error instanceof Error
? error.message
: "Gagal mengupdate kategori produk"
);
return false;
} finally {
kategoriProduk.edit.loading = false;
// If JSON parsing fails, try to get the response text for better error messages
try {
const text = await responseClone.text();
console.error("Error response text:", text);
throw new Error(`Gagal memproses respons dari server: ${text}`);
} catch (textError) {
console.error("Error parsing response as text:", textError);
console.error("Original error:", error);
throw new Error("Gagal memproses respons dari server");
}
}
},
reset() {
kategoriProduk.edit.id = "";
kategoriProduk.edit.form = { ...kategoriProdukDefaultForm };
},
} catch (error) {
console.error("Error updating kategori produk:", error);
toast.error(
error instanceof Error
? error.message
: "Gagal mengupdate kategori produk"
);
return false;
} finally {
kategoriProduk.edit.loading = false;
}
},
reset() {
kategoriProduk.edit.id = "";
kategoriProduk.edit.form = { ...kategoriProdukDefaultForm };
},
},
});
const pasarDesaState = proxy({
pasarDesa,
kategoriProduk
kategoriProduk,
});
export default pasarDesaState;

View File

@@ -30,9 +30,9 @@ const posisiOrganisasi = proxy({
try {
this.loading = true;
const res = await ApiFetch.api.ekonomi["struktur-organisasi"]["posisi-organisasi"]["create"].post(
this.form
);
const res = await ApiFetch.api.ekonomi["struktur-organisasi"][
"posisi-organisasi"
]["create"].post(this.form);
if (res.status === 200) {
toast.success("Berhasil menambahkan posisi organisasi");
posisiOrganisasi.findMany.load();
@@ -62,12 +62,15 @@ const posisiOrganisasi = proxy({
return null;
}
try {
const response = await fetch(`/api/ekonomi/struktur-organisasi/posisi-organisasi/${id}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
const response = await fetch(
`/api/ekonomi/struktur-organisasi/posisi-organisasi/${id}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
}
);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
@@ -160,7 +163,9 @@ const posisiOrganisasi = proxy({
}>,
async load() {
try {
const res = await ApiFetch.api.ekonomi["struktur-organisasi"]["posisi-organisasi"]["find-many"].get();
const res = await ApiFetch.api.ekonomi["struktur-organisasi"][
"posisi-organisasi"
]["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 ?? [];
@@ -209,238 +214,278 @@ const posisiOrganisasi = proxy({
});
const templatePegawai = z.object({
namaLengkap: z.string().min(1, "Nama wajib diisi"),
gelarAkademik: z.string().optional(),
imageId: z.string().nullable().optional(),
tanggalMasuk: z.string().optional(), // ISO format
email: z.string().email("Email tidak valid").optional(),
telepon: z.string().optional(),
alamat: z.string().optional(),
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.ekonomi["struktur-organisasi"]["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;
}
},
},
findMany: {
data: null as (Prisma.PegawaiGetPayload<{ include: { posisi: true, image: true } }> & { isActive: boolean })[] | null,
async load() {
try {
const res = await ApiFetch.api.ekonomi["struktur-organisasi"]["pegawai"]["find-many"].get();
if (res.status === 200) {
pegawai.findMany.data = (res.data?.data ?? []).map((item: any) => ({
...item,
posisi: item.posisi || { id: '', nama: '' }, // Ensure posisi exists with required fields
isActive: item.isActive ?? true // Default to true if not provided
}));
} else {
console.error('Failed to load pegawai:', res.data?.message);
}
} catch (error) {
console.error('Error loading pegawai:', error);
pegawai.findMany.data = [];
}
},
},
findUnique: {
data: null as (Prisma.PegawaiGetPayload<{ include: { posisi: true, image: true } }> & { isActive: boolean }) | null,
async load(id: string) {
const res = await fetch(`/api/ekonomi/struktur-organisasi/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/ekonomi/struktur-organisasi/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/ekonomi/struktur-organisasi/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/ekonomi/struktur-organisasi/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 };
},
},
});
namaLengkap: z.string().min(1, "Nama wajib diisi"),
gelarAkademik: z.string().optional(),
imageId: z.string().nullable().optional(),
tanggalMasuk: z.string().optional(), // ISO format
email: z.string().email("Email tidak valid").optional(),
telepon: z.string().optional(),
alamat: z.string().optional(),
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.ekonomi["struktur-organisasi"][
"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.ekonomi["struktur-organisasi"][
"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/ekonomi/struktur-organisasi/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/ekonomi/struktur-organisasi/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/ekonomi/struktur-organisasi/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/ekonomi/struktur-organisasi/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 };
},
},
});
// Schema Zod untuk form validasi
const templateHubunganOrganisasiForm = z.object({
@@ -474,7 +519,9 @@ const hubunganOrganisasi = proxy({
try {
hubunganOrganisasi.create.loading = true;
const res = await ApiFetch.api.ekonomi["struktur-organisasi"]["hubungan-organisasi"]["create"].post(hubunganOrganisasi.create.form);
const res = await ApiFetch.api.ekonomi["struktur-organisasi"][
"hubungan-organisasi"
]["create"].post(hubunganOrganisasi.create.form);
if (res.status === 200 && res.data?.success) {
hubunganOrganisasi.findMany.load();
@@ -482,7 +529,7 @@ const hubunganOrganisasi = proxy({
} else {
return toast.error(res.data?.message || "Gagal menambahkan data");
}
} catch (error) {
} catch (error) {
console.error("Gagal create:", error);
toast.error("Terjadi kesalahan saat menambahkan");
} finally {
@@ -490,7 +537,7 @@ const hubunganOrganisasi = proxy({
}
},
},
findMany: {
findMany: {
data: null as Array<{
id: string;
atasanId: string;
@@ -528,20 +575,29 @@ const hubunganOrganisasi = proxy({
async load() {
try {
const res = await ApiFetch.api.ekonomi["struktur-organisasi"]["hubungan-organisasi"]["find-many"].get();
const res = await ApiFetch.api.ekonomi["struktur-organisasi"][
"hubungan-organisasi"
]["find-many"].get();
if (res.status === 200) {
hubunganOrganisasi.findMany.data = (res.data?.data ?? []).map((item: any) => ({
...item,
atasan: item.atasan ? {
...item.atasan,
isActive: item.atasan.isActive ?? item.atasan.aktif ?? true
} : null,
bawahan: item.bawahan ? {
...item.bawahan,
isActive: item.bawahan.isActive ?? item.bawahan.aktif ?? true
} : null
}));
hubunganOrganisasi.findMany.data = (res.data?.data ?? []).map(
(item: any) => ({
...item,
atasan: item.atasan
? {
...item.atasan,
isActive: item.atasan.isActive ?? item.atasan.aktif ?? true,
}
: null,
bawahan: item.bawahan
? {
...item.bawahan,
isActive:
item.bawahan.isActive ?? item.bawahan.aktif ?? true,
}
: null,
})
);
} else {
hubunganOrganisasi.findMany.data = [];
}
@@ -591,7 +647,9 @@ const hubunganOrganisasi = proxy({
async load(id: string) {
try {
const res = await fetch(`/api/ekonomi/struktur-organisasi/hubungan-organisasi/${id}`);
const res = await fetch(
`/api/ekonomi/struktur-organisasi/hubungan-organisasi/${id}`
);
const result = await res.json();
if (res.ok && result?.success) {
@@ -616,7 +674,9 @@ const hubunganOrganisasi = proxy({
if (!id) return toast.warn("ID tidak valid");
try {
const res = await fetch(`/api/ekonomi/struktur-organisasi/hubungan-organisasi/${id}`);
const res = await fetch(
`/api/ekonomi/struktur-organisasi/hubungan-organisasi/${id}`
);
const result = await res.json();
if (res.ok && result?.success) {
@@ -633,7 +693,9 @@ const hubunganOrganisasi = proxy({
}
} catch (error) {
console.error("Error loading:", error);
toast.error(error instanceof Error ? error.message : "Gagal memuat data");
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
@@ -690,9 +752,12 @@ const hubunganOrganisasi = proxy({
try {
hubunganOrganisasi.delete.loading = true;
const res = await fetch(`/api/ekonomi/struktur-organisasi/hubungan-organisasi/del/${id}`, {
method: "DELETE",
});
const res = await fetch(
`/api/ekonomi/struktur-organisasi/hubungan-organisasi/del/${id}`,
{
method: "DELETE",
}
);
const result = await res.json();
if (res.ok && result?.success) {

View File

@@ -0,0 +1,123 @@
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(1).max(50),
deskripsi: z.string().min(1).max(5000),
alamat: z.string().min(1).max(5000),
namaIde: z.string().min(1).max(5000),
masalah: z.string().min(1).max(5000),
benefit: z.string().min(1).max(5000),
});
const defaultForm = {
name: "",
deskripsi: "",
alamat: "",
namaIde: "",
masalah: "",
benefit: "",
};
const ajukanIdeInovatifState = proxy({
create: {
form: { ...defaultForm },
loading: false,
async create() {
const cek = templateForm.safeParse(ajukanIdeInovatifState.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
ajukanIdeInovatifState.create.loading = true;
const res = await ApiFetch.api.inovasi.ajukanideinovatif["create"].post(
ajukanIdeInovatifState.create.form
);
if (res.status === 200) {
ajukanIdeInovatifState.findMany.load();
return toast.success("Ajukan Ide Inovatif berhasil di kirim");
}
console.log(res);
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
ajukanIdeInovatifState.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.AjukanIdeInovatifGetPayload<{
omit: {
isActive: true;
};
}>[]
| null,
async load() {
const res = await ApiFetch.api.inovasi.ajukanideinovatif["find-many"].get();
if (res.status === 200) {
ajukanIdeInovatifState.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.AjukanIdeInovatifGetPayload<{
omit: {
isActive: true;
};
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/inovasi/ajukanideinovatif/${id}`);
if (res.ok) {
const data = await res.json();
ajukanIdeInovatifState.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
ajukanIdeInovatifState.findUnique.data = null;
}
} catch (error) {
console.error("Error loading ajukan ide inovatif:", error);
ajukanIdeInovatifState.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
ajukanIdeInovatifState.delete.loading = true;
const response = await fetch(`/api/inovasi/ajukanideinovatif/del/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
const result = await response.json();
if (response.ok) {
toast.success(result.message || "Ajukan Ide Inovatif berhasil dihapus");
await ajukanIdeInovatifState.findMany.load();
} else {
toast.error(result?.message || "Gagal menghapus ajukan ide inovatif");
}
} catch (error) {
console.log((error as Error).message);
toast.error("Terjadi kesalahan saat menghapus ajukan ide inovatif");
} finally {
ajukanIdeInovatifState.delete.loading = false;
}
},
},
});
export default ajukanIdeInovatifState;

View File

@@ -0,0 +1,216 @@
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(1).max(50),
deskripsi: z.string().min(1).max(5000),
imageId: z.string().min(1).max(50),
});
const defaultForm = {
name: "",
deskripsi: "",
imageId: "",
};
const desaDigitalState = proxy({
create: {
form: { ...defaultForm },
loading: false,
async create() {
const cek = templateForm.safeParse(desaDigitalState.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
desaDigitalState.create.loading = true;
const res = await ApiFetch.api.inovasi.desadigital["create"].post(
desaDigitalState.create.form
);
if (res.status === 200) {
desaDigitalState.findMany.load();
return toast.success("success create");
}
console.log(res);
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
desaDigitalState.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.DesaDigitalGetPayload<{
include: {
image: true;
};
}>[]
| null,
async load() {
const res = await ApiFetch.api.inovasi.desadigital["find-many"].get();
if (res.status === 200) {
desaDigitalState.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.DesaDigitalGetPayload<{
include: {
image: true;
};
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/inovasi/desadigital/${id}`);
if (res.ok) {
const data = await res.json();
desaDigitalState.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
desaDigitalState.findUnique.data = null;
}
} catch (error) {
console.error("Error loading desa digital:", error);
desaDigitalState.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
desaDigitalState.delete.loading = true;
const response = await fetch(`/api/inovasi/desadigital/del/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
const result = await response.json();
if (response.ok) {
toast.success(result.message || "Desa Digital berhasil dihapus");
await desaDigitalState.findMany.load();
} else {
toast.error(result?.message || "Gagal menghapus desa digital");
}
} catch (error) {
console.log((error as Error).message);
toast.error("Terjadi kesalahan saat menghapus desa digital");
} finally {
desaDigitalState.delete.loading = false;
}
},
},
edit: {
id: "",
form: { ...defaultForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(`/api/inovasi/desadigital/${id}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
if (result?.success) {
const data = result.data;
this.id = data.id;
this.form = {
name: data.name,
deskripsi: data.deskripsi,
imageId: data.imageId,
};
return data;
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error loading desa digital:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async update() {
const cek = templateForm.safeParse(desaDigitalState.edit.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return false;
}
try {
desaDigitalState.edit.loading = true;
const response = await fetch(
`/api/inovasi/desadigital/${desaDigitalState.edit.id}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: this.form.name,
deskripsi: this.form.deskripsi,
imageId: this.form.imageId,
}),
}
);
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(
errorData.message || `HTTP error! status: ${response.status}`
);
}
const result = await response.json();
if (result.success) {
toast.success("Berhasil update desa digital");
await desaDigitalState.findMany.load();
return true;
} else {
throw new Error(result?.message || "Gagal update desa digital");
}
} catch (error) {
console.error("Error updating desa digital:", error);
toast.error(
error instanceof Error
? error.message
: "Terjadi kesalahan saat update desa digital"
);
return false;
} finally {
desaDigitalState.edit.loading = false;
}
},
reset() {
desaDigitalState.edit.id = "";
desaDigitalState.edit.form = { ...defaultForm };
},
},
});
export default desaDigitalState;

View File

@@ -0,0 +1,216 @@
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(1).max(50),
deskripsi: z.string().min(1).max(5000),
imageId: z.string().min(1).max(50),
});
const defaultForm = {
name: "",
deskripsi: "",
imageId: "",
};
const infoTeknoState = proxy({
create: {
form: { ...defaultForm },
loading: false,
async create() {
const cek = templateForm.safeParse(infoTeknoState.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
infoTeknoState.create.loading = true;
const res = await ApiFetch.api.inovasi.infotekno["create"].post(
infoTeknoState.create.form
);
if (res.status === 200) {
infoTeknoState.findMany.load();
return toast.success("success create");
}
console.log(res);
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
infoTeknoState.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.InfoTeknoGetPayload<{
include: {
image: true;
};
}>[]
| null,
async load() {
const res = await ApiFetch.api.inovasi.infotekno["find-many"].get();
if (res.status === 200) {
infoTeknoState.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.InfoTeknoGetPayload<{
include: {
image: true;
};
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/inovasi/infotekno/${id}`);
if (res.ok) {
const data = await res.json();
infoTeknoState.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
infoTeknoState.findUnique.data = null;
}
} catch (error) {
console.error("Error loading desa digital:", error);
infoTeknoState.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
infoTeknoState.delete.loading = true;
const response = await fetch(`/api/inovasi/infotekno/del/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
const result = await response.json();
if (response.ok) {
toast.success(result.message || "Info Tekno berhasil dihapus");
await infoTeknoState.findMany.load();
} else {
toast.error(result?.message || "Gagal menghapus info tekno");
}
} catch (error) {
console.log((error as Error).message);
toast.error("Terjadi kesalahan saat menghapus info tekno");
} finally {
infoTeknoState.delete.loading = false;
}
},
},
edit: {
id: "",
form: { ...defaultForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(`/api/inovasi/infotekno/${id}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
if (result?.success) {
const data = result.data;
this.id = data.id;
this.form = {
name: data.name,
deskripsi: data.deskripsi,
imageId: data.imageId,
};
return data;
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error loading info tekno:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async update() {
const cek = templateForm.safeParse(infoTeknoState.edit.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return false;
}
try {
infoTeknoState.edit.loading = true;
const response = await fetch(
`/api/inovasi/infotekno/${infoTeknoState.edit.id}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: this.form.name,
deskripsi: this.form.deskripsi,
imageId: this.form.imageId,
}),
}
);
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(
errorData.message || `HTTP error! status: ${response.status}`
);
}
const result = await response.json();
if (result.success) {
toast.success("Berhasil update info tekno");
await infoTeknoState.findMany.load();
return true;
} else {
throw new Error(result?.message || "Gagal update info tekno");
}
} catch (error) {
console.error("Error updating info tekno:", error);
toast.error(
error instanceof Error
? error.message
: "Terjadi kesalahan saat update info tekno"
);
return false;
} finally {
infoTeknoState.edit.loading = false;
}
},
reset() {
infoTeknoState.edit.id = "";
infoTeknoState.edit.form = { ...defaultForm };
},
},
});
export default infoTeknoState;

View File

@@ -0,0 +1,234 @@
/* 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(1, "Nama minimal 1 karakter"),
tahun: z.number().min(4, "Tahun minimal 4 karakter"),
slug: z.string().min(1, "Deskripsi singkat minimal 1 karakter"),
deskripsi: z.string().min(1, "Deskripsi minimal 1 karakter"),
kolaborator: z.string().min(1, "Kolaborator minimal 1 karakter"),
imageId: z.string().min(1, "Image ID minimal 1 karakter"),
})
const defaultForm = {
name: "",
tahun: 0,
slug: "",
deskripsi: "",
kolaborator: "",
imageId: "",
}
const kolaborasiInovasiState = proxy({
create: {
form: { ...defaultForm },
loading: false,
async create() {
const cek = templateForm.safeParse(kolaborasiInovasiState.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
kolaborasiInovasiState.create.loading = true;
const res = await ApiFetch.api.inovasi.kolaborasiinovasi["create"].post(
kolaborasiInovasiState.create.form
);
if (res.status === 200) {
kolaborasiInovasiState.findMany.load();
return toast.success("success create");
}
console.log(res);
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
kolaborasiInovasiState.create.loading = false;
}
},
},
findMany: {
data: null as any[] | null,
page: 1,
totalPages: 1,
total: 0,
loading: false,
load: async (page = 1, limit = 10) => {
// Change to arrow function
kolaborasiInovasiState.findMany.loading = true; // Use the full path to access the property
kolaborasiInovasiState.findMany.page = page;
try {
const res = await ApiFetch.api.inovasi.kolaborasiinovasi["find-many"].get({
query: { page, limit },
});
if (res.status === 200 && res.data?.success) {
kolaborasiInovasiState.findMany.data = res.data.data || [];
kolaborasiInovasiState.findMany.total = res.data.total || 0;
kolaborasiInovasiState.findMany.totalPages = res.data.totalPages || 1;
} else {
console.error(
"Failed to load grafik berdasarkan jenis kelamin:",
res.data?.message
);
kolaborasiInovasiState.findMany.data = [];
kolaborasiInovasiState.findMany.total = 0;
kolaborasiInovasiState.findMany.totalPages = 1;
}
} catch (error) {
console.error("Error loading grafik berdasarkan jenis kelamin:", error);
kolaborasiInovasiState.findMany.data = [];
kolaborasiInovasiState.findMany.total = 0;
kolaborasiInovasiState.findMany.totalPages = 1;
} finally {
kolaborasiInovasiState.findMany.loading = false;
}
},
},
update: {
id: "",
form: { ...defaultForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(`/api/inovasi/kolaborasiinovasi/${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,
tahun: data.tahun,
slug: data.slug,
deskripsi: data.deskripsi,
kolaborator: data.kolaborator,
imageId: data.imageId,
};
return data;
} else {
throw new Error(result?.message || "Gagal mengambil data");
}
} catch (error) {
console.error("Error loading kolaborasi inovasi:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async submit() {
const id = this.id;
if (!id) {
toast.warn("ID tidak valid");
return null;
}
const cek = templateForm.safeParse(this.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return null;
}
this.loading = true;
try {
const response = await fetch(`/api/inovasi/kolaborasiinovasi/${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 kolaborasiInovasiState.findMany.load();
return result.data;
} catch (error) {
console.error("Error update data:", error);
toast.error("Gagal update data kolaborasi inovasi");
} finally {
this.loading = false;
}
},
},
findUnique: {
data: null as Prisma.KolaborasiInovasiGetPayload<{
include: { image: true };
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/inovasi/kolaborasiinovasi/${id}`);
if (res.ok) {
const data = await res.json();
kolaborasiInovasiState.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
kolaborasiInovasiState.findUnique.data = null;
}
} catch (error) {
console.error("Error loading kolaborasi inovasi:", error);
kolaborasiInovasiState.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
kolaborasiInovasiState.delete.loading = true;
const response = await fetch(`/api/inovasi/kolaborasiinovasi/del/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Kolaborasi inovasi berhasil dihapus");
await kolaborasiInovasiState.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus kolaborasi inovasi");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus kolaborasi inovasi");
} finally {
kolaborasiInovasiState.delete.loading = false;
}
},
},
});
export default kolaborasiInovasiState;

View File

@@ -0,0 +1,803 @@
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
import { proxy } from "valtio";
import { z } from "zod";
// ========================================= ADMINISTRASI ONLINE ========================================= //
const templateAdministrasiOnlineForm = z.object({
name: z.string().min(1, "Nama minimal 1 karakter"),
alamat: z.string().min(1, "Alamat minimal 1 karakter"),
nomorTelepon: z.string().min(1, "Nomor telepon minimal 1 karakter"),
jenisLayananId: z.string().min(1, "Jenis layanan minimal 1 karakter"),
});
const defaultAdministrasiOnlineForm = {
name: "",
alamat: "",
nomorTelepon: "",
jenisLayananId: "",
};
const administrasiOnline = proxy({
create: {
form: { ...defaultAdministrasiOnlineForm },
loading: false,
async create() {
const cek = templateAdministrasiOnlineForm.safeParse(
administrasiOnline.create.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
administrasiOnline.create.loading = true;
const res =
await ApiFetch.api.inovasi.layananonlinedesa.administrasionline[
"create"
].post(administrasiOnline.create.form);
if (res.status === 200) {
administrasiOnline.findMany.load();
return toast.success("Data berhasil ditambahkan");
}
return toast.error("Gagal menambahkan data");
} catch (error) {
console.log(error);
toast.error("Gagal menambahkan data");
} finally {
administrasiOnline.create.loading = false;
}
},
},
findMany: {
data: null as Array<
Prisma.AdministrasiOnlineGetPayload<{
include: {
jenisLayanan: true;
};
}>
> | null,
page: 1,
totalPages: 1,
loading: false,
async load(page = 1, limit = 10) {
administrasiOnline.findMany.loading = true;
administrasiOnline.findMany.page = page;
try {
const res =
await ApiFetch.api.inovasi.layananonlinedesa.administrasionline[
"find-many"
].get({
query: {
page,
limit,
},
});
if (res.status === 200 && res.data?.success) {
administrasiOnline.findMany.data = res.data.data ?? [];
administrasiOnline.findMany.totalPages = res.data.totalPages ?? 1;
}
} catch (err) {
console.error("Gagal fetch administrasi online paginated:", err);
} finally {
administrasiOnline.findMany.loading = false;
}
},
},
findUnique: {
data: null as Prisma.AdministrasiOnlineGetPayload<{
include: {
jenisLayanan: true;
};
}> | null,
async load(id: string) {
try {
const res = await fetch(
`/api/inovasi/layananonlinedesa/administrasionline/${id}`
);
if (res.ok) {
const data = await res.json();
administrasiOnline.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch administrasi online:", res.statusText);
administrasiOnline.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching administrasi online:", error);
administrasiOnline.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
administrasiOnline.delete.loading = true;
const response = await fetch(
`/api/inovasi/layananonlinedesa/administrasionline/del/${id}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
);
const result = await response.json();
if (response.ok && result?.success) {
toast.success(
result.message || "Administrasi online berhasil dihapus"
);
await administrasiOnline.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus administrasi online");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus administrasi online");
} finally {
administrasiOnline.delete.loading = false;
}
},
},
});
// ========================================= JENIS LAYANAN ========================================= //
const templateJenisLayananForm = z.object({
nama: z.string().min(1, "Nama minimal 1 karakter"),
deskripsi: z.string().min(1, "Deskripsi minimal 1 karakter"),
});
const defaultJenisLayananForm = {
nama: "",
deskripsi: "",
};
const jenisLayanan = proxy({
create: {
form: { ...defaultJenisLayananForm },
loading: false,
async create() {
const cek = templateJenisLayananForm.safeParse(jenisLayanan.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
jenisLayanan.create.loading = true;
const res =
await ApiFetch.api.inovasi.layananonlinedesa.administrasionline.jenislayanan[
"create"
].post(jenisLayanan.create.form);
if (res.status === 200) {
jenisLayanan.findMany.load();
return toast.success("Data berhasil ditambahkan");
}
return toast.error("Gagal menambahkan data");
} catch (error) {
console.log(error);
toast.error("Gagal menambahkan data");
} finally {
jenisLayanan.create.loading = false;
}
},
},
findMany: {
data: null as Array<{
id: string;
nama: string;
deskripsi: string;
}> | null,
async load() {
const res =
await ApiFetch.api.inovasi.layananonlinedesa.administrasionline.jenislayanan[
"find-many"
].get();
if (res.status === 200) {
jenisLayanan.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.JenisLayananGetPayload<{
omit: { isActive: true };
}> | null,
async load(id: string) {
try {
const res = await fetch(
`/api/inovasi/layananonlinedesa/administrasionline/jenislayanan/${id}`
);
if (res.ok) {
const data = await res.json();
jenisLayanan.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
jenisLayanan.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching data:", error);
jenisLayanan.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
jenisLayanan.delete.loading = true;
const response = await fetch(
`/api/inovasi/layananonlinedesa/administrasionline/jenislayanan/del/${id}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
);
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Jenis layanan berhasil dihapus");
await jenisLayanan.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus jenis layanan");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus jenis layanan");
} finally {
jenisLayanan.delete.loading = false;
}
},
},
edit: {
id: "",
form: { ...defaultJenisLayananForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(
`/api/inovasi/layananonlinedesa/administrasionline/jenislayanan/${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,
};
return data;
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error loading jenis layanan:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async update() {
const cek = templateJenisLayananForm.safeParse(jenisLayanan.edit.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return false;
}
try {
jenisLayanan.edit.loading = true;
const response = await fetch(
`/api/inovasi/layananonlinedesa/administrasionline/jenislayanan/${jenisLayanan.edit.id}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
nama: jenisLayanan.edit.form.nama,
deskripsi: jenisLayanan.edit.form.deskripsi,
}),
}
);
// Clone the response to avoid 'body already read' error
const responseClone = response.clone();
try {
const result = await response.json();
if (!response.ok) {
console.error(
"Update failed with status:",
response.status,
"Response:",
result
);
throw new Error(
result?.message ||
`Gagal mengupdate jenis layanan (${response.status})`
);
}
if (result.success) {
toast.success(
result.message || "Berhasil memperbarui jenis layanan"
);
await jenisLayanan.findMany.load(); // refresh list
return true;
} else {
throw new Error(result.message || "Gagal mengupdate jenis layanan");
}
} catch (error) {
// If JSON parsing fails, try to get the response text for better error messages
try {
const text = await responseClone.text();
console.error("Error response text:", text);
throw new Error(`Gagal memproses respons dari server: ${text}`);
} catch (textError) {
console.error("Error parsing response as text:", textError);
console.error("Original error:", error);
throw new Error("Gagal memproses respons dari server");
}
}
} catch (error) {
console.error("Error updating jenis layanan:", error);
toast.error(
error instanceof Error
? error.message
: "Gagal mengupdate jenis layanan"
);
return false;
} finally {
jenisLayanan.edit.loading = false;
}
},
reset() {
jenisLayanan.edit.id = "";
jenisLayanan.edit.form = { ...defaultJenisLayananForm };
},
},
});
// ========================================= PENGADUAN MASYARAKAT ========================================= //
const templatePengaduanMasyarakatForm = z.object({
name: z.string().min(1, "Nama minimal 1 karakter"),
email: z.string().min(1, "Alamat minimal 1 karakter"),
nomorTelepon: z.string().min(1, "Nomor telepon minimal 1 karakter"),
nik: z.string().min(1, "NIK minimal 1 karakter"),
judulPengaduan: z.string().min(1, "Judul pengaduan minimal 1 karakter"),
lokasiKejadian: z.string().min(1, "Lokasi kejadian minimal 1 karakter"),
deskripsiPengaduan: z.string().min(1, "Deskripsi pengaduan minimal 1 karakter"),
jenisPengaduanId: z.string().min(1, "Jenis pengaduan minimal 1 karakter"),
imageId: z.string().min(1, "Image minimal 1 karakter"),
});
const defaultPengaduanMasyarakatForm = {
name: "",
email: "",
nomorTelepon: "",
nik: "",
judulPengaduan: "",
lokasiKejadian: "",
deskripsiPengaduan: "",
jenisPengaduanId: "",
imageId: "",
};
const pengaduanMasyarakat = proxy({
create: {
form: { ...defaultPengaduanMasyarakatForm },
loading: false,
async create() {
const cek = templatePengaduanMasyarakatForm.safeParse(
pengaduanMasyarakat.create.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
pengaduanMasyarakat.create.loading = true;
const res =
await ApiFetch.api.inovasi.layananonlinedesa.pengaduanmasyarakat[
"create"
].post(pengaduanMasyarakat.create.form);
if (res.status === 200) {
pengaduanMasyarakat.findMany.load();
return toast.success("Data berhasil ditambahkan");
}
return toast.error("Gagal menambahkan data");
} catch (error) {
console.log(error);
toast.error("Gagal menambahkan data");
} finally {
pengaduanMasyarakat.create.loading = false;
}
},
},
findMany: {
data: null as Array<
Prisma.PengaduanMasyarakatGetPayload<{
include: {
jenisPengaduan: true;
image: true;
};
}>
> | null,
page: 1,
totalPages: 1,
loading: false,
async load(page = 1, limit = 10) {
pengaduanMasyarakat.findMany.loading = true;
pengaduanMasyarakat.findMany.page = page;
try {
const res =
await ApiFetch.api.inovasi.layananonlinedesa.pengaduanmasyarakat[
"find-many"
].get({
query: {
page,
limit,
},
});
if (res.status === 200 && res.data?.success) {
pengaduanMasyarakat.findMany.data = res.data.data ?? [];
pengaduanMasyarakat.findMany.totalPages = res.data.totalPages ?? 1;
}
} catch (err) {
console.error("Gagal fetch pengaduan masyarakat paginated:", err);
} finally {
pengaduanMasyarakat.findMany.loading = false;
}
},
},
findUnique: {
data: null as Prisma.PengaduanMasyarakatGetPayload<{
include: {
jenisPengaduan: true;
image: true;
};
}> | null,
async load(id: string) {
try {
const res = await fetch(
`/api/inovasi/layananonlinedesa/pengaduanmasyarakat/${id}`
);
if (res.ok) {
const data = await res.json();
pengaduanMasyarakat.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch pengaduan masyarakat:", res.statusText);
pengaduanMasyarakat.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching pengaduan masyarakat:", error);
pengaduanMasyarakat.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
pengaduanMasyarakat.delete.loading = true;
const response = await fetch(
`/api/inovasi/layananonlinedesa/pengaduanmasyarakat/del/${id}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
);
const result = await response.json();
if (response.ok && result?.success) {
toast.success(
result.message || "Pengaduan masyarakat berhasil dihapus"
);
await pengaduanMasyarakat.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus pengaduan masyarakat");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus pengaduan masyarakat");
} finally {
pengaduanMasyarakat.delete.loading = false;
}
},
},
});
// ========================================= JENIS PENGADUAN ========================================= //
const templateJenisPengaduanForm = z.object({
nama: z.string().min(1, "Nama minimal 1 karakter"),
});
const defaultJenisPengaduanForm = {
nama: "",
};
const jenisPengaduan = proxy({
create: {
form: { ...defaultJenisPengaduanForm },
loading: false,
async create() {
const cek = templateJenisPengaduanForm.safeParse(jenisPengaduan.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
jenisPengaduan.create.loading = true;
const res =
await ApiFetch.api.inovasi.layananonlinedesa.pengaduanmasyarakat.jenispengaduan[
"create"
].post(jenisPengaduan.create.form);
if (res.status === 200) {
jenisPengaduan.findMany.load();
return toast.success("Data berhasil ditambahkan");
}
return toast.error("Gagal menambahkan data");
} catch (error) {
console.log(error);
toast.error("Gagal menambahkan data");
} finally {
jenisPengaduan.create.loading = false;
}
},
},
findMany: {
data: null as Array<{
id: string;
nama: string;
}> | null,
async load() {
const res =
await ApiFetch.api.inovasi.layananonlinedesa.pengaduanmasyarakat.jenispengaduan[
"find-many"
].get();
if (res.status === 200) {
jenisPengaduan.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.JenisPengaduanGetPayload<{
omit: { isActive: true };
}> | null,
async load(id: string) {
try {
const res = await fetch(
`/api/inovasi/layananonlinedesa/pengaduanmasyarakat/jenispengaduan/${id}`
);
if (res.ok) {
const data = await res.json();
jenisPengaduan.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
jenisPengaduan.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching data:", error);
jenisPengaduan.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
jenisPengaduan.delete.loading = true;
const response = await fetch(
`/api/inovasi/layananonlinedesa/pengaduanmasyarakat/jenispengaduan/del/${id}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
);
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Jenis pengduan berhasil dihapus");
await jenisPengaduan.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus jenis pengaduan");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus jenis pengaduan");
} finally {
jenisPengaduan.delete.loading = false;
}
},
},
edit: {
id: "",
form: { ...defaultJenisPengaduanForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(
`/api/inovasi/layananonlinedesa/pengaduanmasyarakat/jenispengaduan/${id}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
}
);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
if (result?.success) {
const data = result.data;
this.id = data.id;
this.form = {
nama: data.nama
};
return data;
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error loading jenis pengaduan:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async update() {
const cek = templateJenisPengaduanForm.safeParse(jenisPengaduan.edit.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return false;
}
try {
jenisPengaduan.edit.loading = true;
const response = await fetch(
`/api/inovasi/layananonlinedesa/pengaduanmasyarakat/jenispengaduan/${jenisPengaduan.edit.id}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
nama: jenisPengaduan.edit.form.nama,
}),
}
);
// Clone the response to avoid 'body already read' error
const responseClone = response.clone();
try {
const result = await response.json();
if (!response.ok) {
console.error(
"Update failed with status:",
response.status,
"Response:",
result
);
throw new Error(
result?.message ||
`Gagal mengupdate jenis pengaduan (${response.status})`
);
}
if (result.success) {
toast.success(
result.message || "Berhasil memperbarui jenis pengaduan"
);
await jenisPengaduan.findMany.load(); // refresh list
return true;
} else {
throw new Error(result.message || "Gagal mengupdate jenis pengaduan");
}
} catch (error) {
// If JSON parsing fails, try to get the response text for better error messages
try {
const text = await responseClone.text();
console.error("Error response text:", text);
throw new Error(`Gagal memproses respons dari server: ${text}`);
} catch (textError) {
console.error("Error parsing response as text:", textError);
console.error("Original error:", error);
throw new Error("Gagal memproses respons dari server");
}
}
} catch (error) {
console.error("Error updating jenis pengaduan:", error);
toast.error(
error instanceof Error
? error.message
: "Gagal mengupdate jenis pengaduan"
);
return false;
} finally {
jenisPengaduan.edit.loading = false;
}
},
reset() {
jenisPengaduan.edit.id = "";
jenisPengaduan.edit.form = { ...defaultJenisPengaduanForm };
},
},
});
const layananonlineDesa = proxy({
administrasiOnline,
jenisLayanan,
pengaduanMasyarakat,
jenisPengaduan,
});
export default layananonlineDesa;

View File

@@ -0,0 +1,227 @@
/* 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(1, "Nama minimal 1 karakter"),
deskripsi: z.string().min(1, "Deskripsi minimal 1 karakter"),
slug: z.string().min(1, "Deskripsi singkat minimal 1 karakter"),
icon: z.string().min(1, "Icon minimal 1 karakter"),
});
const defaultForm = {
name: "",
deskripsi: "",
slug: "",
icon: "",
};
const programKreatifState = proxy({
create: {
form: { ...defaultForm },
loading: false,
async create() {
const cek = templateForm.safeParse(programKreatifState.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
programKreatifState.create.loading = true;
const res = await ApiFetch.api.inovasi.programkreatif["create"].post(
programKreatifState.create.form
);
if (res.status === 200) {
programKreatifState.findMany.load();
return toast.success("success create");
}
console.log(res);
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
programKreatifState.create.loading = false;
}
},
},
findMany: {
data: null as any[] | null,
page: 1,
totalPages: 1,
total: 0,
loading: false,
load: async (page = 1, limit = 10) => {
// Change to arrow function
programKreatifState.findMany.loading = true; // Use the full path to access the property
programKreatifState.findMany.page = page;
try {
const res = await ApiFetch.api.inovasi.programkreatif["find-many"].get({
query: { page, limit },
});
if (res.status === 200 && res.data?.success) {
programKreatifState.findMany.data = res.data.data || [];
programKreatifState.findMany.total = res.data.total || 0;
programKreatifState.findMany.totalPages = res.data.totalPages || 1;
} else {
console.error(
"Failed to load grafik berdasarkan jenis kelamin:",
res.data?.message
);
programKreatifState.findMany.data = [];
programKreatifState.findMany.total = 0;
programKreatifState.findMany.totalPages = 1;
}
} catch (error) {
console.error("Error loading grafik berdasarkan jenis kelamin:", error);
programKreatifState.findMany.data = [];
programKreatifState.findMany.total = 0;
programKreatifState.findMany.totalPages = 1;
} finally {
programKreatifState.findMany.loading = false;
}
},
},
update: {
id: "",
form: { ...defaultForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(`/api/inovasi/programkreatif/${id}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
if (result?.success) {
const data = result.data;
this.id = data.id;
this.form = {
name: data.name,
deskripsi: data.deskripsi,
slug: data.slug,
icon: data.icon,
};
return data;
} else {
throw new Error(result?.message || "Gagal mengambil data");
}
} catch (error) {
console.error("Error loading program kreatif:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async submit() {
const id = this.id;
if (!id) {
toast.warn("ID tidak valid");
return null;
}
const cek = templateForm.safeParse(this.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return null;
}
this.loading = true;
try {
const response = await fetch(`/api/inovasi/programkreatif/${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 programKreatifState.findMany.load();
return result.data;
} catch (error) {
console.error("Error update data:", error);
toast.error("Gagal update data program kreatif");
} finally {
this.loading = false;
}
},
},
findUnique: {
data: null as Prisma.ProgramKreatifGetPayload<{
omit: { isActive: true };
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/inovasi/programkreatif/${id}`);
if (res.ok) {
const data = await res.json();
programKreatifState.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
programKreatifState.findUnique.data = null;
}
} catch (error) {
console.error("Error loading program kreatif:", error);
programKreatifState.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
programKreatifState.delete.loading = true;
const response = await fetch(`/api/inovasi/programkreatif/del/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Program kreatif berhasil dihapus");
await programKreatifState.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus program kreatif");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus program kreatif");
} finally {
programKreatifState.delete.loading = false;
}
},
},
});
export default programKreatifState;

View File

@@ -81,7 +81,6 @@ const persentasekelahiran = proxy({
}
},
},
findUnique: {
data: null as Prisma.DataKematian_KelahiranGetPayload<{
omit: { isActive: true };
@@ -176,7 +175,6 @@ const persentasekelahiran = proxy({
},
}
);
const result = await response.json();
if (response.ok && result?.success) {

View File

@@ -0,0 +1,224 @@
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 templateapbDesaForm = z.object({
name: z.string().min(1, "Judul minimal 1 karakter"),
jumlah: z.string().min(1, "Deskripsi minimal 1 karakter"),
imageId: z.string().min(1, "File minimal 1"),
fileId: z.string().min(1, "File minimal 1"),
});
const defaultapbdesForm = {
name: "",
jumlah: "",
imageId: "",
fileId: "",
};
const apbdes = proxy({
create: {
form: { ...defaultapbdesForm },
loading: false,
async create() {
const cek = templateapbDesaForm.safeParse(apbdes.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
apbdes.create.loading = true;
const res = await ApiFetch.api.landingpage.apbdes["create"].post({
...apbdes.create.form,
});
if (res.status === 200) {
apbdes.findMany.load();
return toast.success("Data berhasil ditambahkan");
}
return toast.error("Gagal menambahkan data");
} catch (error) {
console.log(error);
toast.error("Gagal menambahkan data");
} finally {
apbdes.create.loading = false;
}
},
},
findMany: {
data: null as Array<
Prisma.APBDesGetPayload<{
include: {
image: true;
file: true;
};
}>
> | null,
async load() {
const res = await ApiFetch.api.landingpage.apbdes["find-many"].get();
if (res.status === 200) {
apbdes.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.APBDesGetPayload<{
include: {
image: true;
file: true;
};
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/landingpage/apbdes/${id}`);
if (res.ok) {
const data = await res.json();
apbdes.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
apbdes.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching data:", error);
apbdes.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
apbdes.delete.loading = true;
const response = await fetch(`/api/landingpage/apbdes/del/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "apbdes berhasil dihapus");
await apbdes.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus apbdes");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus apbdes");
} finally {
apbdes.delete.loading = false;
}
},
},
edit: {
id: "",
form: { ...defaultapbdesForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
apbdes.edit.loading = true;
const response = await fetch(`/api/landingpage/apbdes/${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,
jumlah: data.jumlah,
imageId: data.imageId,
fileId: data.fileId,
};
return data;
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error loading apbdes:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
} finally {
apbdes.edit.loading = false;
}
},
async update() {
const cek = templateapbDesaForm.safeParse(apbdes.edit.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
apbdes.edit.loading = true;
const response = await fetch(`/api/landingpage/apbdes/${this.id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: this.form.name,
jumlah: this.form.jumlah,
imageId: this.form.imageId,
fileId: this.form.fileId,
}),
});
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 apbdes");
await apbdes.findMany.load(); // refresh list
return true;
} else {
throw new Error(result.message || "Gagal mengupdate apbdes");
}
} catch (error) {
console.error("Error updating apbdes:", error);
toast.error(
error instanceof Error ? error.message : "Gagal mengupdate apbdes"
);
return false;
} finally {
apbdes.edit.loading = false;
}
},
reset() {
apbdes.edit.id = "";
apbdes.edit.form = { ...defaultapbdesForm };
},
},
});
export default apbdes;

View File

@@ -0,0 +1,528 @@
/* 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 templateDesaAntiKorupsiForm = z.object({
name: z.string().min(1, "Judul minimal 1 karakter"),
deskripsi: z.string().min(1, "Deskripsi minimal 1 karakter"),
kategoriId: z.string().min(1, "Kategori minimal 1"),
fileId: z.string().min(1, "File minimal 1"),
});
const defaultDesaAntiKorupsiForm = {
name: "",
deskripsi: "",
kategoriId: "",
fileId: "",
};
const desaAntikorupsi = proxy({
create: {
form: { ...defaultDesaAntiKorupsiForm },
loading: false,
async create() {
const cek = templateDesaAntiKorupsiForm.safeParse(
desaAntikorupsi.create.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
desaAntikorupsi.create.loading = true;
const res = await ApiFetch.api.landingpage.desaantikorupsi[
"create"
].post({
...desaAntikorupsi.create.form,
});
if (res.status === 200) {
desaAntikorupsi.findMany.load();
return toast.success("Data berhasil ditambahkan");
}
return toast.error("Gagal menambahkan data");
} catch (error) {
console.log(error);
toast.error("Gagal menambahkan data");
} finally {
desaAntikorupsi.create.loading = false;
}
},
},
findMany: {
data: null as any[] | null,
page: 1,
totalPages: 1,
total: 0,
loading: false,
load: async (page = 1, limit = 10) => { // Change to arrow function
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;
}
},
},
findUnique: {
data: null as Prisma.DesaAntiKorupsiGetPayload<{
include: {
file: true;
kategori: true;
};
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/landingpage/desaantikorupsi/${id}`);
if (res.ok) {
const data = await res.json();
desaAntikorupsi.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
desaAntikorupsi.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching data:", error);
desaAntikorupsi.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
desaAntikorupsi.delete.loading = true;
const response = await fetch(
`/api/landingpage/desaantikorupsi/del/${id}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
);
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "desa anti korupsi berhasil dihapus");
await desaAntikorupsi.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus desa anti korupsi");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus desa anti korupsi");
} finally {
desaAntikorupsi.delete.loading = false;
}
},
},
edit: {
id: "",
form: { ...defaultDesaAntiKorupsiForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
desaAntikorupsi.edit.loading = true;
const response = await fetch(`/api/landingpage/desaantikorupsi/${id}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
if (result?.success) {
const data = result.data;
this.id = data.id;
this.form = {
name: data.name,
deskripsi: data.deskripsi,
kategoriId: data.kategoriId,
fileId: data.fileId,
};
return data;
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error loading desa anti korupsi:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
} finally {
desaAntikorupsi.edit.loading = false;
}
},
async update() {
const cek = templateDesaAntiKorupsiForm.safeParse(
desaAntikorupsi.edit.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
desaAntikorupsi.edit.loading = true;
const response = await fetch(
`/api/landingpage/desaantikorupsi/${this.id}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: this.form.name,
deskripsi: this.form.deskripsi,
kategoriId: this.form.kategoriId,
fileId: this.form.fileId,
}),
}
);
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 desa anti korupsi");
await desaAntikorupsi.findMany.load(); // refresh list
return true;
} else {
throw new Error(
result.message || "Gagal mengupdate desa anti korupsi"
);
}
} catch (error) {
console.error("Error updating desa anti korupsi:", error);
toast.error(
error instanceof Error
? error.message
: "Gagal mengupdate desa anti korupsi"
);
return false;
} finally {
desaAntikorupsi.edit.loading = false;
}
},
reset() {
desaAntikorupsi.edit.id = "";
desaAntikorupsi.edit.form = { ...defaultDesaAntiKorupsiForm };
},
},
});
// ========================================= KATEGORI desa anti korupsi ========================================= //
const kategoriDesaAntiKorupsiForm = z.object({
name: z.string().min(1, "Nama minimal 1 karakter"),
});
const kategoriDesaAntiKorupsiDefaultForm = {
name: "",
};
const kategoriDesaAntiKorupsi = proxy({
create: {
form: { ...kategoriDesaAntiKorupsiDefaultForm },
loading: false,
async create() {
const cek = kategoriDesaAntiKorupsiForm.safeParse(
kategoriDesaAntiKorupsi.create.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
kategoriDesaAntiKorupsi.create.loading = true;
const res = await ApiFetch.api.landingpage.kategoridak["create"].post(
kategoriDesaAntiKorupsi.create.form
);
if (res.status === 200) {
kategoriDesaAntiKorupsi.findMany.load();
return toast.success("Data berhasil ditambahkan");
}
return toast.error("Gagal menambahkan data");
} catch (error) {
console.log(error);
toast.error("Gagal menambahkan data");
} finally {
kategoriDesaAntiKorupsi.create.loading = false;
}
},
},
findMany: {
data: null as any[] | null,
page: 1,
totalPages: 1,
total: 0,
loading: false,
load: async (page = 1, limit = 10) => { // Change to arrow function
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;
}
},
},
findUnique: {
data: null as Prisma.KategoriDesaAntiKorupsiGetPayload<{
omit: { isActive: true };
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/landingpage/kategoridak/${id}`);
if (res.ok) {
const data = await res.json();
kategoriDesaAntiKorupsi.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
kategoriDesaAntiKorupsi.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching data:", error);
kategoriDesaAntiKorupsi.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
kategoriDesaAntiKorupsi.delete.loading = true;
const response = await fetch(
`/api/landingpage/kategoridak/del/${id}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
);
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Kategori desa anti korupsi berhasil dihapus");
await kategoriDesaAntiKorupsi.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus kategori desa anti korupsi");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus kategori desa anti korupsi");
} finally {
kategoriDesaAntiKorupsi.delete.loading = false;
}
},
},
edit: {
id: "",
form: { ...kategoriDesaAntiKorupsiDefaultForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(`/api/landingpage/kategoridak/${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;
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error loading kategori desa anti korupsi:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async update() {
const cek = kategoriDesaAntiKorupsiForm.safeParse(
kategoriDesaAntiKorupsi.edit.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return false;
}
try {
kategoriDesaAntiKorupsi.edit.loading = true;
const response = await fetch(
`/api/landingpage/kategoridak/${kategoriDesaAntiKorupsi.edit.id}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: kategoriDesaAntiKorupsi.edit.form.name,
}),
}
);
// Clone the response to avoid 'body already read' error
const responseClone = response.clone();
try {
const result = await response.json();
if (!response.ok) {
console.error(
"Update failed with status:",
response.status,
"Response:",
result
);
throw new Error(
result?.message ||
`Gagal mengupdate kategori desa anti korupsi (${response.status})`
);
}
if (result.success) {
toast.success(
result.message ||
"Berhasil memperbarui kategori desa anti korupsi"
);
await kategoriDesaAntiKorupsi.findMany.load(); // refresh list
return true;
} else {
throw new Error(
result.message || "Gagal mengupdate kategori desa anti korupsi"
);
}
} catch (error) {
// If JSON parsing fails, try to get the response text for better error messages
try {
const text = await responseClone.text();
console.error("Error response text:", text);
throw new Error(`Gagal memproses respons dari server: ${text}`);
} catch (textError) {
console.error("Error parsing response as text:", textError);
console.error("Original error:", error);
throw new Error("Gagal memproses respons dari server");
}
}
} catch (error) {
console.error("Error updating kategori desa anti korupsi:", error);
toast.error(
error instanceof Error
? error.message
: "Gagal mengupdate kategori desa anti korupsi"
);
return false;
} finally {
kategoriDesaAntiKorupsi.edit.loading = false;
}
},
reset() {
kategoriDesaAntiKorupsi.edit.id = "";
kategoriDesaAntiKorupsi.edit.form = {
...kategoriDesaAntiKorupsiDefaultForm,
};
},
},
});
const korupsiState = proxy({
desaAntikorupsi,
kategoriDesaAntiKorupsi,
});
export default korupsiState;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,486 @@
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 templateprestasiDesaForm = z.object({
name: z.string().min(1, "Judul minimal 1 karakter"),
deskripsi: z.string().min(1, "Deskripsi minimal 1 karakter"),
imageId: z.string().min(1, "File minimal 1"),
kategoriId: z.string().min(1, "Kategori minimal 1 karakter"),
});
const defaultprestasiDesaForm = {
name: "",
deskripsi: "",
imageId: "",
kategoriId: "",
};
const prestasiDesa = proxy({
create: {
form: { ...defaultprestasiDesaForm },
loading: false,
async create() {
const cek = templateprestasiDesaForm.safeParse(
prestasiDesa.create.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
prestasiDesa.create.loading = true;
const res = await ApiFetch.api.landingpage.prestasidesa[
"create"
].post({
...prestasiDesa.create.form,
});
if (res.status === 200) {
prestasiDesa.findMany.load();
return toast.success("Data berhasil ditambahkan");
}
return toast.error("Gagal menambahkan data");
} catch (error) {
console.log(error);
toast.error("Gagal menambahkan data");
} finally {
prestasiDesa.create.loading = false;
}
},
},
findMany: {
data: null as Array<
Prisma.PrestasiDesaGetPayload<{
include: {
image: true;
kategori: true;
};
}>
> | null,
async load() {
const res = await ApiFetch.api.landingpage.prestasidesa[
"find-many"
].get();
if (res.status === 200) {
prestasiDesa.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.PrestasiDesaGetPayload<{
include: {
image: true;
kategori: true;
};
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/landingpage/prestasidesa/${id}`);
if (res.ok) {
const data = await res.json();
prestasiDesa.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
prestasiDesa.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching data:", error);
prestasiDesa.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
prestasiDesa.delete.loading = true;
const response = await fetch(
`/api/landingpage/prestasidesa/del/${id}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
);
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "prestasi desa berhasil dihapus");
await prestasiDesa.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus prestasi desa");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus prestasi desa");
} finally {
prestasiDesa.delete.loading = false;
}
},
},
edit: {
id: "",
form: { ...defaultprestasiDesaForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
prestasiDesa.edit.loading = true;
const response = await fetch(`/api/landingpage/prestasidesa/${id}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
if (result?.success) {
const data = result.data;
this.id = data.id;
this.form = {
name: data.name,
deskripsi: data.deskripsi,
imageId: data.imageId,
kategoriId: data.kategoriId,
};
return data;
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error loading prestasi desa:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
} finally {
prestasiDesa.edit.loading = false;
}
},
async update() {
const cek = templateprestasiDesaForm.safeParse(
prestasiDesa.edit.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
prestasiDesa.edit.loading = true;
const response = await fetch(
`/api/landingpage/prestasidesa/${this.id}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: this.form.name,
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 prestasi desa");
await prestasiDesa.findMany.load(); // refresh list
return true;
} else {
throw new Error(
result.message || "Gagal mengupdate prestasi desa"
);
}
} catch (error) {
console.error("Error updating prestasi desa:", error);
toast.error(
error instanceof Error
? error.message
: "Gagal mengupdate prestasi desa"
);
return false;
} finally {
prestasiDesa.edit.loading = false;
}
},
reset() {
prestasiDesa.edit.id = "";
prestasiDesa.edit.form = { ...defaultprestasiDesaForm };
},
},
});
// ========================================= KATEGORI kegiatan ========================================= //
const kategoriPrestasiForm = z.object({
name: z.string().min(1, "Nama minimal 1 karakter"),
});
const kategoriPrestasiDefaultForm = {
name: "",
};
const kategoriPrestasi = proxy({
create: {
form: { ...kategoriPrestasiDefaultForm },
loading: false,
async create() {
const cek = kategoriPrestasiForm.safeParse(kategoriPrestasi.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
kategoriPrestasi.create.loading = true;
const res = await ApiFetch.api.landingpage.kategoriprestasi[
"create"
].post(kategoriPrestasi.create.form);
if (res.status === 200) {
kategoriPrestasi.findMany.load();
return toast.success("Data berhasil ditambahkan");
}
return toast.error("Gagal menambahkan data");
} catch (error) {
console.log(error);
toast.error("Gagal menambahkan data");
} finally {
kategoriPrestasi.create.loading = false;
}
},
},
findMany: {
data: null as Array<{
id: string;
name: string;
}> | null,
async load() {
const res = await ApiFetch.api.landingpage.kategoriprestasi[
"find-many"
].get();
if (res.status === 200) {
kategoriPrestasi.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.KategoriPrestasiDesaGetPayload<{
omit: { isActive: true };
}> | null,
async load(id: string) {
try {
const res = await fetch(
`/api/landingpage/kategoriprestasi/${id}`
);
if (res.ok) {
const data = await res.json();
kategoriPrestasi.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
kategoriPrestasi.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching data:", error);
kategoriPrestasi.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
kategoriPrestasi.delete.loading = true;
const response = await fetch(
`/api/landingpage/kategoriprestasi/del/${id}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
);
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Kategori prestasi berhasil dihapus");
await kategoriPrestasi.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus kategori prestasi");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus kategori prestasi");
} finally {
kategoriPrestasi.delete.loading = false;
}
},
},
edit: {
id: "",
form: { ...kategoriPrestasiDefaultForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(
`/api/landingpage/kategoriprestasi/${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;
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error loading kategori prestasi:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async update() {
const cek = kategoriPrestasiForm.safeParse(kategoriPrestasi.edit.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return false;
}
try {
kategoriPrestasi.edit.loading = true;
const response = await fetch(
`/api/landingpage/kategoriprestasi/${kategoriPrestasi.edit.id}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: kategoriPrestasi.edit.form.name,
}),
}
);
// Clone the response to avoid 'body already read' error
const responseClone = response.clone();
try {
const result = await response.json();
if (!response.ok) {
console.error(
"Update failed with status:",
response.status,
"Response:",
result
);
throw new Error(
result?.message ||
`Gagal mengupdate kategori prestasi (${response.status})`
);
}
if (result.success) {
toast.success(
result.message || "Berhasil memperbarui kategori prestasi"
);
await kategoriPrestasi.findMany.load(); // refresh list
return true;
} else {
throw new Error(
result.message || "Gagal mengupdate kategori prestasi"
);
}
} catch (error) {
// If JSON parsing fails, try to get the response text for better error messages
try {
const text = await responseClone.text();
console.error("Error response text:", text);
throw new Error(`Gagal memproses respons dari server: ${text}`);
} catch (textError) {
console.error("Error parsing response as text:", textError);
console.error("Original error:", error);
throw new Error("Gagal memproses respons dari server");
}
}
} catch (error) {
console.error("Error updating kategori prestasi:", error);
toast.error(
error instanceof Error
? error.message
: "Gagal mengupdate kategori prestasi"
);
return false;
} finally {
kategoriPrestasi.edit.loading = false;
}
},
reset() {
kategoriPrestasi.edit.id = "";
kategoriPrestasi.edit.form = { ...kategoriPrestasiDefaultForm };
},
},
});
const prestasiState = proxy({
prestasiDesa,
kategoriPrestasi,
});
export default prestasiState;

View File

@@ -0,0 +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 templateProgramInovasi = z.object({
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(1, "Link minimal 1 karakter"),
});
type ProgramInovasiForm = Prisma.ProgramInovasiGetPayload<{
select: {
name: true;
description: true;
imageId: true;
link: true;
};
}>;
const programInovasi = proxy({
create: {
form: {} as ProgramInovasiForm,
loading: false,
async create() {
// Ensure all required fields are non-null
const formData = {
name: programInovasi.create.form.name || "",
description: programInovasi.create.form.description || "",
imageId: programInovasi.create.form.imageId || "",
link: programInovasi.create.form.link || "",
};
const cek = templateProgramInovasi.safeParse(formData);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
programInovasi.create.loading = true;
const res = await ApiFetch.api.landingpage.programinovasi[
"create"
].post(formData);
if (res.status === 200) {
programInovasi.findMany.load();
return toast.success("success create");
}
console.log(res);
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
programInovasi.create.loading = false;
}
},
},
findMany: {
data: null as any[] | null,
page: 1,
totalPages: 1,
total: 0,
loading: false,
load: async (page = 1, limit = 10) => { // Change to arrow function
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;
}
},
},
findUnique: {
data: null as Prisma.ProgramInovasiGetPayload<{
include: {
image: true;
};
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/landingpage/programinovasi/${id}`);
if (res.ok) {
const data = await res.json();
programInovasi.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch program inovasi:", res.statusText);
programInovasi.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching program inovasi:", error);
programInovasi.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
programInovasi.delete.loading = true;
const response = await fetch(
`/api/landingpage/programinovasi/del/${id}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
);
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Program inovasi berhasil dihapus");
await programInovasi.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus program inovasi");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus program inovasi");
} finally {
programInovasi.delete.loading = false;
}
},
},
update: {
id: "",
form: {} as ProgramInovasiForm,
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(`/api/landingpage/programinovasi/${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,
description: data.description,
imageId: data.imageId,
link: data.link,
};
return data;
} else {
throw new Error(
result?.message || "Gagal mengambil data program inovasi"
);
}
} catch (error) {
console.error((error as Error).message);
toast.error("Terjadi kesalahan saat mengambil data program inovasi");
} finally {
programInovasi.update.loading = false;
}
},
async update() {
const cek = templateProgramInovasi.safeParse(programInovasi.update.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return false;
}
try {
programInovasi.update.loading = true;
const response = await fetch(
`/api/landingpage/programinovasi/${this.id}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: this.form.name,
description: this.form.description,
imageId: this.form.imageId,
link: this.form.link,
}),
}
);
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 program inovasi");
await programInovasi.findMany.load(); // refresh list
return true;
} else {
throw new Error(result.message || "Gagal update program inovasi");
}
} catch (error) {
console.error("Error updating program inovasi:", error);
toast.error(
error instanceof Error
? error.message
: "Terjadi kesalahan saat update program inovasi"
);
return false;
} finally {
programInovasi.update.loading = false;
}
},
},
});
const templatePejabatDesa = z.object({
name: z.string().min(3, "Nama minimal 3 karakter"),
position: z.string().min(3, "Posisi minimal 3 karakter"),
imageId: z.string().min(1, "Gambar wajib dipilih"),
});
const defaultFormPejabatDesa = {
name: "",
position: "",
imageId: "",
};
type PejabatDesaForm = {
id: string;
name: string;
position: string;
imageId: string | null;
image?: {
id: string;
name: string;
link: string;
path: string;
mimeType: string;
realName: string;
isActive: boolean;
createdAt: Date;
updatedAt: Date;
deletedAt: Date | null;
} | null;
createdAt: Date;
updatedAt: Date;
deletedAt: Date | null;
isActive: boolean;
};
const pejabatDesa = proxy({
findUnique: {
data: null as PejabatDesaForm | null,
loading: false,
error: null as string | null,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
this.loading = true;
this.error = null;
try {
const response = await fetch(`/api/landingpage/pejabatdesa/${id}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
if (result.success) {
this.data = result.data;
return result.data;
} else {
throw new Error(
result.message || "Gagal mengambil data pejabat desa"
);
}
} catch (error) {
const errorMessage = (error as Error).message;
this.error = errorMessage;
console.error("Load pejabat desa error:", errorMessage);
toast.error("Terjadi kesalahan saat mengambil data pejabat desa");
return null;
} finally {
this.loading = false;
}
},
reset() {
this.data = null;
this.error = null;
this.loading = false;
},
},
edit: {
id: "",
form: { ...defaultFormPejabatDesa },
loading: false,
error: null as string | null,
isReadOnly: false,
initialize(profileData: PejabatDesaForm) {
this.id = profileData.id;
this.isReadOnly = false; // Semua data bisa diedit
this.form = {
name: profileData.name || "",
position: profileData.position || "",
imageId: profileData.imageId || "",
};
},
// Update form field
updateField(field: keyof typeof defaultFormPejabatDesa, value: string) {
this.form[field] = value;
},
// Submit form
async submit() {
// Validate form
const validation = templatePejabatDesa.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/landingpage/pejabatdesa/${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 profile");
// Refresh profile data
await pejabatDesa.findUnique.load(this.id);
return true;
} else {
throw new Error(result.message || "Gagal update profile");
}
} catch (error) {
const errorMessage = (error as Error).message;
this.error = errorMessage;
console.error("Update profile error:", errorMessage);
toast.error("Terjadi kesalahan saat update profile");
return false;
} finally {
this.loading = false;
}
},
reset() {
this.id = "";
this.form = { ...defaultFormPejabatDesa };
this.error = null;
this.loading = false;
this.isReadOnly = false;
},
},
});
const templateMediaSosial = z.object({
name: z.string().min(3, "Nama minimal 3 karakter"),
imageId: z.string().min(1, "Gambar wajib dipilih"),
iconUrl: z.string().min(3, "Icon URL minimal 3 karakter"),
});
type MediaSosialForm = {
name: string;
imageId: string;
iconUrl: string;
};
const mediaSosial = proxy({
create: {
form: {} as MediaSosialForm,
loading: false,
async create() {
// Ensure all required fields are non-null
const formData = {
name: mediaSosial.create.form.name || "",
imageId: mediaSosial.create.form.imageId || "",
iconUrl: mediaSosial.create.form.iconUrl || "",
};
const cek = templateMediaSosial.safeParse(formData);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
mediaSosial.create.loading = true;
const res = await ApiFetch.api.landingpage.mediasosial["create"].post(
formData
);
if (res.status === 200) {
mediaSosial.findMany.load();
return toast.success("success create");
}
console.log(res);
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
mediaSosial.create.loading = false;
}
},
},
findMany: {
data: null as any[] | null,
page: 1,
totalPages: 1,
total: 0,
loading: false,
load: async (page = 1, limit = 10) => { // Change to arrow function
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;
}
},
},
findUnique: {
data: null as Prisma.MediaSosialGetPayload<{
include: {
image: true;
};
}> | null,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
mediaSosial.update.loading = true;
try {
const res = await fetch(`/api/landingpage/mediasosial/${id}`);
if (res.ok) {
const data = await res.json();
mediaSosial.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch media sosial:", res.statusText);
mediaSosial.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching media sosial:", error);
mediaSosial.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
mediaSosial.delete.loading = true;
const response = await fetch(`/api/landingpage/mediasosial/del/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Media Sosial berhasil dihapus");
await mediaSosial.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus media sosial");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus media sosial");
} finally {
mediaSosial.delete.loading = false;
}
},
},
update: {
id: "",
form: {} as MediaSosialForm,
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
mediaSosial.update.loading = true; // ✅ Tambahkan ini di awal
try {
const response = await fetch(`/api/landingpage/mediasosial/${id}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
if (result?.success) {
const data = result.data;
this.id = data.id;
this.form = {
name: data.name || "",
imageId: data.imageId || "",
iconUrl: data.iconUrl || "",
};
return data;
} else {
throw new Error(result?.message || "Gagal mengambil data media sosial");
}
} catch (error) {
console.error((error as Error).message);
toast.error("Terjadi kesalahan saat mengambil data media sosial");
} finally {
mediaSosial.update.loading = false; // ✅ Supaya berhenti loading walau error
}
},
async update() {
const cek = templateMediaSosial.safeParse(mediaSosial.update.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return false;
}
try {
mediaSosial.update.loading = true;
const response = await fetch(`/api/landingpage/mediasosial/${this.id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: this.form.name,
imageId: this.form.imageId,
iconUrl: this.form.iconUrl,
}),
});
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 media sosial");
await mediaSosial.findMany.load(); // refresh list
return true;
} else {
throw new Error(result.message || "Gagal update media sosial");
}
} catch (error) {
console.error("Error updating media sosial:", error);
toast.error(
error instanceof Error
? error.message
: "Terjadi kesalahan saat update media sosial"
);
return false;
} finally {
mediaSosial.update.loading = false;
}
},
},
});
const profileLandingPageState = proxy({
programInovasi,
pejabatDesa,
mediaSosial,
});
export default profileLandingPageState;

View File

@@ -0,0 +1,236 @@
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 templatesdgsDesaForm = z.object({
name: z.string().min(1, "Judul minimal 1 karakter"),
jumlah: z.string().min(1, "Deskripsi minimal 1 karakter"),
imageId: z.string().min(1, "File minimal 1"),
});
const defaultsdgsDesaForm = {
name: "",
jumlah: "",
imageId: "",
};
const sdgsDesa = proxy({
create: {
form: { ...defaultsdgsDesaForm },
loading: false,
async create() {
const cek = templatesdgsDesaForm.safeParse(
sdgsDesa.create.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
sdgsDesa.create.loading = true;
const res = await ApiFetch.api.landingpage.sdgsdesa[
"create"
].post({
...sdgsDesa.create.form,
});
if (res.status === 200) {
sdgsDesa.findMany.load();
return toast.success("Data berhasil ditambahkan");
}
return toast.error("Gagal menambahkan data");
} catch (error) {
console.log(error);
toast.error("Gagal menambahkan data");
} finally {
sdgsDesa.create.loading = false;
}
},
},
findMany: {
data: null as Array<
Prisma.SDGSDesaGetPayload<{
include: {
image: true;
};
}>
> | null,
async load() {
const res = await ApiFetch.api.landingpage.sdgsdesa[
"find-many"
].get();
if (res.status === 200) {
sdgsDesa.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.SDGSDesaGetPayload<{
include: {
image: true;
};
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/landingpage/sdgsdesa/${id}`);
if (res.ok) {
const data = await res.json();
sdgsDesa.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
sdgsDesa.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching data:", error);
sdgsDesa.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
sdgsDesa.delete.loading = true;
const response = await fetch(
`/api/landingpage/sdgsdesa/del/${id}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
);
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "sdgs desa berhasil dihapus");
await sdgsDesa.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus sdgs desa");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus sdgs desa");
} finally {
sdgsDesa.delete.loading = false;
}
},
},
edit: {
id: "",
form: { ...defaultsdgsDesaForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
sdgsDesa.edit.loading = true;
const response = await fetch(`/api/landingpage/sdgsdesa/${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,
jumlah: data.jumlah,
imageId: data.imageId,
};
return data;
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error loading sdgs desa:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
} finally {
sdgsDesa.edit.loading = false;
}
},
async update() {
const cek = templatesdgsDesaForm.safeParse(
sdgsDesa.edit.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
sdgsDesa.edit.loading = true;
const response = await fetch(
`/api/landingpage/sdgsdesa/${this.id}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: this.form.name,
jumlah: this.form.jumlah,
imageId: this.form.imageId,
}),
}
);
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(
errorData.message || `HTTP error! status: ${response.status}`
);
}
const result = await response.json();
if (result.success) {
toast.success("Berhasil update sdgs desa");
await sdgsDesa.findMany.load(); // refresh list
return true;
} else {
throw new Error(
result.message || "Gagal mengupdate sdgs desa"
);
}
} catch (error) {
console.error("Error updating sdgs desa:", error);
toast.error(
error instanceof Error
? error.message
: "Gagal mengupdate sdgs desa"
);
return false;
} finally {
sdgsDesa.edit.loading = false;
}
},
reset() {
sdgsDesa.edit.id = "";
sdgsDesa.edit.form = { ...defaultsdgsDesaForm };
},
},
});
export default sdgsDesa;

View File

@@ -0,0 +1,227 @@
/* 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(1, "Nama minimal 1 karakter"),
deskripsi: z.string().min(1, "Deskripsi minimal 1 karakter"),
jumlah: z.string().min(1, "Jumlah minimal 1 karakter"),
icon: z.string().min(1, "Icon minimal 1 karakter"),
});
const defaultForm = {
name: "",
deskripsi: "",
jumlah: "",
icon: "",
};
const dataLingkunganDesaState = proxy({
create: {
form: { ...defaultForm },
loading: false,
async create() {
const cek = templateForm.safeParse(dataLingkunganDesaState.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
dataLingkunganDesaState.create.loading = true;
const res = await ApiFetch.api.lingkungan.datalingkungandesa["create"].post(
dataLingkunganDesaState.create.form
);
if (res.status === 200) {
dataLingkunganDesaState.findMany.load();
return toast.success("success create");
}
console.log(res);
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
dataLingkunganDesaState.create.loading = false;
}
},
},
findMany: {
data: null as any[] | null,
page: 1,
totalPages: 1,
total: 0,
loading: false,
load: async (page = 1, limit = 10) => {
// Change to arrow function
dataLingkunganDesaState.findMany.loading = true; // Use the full path to access the property
dataLingkunganDesaState.findMany.page = page;
try {
const res = await ApiFetch.api.lingkungan.datalingkungandesa["find-many"].get({
query: { page, limit },
});
if (res.status === 200 && res.data?.success) {
dataLingkunganDesaState.findMany.data = res.data.data || [];
dataLingkunganDesaState.findMany.total = res.data.total || 0;
dataLingkunganDesaState.findMany.totalPages = res.data.totalPages || 1;
} else {
console.error(
"Failed to load berdasarkan data lingkungan desa :",
res.data?.message
);
dataLingkunganDesaState.findMany.data = [];
dataLingkunganDesaState.findMany.total = 0;
dataLingkunganDesaState.findMany.totalPages = 1;
}
} catch (error) {
console.error("Error loading berdasarkan data lingkungan desa :", error);
dataLingkunganDesaState.findMany.data = [];
dataLingkunganDesaState.findMany.total = 0;
dataLingkunganDesaState.findMany.totalPages = 1;
} finally {
dataLingkunganDesaState.findMany.loading = false;
}
},
},
update: {
id: "",
form: { ...defaultForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(`/api/lingkungan/datalingkungandesa/${id}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
if (result?.success) {
const data = result.data;
this.id = data.id;
this.form = {
name: data.name,
deskripsi: data.deskripsi,
jumlah: data.jumlah,
icon: data.icon,
};
return data;
} else {
throw new Error(result?.message || "Gagal mengambil data");
}
} catch (error) {
console.error("Error loading data lingkungan desa :", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async submit() {
const id = this.id;
if (!id) {
toast.warn("ID tidak valid");
return null;
}
const cek = templateForm.safeParse(this.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return null;
}
this.loading = true;
try {
const response = await fetch(`/api/lingkungan/datalingkungandesa/${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 dataLingkunganDesaState.findMany.load();
return result.data;
} catch (error) {
console.error("Error update data:", error);
toast.error("Gagal update data data lingkungan desa");
} finally {
this.loading = false;
}
},
},
findUnique: {
data: null as Prisma.DataLingkunganDesaGetPayload<{
omit: { isActive: true };
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/lingkungan/datalingkungandesa/${id}`);
if (res.ok) {
const data = await res.json();
dataLingkunganDesaState.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
dataLingkunganDesaState.findUnique.data = null;
}
} catch (error) {
console.error("Error loading data lingkungan desa:", error);
dataLingkunganDesaState.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
dataLingkunganDesaState.delete.loading = true;
const response = await fetch(`/api/lingkungan/datalingkungandesa/del/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Data lingkungan desa berhasil dihapus");
await dataLingkunganDesaState.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus data lingkungan desa");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus data lingkungan desa");
} finally {
dataLingkunganDesaState.delete.loading = false;
}
},
},
});
export default dataLingkunganDesaState;

View File

@@ -0,0 +1,240 @@
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 templateTujuanEdukasiForm = z.object({
judul: z.string().min(3, "Judul minimal 3 karakter"),
deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"),
});
type TujuanEdukasiForm = Prisma.TujuanEdukasiLingkunganGetPayload<{
select: {
id: true;
judul: true;
deskripsi: true;
};
}>;
const stateTujuanEdukasi = proxy({
findById: {
data: null as TujuanEdukasiForm | null,
loading: false,
initialize() {
stateTujuanEdukasi.findById.data = {
id: '',
judul: '',
deskripsi: '',
} as TujuanEdukasiForm;
},
async load(id: string) {
try {
stateTujuanEdukasi.findById.loading = true;
const res = await ApiFetch.api.lingkungan.edukasilingkungan.tujuanedukasilingkungan["find-by-id"].get({
query: { id },
});
if (res.status === 200) {
stateTujuanEdukasi.findById.data = res.data?.data ?? null;
} else {
toast.error("Gagal mengambil data tujuan edukasi");
}
} catch (error) {
console.error((error as Error).message);
toast.error("Terjadi kesalahan saat mengambil data tujuan edukasi");
} finally {
stateTujuanEdukasi.findById.loading = false;
}
},
},
update: {
loading: false,
async save(data: TujuanEdukasiForm) {
const cek = templateTujuanEdukasiForm.safeParse(data);
if (!cek.success) {
const errors = cek.error.issues
.map((issue) => `${issue.path.join(".")}: ${issue.message}`)
.join(", ");
toast.error(`Form tidak valid: ${errors}`);
return;
}
try {
stateTujuanEdukasi.update.loading = true;
const res = await ApiFetch.api.lingkungan.edukasilingkungan.tujuanedukasilingkungan["update"].post(data);
if (res.status === 200) {
toast.success("Data tujuan edukasi berhasil diubah");
await stateTujuanEdukasi.findById.load(data.id);
} else {
toast.error("Gagal mengubah data tujuan edukasi");
}
} catch (error) {
console.error((error as Error).message);
toast.error("Terjadi kesalahan saat mengubah data tujuan edukasi");
} finally {
stateTujuanEdukasi.update.loading = false;
}
},
},
});
const templateMateriEdukasiLingkunganForm = z.object({
judul: z.string().min(3, "Judul minimal 3 karakter"),
deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"),
});
type MateriEdukasiLingkunganForm = Prisma.MateriEdukasiLingkunganGetPayload<{
select: {
id: true;
judul: true;
deskripsi: true;
};
}>;
const stateMateriEdukasiLingkungan = proxy({
findById: {
data: null as MateriEdukasiLingkunganForm | null,
loading: false,
initialize() {
stateMateriEdukasiLingkungan.findById.data = {
id: '',
judul: '',
deskripsi: '',
} as MateriEdukasiLingkunganForm;
},
async load(id: string) {
try {
stateMateriEdukasiLingkungan.findById.loading = true;
const res = await ApiFetch.api.lingkungan.edukasilingkungan.materiedukasilingkungan["find-by-id"].get({
query: { id },
});
if (res.status === 200) {
stateMateriEdukasiLingkungan.findById.data = res.data?.data ?? null;
} else {
toast.error("Gagal mengambil data materi edukasi");
}
} catch (error) {
console.error((error as Error).message);
toast.error("Terjadi kesalahan saat mengambil data materi edukasi");
} finally {
stateMateriEdukasiLingkungan.findById.loading = false;
}
},
},
update: {
loading: false,
async save(data: MateriEdukasiLingkunganForm) {
const cek = templateMateriEdukasiLingkunganForm.safeParse(data);
if (!cek.success) {
const errors = cek.error.issues
.map((issue) => `${issue.path.join(".")}: ${issue.message}`)
.join(", ");
toast.error(`Form tidak valid: ${errors}`);
return;
}
try {
stateMateriEdukasiLingkungan.update.loading = true;
const res = await ApiFetch.api.lingkungan.edukasilingkungan.materiedukasilingkungan["update"].post(data);
if (res.status === 200) {
toast.success("Data materi edukasi berhasil diubah");
await stateMateriEdukasiLingkungan.findById.load(data.id);
} else {
toast.error("Gagal mengubah data materi edukasi");
}
} catch (error) {
console.error((error as Error).message);
toast.error("Terjadi kesalahan saat mengubah data materi edukasi");
} finally {
stateMateriEdukasiLingkungan.update.loading = false;
}
},
},
});
const templateContohEdukasiLingkunganForm = z.object({
judul: z.string().min(3, "Judul minimal 3 karakter"),
deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"),
});
type ContohEdukasiLingkunganForm = Prisma.ContohEdukasiLingkunganGetPayload<{
select: {
id: true;
judul: true;
deskripsi: true;
};
}>;
const stateContohEdukasiLingkungan = proxy({
findById: {
data: null as ContohEdukasiLingkunganForm | null,
loading: false,
initialize() {
stateContohEdukasiLingkungan.findById.data = {
id: '',
judul: '',
deskripsi: '',
} as ContohEdukasiLingkunganForm;
},
async load(id: string) {
try {
stateContohEdukasiLingkungan.findById.loading = true;
const res = await ApiFetch.api.lingkungan.edukasilingkungan.contohkegiatandesa["find-by-id"].get({
query: { id },
});
if (res.status === 200) {
stateContohEdukasiLingkungan.findById.data = res.data?.data ?? null;
} else {
toast.error("Gagal mengambil data contoh edukasi");
}
} catch (error) {
console.error((error as Error).message);
toast.error("Terjadi kesalahan saat mengambil data contoh edukasi");
} finally {
stateContohEdukasiLingkungan.findById.loading = false;
}
},
},
update: {
loading: false,
async save(data: ContohEdukasiLingkunganForm) {
const cek = templateContohEdukasiLingkunganForm.safeParse(data);
if (!cek.success) {
const errors = cek.error.issues
.map((issue) => `${issue.path.join(".")}: ${issue.message}`)
.join(", ");
toast.error(`Form tidak valid: ${errors}`);
return;
}
try {
stateContohEdukasiLingkungan.update.loading = true;
const res = await ApiFetch.api.lingkungan.edukasilingkungan.contohkegiatandesa["update"].post(data);
if (res.status === 200) {
toast.success("Data contoh edukasi berhasil diubah");
await stateContohEdukasiLingkungan.findById.load(data.id);
} else {
toast.error("Gagal mengubah data contoh edukasi");
}
} catch (error) {
console.error((error as Error).message);
toast.error("Terjadi kesalahan saat mengubah data contoh edukasi");
} finally {
stateContohEdukasiLingkungan.update.loading = false;
}
},
},
});
const stateEdukasiLingkungan = proxy({
stateTujuanEdukasi,
stateMateriEdukasiLingkungan,
stateContohEdukasiLingkungan
})
export default stateEdukasiLingkungan;

View File

@@ -0,0 +1,492 @@
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 templateKegiatanDesaForm = z.object({
judul: z.string().min(1, "Judul minimal 1 karakter"),
deskripsiSingkat: z.string().min(1, "Deskripsi singkat minimal 1 karakter"),
deskripsiLengkap: z.string().min(1, "Deskripsi lengkap minimal 1 karakter"),
tanggal: z.date(),
lokasi: z.string().min(1, "Lokasi minimal 1 karakter"),
partisipan: z.number().min(1, "Partisipan minimal 1"),
imageId: z.string().min(1, "Gambar wajib dipilih"),
kategoriKegiatanId: z.string().min(1, "Kategori kegiatan minimal 1"),
});
const defaultKegiatanDesaForm = {
judul: "",
deskripsiSingkat: "",
deskripsiLengkap: "",
tanggal: new Date(),
lokasi: "",
partisipan: 0,
imageId: "",
kategoriKegiatanId: "",
};
const kegiatanDesa = proxy({
create: {
form: { ...defaultKegiatanDesaForm },
loading: false,
async create() {
const cek = templateKegiatanDesaForm.safeParse(kegiatanDesa.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
kegiatanDesa.create.loading = true;
const res = await ApiFetch.api.lingkungan.kegiatandesa["create"].post({
...kegiatanDesa.create.form,
tanggal: kegiatanDesa.create.form.tanggal.toISOString(), // ✅ convert Date -> string
});
if (res.status === 200) {
kegiatanDesa.findMany.load();
return toast.success("Data berhasil ditambahkan");
}
return toast.error("Gagal menambahkan data");
} catch (error) {
console.log(error);
toast.error("Gagal menambahkan data");
} finally {
kegiatanDesa.create.loading = false;
}
},
},
findMany: {
data: null as Array<
Prisma.KegiatanDesaGetPayload<{
include: {
image: true;
kategoriKegiatan: true;
};
}>
> | null,
async load() {
const res = await ApiFetch.api.lingkungan.kegiatandesa["find-many"].get();
if (res.status === 200) {
kegiatanDesa.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.KegiatanDesaGetPayload<{
include: {
image: true;
kategoriKegiatan: true;
};
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/lingkungan/kegiatandesa/${id}`);
if (res.ok) {
const data = await res.json();
kegiatanDesa.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
kegiatanDesa.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching data:", error);
kegiatanDesa.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
kegiatanDesa.delete.loading = true;
const response = await fetch(`/api/lingkungan/kegiatandesa/del/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "kegiatan desa berhasil dihapus");
await kegiatanDesa.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus pasar desa");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus pasar desa");
} finally {
kegiatanDesa.delete.loading = false;
}
},
},
edit: {
id: "",
form: { ...defaultKegiatanDesaForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
kegiatanDesa.edit.loading = true;
const response = await fetch(`/api/lingkungan/kegiatandesa/${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,
deskripsiSingkat: data.deskripsiSingkat,
deskripsiLengkap: data.deskripsiLengkap,
tanggal: data.tanggal,
lokasi: data.lokasi,
partisipan: data.partisipan,
imageId: data.imageId,
kategoriKegiatanId: data.kategoriKegiatanId,
};
return data;
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error loading kegiatan desa:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
} finally {
kegiatanDesa.edit.loading = false;
}
},
async update() {
const cek = templateKegiatanDesaForm.safeParse(kegiatanDesa.edit.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
kegiatanDesa.edit.loading = true;
const response = await fetch(
`/api/lingkungan/kegiatandesa/${this.id}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
judul: this.form.judul,
deskripsiSingkat: this.form.deskripsiSingkat,
deskripsiLengkap: this.form.deskripsiLengkap,
tanggal:
typeof this.form.tanggal === "string"
? this.form.tanggal
: this.form.tanggal.toISOString(),
lokasi: this.form.lokasi,
partisipan: this.form.partisipan,
imageId: this.form.imageId,
kategoriKegiatanId: this.form.kategoriKegiatanId,
}),
}
);
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 kegiatan desa");
await kegiatanDesa.findMany.load(); // refresh list
return true;
} else {
throw new Error(result.message || "Gagal mengupdate kegiatan desa");
}
} catch (error) {
console.error("Error updating kegiatan desa:", error);
toast.error(
error instanceof Error
? error.message
: "Gagal mengupdate kegiatan desa"
);
return false;
} finally {
kegiatanDesa.edit.loading = false;
}
},
reset() {
kegiatanDesa.edit.id = "";
kegiatanDesa.edit.form = { ...defaultKegiatanDesaForm };
},
},
});
// ========================================= KATEGORI kegiatan ========================================= //
const kategoriKegiatanForm = z.object({
nama: z.string().min(1, "Nama minimal 1 karakter"),
});
const kategoriKegiatanDefaultForm = {
nama: "",
};
const kategoriKegiatan = proxy({
create: {
form: { ...kategoriKegiatanDefaultForm },
loading: false,
async create() {
const cek = kategoriKegiatanForm.safeParse(kategoriKegiatan.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
kategoriKegiatan.create.loading = true;
const res = await ApiFetch.api.lingkungan.kategorikegiatan[
"create"
].post(kategoriKegiatan.create.form);
if (res.status === 200) {
kategoriKegiatan.findMany.load();
return toast.success("Data berhasil ditambahkan");
}
return toast.error("Gagal menambahkan data");
} catch (error) {
console.log(error);
toast.error("Gagal menambahkan data");
} finally {
kategoriKegiatan.create.loading = false;
}
},
},
findMany: {
data: null as Array<{
id: string;
nama: string;
}> | null,
async load() {
const res = await ApiFetch.api.lingkungan.kategorikegiatan[
"find-many"
].get();
if (res.status === 200) {
kategoriKegiatan.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.KategoriKegiatanGetPayload<{
omit: { isActive: true };
}> | null,
async load(id: string) {
try {
const res = await fetch(
`/api/lingkungan/kategorikegiatan/${id}`
);
if (res.ok) {
const data = await res.json();
kategoriKegiatan.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
kategoriKegiatan.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching data:", error);
kategoriKegiatan.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
kategoriKegiatan.delete.loading = true;
const response = await fetch(
`/api/lingkungan/kategorikegiatan/del/${id}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
);
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Kategori kegiatan berhasil dihapus");
await kategoriKegiatan.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus kategori kegiatan");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus kategori kegiatan");
} finally {
kategoriKegiatan.delete.loading = false;
}
},
},
edit: {
id: "",
form: { ...kategoriKegiatanDefaultForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(
`/api/lingkungan/kategorikegiatan/${id}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
}
);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
if (result?.success) {
const data = result.data;
this.id = data.id;
this.form = {
nama: data.nama,
};
return data;
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error loading kategori kegiatan:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async update() {
const cek = kategoriKegiatanForm.safeParse(kategoriKegiatan.edit.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return false;
}
try {
kategoriKegiatan.edit.loading = true;
const response = await fetch(
`/api/lingkungan/kategorikegiatan/${kategoriKegiatan.edit.id}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
nama: kategoriKegiatan.edit.form.nama,
}),
}
);
// Clone the response to avoid 'body already read' error
const responseClone = response.clone();
try {
const result = await response.json();
if (!response.ok) {
console.error(
"Update failed with status:",
response.status,
"Response:",
result
);
throw new Error(
result?.message ||
`Gagal mengupdate kategori kegiatan (${response.status})`
);
}
if (result.success) {
toast.success(
result.message || "Berhasil memperbarui kategori kegiatan"
);
await kategoriKegiatan.findMany.load(); // refresh list
return true;
} else {
throw new Error(
result.message || "Gagal mengupdate kategori kegiatan"
);
}
} catch (error) {
// If JSON parsing fails, try to get the response text for better error messages
try {
const text = await responseClone.text();
console.error("Error response text:", text);
throw new Error(`Gagal memproses respons dari server: ${text}`);
} catch (textError) {
console.error("Error parsing response as text:", textError);
console.error("Original error:", error);
throw new Error("Gagal memproses respons dari server");
}
}
} catch (error) {
console.error("Error updating kategori kegiatan:", error);
toast.error(
error instanceof Error
? error.message
: "Gagal mengupdate kategori kegiatan"
);
return false;
} finally {
kategoriKegiatan.edit.loading = false;
}
},
reset() {
kategoriKegiatan.edit.id = "";
kategoriKegiatan.edit.form = { ...kategoriKegiatanDefaultForm };
},
},
});
const gotongRoyongState = proxy({
kegiatanDesa,
kategoriKegiatan,
});
export default gotongRoyongState;

View File

@@ -0,0 +1,274 @@
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 templateFilosofiTriHitaForm = z.object({
judul: z.string().min(3, "Judul minimal 3 karakter"),
deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"),
});
type FilosofiTriHitaForm = Prisma.FilosofiTriHitaGetPayload<{
select: {
id: true;
judul: true;
deskripsi: true;
};
}>;
const stateFilosofiTriHita = proxy({
findById: {
data: null as FilosofiTriHitaForm | null,
loading: false,
initialize() {
stateFilosofiTriHita.findById.data = {
id: "",
judul: "",
deskripsi: "",
} as FilosofiTriHitaForm;
},
async load(id: string) {
try {
stateFilosofiTriHita.findById.loading = true;
const res =
await ApiFetch.api.lingkungan.konservasiadatbali.filosofitrihitakarana[
"find-by-id"
].get({
query: { id },
});
if (res.status === 200) {
stateFilosofiTriHita.findById.data = res.data?.data ?? null;
} else {
toast.error("Gagal mengambil data filosofi tri hita karana");
}
} catch (error) {
console.error((error as Error).message);
toast.error(
"Terjadi kesalahan saat mengambil data filosofi tri hita karana"
);
} finally {
stateFilosofiTriHita.findById.loading = false;
}
},
},
update: {
loading: false,
async save(data: FilosofiTriHitaForm) {
const cek = templateFilosofiTriHitaForm.safeParse(data);
if (!cek.success) {
const errors = cek.error.issues
.map((issue) => `${issue.path.join(".")}: ${issue.message}`)
.join(", ");
toast.error(`Form tidak valid: ${errors}`);
return;
}
try {
stateFilosofiTriHita.update.loading = true;
const res =
await ApiFetch.api.lingkungan.konservasiadatbali.filosofitrihitakarana[
"update"
].post(data);
if (res.status === 200) {
toast.success("Data filosofi tri hita karana berhasil diubah");
await stateFilosofiTriHita.findById.load(data.id);
} else {
toast.error("Gagal mengubah data filosofi tri hita karana");
}
} catch (error) {
console.error((error as Error).message);
toast.error(
"Terjadi kesalahan saat mengubah data filosofi tri hita karana"
);
} finally {
stateFilosofiTriHita.update.loading = false;
}
},
},
});
const templateNilaiKonservasiAdatForm = z.object({
judul: z.string().min(3, "Judul minimal 3 karakter"),
deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"),
});
type NilaiKonservasiAdatForm = Prisma.NilaiKonservasiAdatGetPayload<{
select: {
id: true;
judul: true;
deskripsi: true;
};
}>;
const stateNilaiKonservasiAdat = proxy({
findById: {
data: null as NilaiKonservasiAdatForm | null,
loading: false,
initialize() {
stateNilaiKonservasiAdat.findById.data = {
id: "",
judul: "",
deskripsi: "",
} as NilaiKonservasiAdatForm;
},
async load(id: string) {
try {
stateNilaiKonservasiAdat.findById.loading = true;
const res =
await ApiFetch.api.lingkungan.konservasiadatbali.nilaikonservasiadatbali[
"find-by-id"
].get({
query: { id },
});
if (res.status === 200) {
stateNilaiKonservasiAdat.findById.data = res.data?.data ?? null;
} else {
toast.error("Gagal mengambil data nilai konservasi adat");
}
} catch (error) {
console.error((error as Error).message);
toast.error(
"Terjadi kesalahan saat mengambil data nilai konservasi adat"
);
} finally {
stateNilaiKonservasiAdat.findById.loading = false;
}
},
},
update: {
loading: false,
async save(data: NilaiKonservasiAdatForm) {
const cek = templateNilaiKonservasiAdatForm.safeParse(data);
if (!cek.success) {
const errors = cek.error.issues
.map((issue) => `${issue.path.join(".")}: ${issue.message}`)
.join(", ");
toast.error(`Form tidak valid: ${errors}`);
return;
}
try {
stateNilaiKonservasiAdat.update.loading = true;
const res =
await ApiFetch.api.lingkungan.konservasiadatbali.nilaikonservasiadatbali[
"update"
].post(data);
if (res.status === 200) {
toast.success("Data nilai konservasi adat berhasil diubah");
await stateNilaiKonservasiAdat.findById.load(data.id);
} else {
toast.error("Gagal mengubah data nilai konservasi adat");
}
} catch (error) {
console.error((error as Error).message);
toast.error(
"Terjadi kesalahan saat mengubah data nilai konservasi adat"
);
} finally {
stateNilaiKonservasiAdat.update.loading = false;
}
},
},
});
const templateBentukKonservasiBerdasarkanAdatForm = z.object({
judul: z.string().min(3, "Judul minimal 3 karakter"),
deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"),
});
type BentukKonservasiBerdasarkanAdatForm =
Prisma.BentukKonservasiBerdasarkanAdatGetPayload<{
select: {
id: true;
judul: true;
deskripsi: true;
};
}>;
const stateBentukKonservasiBerdasarkanAdat = proxy({
findById: {
data: null as BentukKonservasiBerdasarkanAdatForm | null,
loading: false,
initialize() {
stateBentukKonservasiBerdasarkanAdat.findById.data = {
id: "",
judul: "",
deskripsi: "",
} as BentukKonservasiBerdasarkanAdatForm;
},
async load(id: string) {
try {
stateBentukKonservasiBerdasarkanAdat.findById.loading = true;
const res =
await ApiFetch.api.lingkungan.konservasiadatbali.bentukkonservasiberdasarkanadat[
"find-by-id"
].get({
query: { id },
});
if (res.status === 200) {
stateBentukKonservasiBerdasarkanAdat.findById.data =
res.data?.data ?? null;
} else {
toast.error(
"Gagal mengambil data bentuk konservasi berdasarkan adat"
);
}
} catch (error) {
console.error((error as Error).message);
toast.error(
"Terjadi kesalahan saat mengambil data bentuk konservasi berdasarkan adat"
);
} finally {
stateBentukKonservasiBerdasarkanAdat.findById.loading = false;
}
},
},
update: {
loading: false,
async save(data: BentukKonservasiBerdasarkanAdatForm) {
const cek = templateBentukKonservasiBerdasarkanAdatForm.safeParse(data);
if (!cek.success) {
const errors = cek.error.issues
.map((issue) => `${issue.path.join(".")}: ${issue.message}`)
.join(", ");
toast.error(`Form tidak valid: ${errors}`);
return;
}
try {
stateBentukKonservasiBerdasarkanAdat.update.loading = true;
const res =
await ApiFetch.api.lingkungan.konservasiadatbali.bentukkonservasiberdasarkanadat[
"update"
].post(data);
if (res.status === 200) {
toast.success(
"Data bentuk konservasi berdasarkan adat berhasil diubah"
);
await stateBentukKonservasiBerdasarkanAdat.findById.load(data.id);
} else {
toast.error("Gagal mengubah data bentuk konservasi berdasarkan adat");
}
} catch (error) {
console.error((error as Error).message);
toast.error(
"Terjadi kesalahan saat mengubah data bentuk konservasi berdasarkan adat"
);
} finally {
stateBentukKonservasiBerdasarkanAdat.update.loading = false;
}
},
},
});
const stateKonservasiAdatBali = proxy({
stateFilosofiTriHita,
stateNilaiKonservasiAdat,
stateBentukKonservasiBerdasarkanAdat,
});
export default stateKonservasiAdatBali;

View File

@@ -0,0 +1,460 @@
/* 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(1, "Nama minimal 1 karakter"),
icon: z.string().min(1, "Icon minimal 1 karakter"),
});
const defaultForm = {
name: "",
icon: "",
};
const pengelolaanSampah = proxy({
create: {
form: { ...defaultForm },
loading: false,
async create() {
const cek = templateForm.safeParse(pengelolaanSampah.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
pengelolaanSampah.create.loading = true;
const res = await ApiFetch.api.lingkungan.pengelolaansampah[
"create"
].post(pengelolaanSampah.create.form);
if (res.status === 200) {
pengelolaanSampah.findMany.load();
return toast.success("success create");
}
console.log(res);
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
pengelolaanSampah.create.loading = false;
}
},
},
findMany: {
data: null as any[] | null,
page: 1,
totalPages: 1,
total: 0,
loading: false,
load: async (page = 1, limit = 10) => {
// Change to arrow function
pengelolaanSampah.findMany.loading = true; // Use the full path to access the property
pengelolaanSampah.findMany.page = page;
try {
const res = await ApiFetch.api.lingkungan.pengelolaansampah[
"find-many"
].get({
query: { page, limit },
});
if (res.status === 200 && res.data?.success) {
pengelolaanSampah.findMany.data = res.data.data || [];
pengelolaanSampah.findMany.total = res.data.total || 0;
pengelolaanSampah.findMany.totalPages = res.data.totalPages || 1;
} else {
console.error(
"Failed to load pengelolaan sampah:",
res.data?.message
);
pengelolaanSampah.findMany.data = [];
pengelolaanSampah.findMany.total = 0;
pengelolaanSampah.findMany.totalPages = 1;
}
} catch (error) {
console.error("Error loading pengelolaan sampah:", error);
pengelolaanSampah.findMany.data = [];
pengelolaanSampah.findMany.total = 0;
pengelolaanSampah.findMany.totalPages = 1;
} finally {
pengelolaanSampah.findMany.loading = false;
}
},
},
update: {
id: "",
form: { ...defaultForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(
`/api/lingkungan/pengelolaansampah/${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,
icon: data.icon,
};
return data;
} else {
throw new Error(result?.message || "Gagal mengambil data");
}
} catch (error) {
console.error("Error loading pengelolaan sampah:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async submit() {
const id = this.id;
if (!id) {
toast.warn("ID tidak valid");
return null;
}
const cek = templateForm.safeParse(this.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return null;
}
this.loading = true;
try {
const response = await fetch(
`/api/lingkungan/pengelolaansampah/${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 pengelolaanSampah.findMany.load();
return result.data;
} catch (error) {
console.error("Error update data:", error);
toast.error("Gagal update data pengelolaan sampah");
} finally {
this.loading = false;
}
},
},
findUnique: {
data: null as Prisma.ProgramKreatifGetPayload<{
omit: { isActive: true };
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/lingkungan/pengelolaansampah/${id}`);
if (res.ok) {
const data = await res.json();
pengelolaanSampah.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
pengelolaanSampah.findUnique.data = null;
}
} catch (error) {
console.error("Error loading pengelolaan sampah:", error);
pengelolaanSampah.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
pengelolaanSampah.delete.loading = true;
const response = await fetch(
`/api/lingkungan/pengelolaansampah/del/${id}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
);
const result = await response.json();
if (response.ok && result?.success) {
toast.success(
result.message || "pengelolaan sampah berhasil dihapus"
);
await pengelolaanSampah.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus pengelolaan sampah");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus pengelolaan sampah");
} finally {
pengelolaanSampah.delete.loading = false;
}
},
},
});
const templateKeteranganSampahForm = z.object({
name: z.string().min(1, "Nama minimal 1 karakter"),
alamat: z.string().min(1, "Alamat minimal 1 karakter"),
namaTempatMaps: z.string().min(1, "Nama Tempat Maps minimal 1 karakter"),
lat: z.number(),
lng: z.number(),
});
const defaultKeteranganSampahForm = {
name: "",
alamat: "",
namaTempatMaps: "",
lat: 0,
lng: 0,
};
const keteranganSampah = proxy({
create: {
form: { ...defaultKeteranganSampahForm },
loading: false,
async create() {
const cek = templateKeteranganSampahForm.safeParse(
keteranganSampah.create.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
keteranganSampah.create.loading = true;
const res =
await ApiFetch.api.lingkungan.pengelolaansampah.keteranganbankterdekat[
"create"
].post(keteranganSampah.create.form);
if (res.status === 200) {
keteranganSampah.findMany.load();
return toast.success("Data berhasil ditambahkan");
}
return toast.error("Gagal menambahkan data");
} catch (error) {
console.log(error);
toast.error("Gagal menambahkan data");
} finally {
keteranganSampah.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.KeteranganBankSampahTerdekatGetPayload<{
omit: { isActive: true };
}>[]
| null,
async load() {
const res = await ApiFetch.api.lingkungan.pengelolaansampah.keteranganbankterdekat[
"find-many"
].get();
if (res.status === 200) {
keteranganSampah.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.KeteranganBankSampahTerdekatGetPayload<{
omit: { isActive: true };
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/lingkungan/pengelolaansampah/keteranganbankterdekat/${id}`);
if (res.ok) {
const data = await res.json();
keteranganSampah.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
keteranganSampah.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching data:", error);
keteranganSampah.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
keteranganSampah.delete.loading = true;
const response = await fetch(`/api/lingkungan/pengelolaansampah/keteranganbankterdekat/del/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Keterangan sampah berhasil dihapus");
await keteranganSampah.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus keterangan sampah");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus keterangan sampah");
} finally {
keteranganSampah.delete.loading = false;
}
},
},
edit: {
id: "",
form: { ...defaultKeteranganSampahForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(`/api/lingkungan/pengelolaansampah/keteranganbankterdekat/${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,
alamat: data.alamat,
namaTempatMaps: data.namaTempatMaps,
lat: data.lat,
lng: data.lng,
};
return data;
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error loading keterangan sampah:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async update() {
const cek = templateKeteranganSampahForm.safeParse(keteranganSampah.edit.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
keteranganSampah.edit.loading = true;
const response = await fetch(
`/api/lingkungan/pengelolaansampah/keteranganbankterdekat/${this.id}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: this.form.name,
alamat: this.form.alamat,
namaTempatMaps: this.form.namaTempatMaps,
lat: this.form.lat,
lng: this.form.lng,
}),
}
);
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 keterangan sampah");
await keteranganSampah.findMany.load(); // refresh list
return true;
} else {
throw new Error(result.message || "Gagal mengupdate keterangan sampah");
}
} catch (error) {
console.error("Error updating keterangan sampah:", error);
toast.error(
error instanceof Error
? error.message
: "Gagal mengupdate keterangan sampah"
);
return false;
} finally {
keteranganSampah.edit.loading = false;
}
},
reset() {
keteranganSampah.edit.id = "";
keteranganSampah.edit.form = { ...defaultKeteranganSampahForm };
},
},
});
const pengelolaanSampahState = proxy({
pengelolaanSampah,
keteranganSampah,
});
export default pengelolaanSampahState;

View File

@@ -0,0 +1,227 @@
/* 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(1, "Nama minimal 1 karakter"),
deskripsi: z.string().min(1, "Deskripsi minimal 1 karakter"),
judul: z.string().min(1, "Judul minimal 1 karakter"),
icon: z.string().min(1, "Icon minimal 1 karakter"),
});
const defaultForm = {
name: "",
deskripsi: "",
judul: "",
icon: "",
};
const programPenghijauanState = proxy({
create: {
form: { ...defaultForm },
loading: false,
async create() {
const cek = templateForm.safeParse(programPenghijauanState.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
programPenghijauanState.create.loading = true;
const res = await ApiFetch.api.lingkungan.programpenghijauan["create"].post(
programPenghijauanState.create.form
);
if (res.status === 200) {
programPenghijauanState.findMany.load();
return toast.success("success create");
}
console.log(res);
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
programPenghijauanState.create.loading = false;
}
},
},
findMany: {
data: null as any[] | null,
page: 1,
totalPages: 1,
total: 0,
loading: false,
load: async (page = 1, limit = 10) => {
// Change to arrow function
programPenghijauanState.findMany.loading = true; // Use the full path to access the property
programPenghijauanState.findMany.page = page;
try {
const res = await ApiFetch.api.lingkungan.programpenghijauan["find-many"].get({
query: { page, limit },
});
if (res.status === 200 && res.data?.success) {
programPenghijauanState.findMany.data = res.data.data || [];
programPenghijauanState.findMany.total = res.data.total || 0;
programPenghijauanState.findMany.totalPages = res.data.totalPages || 1;
} else {
console.error(
"Failed to load grafik berdasarkan program penghijauan:",
res.data?.message
);
programPenghijauanState.findMany.data = [];
programPenghijauanState.findMany.total = 0;
programPenghijauanState.findMany.totalPages = 1;
}
} catch (error) {
console.error("Error loading grafik berdasarkan program penghijauan:", error);
programPenghijauanState.findMany.data = [];
programPenghijauanState.findMany.total = 0;
programPenghijauanState.findMany.totalPages = 1;
} finally {
programPenghijauanState.findMany.loading = false;
}
},
},
update: {
id: "",
form: { ...defaultForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(`/api/lingkungan/programpenghijauan/${id}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
if (result?.success) {
const data = result.data;
this.id = data.id;
this.form = {
name: data.name,
deskripsi: data.deskripsi,
judul: data.judul,
icon: data.icon,
};
return data;
} else {
throw new Error(result?.message || "Gagal mengambil data");
}
} catch (error) {
console.error("Error loading program penghijauan:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async submit() {
const id = this.id;
if (!id) {
toast.warn("ID tidak valid");
return null;
}
const cek = templateForm.safeParse(this.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return null;
}
this.loading = true;
try {
const response = await fetch(`/api/lingkungan/programpenghijauan/${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 programPenghijauanState.findMany.load();
return result.data;
} catch (error) {
console.error("Error update data:", error);
toast.error("Gagal update data program penghijauan");
} finally {
this.loading = false;
}
},
},
findUnique: {
data: null as Prisma.ProgramPenghijauanGetPayload<{
omit: { isActive: true };
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/lingkungan/programpenghijauan/${id}`);
if (res.ok) {
const data = await res.json();
programPenghijauanState.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
programPenghijauanState.findUnique.data = null;
}
} catch (error) {
console.error("Error loading program penghijauan:", error);
programPenghijauanState.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
programPenghijauanState.delete.loading = true;
const response = await fetch(`/api/lingkungan/programpenghijauan/del/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Program penghijauan berhasil dihapus");
await programPenghijauanState.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus program penghijauan");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus program penghijauan");
} finally {
programPenghijauanState.delete.loading = false;
}
},
},
});
export default programPenghijauanState;

View 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;

View File

@@ -0,0 +1,260 @@
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
import { proxy } from "valtio";
import { z } from "zod";
// ========================================= TUJUAN PROGRAM ========================================= //
const templateTujuanProgramForm = z.object({
judul: z.string().min(3, "Judul minimal 3 karakter"),
deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"),
});
type TujuanProgramForm = Prisma.TujuanBimbinganBelajarDesaGetPayload<{
select: {
id: true;
judul: true;
deskripsi: true;
};
}>;
const stateTujuanProgram = proxy({
findById: {
data: null as TujuanProgramForm | null,
loading: false,
initialize() {
stateTujuanProgram.findById.data = {
id: "",
judul: "",
deskripsi: "",
} as TujuanProgramForm;
},
async load(id: string) {
try {
stateTujuanProgram.findById.loading = true;
const res =
await ApiFetch.api.pendidikan.bimbinganbelajardesa.tujuanprogram[
"find-by-id"
].get({
query: { id },
});
if (res.status === 200) {
stateTujuanProgram.findById.data = res.data?.data ?? null;
} else {
toast.error("Gagal mengambil data tujuan program");
}
} catch (error) {
console.error((error as Error).message);
toast.error("Terjadi kesalahan saat mengambil data tujuan program");
} finally {
stateTujuanProgram.findById.loading = false;
}
},
},
update: {
loading: false,
async save(data: TujuanProgramForm) {
const cek = templateTujuanProgramForm.safeParse(data);
if (!cek.success) {
const errors = cek.error.issues
.map((issue) => `${issue.path.join(".")}: ${issue.message}`)
.join(", ");
toast.error(`Form tidak valid: ${errors}`);
return;
}
try {
stateTujuanProgram.update.loading = true;
const res =
await ApiFetch.api.pendidikan.bimbinganbelajardesa.tujuanprogram[
"update"
].post(data);
if (res.status === 200) {
toast.success("Data tujuan program berhasil diubah");
await stateTujuanProgram.findById.load(data.id);
} else {
toast.error("Gagal mengubah data tujuan program");
}
} catch (error) {
console.error((error as Error).message);
toast.error("Terjadi kesalahan saat mengubah data tujuan program");
} finally {
stateTujuanProgram.update.loading = false;
}
},
},
});
// ========================================= LOKASI DAN JADWAL ========================================= //
const templateLokasiDanJadwalForm = z.object({
judul: z.string().min(3, "Judul minimal 3 karakter"),
deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"),
});
type LokasiDanJadwalForm = Prisma.LokasiJadwalBimbinganBelajarDesaGetPayload<{
select: {
id: true;
judul: true;
deskripsi: true;
};
}>;
const lokasiDanJadwalState = proxy({
findById: {
data: null as LokasiDanJadwalForm | null,
loading: false,
initialize() {
lokasiDanJadwalState.findById.data = {
id: "",
judul: "",
deskripsi: "",
} as LokasiDanJadwalForm;
},
async load(id: string) {
try {
lokasiDanJadwalState.findById.loading = true;
const res =
await ApiFetch.api.pendidikan.bimbinganbelajardesa.lokasidanjadwal[
"find-by-id"
].get({
query: { id },
});
if (res.status === 200) {
lokasiDanJadwalState.findById.data = res.data?.data ?? null;
} else {
toast.error("Gagal mengambil data lokasi dan jadwal");
}
} catch (error) {
console.error((error as Error).message);
toast.error("Terjadi kesalahan saat mengambil data lokasi dan jadwal");
} finally {
lokasiDanJadwalState.findById.loading = false;
}
},
},
update: {
loading: false,
async save(data: LokasiDanJadwalForm) {
const cek = templateLokasiDanJadwalForm.safeParse(data);
if (!cek.success) {
const errors = cek.error.issues
.map((issue) => `${issue.path.join(".")}: ${issue.message}`)
.join(", ");
toast.error(`Form tidak valid: ${errors}`);
return;
}
try {
lokasiDanJadwalState.update.loading = true;
const res =
await ApiFetch.api.pendidikan.bimbinganbelajardesa.lokasidanjadwal[
"update"
].post(data);
if (res.status === 200) {
toast.success("Data lokasi dan jadwal berhasil diubah");
await lokasiDanJadwalState.findById.load(data.id);
} else {
toast.error("Gagal mengubah data lokasi dan jadwal");
}
} catch (error) {
console.error((error as Error).message);
toast.error("Terjadi kesalahan saat mengubah data lokasi dan jadwal");
} finally {
lokasiDanJadwalState.update.loading = false;
}
},
},
});
// ========================================= FASILITAS YANG DISEDIAKAN ========================================= //
const templateFasilitasYangDisediakanForm = z.object({
judul: z.string().min(3, "Judul minimal 3 karakter"),
deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"),
});
type FasilitasYangDisediakanForm = Prisma.FasilitasBimbinganBelajarDesaGetPayload<{
select: {
id: true;
judul: true;
deskripsi: true;
};
}>;
const fasilitasYangDisediakanState = proxy({
findById: {
data: null as FasilitasYangDisediakanForm | null,
loading: false,
initialize() {
fasilitasYangDisediakanState.findById.data = {
id: "",
judul: "",
deskripsi: "",
} as FasilitasYangDisediakanForm;
},
async load(id: string) {
try {
fasilitasYangDisediakanState.findById.loading = true;
const res =
await ApiFetch.api.pendidikan.bimbinganbelajardesa.fasilitasyangdisediakan[
"find-by-id"
].get({
query: { id },
});
if (res.status === 200) {
fasilitasYangDisediakanState.findById.data = res.data?.data ?? null;
} else {
toast.error("Gagal mengambil data fasilitas yang disediakan");
}
} catch (error) {
console.error((error as Error).message);
toast.error("Terjadi kesalahan saat mengambil data fasilitas yang disediakan");
} finally {
fasilitasYangDisediakanState.findById.loading = false;
}
},
},
update: {
loading: false,
async save(data: FasilitasYangDisediakanForm) {
const cek = templateFasilitasYangDisediakanForm.safeParse(data);
if (!cek.success) {
const errors = cek.error.issues
.map((issue) => `${issue.path.join(".")}: ${issue.message}`)
.join(", ");
toast.error(`Form tidak valid: ${errors}`);
return;
}
try {
fasilitasYangDisediakanState.update.loading = true;
const res =
await ApiFetch.api.pendidikan.bimbinganbelajardesa.fasilitasyangdisediakan[
"update"
].post(data);
if (res.status === 200) {
toast.success("Data fasilitas yang disediakan berhasil diubah");
await fasilitasYangDisediakanState.findById.load(data.id);
} else {
toast.error("Gagal mengubah data fasilitas yang disediakan");
}
} catch (error) {
console.error((error as Error).message);
toast.error("Terjadi kesalahan saat mengubah data fasilitas yang disediakan");
} finally {
fasilitasYangDisediakanState.update.loading = false;
}
},
},
});
const stateBimbinganBelajarDesa = proxy({
stateTujuanProgram,
lokasiDanJadwalState,
fasilitasYangDisediakanState,
});
export default stateBimbinganBelajarDesa;

View 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;

View File

@@ -0,0 +1,998 @@
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
import { proxy } from "valtio";
import { z } from "zod";
// ========================================= JENJANG PENDIDIKAN ========================================= //
const jenjangPendidikanForm = z.object({
nama: z.string().min(1, "Nama minimal 1 karakter"),
});
const jenjangPendidikanDefaultForm = {
nama: "",
};
const jenjangPendidikan = proxy({
create: {
form: { ...jenjangPendidikanDefaultForm },
loading: false,
async create() {
const cek = jenjangPendidikanForm.safeParse(
jenjangPendidikan.create.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
jenjangPendidikan.create.loading = true;
const res =
await ApiFetch.api.pendidikan.infosekolahpaud.jenjangpendidikan[
"create"
].post(jenjangPendidikan.create.form);
if (res.status === 200) {
jenjangPendidikan.findMany.load();
return toast.success("Data berhasil ditambahkan");
}
return toast.error("Gagal menambahkan data");
} catch (error) {
console.log(error);
toast.error("Gagal menambahkan data");
} finally {
jenjangPendidikan.create.loading = false;
}
},
},
findMany: {
data: null as Array<{
id: string;
nama: string;
}> | null,
async load() {
const res =
await ApiFetch.api.pendidikan.infosekolahpaud.jenjangpendidikan[
"find-many"
].get();
if (res.status === 200) {
jenjangPendidikan.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.JenjangPendidikanGetPayload<{
omit: { isActive: true };
}> | null,
async load(id: string) {
try {
const res = await fetch(
`/api/pendidikan/infosekolahpaud/jenjangpendidikan/${id}`
);
if (res.ok) {
const data = await res.json();
jenjangPendidikan.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
jenjangPendidikan.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching data:", error);
jenjangPendidikan.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
jenjangPendidikan.delete.loading = true;
const response = await fetch(
`/api/pendidikan/infosekolahpaud/jenjangpendidikan/del/${id}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
);
const result = await response.json();
if (response.ok && result?.success) {
toast.success(
result.message || "jenjang pendidikan berhasil dihapus"
);
await jenjangPendidikan.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus jenjang pendidikan");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus jenjang pendidikan");
} finally {
jenjangPendidikan.delete.loading = false;
}
},
},
edit: {
id: "",
form: { ...jenjangPendidikanDefaultForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(
`/api/pendidikan/infosekolahpaud/jenjangpendidikan/${id}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
}
);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
if (result?.success) {
const data = result.data;
this.id = data.id;
this.form = {
nama: data.nama,
};
return data;
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error loading jenjang pendidikan:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async update() {
const cek = jenjangPendidikanForm.safeParse(jenjangPendidikan.edit.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return false;
}
try {
jenjangPendidikan.edit.loading = true;
const response = await fetch(
`/api/pendidikan/infosekolahpaud/jenjangpendidikan/${jenjangPendidikan.edit.id}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
nama: jenjangPendidikan.edit.form.nama,
}),
}
);
// Clone the response to avoid 'body already read' error
const responseClone = response.clone();
try {
const result = await response.json();
if (!response.ok) {
console.error(
"Update failed with status:",
response.status,
"Response:",
result
);
throw new Error(
result?.message ||
`Gagal mengupdate jenjang pendidikan (${response.status})`
);
}
if (result.success) {
toast.success(
result.message || "Berhasil memperbarui jenjang pendidikan"
);
await jenjangPendidikan.findMany.load(); // refresh list
return true;
} else {
throw new Error(
result.message || "Gagal mengupdate jenjang pendidikan"
);
}
} catch (error) {
// If JSON parsing fails, try to get the response text for better error messages
try {
const text = await responseClone.text();
console.error("Error response text:", text);
throw new Error(`Gagal memproses respons dari server: ${text}`);
} catch (textError) {
console.error("Error parsing response as text:", textError);
console.error("Original error:", error);
throw new Error("Gagal memproses respons dari server");
}
}
} catch (error) {
console.error("Error updating jenjang pendidikan:", error);
toast.error(
error instanceof Error
? error.message
: "Gagal mengupdate jenjang pendidikan"
);
return false;
} finally {
jenjangPendidikan.edit.loading = false;
}
},
reset() {
jenjangPendidikan.edit.id = "";
jenjangPendidikan.edit.form = { ...jenjangPendidikanDefaultForm };
},
},
});
// ========================================= LEMBAGA PENDIDIKAN ========================================= //
const lembagaPendidikanForm = z.object({
nama: z.string().min(1, "Nama minimal 1 karakter"),
jenjangId: z.string().min(1, "Jenjang pendidikan minimal 1"),
});
const lembagaPendidikanDefaultForm = {
nama: "",
jenjangId: "",
};
const lembagaPendidikan = proxy({
create: {
form: { ...lembagaPendidikanDefaultForm },
loading: false,
async create() {
const cek = lembagaPendidikanForm.safeParse(
lembagaPendidikan.create.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
lembagaPendidikan.create.loading = true;
const res =
await ApiFetch.api.pendidikan.infosekolahpaud.lembagapendidikan[
"create"
].post(lembagaPendidikan.create.form);
if (res.status === 200) {
lembagaPendidikan.findMany.load();
return toast.success("Data berhasil ditambahkan");
}
return toast.error("Gagal menambahkan data");
} catch (error) {
console.log(error);
toast.error("Gagal menambahkan data");
} finally {
lembagaPendidikan.create.loading = false;
}
},
},
findMany: {
data: null as Array<
Prisma.LembagaGetPayload<{
include: {
jenjangPendidikan: true;
siswa: true;
pengajar: true;
};
}>
> | null,
async load() {
const res =
await ApiFetch.api.pendidikan.infosekolahpaud.lembagapendidikan[
"find-many"
].get();
if (res.status === 200) {
lembagaPendidikan.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.LembagaGetPayload<{
include: {
jenjangPendidikan: true;
siswa: true;
pengajar: true;
};
}> | null,
async load(id: string) {
try {
const res = await fetch(
`/api/pendidikan/infosekolahpaud/lembagapendidikan/${id}`
);
if (res.ok) {
const data = await res.json();
lembagaPendidikan.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
lembagaPendidikan.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching data:", error);
lembagaPendidikan.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
lembagaPendidikan.delete.loading = true;
const response = await fetch(
`/api/pendidikan/infosekolahpaud/lembagapendidikan/del/${id}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
);
const result = await response.json();
if (response.ok && result?.success) {
toast.success(
result.message || "lembaga pendidikan berhasil dihapus"
);
await lembagaPendidikan.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus lembaga pendidikan");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus lembaga pendidikan");
} finally {
lembagaPendidikan.delete.loading = false;
}
},
},
edit: {
id: "",
form: { ...lembagaPendidikanDefaultForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(
`/api/pendidikan/infosekolahpaud/lembagapendidikan/${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,
jenjangId: data.jenjangId,
};
return data;
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error loading lembaga pendidikan:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async update() {
const cek = lembagaPendidikanForm.safeParse(lembagaPendidikan.edit.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return false;
}
try {
lembagaPendidikan.edit.loading = true;
const response = await fetch(
`/api/pendidikan/infosekolahpaud/lembagapendidikan/${lembagaPendidikan.edit.id}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
nama: lembagaPendidikan.edit.form.nama,
jenjangId: lembagaPendidikan.edit.form.jenjangId,
}),
}
);
// Clone the response to avoid 'body already read' error
const responseClone = response.clone();
try {
const result = await response.json();
if (!response.ok) {
console.error(
"Update failed with status:",
response.status,
"Response:",
result
);
throw new Error(
result?.message ||
`Gagal mengupdate lembaga pendidikan (${response.status})`
);
}
if (result.success) {
toast.success(
result.message || "Berhasil memperbarui lembaga pendidikan"
);
await lembagaPendidikan.findMany.load(); // refresh list
return true;
} else {
throw new Error(
result.message || "Gagal mengupdate lembaga pendidikan"
);
}
} catch (error) {
// If JSON parsing fails, try to get the response text for better error messages
try {
const text = await responseClone.text();
console.error("Error response text:", text);
throw new Error(`Gagal memproses respons dari server: ${text}`);
} catch (textError) {
console.error("Error parsing response as text:", textError);
console.error("Original error:", error);
throw new Error("Gagal memproses respons dari server");
}
}
} catch (error) {
console.error("Error updating lembaga pendidikan:", error);
toast.error(
error instanceof Error
? error.message
: "Gagal mengupdate lembaga pendidikan"
);
return false;
} finally {
lembagaPendidikan.edit.loading = false;
}
},
reset() {
lembagaPendidikan.edit.id = "";
lembagaPendidikan.edit.form = { ...lembagaPendidikanDefaultForm };
},
},
});
// ========================================= SISWA ========================================= //
const siswaForm = z.object({
nama: z.string().min(1, "Nama minimal 1 karakter"),
lembagaId: z.string().min(1, "lembaga pendidikan minimal 1"),
});
const siswaDefaultForm = {
nama: "",
lembagaId: "",
};
const siswa = proxy({
create: {
form: { ...siswaDefaultForm },
loading: false,
async create() {
const cek = siswaForm.safeParse(siswa.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
siswa.create.loading = true;
const res = await ApiFetch.api.pendidikan.infosekolahpaud.siswa[
"create"
].post(siswa.create.form);
if (res.status === 200) {
siswa.findMany.load();
return toast.success("Data berhasil ditambahkan");
}
return toast.error("Gagal menambahkan data");
} catch (error) {
console.log(error);
toast.error("Gagal menambahkan data");
} finally {
siswa.create.loading = false;
}
},
},
findMany: {
data: null as Array<
Prisma.SiswaGetPayload<{
include: {
lembaga: true;
};
}>
> | null,
async load() {
const res = await ApiFetch.api.pendidikan.infosekolahpaud.siswa[
"find-many"
].get();
if (res.status === 200) {
siswa.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.SiswaGetPayload<{
include: {
lembaga: true;
};
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/pendidikan/infosekolahpaud/siswa/${id}`);
if (res.ok) {
const data = await res.json();
siswa.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
siswa.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching data:", error);
siswa.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
siswa.delete.loading = true;
const response = await fetch(
`/api/pendidikan/infosekolahpaud/siswa/del/${id}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
);
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "siswa berhasil dihapus");
await siswa.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus siswa");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus siswa");
} finally {
siswa.delete.loading = false;
}
},
},
edit: {
id: "",
form: { ...siswaDefaultForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(
`/api/pendidikan/infosekolahpaud/siswa/${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,
lembagaId: data.lembagaId,
};
return data;
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error loading siswa:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async update() {
const cek = siswaForm.safeParse(siswa.edit.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return false;
}
try {
siswa.edit.loading = true;
const response = await fetch(
`/api/pendidikan/infosekolahpaud/siswa/${siswa.edit.id}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
nama: siswa.edit.form.nama,
lembagaId: siswa.edit.form.lembagaId,
}),
}
);
// Clone the response to avoid 'body already read' error
const responseClone = response.clone();
try {
const result = await response.json();
if (!response.ok) {
console.error(
"Update failed with status:",
response.status,
"Response:",
result
);
throw new Error(
result?.message || `Gagal mengupdate siswa (${response.status})`
);
}
if (result.success) {
toast.success(result.message || "Berhasil memperbarui siswa");
await siswa.findMany.load(); // refresh list
return true;
} else {
throw new Error(result.message || "Gagal mengupdate siswa");
}
} catch (error) {
// If JSON parsing fails, try to get the response text for better error messages
try {
const text = await responseClone.text();
console.error("Error response text:", text);
throw new Error(`Gagal memproses respons dari server: ${text}`);
} catch (textError) {
console.error("Error parsing response as text:", textError);
console.error("Original error:", error);
throw new Error("Gagal memproses respons dari server");
}
}
} catch (error) {
console.error("Error updating siswa:", error);
toast.error(
error instanceof Error ? error.message : "Gagal mengupdate siswa"
);
return false;
} finally {
siswa.edit.loading = false;
}
},
reset() {
siswa.edit.id = "";
siswa.edit.form = { ...siswaDefaultForm };
},
},
});
// ========================================= PENGAJAR ========================================= //
const pengajarForm = z.object({
nama: z.string().min(1, "Nama minimal 1 karakter"),
lembagaId: z.string().min(1, "lembaga pendidikan minimal 1"),
});
const pengajarDefaultForm = {
nama: "",
lembagaId: "",
};
const pengajar = proxy({
create: {
form: { ...pengajarDefaultForm },
loading: false,
async create() {
const cek = pengajarForm.safeParse(pengajar.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
pengajar.create.loading = true;
const res = await ApiFetch.api.pendidikan.infosekolahpaud.pengajar[
"create"
].post(pengajar.create.form);
if (res.status === 200) {
pengajar.findMany.load();
return toast.success("Data berhasil ditambahkan");
}
return toast.error("Gagal menambahkan data");
} catch (error) {
console.log(error);
toast.error("Gagal menambahkan data");
} finally {
pengajar.create.loading = false;
}
},
},
findMany: {
data: null as Array<
Prisma.PengajarGetPayload<{
include: {
lembaga: true;
};
}>
> | null,
async load() {
const res = await ApiFetch.api.pendidikan.infosekolahpaud.pengajar[
"find-many"
].get();
if (res.status === 200) {
pengajar.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.PengajarGetPayload<{
include: {
lembaga: true;
};
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/pendidikan/infosekolahpaud/pengajar/${id}`);
if (res.ok) {
const data = await res.json();
pengajar.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
pengajar.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching data:", error);
pengajar.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
pengajar.delete.loading = true;
const response = await fetch(
`/api/pendidikan/infosekolahpaud/pengajar/del/${id}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
);
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "pengajar berhasil dihapus");
await pengajar.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus pengajar");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus pengajar");
} finally {
pengajar.delete.loading = false;
}
},
},
edit: {
id: "",
form: { ...pengajarDefaultForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(
`/api/pendidikan/infosekolahpaud/pengajar/${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,
lembagaId: data.lembagaId,
};
return data;
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error loading siswa:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async update() {
const cek = pengajarForm.safeParse(pengajar.edit.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return false;
}
try {
pengajar.edit.loading = true;
const response = await fetch(
`/api/pendidikan/infosekolahpaud/pengajar/${pengajar.edit.id}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
nama: pengajar.edit.form.nama,
lembagaId: pengajar.edit.form.lembagaId,
}),
}
);
// Clone the response to avoid 'body already read' error
const responseClone = response.clone();
try {
const result = await response.json();
if (!response.ok) {
console.error(
"Update failed with status:",
response.status,
"Response:",
result
);
throw new Error(
result?.message || `Gagal mengupdate pengajar (${response.status})`
);
}
if (result.success) {
toast.success(result.message || "Berhasil memperbarui pengajar");
await pengajar.findMany.load(); // refresh list
return true;
} else {
throw new Error(result.message || "Gagal mengupdate pengajar");
}
} catch (error) {
// If JSON parsing fails, try to get the response text for better error messages
try {
const text = await responseClone.text();
console.error("Error response text:", text);
throw new Error(`Gagal memproses respons dari server: ${text}`);
} catch (textError) {
console.error("Error parsing response as text:", textError);
console.error("Original error:", error);
throw new Error("Gagal memproses respons dari server");
}
}
} catch (error) {
console.error("Error updating pengajar:", error);
toast.error(
error instanceof Error ? error.message : "Gagal mengupdate pengajar"
);
return false;
} finally {
pengajar.edit.loading = false;
}
},
reset() {
pengajar.edit.id = "";
pengajar.edit.form = { ...pengajarDefaultForm };
},
},
});
const infoSekolahPaud = proxy({
jenjangPendidikan,
lembagaPendidikan,
siswa,
pengajar,
});
export default infoSekolahPaud;

View File

@@ -0,0 +1,267 @@
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
import { proxy } from "valtio";
import { z } from "zod";
// ========================================= TUJUAN PENDIDIKAN NON FORMAL ========================================= //
const templateTujuanPendidikanNonFormalForm = z.object({
judul: z.string().min(3, "Judul minimal 3 karakter"),
deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"),
});
type TujuanPendidikanNonFormalForm =
Prisma.TujuanPendidikanNonFormalGetPayload<{
select: {
id: true;
judul: true;
deskripsi: true;
};
}>;
const stateTujuanPendidikanNonFormal = proxy({
findById: {
data: null as TujuanPendidikanNonFormalForm | null,
loading: false,
initialize() {
stateTujuanPendidikanNonFormal.findById.data = {
id: "",
judul: "",
deskripsi: "",
} as TujuanPendidikanNonFormalForm;
},
async load(id: string) {
try {
stateTujuanPendidikanNonFormal.findById.loading = true;
const res =
await ApiFetch.api.pendidikan.pendidikannonformal.tujuanpendidikannonformal[
"find-by-id"
].get({
query: { id },
});
if (res.status === 200) {
stateTujuanPendidikanNonFormal.findById.data = res.data?.data ?? null;
} else {
toast.error("Gagal mengambil data tujuan pendidikan non formal");
}
} catch (error) {
console.error((error as Error).message);
toast.error(
"Terjadi kesalahan saat mengambil data tujuan pendidikan non formal"
);
} finally {
stateTujuanPendidikanNonFormal.findById.loading = false;
}
},
},
update: {
loading: false,
async save(data: TujuanPendidikanNonFormalForm) {
const cek = templateTujuanPendidikanNonFormalForm.safeParse(data);
if (!cek.success) {
const errors = cek.error.issues
.map((issue) => `${issue.path.join(".")}: ${issue.message}`)
.join(", ");
toast.error(`Form tidak valid: ${errors}`);
return;
}
try {
stateTujuanPendidikanNonFormal.update.loading = true;
const res =
await ApiFetch.api.pendidikan.pendidikannonformal.tujuanpendidikannonformal[
"update"
].post(data);
if (res.status === 200) {
toast.success("Data tujuan pendidikan non formal berhasil diubah");
await stateTujuanPendidikanNonFormal.findById.load(data.id);
} else {
toast.error("Gagal mengubah data tujuan pendidikan non formal");
}
} catch (error) {
console.error((error as Error).message);
toast.error(
"Terjadi kesalahan saat mengubah data tujuan pendidikan non formal"
);
} finally {
stateTujuanPendidikanNonFormal.update.loading = false;
}
},
},
});
// ========================================= TEMPAT KEGIATAN ========================================= //
const templateTempatKegiatanForm = z.object({
judul: z.string().min(3, "Judul minimal 3 karakter"),
deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"),
});
type TempatKegiatanForm = Prisma.TempatKegiatanGetPayload<{
select: {
id: true;
judul: true;
deskripsi: true;
};
}>;
const stateTempatKegiatan = proxy({
findById: {
data: null as TempatKegiatanForm | null,
loading: false,
initialize() {
stateTempatKegiatan.findById.data = {
id: "",
judul: "",
deskripsi: "",
} as TempatKegiatanForm;
},
async load(id: string) {
try {
stateTempatKegiatan.findById.loading = true;
const res =
await ApiFetch.api.pendidikan.pendidikannonformal.tempatkegiatan[
"find-by-id"
].get({
query: { id },
});
if (res.status === 200) {
stateTempatKegiatan.findById.data = res.data?.data ?? null;
} else {
toast.error("Gagal mengambil data tempat kegiatan");
}
} catch (error) {
console.error((error as Error).message);
toast.error("Terjadi kesalahan saat mengambil data tempat kegiatan");
} finally {
stateTempatKegiatan.findById.loading = false;
}
},
},
update: {
loading: false,
async save(data: TempatKegiatanForm) {
const cek = templateTempatKegiatanForm.safeParse(data);
if (!cek.success) {
const errors = cek.error.issues
.map((issue) => `${issue.path.join(".")}: ${issue.message}`)
.join(", ");
toast.error(`Form tidak valid: ${errors}`);
return;
}
try {
stateTempatKegiatan.update.loading = true;
const res =
await ApiFetch.api.pendidikan.pendidikannonformal.tempatkegiatan[
"update"
].post(data);
if (res.status === 200) {
toast.success("Data tempat kegiatan berhasil diubah");
await stateTempatKegiatan.findById.load(data.id);
} else {
toast.error("Gagal mengubah data tempat kegiatan");
}
} catch (error) {
console.error((error as Error).message);
toast.error("Terjadi kesalahan saat mengubah data tempat kegiatan");
} finally {
stateTempatKegiatan.update.loading = false;
}
},
},
});
// ========================================= JENIS PROGRAM YANG DISELENGGARAKAN ========================================= //
const templateJenisProgramForm = z.object({
judul: z.string().min(3, "Judul minimal 3 karakter"),
deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"),
});
type JenisProgramForm = Prisma.JenisProgramYangDiselenggarakanGetPayload<{
select: {
id: true;
judul: true;
deskripsi: true;
};
}>;
const stateJenisProgram = proxy({
findById: {
data: null as JenisProgramForm | null,
loading: false,
initialize() {
stateJenisProgram.findById.data = {
id: "",
judul: "",
deskripsi: "",
} as JenisProgramForm;
},
async load(id: string) {
try {
stateJenisProgram.findById.loading = true;
const res =
await ApiFetch.api.pendidikan.pendidikannonformal.jenisprogramyangdiselenggarakan[
"find-by-id"
].get({
query: { id },
});
if (res.status === 200) {
stateJenisProgram.findById.data = res.data?.data ?? null;
} else {
toast.error("Gagal mengambil data jenis program");
}
} catch (error) {
console.error((error as Error).message);
toast.error("Terjadi kesalahan saat mengambil data jenis program");
} finally {
stateJenisProgram.findById.loading = false;
}
},
},
update: {
loading: false,
async save(data: JenisProgramForm) {
const cek = templateJenisProgramForm.safeParse(data);
if (!cek.success) {
const errors = cek.error.issues
.map((issue) => `${issue.path.join(".")}: ${issue.message}`)
.join(", ");
toast.error(`Form tidak valid: ${errors}`);
return;
}
try {
stateJenisProgram.update.loading = true;
const res =
await ApiFetch.api.pendidikan.pendidikannonformal.jenisprogramyangdiselenggarakan[
"update"
].post(data);
if (res.status === 200) {
toast.success("Data jenis program berhasil diubah");
await stateJenisProgram.findById.load(data.id);
} else {
toast.error("Gagal mengubah data jenis program");
}
} catch (error) {
console.error((error as Error).message);
toast.error("Terjadi kesalahan saat mengubah data jenis program");
} finally {
stateJenisProgram.update.loading = false;
}
},
},
});
const pendidikanNonFormalState = proxy({
stateTujuanPendidikanNonFormal,
stateTempatKegiatan,
stateJenisProgram,
});
export default pendidikanNonFormalState;

View File

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

View File

@@ -0,0 +1,181 @@
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
import { proxy } from "valtio";
import { z } from "zod";
// ========================================= TUJUAN PROGRAM ========================================= //
const templateTujuanProgramForm = z.object({
judul: z.string().min(3, "Judul minimal 3 karakter"),
deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"),
});
type TujuanProgramForm = Prisma.TujuanProgramGetPayload<{
select: {
id: true;
judul: true;
deskripsi: true;
};
}>;
const stateTujuanProgram = proxy({
findById: {
data: null as TujuanProgramForm | null,
loading: false,
initialize() {
stateTujuanProgram.findById.data = {
id: "",
judul: "",
deskripsi: "",
} as TujuanProgramForm;
},
async load(id: string) {
try {
stateTujuanProgram.findById.loading = true;
const res =
await ApiFetch.api.pendidikan.programpendidikananak.tujuanprogram[
"find-by-id"
].get({
query: { id },
});
if (res.status === 200) {
stateTujuanProgram.findById.data = res.data?.data ?? null;
} else {
toast.error("Gagal mengambil data tujuan program");
}
} catch (error) {
console.error((error as Error).message);
toast.error("Terjadi kesalahan saat mengambil data tujuan program");
} finally {
stateTujuanProgram.findById.loading = false;
}
},
},
update: {
loading: false,
async save(data: TujuanProgramForm) {
const cek = templateTujuanProgramForm.safeParse(data);
if (!cek.success) {
const errors = cek.error.issues
.map((issue) => `${issue.path.join(".")}: ${issue.message}`)
.join(", ");
toast.error(`Form tidak valid: ${errors}`);
return;
}
try {
stateTujuanProgram.update.loading = true;
const res =
await ApiFetch.api.pendidikan.programpendidikananak.tujuanprogram[
"update"
].post(data);
if (res.status === 200) {
toast.success("Data tujuan program berhasil diubah");
await stateTujuanProgram.findById.load(data.id);
} else {
toast.error("Gagal mengubah data tujuan program");
}
} catch (error) {
console.error((error as Error).message);
toast.error("Terjadi kesalahan saat mengubah data tujuan program");
} finally {
stateTujuanProgram.update.loading = false;
}
},
},
});
// ========================================= PROGRAM UNGGULAN ========================================= //
const templateProgramUnggulanForm = z.object({
judul: z.string().min(3, "Judul minimal 3 karakter"),
deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"),
});
type ProgramUnggulanForm = Prisma.ProgramUnggulanGetPayload<{
select: {
id: true;
judul: true;
deskripsi: true;
};
}>;
const programUnggulanState = proxy({
findById: {
data: null as ProgramUnggulanForm | null,
loading: false,
initialize() {
programUnggulanState.findById.data = {
id: "",
judul: "",
deskripsi: "",
} as ProgramUnggulanForm;
},
async load(id: string) {
try {
programUnggulanState.findById.loading = true;
const res =
await ApiFetch.api.pendidikan.programpendidikananak.programunggulan[
"find-by-id"
].get({
query: { id },
});
if (res.status === 200) {
programUnggulanState.findById.data = res.data?.data ?? null;
} else {
toast.error("Gagal mengambil data program pendidikan anak");
}
} catch (error) {
console.error((error as Error).message);
toast.error(
"Terjadi kesalahan saat mengambil data program pendidikan anak"
);
} finally {
programUnggulanState.findById.loading = false;
}
},
},
update: {
loading: false,
async save(data: ProgramUnggulanForm) {
const cek = templateProgramUnggulanForm.safeParse(data);
if (!cek.success) {
const errors = cek.error.issues
.map((issue) => `${issue.path.join(".")}: ${issue.message}`)
.join(", ");
toast.error(`Form tidak valid: ${errors}`);
return;
}
try {
programUnggulanState.update.loading = true;
const res =
await ApiFetch.api.pendidikan.programpendidikananak.programunggulan[
"update"
].post(data);
if (res.status === 200) {
toast.success("Data program pendidikan anak berhasil diubah");
await programUnggulanState.findById.load(data.id);
} else {
toast.error("Gagal mengubah data program pendidikan anak");
}
} catch (error) {
console.error((error as Error).message);
toast.error(
"Terjadi kesalahan saat mengubah data program pendidikan anak"
);
} finally {
programUnggulanState.update.loading = false;
}
},
},
});
const stateProgramPendidikanAnak = proxy({
stateTujuanProgram,
programUnggulanState,
});
export default stateProgramPendidikanAnak;

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
@@ -16,17 +17,9 @@ const defaultForm = {
tanggal: "",
};
type DaftarInformasi = Prisma.DaftarInformasiPublikGetPayload<{
select: {
jenisInformasi: true;
deskripsi: true;
tanggal: true;
};
}>;
const daftarInformasiPublik = proxy({
create: {
form: {} as DaftarInformasi,
form: {...defaultForm},
loading: false,
async create() {
const cek = templateDaftarInformasi.safeParse(
@@ -56,15 +49,38 @@ const daftarInformasiPublik = proxy({
},
},
findMany: {
data: null as
| Prisma.DaftarInformasiPublikGetPayload<{ omit: { isActive: true } }>[]
| null,
async load() {
const res = await ApiFetch.api.ppid.daftarinformasipublik[
"find-many"
].get();
if (res.status === 200) {
daftarInformasiPublik.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
daftarInformasiPublik.findMany.loading = true; // Use the full path to access the property
daftarInformasiPublik.findMany.page = page;
try {
const res = await ApiFetch.api.ppid.daftarinformasipublik[
"find-many"
].get({
query: { page, limit },
});
if (res.status === 200 && res.data?.success) {
daftarInformasiPublik.findMany.data = res.data.data || [];
daftarInformasiPublik.findMany.total = res.data.total || 0;
daftarInformasiPublik.findMany.totalPages = res.data.totalPages || 1;
} else {
console.error("Failed to load daftar informasi publik:", res.data?.message);
daftarInformasiPublik.findMany.data = [];
daftarInformasiPublik.findMany.total = 0;
daftarInformasiPublik.findMany.totalPages = 1;
}
} catch (error) {
console.error("Error loading daftar informasi publik:", error);
daftarInformasiPublik.findMany.data = [];
daftarInformasiPublik.findMany.total = 0;
daftarInformasiPublik.findMany.totalPages = 1;
} finally {
daftarInformasiPublik.findMany.loading = false;
}
},
},
@@ -186,7 +202,9 @@ const daftarInformasiPublik = proxy({
}
try {
daftarInformasiPublik.edit.loading = true;
const formattedTanggal = this.form.tanggal
? new Date(this.form.tanggal).toISOString()
: undefined;
const response = await fetch(
`/api/ppid/daftarinformasipublik/${this.id}`,
{
@@ -197,7 +215,7 @@ const daftarInformasiPublik = proxy({
body: JSON.stringify({
jenisInformasi: this.form.jenisInformasi,
deskripsi: this.form.deskripsi,
tanggal: this.form.tanggal,
tanggal: formattedTanggal,
}),
}
);

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
@@ -9,71 +10,75 @@ const templateGrafikJenisKelamin = z.object({
perempuan: z.string().min(1, "Data perempuan harus diisi"),
});
type GrafikJenisKelamin = Prisma.GrafikBerdasarkanJenisKelaminGetPayload<{
select: {
id: true;
laki: true;
perempuan: true;
};
}>;
const defaultForm: Omit<GrafikJenisKelamin, 'id'> & { id?: string } = {
const defaultForm = {
laki: "",
perempuan: "",
};
const grafikBerdasarkanJenisKelamin = proxy({
create: {
form: defaultForm,
form: {...defaultForm},
loading: false,
async create() {
const cek = templateGrafikJenisKelamin.safeParse(
grafikBerdasarkanJenisKelamin.create.form
);
async create(){
const cek = templateGrafikJenisKelamin.safeParse(grafikBerdasarkanJenisKelamin.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
const err = cek.error.issues.map((i) => i.message).join("\n");
toast.error(err);
return;
}
try {
grafikBerdasarkanJenisKelamin.create.loading = true;
const res = await ApiFetch.api.ppid.grafikberdasarkanjeniskelamin[
"create"
].post(grafikBerdasarkanJenisKelamin.create.form);
if (res.status === 200) {
const id = res.data?.data?.id;
if (id) {
toast.success("Success create");
grafikBerdasarkanJenisKelamin.create.form = {
laki: "",
perempuan: "",
};
grafikBerdasarkanJenisKelamin.findMany.load();
return id;
}
toast.success("Grafik berdasarkan jenis kelamin berhasil ditambahkan");
await grafikBerdasarkanJenisKelamin.findMany.load();
} else {
toast.error(res.data?.message ?? "Gagal tambah grafik berdasarkan jenis kelamin");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
console.error("Gagal create:", error);
toast.error("Terjadi kesalahan saat menambahkan grafik berdasarkan jenis kelamin");
} finally {
grafikBerdasarkanJenisKelamin.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.GrafikBerdasarkanJenisKelaminGetPayload<{
omit: { isActive: true };
}>[]
| null,
data: null as any[] | null,
page: 1,
totalPages: 1,
total: 0,
loading: false,
async load() {
const res = await ApiFetch.api.ppid.grafikberdasarkanjeniskelamin[
"find-many"
].get();
if (res.status === 200) {
grafikBerdasarkanJenisKelamin.findMany.data = res.data?.data ?? [];
load: async (page = 1, limit = 10) => { // Change to arrow function
grafikBerdasarkanJenisKelamin.findMany.loading = true; // Use the full path to access the property
grafikBerdasarkanJenisKelamin.findMany.page = page;
try {
const res = await ApiFetch.api.ppid.grafikberdasarkanjeniskelamin[
"find-many"
].get({
query: { page, limit },
});
if (res.status === 200 && res.data?.success) {
grafikBerdasarkanJenisKelamin.findMany.data = res.data.data || [];
grafikBerdasarkanJenisKelamin.findMany.total = res.data.total || 0;
grafikBerdasarkanJenisKelamin.findMany.totalPages = res.data.totalPages || 1;
} else {
console.error("Failed to load grafik berdasarkan jenis kelamin:", res.data?.message);
grafikBerdasarkanJenisKelamin.findMany.data = [];
grafikBerdasarkanJenisKelamin.findMany.total = 0;
grafikBerdasarkanJenisKelamin.findMany.totalPages = 1;
}
} catch (error) {
console.error("Error loading grafik berdasarkan jenis kelamin:", error);
grafikBerdasarkanJenisKelamin.findMany.data = [];
grafikBerdasarkanJenisKelamin.findMany.total = 0;
grafikBerdasarkanJenisKelamin.findMany.totalPages = 1;
} finally {
grafikBerdasarkanJenisKelamin.findMany.loading = false;
}
},
},

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
@@ -11,17 +12,7 @@ const templateGrafikResponden = z.object({
tidakbaik: z.string().min(1, "Data tidak baik harus diisi"),
});
type GrafikResponden = Prisma.GrafikBerdasarkanRespondenGetPayload<{
select: {
id: true;
sangatbaik: true;
baik: true;
kurangbaik: true;
tidakbaik: true;
};
}>;
const defaultForm: Omit<GrafikResponden, 'id'> & { id?: string } = {
const defaultForm = {
sangatbaik: "",
baik: "",
kurangbaik: "",
@@ -30,7 +21,7 @@ const defaultForm: Omit<GrafikResponden, 'id'> & { id?: string } = {
const grafikBerdasarkanResponden = proxy({
create: {
form: defaultForm,
form: {...defaultForm},
loading: false,
async create() {
const cek = templateGrafikResponden.safeParse(
@@ -48,40 +39,52 @@ const grafikBerdasarkanResponden = proxy({
"create"
].post(grafikBerdasarkanResponden.create.form);
if (res.status === 200) {
const id = res.data?.data?.id;
if (id) {
toast.success("Success create");
grafikBerdasarkanResponden.create.form = {
sangatbaik: "",
baik: "",
kurangbaik: "",
tidakbaik: "",
};
grafikBerdasarkanResponden.findMany.load();
return id;
}
toast.success("Grafik berdasarkan responden berhasil ditambahkan");
await grafikBerdasarkanResponden.findMany.load();
} else {
toast.error(res.data?.message ?? "Gagal tambah grafik berdasarkan responden");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
console.error("Gagal create:", error);
toast.error("Terjadi kesalahan saat menambahkan grafik berdasarkan responden");
} finally {
grafikBerdasarkanResponden.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.GrafikBerdasarkanRespondenGetPayload<{
omit: { isActive: true };
}>[]
| null,
data: null as any[] | null,
page: 1,
totalPages: 1,
total: 0,
loading: false,
async load() {
const res = await ApiFetch.api.ppid.grafikberdasarkanresponden[
"find-many"
].get();
if (res.status === 200) {
grafikBerdasarkanResponden.findMany.data = res.data?.data ?? [];
load: async (page = 1, limit = 10) => { // Change to arrow function
grafikBerdasarkanResponden.findMany.loading = true; // Use the full path to access the property
grafikBerdasarkanResponden.findMany.page = page;
try {
const res = await ApiFetch.api.ppid.grafikberdasarkanresponden[
"find-many"
].get({
query: { page, limit },
});
if (res.status === 200 && res.data?.success) {
grafikBerdasarkanResponden.findMany.data = res.data.data || [];
grafikBerdasarkanResponden.findMany.total = res.data.total || 0;
grafikBerdasarkanResponden.findMany.totalPages = res.data.totalPages || 1;
} else {
console.error("Failed to load grafik berdasarkan responden:", res.data?.message);
grafikBerdasarkanResponden.findMany.data = [];
grafikBerdasarkanResponden.findMany.total = 0;
grafikBerdasarkanResponden.findMany.totalPages = 1;
}
} catch (error) {
console.error("Error loading grafikBerdasarkanResponden:", error);
grafikBerdasarkanResponden.findMany.data = [];
grafikBerdasarkanResponden.findMany.total = 0;
grafikBerdasarkanResponden.findMany.totalPages = 1;
} finally {
grafikBerdasarkanResponden.findMany.loading = false;
}
},
},

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
@@ -11,17 +12,7 @@ const templateGrafikUmur = z.object({
lansia: z.string().min(1, "Data lansia harus diisi"),
});
type GrafikUmur = Prisma.GrafikBerdasarkanUmurGetPayload<{
select: {
id: true;
remaja: true;
dewasa: true;
orangtua: true;
lansia: true;
};
}>;
const defaultForm: Omit<GrafikUmur, "id"> & { id?: string } = {
const defaultForm = {
remaja: "",
dewasa: "",
orangtua: "",
@@ -30,7 +21,7 @@ const defaultForm: Omit<GrafikUmur, "id"> & { id?: string } = {
const grafikBerdasarkanUmur = proxy({
create: {
form: defaultForm,
form: {...defaultForm},
loading: false,
async create() {
const cek = templateGrafikUmur.safeParse(
@@ -70,18 +61,38 @@ const grafikBerdasarkanUmur = proxy({
},
},
findMany: {
data: null as
| Prisma.GrafikBerdasarkanUmurGetPayload<{
omit: { isActive: true };
}>[]
| null,
data: null as any[] | null,
page: 1,
totalPages: 1,
total: 0,
loading: false,
async load() {
const res = await ApiFetch.api.ppid.grafikberdasarkanumur[
"find-many"
].get();
if (res.status === 200) {
grafikBerdasarkanUmur.findMany.data = res.data?.data ?? [];
load: async (page = 1, limit = 10) => { // Change to arrow function
grafikBerdasarkanUmur.findMany.loading = true; // Use the full path to access the property
grafikBerdasarkanUmur.findMany.page = page;
try {
const res = await ApiFetch.api.ppid.grafikberdasarkanumur[
"find-many"
].get({
query: { page, limit },
});
if (res.status === 200 && res.data?.success) {
grafikBerdasarkanUmur.findMany.data = res.data.data || [];
grafikBerdasarkanUmur.findMany.total = res.data.total || 0;
grafikBerdasarkanUmur.findMany.totalPages = res.data.totalPages || 1;
} else {
console.error("Failed to load grafik berdasarkan umur:", res.data?.message);
grafikBerdasarkanUmur.findMany.data = [];
grafikBerdasarkanUmur.findMany.total = 0;
grafikBerdasarkanUmur.findMany.totalPages = 1;
}
} catch (error) {
console.error("Error loading grafik berdasarkan umur:", error);
grafikBerdasarkanUmur.findMany.data = [];
grafikBerdasarkanUmur.findMany.total = 0;
grafikBerdasarkanUmur.findMany.totalPages = 1;
} finally {
grafikBerdasarkanUmur.findMany.loading = false;
}
},
},

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
@@ -9,22 +10,14 @@ const templateGrafikHasilKepuasanMasyarakat = z.object({
kepuasan: z.string().min(1, "Kepuasan harus diisi"),
});
type GrafikHasilKepuasanMasyarakat = Prisma.IndeksKepuasanMasyarakatGetPayload<{
select: {
id: true;
label: true;
kepuasan: true;
};
}>;
const defaultForm: Omit<GrafikHasilKepuasanMasyarakat, 'id'> & { id?: string } = {
const defaultForm = {
label: "",
kepuasan: "",
};
const grafikHasilKepuasanMasyarakat = proxy({
create: {
form: defaultForm,
form: {...defaultForm},
loading: false,
async create() {
const cek = templateGrafikHasilKepuasanMasyarakat.safeParse(
@@ -38,42 +31,52 @@ const grafikHasilKepuasanMasyarakat = proxy({
}
try {
grafikHasilKepuasanMasyarakat.create.loading = true;
const res = await ApiFetch.api.ppid.grafikhasilkepuasamanmasyarakat["create"].post(
grafikHasilKepuasanMasyarakat.create.form
);
const res = await ApiFetch.api.ppid.grafikhasilkepuasamanmasyarakat["create"].post(grafikHasilKepuasanMasyarakat.create.form);
if (res.status === 200) {
const id = res.data?.data?.id;
if (id) {
toast.success("Success create");
grafikHasilKepuasanMasyarakat.create.form = {
label: "",
kepuasan: "",
};
grafikHasilKepuasanMasyarakat.findMany.load();
return id;
}
toast.success("Grafik hasil kepuasan masyarakat berhasil ditambahkan");
await grafikHasilKepuasanMasyarakat.findMany.load();
} else {
toast.error(res.data?.message ?? "Gagal tambah grafik hasil kepuasan masyarakat");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
console.error("Gagal create:", error);
toast.error("Terjadi kesalahan saat menambahkan grafik hasil kepuasan masyarakat");
} finally {
grafikHasilKepuasanMasyarakat.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.IndeksKepuasanMasyarakatGetPayload<{
omit: { isActive: true };
}>[]
| null,
async load() {
const res = await ApiFetch.api.ppid.grafikhasilkepuasamanmasyarakat[
"find-many"
].get();
if (res.status === 200) {
grafikHasilKepuasanMasyarakat.findMany.data = res.data?.data ?? [];
data: null as any[] | null,
page: 1,
totalPages: 1,
total: 0,
loading: false,
load: async (page = 1, limit = 10) => { // Change to arrow function
grafikHasilKepuasanMasyarakat.findMany.loading = true; // Use the full path to access the property
grafikHasilKepuasanMasyarakat.findMany.page = page;
try {
const res = await ApiFetch.api.ppid.grafikhasilkepuasamanmasyarakat["find-many"].get({
query: { page, limit },
});
if (res.status === 200 && res.data?.success) {
grafikHasilKepuasanMasyarakat.findMany.data = res.data.data || [];
grafikHasilKepuasanMasyarakat.findMany.total = res.data.total || 0;
grafikHasilKepuasanMasyarakat.findMany.totalPages = res.data.totalPages || 1;
} else {
console.error("Failed to load grafik hasil kepuasan masyarakat:", res.data?.message);
grafikHasilKepuasanMasyarakat.findMany.data = [];
grafikHasilKepuasanMasyarakat.findMany.total = 0;
grafikHasilKepuasanMasyarakat.findMany.totalPages = 1;
}
} catch (error) {
console.error("Error loading grafik hasil kepuasan masyarakat:", error);
grafikHasilKepuasanMasyarakat.findMany.data = [];
grafikHasilKepuasanMasyarakat.findMany.total = 0;
grafikHasilKepuasanMasyarakat.findMany.totalPages = 1;
} finally {
grafikHasilKepuasanMasyarakat.findMany.loading = false;
}
},
},

View File

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

View 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

View File

@@ -18,8 +18,6 @@ import { useParams, useRouter } from "next/navigation";
import { useEffect, useState } from "react";
import { toast } from "react-toastify";
import { useProxy } from "valtio/utils";
import EditEditor from "@/app/admin/(dashboard)/_com/editEditor";
import colors from "@/con/colors";
import ApiFetch from "@/lib/api-fetch";

View File

@@ -1,6 +1,6 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Grid, GridCol, Image, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { Box, Button, Center, Grid, GridCol, Image, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconCircleDashedPlus, IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
@@ -29,12 +29,21 @@ function Berita() {
function ListBerita({ search }: { search: string }) {
const beritaState = useProxy(stateDashboardBerita)
const router = useRouter()
const {
data,
page,
totalPages,
loading,
load,
} = beritaState.berita.findMany;
// Fetch pertama kali
useShallowEffect(() => {
beritaState.berita.findMany.load()
}, [])
load(page, 10); // awal page = 1
}, [page]);
const filteredData = (beritaState.berita.findMany.data || []).filter(item => {
const filteredData = (data || []).filter((item) => {
const keyword = search.toLowerCase();
return (
item.judul.toLowerCase().includes(keyword) ||
@@ -42,67 +51,85 @@ function ListBerita({ search }: { search: string }) {
);
});
if (!beritaState.berita.findMany.data) {
return (
<Stack py={10}>
<Skeleton h={500} />
</Stack>
)
if (loading || !data) {
return <Skeleton h={500} />;
}
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<Paper bg={colors["white-1"]} p={"md"}>
<Stack>
<Grid>
<GridCol span={{ base: 12, md: 11 }}>
<Text fz={"xl"} fw={"bold"}>List Berita</Text>
<Text fz={"xl"} fw={"bold"}>
List Berita
</Text>
</GridCol>
<GridCol span={{ base: 12, md: 1 }}>
<Button onClick={() => router.push("/admin/desa/berita/create")} bg={colors['blue-button']}>
<Button
onClick={() => router.push("/admin/desa/berita/create")}
bg={colors["blue-button"]}
>
<IconCircleDashedPlus size={25} />
</Button>
</GridCol>
</Grid>
<Box style={{ overflowX: "auto" }}>
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
<Table
striped
withRowBorders
withTableBorder
style={{ minWidth: "700px" }}
>
<TableThead>
<TableTr>
<TableTh w={250}>Judul</TableTh>
<TableTh w={250}>Kategori</TableTh>
<TableTh w={250}>Image</TableTh>
<TableTh w={200}>Detail</TableTh>
</TableTr>
</TableThead>
<TableTbody >
<TableTbody>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd >
<TableTd>
<Box w={100}>
<Text truncate="end" fz={"sm"}>{item.judul}</Text>
<Text truncate="end" fz={"sm"}>
{item.judul}
</Text>
</Box>
</TableTd>
<TableTd >{item.kategoriBerita?.name}</TableTd>
<TableTd>{item.kategoriBerita?.name}</TableTd>
<TableTd>
<Image w={100} src={item.image?.link} alt="gambar" />
</TableTd>
<TableTd>
<Button bg={"green"} onClick={() => router.push(`/admin/desa/berita/${item.id}`)}>
<Button
bg={"green"}
onClick={() =>
router.push(`/admin/desa/berita/${item.id}`)
}
>
<IconDeviceImacCog size={25} />
</Button>
</TableTd>
</TableTr>
))}
</TableTbody>
</Table> </Box>
</Table>
</Box>
</Stack>
</Paper>
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage)} // ini penting!
total={totalPages}
mt="md"
mb="md"
/>
</Center>
</Box>
)
);
}
export default Berita;

View File

@@ -3,7 +3,7 @@ import colors from '@/con/colors';
import { Box, Button, Paper, Skeleton, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import JudulListTab from '../../../_com/jusulListTab';
import JudulListTab from '../../../_com/judulListTab';
import { useProxy } from 'valtio/utils';
import stateGallery from '../../../_state/desa/gallery';
import { useShallowEffect } from '@mantine/hooks';

View File

@@ -3,7 +3,7 @@ import colors from '@/con/colors';
import { Box, Button, Paper, Skeleton, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import JudulListTab from '../../../_com/jusulListTab';
import JudulListTab from '../../../_com/judulListTab';
import { useProxy } from 'valtio/utils';
import stateGallery from '../../../_state/desa/gallery';
import { useShallowEffect } from '@mantine/hooks';

View File

@@ -1,5 +1,5 @@
'use client'
import JudulListTab from '@/app/admin/(dashboard)/_com/jusulListTab';
import JudulListTab from '@/app/admin/(dashboard)/_com/judulListTab';
import colors from '@/con/colors';
import { Box, Button, Image, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';

View File

@@ -1,5 +1,5 @@
'use client'
import JudulListTab from '@/app/admin/(dashboard)/_com/jusulListTab';
import JudulListTab from '@/app/admin/(dashboard)/_com/judulListTab';
import colors from '@/con/colors';
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';

View File

@@ -6,7 +6,7 @@ import { useShallowEffect } from '@mantine/hooks';
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useProxy } from 'valtio/utils';
import JudulListTab from '../../_com/jusulListTab';
import JudulListTab from '../../_com/judulListTab';
import { useState } from 'react';
import HeaderSearch from '../../_com/header';

View File

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

View File

@@ -0,0 +1,73 @@
/* 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: "APB Desa",
value: "apbdesa",
href: "/admin/ekonomi/PADesa-pendapatan-asli-desa/apbdesa"
},
{
label: "Pendapatan",
value: "pendapatan",
href: "/admin/ekonomi/PADesa-pendapatan-asli-desa/pendapatan"
},
{
label: "Belanja",
value: "belanja",
href: "/admin/ekonomi/PADesa-pendapatan-asli-desa/belanja"
},
{
label: "Pembiayaan",
value: "pembiayaan",
href: "/admin/ekonomi/PADesa-pendapatan-asli-desa/pembiayaan"
},
];
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}>Pendapatan Asli 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;

View File

@@ -0,0 +1,234 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
'use client'
/* eslint-disable react-hooks/exhaustive-deps */
import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa';
import colors from '@/con/colors';
import { Box, Button, MultiSelect, Paper, Skeleton, Stack, Text, TextInput, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
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 EditAPBDesa() {
const apbState = useProxy(PendapatanAsliDesa.ApbDesa);
const router = useRouter();
const params = useParams();
const [formData, setFormData] = useState({
tahun: apbState.update.form.tahun || '',
pendapatanIds: apbState.update.form.pendapatanIds || [],
belanjaIds: apbState.update.form.belanjaIds || [],
pembiayaanIds: apbState.update.form.pembiayaanIds || [],
});
// Load APB desa by id saat pertama kali
useEffect(() => {
const loadAPBdesa = async () => {
const id = params?.id as string;
if (!id) return;
try {
const data = await apbState.update.load(id);
if (data) {
setFormData({
tahun: data.tahun || 0,
pendapatanIds: data.pendapatan?.map((p: any) => p.id) || [],
belanjaIds: data.belanja?.map((b: any) => b.id) || [],
pembiayaanIds: data.pembiayaan?.map((p: any) => p.id) || [],
});
}
} catch (error) {
console.error("Error loading APBdesa:", error);
toast.error("Gagal memuat data APBdesa");
}
};
loadAPBdesa();
}, [params?.id]); // ✅ hapus beritaState dari dependency
const handleSubmit = async () => {
try {
// Update global state with form data
apbState.update.form = {
...apbState.update.form,
tahun: Number(formData.tahun),
pendapatanIds: formData.pendapatanIds,
belanjaIds: formData.belanjaIds,
pembiayaanIds: formData.pembiayaanIds,
};
await apbState.update.update();
toast.success("APB Desa berhasil diperbarui!");
router.push("/admin/ekonomi/PADesa-pendapatan-asli-desa/apbdesa");
} catch (error) {
console.error("Error updating APBdesa:", error);
toast.error("Terjadi kesalahan saat memperbarui APBdesa");
}
};
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 APB Desa</Title>
<TextInput
type='number'
value={formData.tahun}
onChange={(val) => {
setFormData({ ...formData, tahun: val.target.value });
}}
label={<Text fz={"sm"} fw={"bold"}>Tahun</Text>}
placeholder="masukkan tahun"
/>
<SelectPendapatan
selectedIds={formData.pendapatanIds}
onSelectionChange={(ids) => {
setFormData({ ...formData, pendapatanIds: ids });
}}
/>
<SelectBelanja
selectedIds={formData.belanjaIds}
onSelectionChange={(ids) => {
setFormData({ ...formData, belanjaIds: ids });
}}
/>
<SelectPembiayaan
selectedIds={formData.pembiayaanIds}
onSelectionChange={(ids) => {
setFormData({ ...formData, pembiayaanIds: ids });
}}
/>
<Button onClick={handleSubmit}>Simpan</Button>
</Stack>
</Paper>
</Box>
);
/* Select Pendapatan */
interface SelectPendapatanProps {
selectedIds: string[];
onSelectionChange: (ids: string[]) => void;
}
function SelectPendapatan({
selectedIds = [],
onSelectionChange,
}: SelectPendapatanProps) {
const pendapatanState = useProxy(PendapatanAsliDesa.pendapatan);
useShallowEffect(() => {
pendapatanState.findMany.load().then(() => {
console.log("Pendapatan berhasil dimuat:", pendapatanState.findMany.data);
});
}, []);
if (!pendapatanState.findMany.data) {
return <Skeleton height={38} />;
}
return (
<MultiSelect
label={<Text fz={"sm"} fw={"bold"}>Pendapatan</Text>}
data={pendapatanState.findMany.data.map(p => ({
value: p.id,
label: p.name
}))}
value={selectedIds}
onChange={onSelectionChange}
searchable
clearable
placeholder="Pilih pendapatan..."
nothingFoundMessage="Tidak ditemukan"
/>
);
}
/* Select Belanja */
interface SelectBelanjaProps {
selectedIds: string[];
onSelectionChange: (ids: string[]) => void;
}
function SelectBelanja({
selectedIds = [],
onSelectionChange,
}: SelectBelanjaProps) {
const belanjaState = useProxy(PendapatanAsliDesa.belanja);
useShallowEffect(() => {
belanjaState.findMany.load().then(() => {
console.log("Belanja berhasil dimuat:", belanjaState.findMany.data);
});
}, []);
if (!belanjaState.findMany.data) {
return <Skeleton height={38} />;
}
return (
<MultiSelect
label={<Text fz={"sm"} fw={"bold"}>Belanja</Text>}
data={belanjaState.findMany.data.map(b => ({
value: b.id,
label: b.name
}))}
value={selectedIds}
onChange={onSelectionChange}
searchable
clearable
placeholder="Pilih belanja..."
nothingFoundMessage="Tidak ditemukan"
/>
);
}
/* Select Pembiayaan */
interface SelectPembiayaanProps {
selectedIds: string[];
onSelectionChange: (ids: string[]) => void;
}
function SelectPembiayaan({
selectedIds = [],
onSelectionChange,
}: SelectPembiayaanProps) {
const pembiayaanState = useProxy(PendapatanAsliDesa.pembiayaan);
useShallowEffect(() => {
pembiayaanState.findMany.load().then(() => {
console.log("Pembiayaan berhasil dimuat:", pembiayaanState.findMany.data);
});
}, []);
if (!pembiayaanState.findMany.data) {
return <Skeleton height={38} />;
}
return (
<MultiSelect
label={<Text fz={"sm"} fw={"bold"}>Pembiayaan</Text>}
data={pembiayaanState.findMany.data.map(b => ({
value: b.id,
label: b.name
}))}
value={selectedIds}
onChange={onSelectionChange}
searchable
clearable
placeholder="Pilih pembiayaan..."
nothingFoundMessage="Tidak ditemukan"
/>
);
}
}
export default EditAPBDesa;

View File

@@ -0,0 +1,152 @@
'use client'
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa';
import colors from '@/con/colors';
import { Box, Button, Flex, Paper, Skeleton, Stack, Text } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
import { useParams, useRouter } from 'next/navigation';
import { useState } from 'react';
import { useProxy } from 'valtio/utils';
function DetailAPBDesa() {
const apbState = useProxy(PendapatanAsliDesa.ApbDesa)
const [modalHapus, setModalHapus] = useState(false)
const [selectedId, setSelectedId] = useState<string | null>(null)
const params = useParams()
const router = useRouter()
useShallowEffect(() => {
console.log("PARAM ID:", params?.id)
apbState.findUnique.load(params?.id as string)
}, [])
const formatRupiah = (value: number) => {
return new Intl.NumberFormat('id-ID', {
style: 'currency',
currency: 'IDR',
minimumFractionDigits: 0,
}).format(value);
};
const handleHapus = () => {
if (selectedId) {
apbState.delete.byId(selectedId)
setModalHapus(false)
setSelectedId(null)
router.push("/admin/ekonomi/PADesa-pendapatan-asli-desa/apbdesa")
}
}
if (!apbState.findUnique.data) {
return (
<Stack py={10}>
<Skeleton h={40} />
</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>
<Text fz={"xl"} fw={"bold"}>Detail APB Desa</Text>
{apbState.findUnique.data ? (
<Paper key={apbState.findUnique.data.id} bg={colors['BG-trans']} p={'md'}>
<Stack gap={"xs"}>
<Box>
<Text fw={"bold"} fz={"lg"}>Tahun</Text>
<Text fz={"lg"}>{apbState.findUnique.data?.tahun}</Text>
</Box>
<Box>
<Stack gap={"xs"}>
<Text fw={"bold"} fz={"lg"}>Detail Pembiayaan</Text>
{(apbState.findUnique.data?.pembiayaan || []).map((item) => (
<Text fz={"lg"} key={item.id}>
{item.name}: {formatRupiah(Number(item.value))}
</Text>
))}
<Text fz={"lg"} fw={"bold"}>
Total: {formatRupiah((apbState.findUnique.data?.pembiayaan || [])
.reduce((sum, item) => sum + Number(item.value), 0))}
</Text>
</Stack>
</Box>
<Box>
<Stack gap={"xs"}>
<Text fw={"bold"} fz={"lg"}>Detail Belanja</Text>
{(apbState.findUnique.data?.belanja || []).map((item) => (
<Text fz={"lg"} key={item.id}>
{item.name}: {formatRupiah(Number(item.value))}
</Text>
))}
<Text fz={"lg"} fw={"bold"}>
Total: {formatRupiah((apbState.findUnique.data?.belanja || [])
.reduce((sum, item) => sum + Number(item.value), 0))}
</Text>
</Stack>
</Box>
<Box>
<Stack gap={"xs"}>
<Text fw={"bold"} fz={"lg"}>Detail Pendapatan</Text>
{(apbState.findUnique.data?.pendapatan || []).map((item) => (
<Text fz={"lg"} key={item.id}>
{item.name}: {formatRupiah(Number(item.value))}
</Text>
))}
<Text fz={"lg"} fw={"bold"}>
Total: {formatRupiah((apbState.findUnique.data?.pendapatan || [])
.reduce((sum, item) => sum + Number(item.value), 0))}
</Text>
</Stack>
</Box>
<Flex gap={"xs"} mt={10}>
<Button
onClick={() => {
if (apbState.findUnique.data) {
setSelectedId(apbState.findUnique.data.id);
setModalHapus(true);
}
}}
disabled={apbState.delete.loading || !apbState.findUnique.data}
color={"red"}
>
<IconX size={20} />
</Button>
<Button
onClick={() => {
if (apbState.findUnique.data) {
router.push(`/admin/ekonomi/PADesa-pendapatan-asli-desa/apbdesa/${apbState.findUnique.data.id}/edit`);
}
}}
disabled={!apbState.findUnique.data}
color={"green"}
>
<IconEdit size={20} />
</Button>
</Flex>
</Stack>
</Paper>
) : null}
</Stack>
</Paper>
{/* Modal Konfirmasi Hapus */}
<ModalKonfirmasiHapus
opened={modalHapus}
onClose={() => setModalHapus(false)}
onConfirm={handleHapus}
text='Apakah anda yakin ingin menghapus APB Desa ini?'
/>
</Box>
);
}
export default DetailAPBDesa;

View File

@@ -0,0 +1,190 @@
'use client'
import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa';
import colors from '@/con/colors';
import { Box, Button, MultiSelect, Paper, Skeleton, Stack, Text, TextInput, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconArrowBack } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useProxy } from 'valtio/utils';
function CreateAPBDesa() {
const apbDesaState = useProxy(PendapatanAsliDesa.ApbDesa)
const router = useRouter()
const resetForm = () => {
apbDesaState.create.form = {
tahun: 0,
pendapatanIds: [],
belanjaIds: [],
pembiayaanIds: [],
}
}
const handleSubmit = async () => {
await apbDesaState.create.submit()
resetForm()
router.push("/admin/ekonomi/PADesa-pendapatan-asli-desa/apbdesa")
}
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 APB Desa</Title>
<TextInput
type='number'
value={apbDesaState.create.form.tahun}
onChange={(val) => {
apbDesaState.create.form.tahun = Number(val.target.value);
}}
label={<Text fz={"sm"} fw={"bold"}>Tahun</Text>}
placeholder="masukkan tahun"
/>
<SelectPendapatan
selectedIds={apbDesaState.create.form.pendapatanIds}
onSelectionChange={(ids) => {
apbDesaState.create.form.pendapatanIds = ids;
}}
/>
<SelectBelanja
selectedIds={apbDesaState.create.form.belanjaIds}
onSelectionChange={(ids) => {
apbDesaState.create.form.belanjaIds = ids;
}}
/>
<SelectPembiayaan
selectedIds={apbDesaState.create.form.pembiayaanIds}
onSelectionChange={(ids) => {
apbDesaState.create.form.pembiayaanIds = ids;
}}
/>
<Button bg={colors['blue-button']} onClick={handleSubmit}>Simpan</Button>
</Stack>
</Paper>
</Box>
);
/* Select Pendapatan */
interface SelectPendapatanProps {
selectedIds: string[];
onSelectionChange: (ids: string[]) => void;
}
function SelectPendapatan({
selectedIds = [],
onSelectionChange,
}: SelectPendapatanProps) {
const pendapatanState = useProxy(PendapatanAsliDesa.pendapatan);
useShallowEffect(() => {
pendapatanState.findMany.load().then(() => {
console.log("Pendapatan berhasil dimuat:", pendapatanState.findMany.data);
});
}, []);
if (!pendapatanState.findMany.data) {
return <Skeleton height={38} />;
}
return (
<MultiSelect
label={<Text fz={"sm"} fw={"bold"}>Pendapatan</Text>}
data={pendapatanState.findMany.data.map(p => ({
value: p.id,
label: p.name
}))}
value={selectedIds}
onChange={onSelectionChange}
searchable
clearable
placeholder="Pilih pendapatan..."
nothingFoundMessage="Tidak ditemukan"
/>
);
}
/* Select Belanja */
interface SelectBelanjaProps {
selectedIds: string[];
onSelectionChange: (ids: string[]) => void;
}
function SelectBelanja({
selectedIds = [],
onSelectionChange,
}: SelectBelanjaProps) {
const belanjaState = useProxy(PendapatanAsliDesa.belanja);
useShallowEffect(() => {
belanjaState.findMany.load().then(() => {
console.log("Belanja berhasil dimuat:", belanjaState.findMany.data);
});
}, []);
if (!belanjaState.findMany.data) {
return <Skeleton height={38} />;
}
return (
<MultiSelect
label={<Text fz={"sm"} fw={"bold"}>Belanja</Text>}
data={belanjaState.findMany.data.map(b => ({
value: b.id,
label: b.name
}))}
value={selectedIds}
onChange={onSelectionChange}
searchable
clearable
placeholder="Pilih belanja..."
nothingFoundMessage="Tidak ditemukan"
/>
);
}
/* Select Pembiayaan */
interface SelectPembiayaanProps {
selectedIds: string[];
onSelectionChange: (ids: string[]) => void;
}
function SelectPembiayaan({
selectedIds = [],
onSelectionChange,
}: SelectPembiayaanProps) {
const pembiayaanState = useProxy(PendapatanAsliDesa.pembiayaan);
useShallowEffect(() => {
pembiayaanState.findMany.load().then(() => {
console.log("Pembiayaan berhasil dimuat:", pembiayaanState.findMany.data);
});
}, []);
if (!pembiayaanState.findMany.data) {
return <Skeleton height={38} />;
}
return (
<MultiSelect
label={<Text fz={"sm"} fw={"bold"}>Pembiayaan</Text>}
data={pembiayaanState.findMany.data.map(b => ({
value: b.id,
label: b.name
}))}
value={selectedIds}
onChange={onSelectionChange}
searchable
clearable
placeholder="Pilih pembiayaan..."
nothingFoundMessage="Tidak ditemukan"
/>
);
}
}
export default CreateAPBDesa;

View File

@@ -0,0 +1,106 @@
'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 { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import { useProxy } from 'valtio/utils';
import HeaderSearch from '../../../_com/header';
import JudulList from '../../../_com/judulList';
import PendapatanAsliDesa from '../../../_state/ekonomi/PADesa';
function APBDesa() {
const [search, setSearch] = useState("");
return (
<Box>
<HeaderSearch
title='APB Desa'
placeholder='pencarian'
searchIcon={<IconSearch size={20} />}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<ListAPBDesa search={search} />
</Box>
);
}
function ListAPBDesa({ search }: { search: string }) {
const apbDesaState = useProxy(PendapatanAsliDesa.ApbDesa)
const router = useRouter();
const formatRupiah = (value: number) => {
return new Intl.NumberFormat('id-ID', {
style: 'currency',
currency: 'IDR',
minimumFractionDigits: 0,
}).format(value);
};
useShallowEffect(() => {
apbDesaState.findMany.load();
}, [])
const filteredData = (apbDesaState.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.tahun.toString().toLowerCase().includes(keyword) ||
item.pembiayaan.map((item) => item.value.toString()).includes(keyword) ||
item.belanja.map((item) => item.value.toString()).includes(keyword) ||
item.pendapatan.map((item) => item.value.toString()).includes(keyword)
);
});
if (!apbDesaState.findMany.data) {
return (
<Stack py={10}>
<Skeleton h={500} />
</Stack>
)
}
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<JudulList
title='List APB Desa'
href='/admin/ekonomi/PADesa-pendapatan-asli-desa/apbdesa/create'
/>
<Table striped withTableBorder withRowBorders>
<TableThead>
<TableTr>
<TableTh>Tahun</TableTh>
<TableTh>Pembiayaan</TableTh>
<TableTh>Belanja</TableTh>
<TableTh>Pendapatan</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>{item.tahun}</TableTd>
<TableTd>{formatRupiah(item.pembiayaan.reduce((sum, item) => sum + Number(item.value), 0))}</TableTd>
<TableTd>{formatRupiah(item.belanja.reduce((sum, item) => sum + Number(item.value), 0))}</TableTd>
<TableTd>{formatRupiah(item.pendapatan.reduce((sum, item) => sum + Number(item.value), 0))}</TableTd>
<TableTd>
<Button
bg={"green"}
onClick={() =>
router.push(`/admin/ekonomi/PADesa-pendapatan-asli-desa/apbdesa/${item.id}`)
}
>
<IconDeviceImacCog size={25} />
</Button>
</TableTd>
</TableTr>
))}
</TableTbody>
</Table>
</Paper>
</Box>
);
}
export default APBDesa;

View File

@@ -0,0 +1,112 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa';
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 EditBelanja() {
const belanjaState = useProxy(PendapatanAsliDesa.belanja);
const router = useRouter();
const params = useParams();
const [formData, setFormData] = useState({
name: belanjaState.update.form.name || '',
value: belanjaState.update.form.value || '',
});
const formatRupiah = (value: number | string) => {
const number = typeof value === 'number' ? value : Number(value.replace(/\D/g, ''));
return new Intl.NumberFormat('id-ID', {
style: 'currency',
currency: 'IDR',
minimumFractionDigits: 0,
}).format(number);
};
const unformatRupiah = (value: string) => {
return Number(value.replace(/\D/g, ''));
};
useEffect(() => {
const loadBelanja = async () => {
const id = params?.id as string;
if (!id) return;
try {
const data = await belanjaState.update.load(id);
if (data) {
setFormData({
name: data.name || '',
value: data.value || '',
});
}
} catch (error) {
console.error("Error loading belanja:", error);
toast.error("Gagal memuat data belanja");
}
};
loadBelanja();
}, [params?.id]);
const handleSubmit = async () => {
try {
belanjaState.update.form = {
...belanjaState.update.form,
name: formData.name,
value: Number(formData.value),
}
await belanjaState.update.update();
toast.success("Jenis Belanja berhasil diperbarui!");
router.push("/admin/ekonomi/PADesa-pendapatan-asli-desa/belanja");
} catch (error) {
console.error("Error updating jenis belanja:", error);
toast.error("Terjadi kesalahan saat memperbarui jenis belanja");
}
}
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 Jenis Pendapatan</Title>
<TextInput
value={formData.name}
onChange={(val) => {
setFormData({ ...formData, name: val.target.value });
}}
label={<Text fw={"bold"} fz={"sm"}>Nama Jenis Pendapatan</Text>}
placeholder='Masukkan nama Jenis Pendapatan'
/>
<TextInput
label={<Text fw={"bold"} fz={"sm"}>Nilai</Text>}
placeholder='Masukkan nilai'
value={formatRupiah(formData.value)}
onChange={(val) => {
const raw = val.currentTarget.value;
const cleanValue = unformatRupiah(raw);
setFormData({ ...formData, value: cleanValue });
}}
/>
<Group>
<Button bg={colors['blue-button']} onClick={handleSubmit}>Submit</Button>
</Group>
</Stack>
</Paper>
</Box>
);
}
export default EditBelanja;

View File

@@ -0,0 +1,77 @@
'use client'
import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa';
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 CreateBelanja() {
const belanjaState = useProxy(PendapatanAsliDesa.belanja)
const router = useRouter()
const formatRupiah = (value: number | string) => {
const number = typeof value === 'number' ? value : Number(value.replace(/\D/g, ''));
return new Intl.NumberFormat('id-ID', {
style: 'currency',
currency: 'IDR',
minimumFractionDigits: 0,
}).format(number);
};
const unformatRupiah = (value: string) => {
return Number(value.replace(/\D/g, ''));
};
const resetForm = () => {
belanjaState.create.form = {
name: "",
value: 0,
}
}
const handleSubmit = async () => {
await belanjaState.create.submit();
resetForm()
router.push("/admin/ekonomi/PADesa-pendapatan-asli-desa/belanja")
}
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 Jenis Belanja</Title>
<TextInput
value={belanjaState.create.form.name}
onChange={(val) => {
belanjaState.create.form.name = val.target.value;
}}
label={<Text fw={"bold"} fz={"sm"}>Nama Jenis Belanja</Text>}
placeholder='Masukkan nama jenis belanja'
/>
<TextInput
type='text'
value={formatRupiah(belanjaState.create.form.value)}
onChange={(val) => {
const raw = val.currentTarget.value;
const cleanValue = unformatRupiah(raw);
belanjaState.create.form.value = cleanValue;
}}
label={<Text fw={"bold"} fz={"sm"}>Nilai</Text>}
placeholder='Masukkan nilai'
/>
<Group>
<Button bg={colors['blue-button']} onClick={handleSubmit}>Submit</Button>
</Group>
</Stack>
</Paper>
</Box>
);
}
export default CreateBelanja;

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