Compare commits

..

52 Commits

Author SHA1 Message Date
124dfb8160 UI & API Menu Ekonomi, Submenu Demografi 2025-07-08 23:30:48 +08:00
46c79b8ded UI & API Submenu Sektor Unggulan, Menu Ekonomi 2025-07-08 17:16:13 +08:00
d105293149 UI & API Menu Ekonomi, Submenu Jumlah Penduduk Miskin 2025-07-08 16:30:33 +08:00
adcbe3aa3d UI & API Menu Ekonomi, Submenu Jumlah Penduduk usia yang menganggur 2025-07-08 15:20:26 +08:00
bffe648802 Fix Tabs Pegawai, Submenu Struktur & Organisasi, Menu Ekonomi 2025-07-08 12:05:10 +08:00
7e95d5fbb4 Add Fitur Search Di setiap menu 2025-07-08 00:30:55 +08:00
2725c2c064 UI & API Menu Struktur Organisasi Tabs Hubungan Organisasi 2025-07-07 22:24:46 +08:00
be189df37c UI & API Struktur organisasi sudah bisa aktif & tidak aktif 2025-07-07 21:45:04 +08:00
c0b941395d UI & API Struktur organisasi sudah bisa aktif & tidak aktif 2025-07-07 21:41:27 +08:00
a2e25a3e3a API & UI Menu Struktur bagian pegawai 2025-07-07 17:14:44 +08:00
d86824a943 API & UI Struktur Organisasi Posisi Organisasi 2025-07-07 11:33:05 +08:00
4f97c01501 Perbaikan UI & API Menu Ekonomi Pasar Desa 2025-07-04 11:09:06 +08:00
0fd47e3e94 API & UI Pasar Desa Menu Ekonomi 2025-07-04 00:11:55 +08:00
b92a974dcd API & UI Admin Menu Keamanan Done 2025-07-03 16:09:39 +08:00
10361770b4 API & UI Program Kemiskinan Menu Ekonomi 2025-07-03 12:21:08 +08:00
aec2f5094a Push 1 Program Kemiskinan 2025-07-03 11:24:54 +08:00
72d39b020a API/UI Admin Ekonomi Lowongan kerja 2025-07-02 17:03:20 +08:00
51d67736ef API & UI Admin Keamanan Laporan Publik 2025-07-02 15:35:10 +08:00
406c6f3c9f UI & API Menu Keamanan, Kontak Darurat 2025-07-02 14:10:22 +08:00
1c5e4410c4 Save 2025-07-01 20:57:32 +08:00
4724b7473d Try Fix UI & API Menu Ekonomi Sub Menu Pasar Desa 2025-07-01 16:48:44 +08:00
c5fc4f4cea UI & API Menu Keamanan baru 3 Menu : Keamanan Lingkungan, Polsek Terdekat, & Tips Keamanan 2025-07-01 11:16:53 +08:00
dd7ce6943d Keperluan Deploy 2025-06-30 11:04:20 +08:00
ee10f339e9 API Admin Menu Keamanan Done 2025-06-30 10:56:46 +08:00
02462b2c19 API 2 Menu Keamanan 2025-06-29 02:46:17 +08:00
41181d4cb3 Fix Dibagian Data Kesehatan Warga 2025-06-28 00:10:45 +08:00
6d5b8dcf64 UI & API Admin Menu Kesehatan Done 2025-06-27 23:52:00 +08:00
924be5b11b Sisa 1 Tabs aja yang data kesehatan warga 2025-06-26 17:24:57 +08:00
4f6cc66b7c Kebutuhan Deploy 2025-06-26 11:00:15 +08:00
4683034cd7 UI & API Admin Kesehatan Menu Data Kesehatan Warga Sisa 2 Tabs 2025-06-25 15:50:24 +08:00
37de71a75a UI & API Admin Kesehatan Menu Data Kesehatan Warga Sisa 2 Tabs 2025-06-25 15:47:05 +08:00
27fa7ac0fc UI & API Data Kesehatan warga sisa 3 tabs 2025-06-24 17:25:43 +08:00
fc08b2e790 UI & API Admin Kesehatan Done 2025-06-20 08:11:31 +08:00
4a5524ce88 API & UI Kesehatan Sudah Sampai Di Penanganan Darurat 2025-06-20 00:08:13 +08:00
899883ca2a Fix UI & API Admin Kesehatan Puskesmas 2025-06-19 15:40:27 +08:00
10ecc13ad7 API All Kesehatan 2025-06-19 14:12:57 +08:00
58f538425c UI & API Admin Menu Kesehatan 2025-06-19 10:24:50 +08:00
d2f53ff69b Keperluan Deploy 2025-06-18 16:37:08 +08:00
40f0294595 Tambahan buat deploy 2025-06-18 16:32:19 +08:00
6ed0246cea API Profile Desa Udah Clear, API Menu desa udah clear
API & UI Profile Desa Clear
2025-06-18 15:32:06 +08:00
af726043bd API Profile Desa aman, tinggal Profil Perbekel
UI Profile Desa aman, tinggal Profil Perbekel
2025-06-18 14:08:02 +08:00
f4888b53ab API Profile Desa Menu Desa
Fix Eror gallery bagian tabs video
Next UI Profile Desa
2025-06-17 17:30:47 +08:00
f7437708c0 Menu Desa, Sub Menu Yang Tersisa Tinga Tinggal Profile Aja 2025-06-16 17:46:25 +08:00
7bf5ee69d5 Api Admin Menu Desa Sub Menu Layanan Tabs 3 Pelayanan Done 2025-06-16 15:01:15 +08:00
e03b071b00 UI Admin Dashboard Layanan Desa 1 tabs
API Admin Dashboard Layanan Desa 1 Tabs
2025-06-16 03:08:34 +08:00
8ded234991 UI Desa Gallery Done
API Desa Gallery Done
2025-06-13 16:32:33 +08:00
1462e1d256 Tampilan UI Admin PPID IKM & API 2025-06-12 17:40:37 +08:00
a1c2821153 API Struktur Admin PPID 2025-06-11 11:19:30 +08:00
9f66b037f9 Add Fitur Status Operasional & Jadwal Kerja 2025-06-10 14:22:51 +08:00
9e725e2eea Fix UI Lingkungan, UI Ekonomi Progress 2025-06-10 11:53:10 +08:00
e4b7418ed3 Tambahan Fix UI PPID 2025-06-10 11:01:13 +08:00
6d312b7a28 Fix UI Admin PPID
Profile PPID clear
2025-06-10 00:54:29 +08:00
702 changed files with 44945 additions and 10139 deletions

BIN
bun.lockb

Binary file not shown.

View File

@@ -1,6 +1,6 @@
{
"name": "desa-darmasaba",
"version": "0.1.2",
"version": "0.1.3",
"private": true,
"scripts": {
"dev": "next dev --turbopack",
@@ -15,15 +15,16 @@
"dependencies": {
"@cubejs-client/core": "^0.31.0",
"@elysiajs/cors": "^1.2.0",
"@elysiajs/eden": "^1.2.0",
"@elysiajs/eden": "^1.3.2",
"@elysiajs/static": "^1.3.0",
"@elysiajs/stream": "^1.1.0",
"@elysiajs/swagger": "^1.2.0",
"@mantine/carousel": "^7.16.2",
"@mantine/charts": "^7.17.1",
"@mantine/core": "^7.17.4",
"@mantine/dates": "^7.17.4",
"@mantine/dropzone": "^7.17.0",
"@mantine/dates": "^8.1.0",
"@mantine/dropzone": "^8.1.1",
"@mantine/form": "^8.1.0",
"@mantine/hooks": "^7.17.4",
"@mantine/tiptap": "^7.17.4",
"@paljs/types": "^8.1.0",
@@ -45,7 +46,7 @@
"bun": "^1.2.2",
"chart.js": "^4.4.8",
"dayjs": "^1.11.13",
"elysia": "^1.2.12",
"elysia": "^1.3.5",
"embla-carousel-autoplay": "^8.5.2",
"embla-carousel-react": "^7.1.0",
"form-data": "^4.0.2",

View File

@@ -0,0 +1,7 @@
[
{
"id": "1",
"name": "Pelayanan Penduduk Non-Permanent",
"deskripsi": "<p>Surat Keterangan Penduduk Non-Permanent adalah dokumen yang dikeluarkan oleh pihak berwenang untuk memberikan keterangan bahwa seseorang atau kelompok orang memiliki status penduduk non-permanent di suatu wilayah. Dokumen ini biasanya digunakan untuk keperluan administratif atau legal, seperti mendapatkan akses ke layanan kesehatan, pendidikan, atau pelayanan publik lainnya.</p>"
}
]

View File

@@ -0,0 +1,8 @@
[
{
"id": "1",
"name": "Pelayanan Perizinan Berusaha Berbasis Risiko Melalui Sistem ONLINE SINGLE SUBMISSION (OSS)",
"deskripsi": "<p>Penyelenggaraan Perizinan Berusaha Berbasis Risiko melalui Sistem Online Single Submission (OSS) merupakan pelaksanaan Undang-Undang Nomor 11 Tahun 2020 Tentang Cipta Kerja. OSS Berbasis Risiko wajib digunakan oleh Pelaku Usaha, Kementerian/Lembaga, Pemerintah Daerah, Administrator Kawasan Ekonomi Khusus (KEK), dan Badan Pengusahaan Kawasan Perdagangan Bebas Pelabuhan Bebas (KPBPB).Berdasarkan Peraturan Pemerintah Nomor 5 Tahun 2021 terdapat 1.702 kegiatan usaha yang terdiri atas 1.349 Klasifikasi Baku Lapangan Usaha Indonesia (KBLI) yang sudah diimplementasikan dalam Sistem OSS Berbasis Risiko.</p>",
"link" : "https://oss.go.id/"
}
]

View File

@@ -0,0 +1,7 @@
[
{
"id": "edit",
"judul": "Lambang Desa",
"deskripsi" : "<ul><li>Memperkokoh kerukunan hidup masyarakat dalam jalinan adat, budaya, olahraga, dan agama.</li><li>Meningkatkan kualitas pelayanan publik dengan menerapkan teknologi informasi dan komunikasi terintegrasi.</li><li>Meningkatkan tata kelola pemerintah desa dengan menerapkan prinsip good governance dan good clean government.</li><li>Meningkatkan kualitas pendidikan, kesehatan, Keluarga Berencana serta pengelolaan kependudukan.</li><li>Memperkuat usaha mikro kecil dan menengah (UMKM) dan BUMDesa sebagai pilar ekonomi masyarakat.</li><li>Mewujudkan tatanan kehidupan bermasyarakat yang menjunjung tinggi penegakan hukum dan HAM.</li><li>Meningkatkan perlindungan dan pengelolaan terhadap sumber daya alam dan lingkungan hidup.</li><li>Memperkuat daya saing desa melalui peningkatan mutu sumber daya manusia dan infrastruktur desa berbasis potensi desa.</li><li>Meningkatkan sinergisitas potensi budaya, pertanian dalam arti luas dan pariwisata.</li><li>Memperkuat daya saing desa melalui peningkatan mutu sumber daya manusia dan infrastruktur desa berbasis potensi desa.</li><li>Meningkatkan sinergisitas potensi budaya, pertanian dalam arti luas dan pariwisata.</li></ul>"
}
]

View File

@@ -0,0 +1,7 @@
[
{
"id": "edit",
"judul": "Maskot Desa",
"deskripsi" : "<p>Pudak adalah bunga dari tanaman sejenis pandan (Pandanaceae). Bentuk bunga ini tersusun dalam beberapa lapisan, terbungkus oleh kelopak warna putih (semacam daun lonjong) yang ujungnya meruncing.</p><p>Bunga Pudak berwarna kuning dan akan terlihat jika kelopak atau pelepahnya telah mekar. Kekhasan dari bunga pudak, yaitu mempunyai aroma wangi yang semerbak nan lembut (tidak menyengat), dan dapat menebar keharuman sepanjang pagi atau pun sore hari. Tanaman ini dapat tumbuh di sepanjang pantai, aliran sungai, di atas batu-batu karang, dan juga di tanah ladang.</p><p>Dalam Kamus Jawa Kuna- Indonesia kata “Pudak” berarti bunga pandan atau Pandanus Moschatus (Mardiwarsito: 1981: 442). Selain itu bunga pudak juga dapat disebut ketaka atau ketaki (Mardiwarsito, 1981: 276). Sedangkan kata “Sategal” berasal dari kata dasar “Tegal” yang berarti ladang (Mardiwarsito, 1981: 593). Jadi Pudak Sategal dapat diartikan sebagai satu ladang luas yang dipenuhi bunga pudak dan menabar keharuman.</p><p>Pada sebuah kesempatan, Ida Pedanda Putu Pemaron menjelaskan mengenai makna dari istilah Pudak Sategal dengan sebuah analogi bahwa, sekuntum bunga pudak memiliki aroma wangi atau keharuman yang sangat kuat, apalagi jika satu ladang penuh bunga pudak, maka dapat dipastikan aroma keharumannya akan membumbung menyebar ke segala penjuru (Wawancara, 18 Mei 2019 di Geria Putra Mandara Kenderan, Tegallalang). “Pudak” ialah sebuah bunga yang memiliki aroma wangi atau keharuman yang semerbak, lembut, dan khas.</p><p>Garapan Tari Maskot Desa Darmasaba Sekar Pudak diwujudkan ke dalam bentuk tari kreasi yang ditarikan secara berkelompok dengan jumlah lima orang penari perempuan (putri).</p><p>Pemilihan penari perempuan dimaksudkan untuk mempresentasikan keindahan, keluwesan, dan keharuman dari bunga pudak. Sedangkan penetapan jumlah penari lima orang didasarkan atas pertimbangan kebutuhan koreografi agar dapat membentuk desain-desain komposisi lantai yang menarik dan dinamis, baik ketika ditarikan di area panggung yang luas atau pun area panggung yang kecil. Penyajian tari maskot ini dirancang dengan durasi waktu 9 menit.</p>"
}
]

View File

@@ -1,6 +1,6 @@
[
{
"id": "1",
"id": "edit",
"biodata": "<p>I.B Surya Prabhawa Manuaba, S.H., M.H., adalah Perbekel Darmasaba periode 2021-2027, seorang advokat, pendiri Mantra Legal Consultants & Advocates, serta aktif di bidang musik dan akademis. Dia menempuh pendidikan hukum di Universitas Udayana dan Universitas Mahasaraswati Denpasar serta memiliki pengalaman luas di berbagai organisasi dan kepemimpinan.</p>",
"pengalaman": "<ul><li>2021 - 2027: Perbekel Desa Darmasaba</li><li>2015 - Sekarang: Founder & Managing Director Mantra Legal Consultants & Advocates</li><li>2020 - Sekarang: Founder Ugawa Record Music Studio</li><li>2010 - 2016: Dosen Fakultas Hukum Universitas Mahasaraswati Denpasar</li></ul>",
"pengalamanOrganisasi": "<ul> <li>1996 1997: Ketua OSIS SMP Negeri 1 Abiansemal</li><li>1999 2000: Ketua OSIS SMA Negeri 1 Mengwi</li> <li>2008 2009: Ketua BEM Universitas Mahasaraswati Denpasar</li> <li>2008 2010: Ketua Sekaa Taruna Sila Dharma, Banjar Tengah, Desa Adat Tegal, Darmasaba</li> <li>2020 Sekarang: Pengurus Young Lawyer Committee Peradi Denpasar</li> <li>2021 Sekarang: Dewan Kehormatan Himpunan Pengusaha Muda Indonesia (HIPMI) Badung</li> <li>2023 2028: Komite Tetap Advokasi Bidang Hukum dan Regulasi Kamar Dagang dan Industri Badung</li> </ul>",

View File

@@ -1,11 +0,0 @@
[
{
"id": "1",
"sejarah" : "<p>Asal usul nama Darmasaba tertuang dalam lontar Usada Bali. Seperti di tulis dalam monografi Desa Darmasaba tahun 1980 silam, nama Darmasaba berkaitan dengan keturunan Danghyang Nirarta diceritakan, Sang kawi-wiku asal Daha (Jawa Timur) itu memiliki cucu bernama Ida Pedanda Sakti Manuaba yang tigggal di Desa Kendran Tegalalang Gianyar. Merasa tidak disenangi sang ayah, Ida Pedanda Sakti Manuaba pergi mengembara bersama dua orang pengiringnya. Pengembaraan sang pendeta sampai di pura Sarin Buana di Jimbaran. Saat mengadakan semedi di tempat ini sang pendeta melihat sinar api. Yang sangat jauh di utara. Timbul keinginan Ida Pedanda Manuaba untuk mengunjungi tempat itu. Sampailah sang Pedanda di pura Batan Bila Peguyangan. Disini Ida Pedanda Manuaba singgah menghadap Ida Pedanda Budha yang tinggal disana. Selanjutnya, kedua pendeta bersama-sama menuju arah utara dan singgah di Taman Cang Ana, sebuah taman milik Arya Lanang Blusung. Di tempat ini kedua pendeta bersama-sama melaksanakan semedi dan menetap untuk sementara waktu.</p>",
"visi" : "<p>Mewujudkan Desa Darmasaba yang sejahtera, unggul, religius, berbudaya, dan aman dengan berlandaskan Tri Hita Karana</p>",
"misi" : "<ul><li>Memperkokoh kerukunan hidup masyarakat dalam jalinan adat, budaya, olahraga, dan agama.</li><li>Meningkatkan kualitas pelayanan publik dengan menerapkan teknologi informasi dan komunikasi terintegrasi.</li><li>Meningkatkan tata kelola pemerintah desa dengan menerapkan prinsip good governance dan good clean government.</li><li>Meningkatkan kualitas pendidikan, kesehatan, Keluarga Berencana serta pengelolaan kependudukan.</li><li>Memperkuat usaha mikro kecil dan menengah (UMKM) dan BUMDesa sebagai pilar ekonomi masyarakat.</li><li>Mewujudkan tatanan kehidupan bermasyarakat yang menjunjung tinggi penegakan hukum dan HAM.</li><li>Meningkatkan perlindungan dan pengelolaan terhadap sumber daya alam dan lingkungan hidup.</li><li>Memperkuat daya saing desa melalui peningkatan mutu sumber daya manusia dan infrastruktur desa berbasis potensi desa.</li><li>Meningkatkan sinergisitas potensi budaya, pertanian dalam arti luas dan pariwisata.</li></ul>",
"lambang" : "<ul><li>Memperkokoh kerukunan hidup masyarakat dalam jalinan adat, budaya, olahraga, dan agama.</li><li>Meningkatkan kualitas pelayanan publik dengan menerapkan teknologi informasi dan komunikasi terintegrasi.</li><li>Meningkatkan tata kelola pemerintah desa dengan menerapkan prinsip good governance dan good clean government.</li><li>Meningkatkan kualitas pendidikan, kesehatan, Keluarga Berencana serta pengelolaan kependudukan.</li><li>Memperkuat usaha mikro kecil dan menengah (UMKM) dan BUMDesa sebagai pilar ekonomi masyarakat.</li><li>Mewujudkan tatanan kehidupan bermasyarakat yang menjunjung tinggi penegakan hukum dan HAM.</li><li>Meningkatkan perlindungan dan pengelolaan terhadap sumber daya alam dan lingkungan hidup.</li><li>Memperkuat daya saing desa melalui peningkatan mutu sumber daya manusia dan infrastruktur desa berbasis potensi desa.</li><li>Meningkatkan sinergisitas potensi budaya, pertanian dalam arti luas dan pariwisata.</li><li>Memperkuat daya saing desa melalui peningkatan mutu sumber daya manusia dan infrastruktur desa berbasis potensi desa.</li><li>Meningkatkan sinergisitas potensi budaya, pertanian dalam arti luas dan pariwisata.</li></ul>",
"maskot" : "<p>Pudak adalah bunga dari tanaman sejenis pandan (Pandanaceae). Bentuk bunga ini tersusun dalam beberapa lapisan, terbungkus oleh kelopak warna putih (semacam daun lonjong) yang ujungnya meruncing.</p><p>Bunga Pudak berwarna kuning dan akan terlihat jika kelopak atau pelepahnya telah mekar. Kekhasan dari bunga pudak, yaitu mempunyai aroma wangi yang semerbak nan lembut (tidak menyengat), dan dapat menebar keharuman sepanjang pagi atau pun sore hari. Tanaman ini dapat tumbuh di sepanjang pantai, aliran sungai, di atas batu-batu karang, dan juga di tanah ladang.</p><p>Dalam Kamus Jawa Kuna- Indonesia kata “Pudak” berarti bunga pandan atau Pandanus Moschatus (Mardiwarsito: 1981: 442). Selain itu bunga pudak juga dapat disebut ketaka atau ketaki (Mardiwarsito, 1981: 276). Sedangkan kata “Sategal” berasal dari kata dasar “Tegal” yang berarti ladang (Mardiwarsito, 1981: 593). Jadi Pudak Sategal dapat diartikan sebagai satu ladang luas yang dipenuhi bunga pudak dan menabar keharuman.</p><p>Pada sebuah kesempatan, Ida Pedanda Putu Pemaron menjelaskan mengenai makna dari istilah Pudak Sategal dengan sebuah analogi bahwa, sekuntum bunga pudak memiliki aroma wangi atau keharuman yang sangat kuat, apalagi jika satu ladang penuh bunga pudak, maka dapat dipastikan aroma keharumannya akan membumbung menyebar ke segala penjuru (Wawancara, 18 Mei 2019 di Geria Putra Mandara Kenderan, Tegallalang). “Pudak” ialah sebuah bunga yang memiliki aroma wangi atau keharuman yang semerbak, lembut, dan khas.</p><p>Garapan Tari Maskot Desa Darmasaba Sekar Pudak diwujudkan ke dalam bentuk tari kreasi yang ditarikan secara berkelompok dengan jumlah lima orang penari perempuan (putri).</p><p>Pemilihan penari perempuan dimaksudkan untuk mempresentasikan keindahan, keluwesan, dan keharuman dari bunga pudak. Sedangkan penetapan jumlah penari lima orang didasarkan atas pertimbangan kebutuhan koreografi agar dapat membentuk desain-desain komposisi lantai yang menarik dan dinamis, baik ketika ditarikan di area panggung yang luas atau pun area panggung yang kecil. Penyajian tari maskot ini dirancang dengan durasi waktu 9 menit.</p>",
"profilPerbekelId" : "1"
}
]

View File

@@ -0,0 +1,7 @@
[
{
"id": "edit",
"judul": "Sejarah Desa",
"deskripsi": "<p>Asal usul nama Darmasaba tertuang dalam lontar Usada Bali. Seperti di tulis dalam monografi Desa Darmasaba tahun 1980 silam, nama Darmasaba berkaitan dengan keturunan Danghyang Nirarta diceritakan, Sang kawi-wiku asal Daha (Jawa Timur) itu memiliki cucu bernama Ida Pedanda Sakti Manuaba yang tigggal di Desa Kendran Tegalalang Gianyar. Merasa tidak disenangi sang ayah, Ida Pedanda Sakti Manuaba pergi mengembara bersama dua orang pengiringnya. Pengembaraan sang pendeta sampai di pura Sarin Buana di Jimbaran. Saat mengadakan semedi di tempat ini sang pendeta melihat sinar api. Yang sangat jauh di utara. Timbul keinginan Ida Pedanda Manuaba untuk mengunjungi tempat itu. Sampailah sang Pedanda di pura Batan Bila Peguyangan. Disini Ida Pedanda Manuaba singgah menghadap Ida Pedanda Budha yang tinggal disana. Selanjutnya, kedua pendeta bersama-sama menuju arah utara dan singgah di Taman Cang Ana, sebuah taman milik Arya Lanang Blusung. Di tempat ini kedua pendeta bersama-sama melaksanakan semedi dan menetap untuk sementara waktu.</p>"
}
]

View File

@@ -0,0 +1,7 @@
[
{
"id" : "edit",
"visi" : "<p>Mewujudkan Desa Darmasaba yang sejahtera, unggul, religius, berbudaya, dan aman dengan berlandaskan Tri Hita Karana</p>",
"misi" : "<ul><li>Memperkokoh kerukunan hidup masyarakat dalam jalinan adat, budaya, olahraga, dan agama.</li><li>Meningkatkan kualitas pelayanan publik dengan menerapkan teknologi informasi dan komunikasi terintegrasi.</li><li>Meningkatkan tata kelola pemerintah desa dengan menerapkan prinsip good governance dan good clean government.</li><li>Meningkatkan kualitas pendidikan, kesehatan, Keluarga Berencana serta pengelolaan kependudukan.</li><li>Memperkuat usaha mikro kecil dan menengah (UMKM) dan BUMDesa sebagai pilar ekonomi masyarakat.</li><li>Mewujudkan tatanan kehidupan bermasyarakat yang menjunjung tinggi penegakan hukum dan HAM.</li><li>Meningkatkan perlindungan dan pengelolaan terhadap sumber daya alam dan lingkungan hidup.</li><li>Memperkuat daya saing desa melalui peningkatan mutu sumber daya manusia dan infrastruktur desa berbasis potensi desa.</li><li>Meningkatkan sinergisitas potensi budaya, pertanian dalam arti luas dan pariwisata.</li></ul>"
}
]

View File

@@ -0,0 +1,10 @@
[
{
"id": "4b95bge6-012e-5ged-9552-4d8g65d44959",
"nama": "Makanan"
},
{
"id": "5c06chf7-123f-6hfe-0663-5e9h76e55060",
"nama": "Minuman"
}
]

View File

@@ -0,0 +1,8 @@
[
{
"id": "650e8400-e29b-41d4-a716-446655440001",
"atasanId": "550e8400-e29b-41d4-a716-446655440001",
"bawahanId": "550e8400-e29b-41d4-a716-446655440002",
"tipe": "Langsung Melapor"
}
]

View File

@@ -0,0 +1,24 @@
[
{
"id": "550e8400-e29b-41d4-a716-446655440001",
"namaLengkap": "Budi Santoso",
"gelarAkademik": "S.IP",
"tanggalMasuk": "2020-01-01T00:00:00.000Z",
"email": "budi@desa.id",
"telepon": "081234567891",
"alamat": "Jl. Raya Desa No. 1",
"posisiId": "kepala_desa",
"isActive": true
},
{
"id": "550e8400-e29b-41d4-a716-446655440002",
"namaLengkap": "Ani Lestari",
"gelarAkademik": "S.Pd",
"tanggalMasuk": "2020-02-01T00:00:00.000Z",
"email": "ani@desa.id",
"telepon": "081234567892",
"alamat": "Jl. Raya Desa No. 2",
"posisiId": "sekretaris_desa",
"isActive": true
}
]

View File

@@ -0,0 +1,27 @@
[
{
"id": "kepala_desa",
"nama": "Kepala Desa",
"deskripsi": "Kepala Desa",
"hierarki": 1
},
{
"id": "sekretaris_desa",
"nama": "Sekretaris Desa",
"deskripsi": "Sekretaris Desa",
"hierarki": 2
},
{
"id": "bendahara_desa",
"nama": "Bendahara Desa",
"deskripsi": "Bendahara Desa",
"hierarki": 3
},
{
"id": "staff_umum",
"nama": "Staff Umum",
"deskripsi": "Staff Umum",
"hierarki": 4
}
]

View File

@@ -1,11 +1,10 @@
[
{
"id": "1",
"id": "edit",
"name": "I.B Surya Prabhawa Manuaba, S.H., M.H.",
"biodata": "<p>I.B Surya Prabhawa Manuaba, S.H., M.H., adalah Perbekel Darmasaba periode 2021-2027, seorang advokat, pendiri Mantra Legal Consultants & Advocates, serta aktif di bidang musik dan akademis. Dia menempuh pendidikan hukum di Universitas Udayana dan Universitas Mahasaraswati Denpasar, serta memiliki pengalaman luas di berbagai organisasi dan kepemimpinan.</p>",
"riwayat": "<ul> <li>2021 - 2027: Perbekel Desa Darmasaba</li> <li>2015 - Sekarang: Founder & Managing Director Mantra Legal Consultants & Advocates</li> <li>2020 - Sekarang: Founder Ugawa Record Music Studio</li> <li>2010 - 2016: Dosen Fakultas Hukum Universitas Mahasaraswati Denpasar</li> </ul>",
"pengalaman": "<ul> <li>1996 1997: Ketua OSIS SMP Negeri 1 Abiansemal</li><li>1999 2000: Ketua OSIS SMA Negeri 1 Mengwi</li> <li>2008 2009: Ketua BEM Universitas Mahasaraswati Denpasar</li> <li>2008 2010: Ketua Sekaa Taruna Sila Dharma, Banjar Tengah, Desa Adat Tegal, Darmasaba</li> <li>2020 Sekarang: Pengurus Young Lawyer Committee Peradi Denpasar</li> <li>2021 Sekarang: Dewan Kehormatan Himpunan Pengusaha Muda Indonesia (HIPMI) Badung</li> <li>2023 2028: Komite Tetap Advokasi Bidang Hukum dan Regulasi Kamar Dagang dan Industri Badung</li> </ul>",
"unggulan": "<h3>Pemberdayaan Ekonomi dan UMKM</h3> <ul> <li>Pelatihan dan pendampingan UMKM lokal</li> <li>Program bantuan modal usaha bagi pelaku usaha kecil</li><li>Digitalisasi UMKM untuk meningkatkan pemasaran produk lokal</li></ul>",
"imageUrl": "/uploads/seeded-images/profile-ppid/perbekel.png"
"unggulan": "<h3>Pemberdayaan Ekonomi dan UMKM</h3> <ul> <li>Pelatihan dan pendampingan UMKM lokal</li> <li>Program bantuan modal usaha bagi pelaku usaha kecil</li><li>Digitalisasi UMKM untuk meningkatkan pemasaran produk lokal</li></ul>"
}
]

View File

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

View File

@@ -0,0 +1,193 @@
/*
Warnings:
- You are about to drop the column `nomor` on the `DaftarInformasiPublik` table. All the data in the column will be lost.
- You are about to drop the column `image` on the `GalleryFoto` table. All the data in the column will be lost.
- You are about to drop the column `video` on the `GalleryVideo` table. All the data in the column will be lost.
- You are about to drop the column `videosId` on the `GalleryVideo` table. All the data in the column will be lost.
- The primary key for the `IndeksKepuasanMasyarakat` table will be changed. If it partially fails, the table could be left without primary key constraint.
- You are about to drop the column `profilPerbekelId` on the `ProfileDesa` table. All the data in the column will be lost.
- You are about to drop the column `imageUrl` on the `ProfilePPID` table. All the data in the column will be lost.
- You are about to drop the `Images` table. If the table is not empty, all the data it contains will be lost.
- You are about to drop the `Videos` table. If the table is not empty, all the data it contains will be lost.
- Added the required column `deskripsi` to the `GalleryFoto` table without a default value. This is not possible if the table is not empty.
- Added the required column `deskripsi` to the `GalleryVideo` table without a default value. This is not possible if the table is not empty.
- Added the required column `linkVideo` to the `GalleryVideo` table without a default value. This is not possible if the table is not empty.
*/
-- DropForeignKey
ALTER TABLE "GalleryFoto" DROP CONSTRAINT "GalleryFoto_imagesId_fkey";
-- DropForeignKey
ALTER TABLE "GalleryVideo" DROP CONSTRAINT "GalleryVideo_videosId_fkey";
-- DropForeignKey
ALTER TABLE "ProfileDesa" DROP CONSTRAINT "ProfileDesa_profilPerbekelId_fkey";
-- DropIndex
DROP INDEX "GalleryVideo_videosId_key";
-- AlterTable
ALTER TABLE "DaftarInformasiPublik" DROP COLUMN "nomor";
-- AlterTable
ALTER TABLE "GalleryFoto" DROP COLUMN "image",
ADD COLUMN "deskripsi" TEXT NOT NULL;
-- AlterTable
ALTER TABLE "GalleryVideo" DROP COLUMN "video",
DROP COLUMN "videosId",
ADD COLUMN "deskripsi" TEXT NOT NULL,
ADD COLUMN "linkVideo" TEXT NOT NULL;
-- AlterTable
ALTER TABLE "IndeksKepuasanMasyarakat" DROP CONSTRAINT "IndeksKepuasanMasyarakat_pkey",
ALTER COLUMN "id" DROP DEFAULT,
ALTER COLUMN "id" SET DATA TYPE TEXT,
ADD CONSTRAINT "IndeksKepuasanMasyarakat_pkey" PRIMARY KEY ("id");
DROP SEQUENCE "IndeksKepuasanMasyarakat_id_seq";
-- AlterTable
ALTER TABLE "ProfileDesa" DROP COLUMN "profilPerbekelId";
-- AlterTable
ALTER TABLE "ProfilePPID" DROP COLUMN "imageUrl",
ADD COLUMN "imageId" TEXT;
-- DropTable
DROP TABLE "Images";
-- DropTable
DROP TABLE "Videos";
-- CreateTable
CREATE TABLE "StrukturPPID" (
"id" TEXT NOT NULL,
"name" 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 "StrukturPPID_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ProfileDesaImage" (
"id" TEXT NOT NULL,
"label" TEXT NOT NULL,
"profileDesaId" TEXT NOT NULL,
"imageId" TEXT NOT NULL,
CONSTRAINT "ProfileDesaImage_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "PelayananSuratKeterangan" (
"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 "PelayananSuratKeterangan_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "PelayananTelunjukSaktiDesa" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"deskripsi" TEXT NOT NULL,
"link" 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 "PelayananTelunjukSaktiDesa_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "PelayananPerizinanBerusaha" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"deskripsi" TEXT NOT NULL,
"link" 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 "PelayananPerizinanBerusaha_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "PelayananPendudukNonPermanen" (
"id" TEXT NOT NULL,
"name" 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 "PelayananPendudukNonPermanen_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Penghargaan" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"juara" 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 "Penghargaan_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Posyandu" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"nomor" 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 "Posyandu_pkey" PRIMARY KEY ("id")
);
-- AddForeignKey
ALTER TABLE "StrukturPPID" ADD CONSTRAINT "StrukturPPID_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ProfilePPID" ADD CONSTRAINT "ProfilePPID_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ProfileDesaImage" ADD CONSTRAINT "ProfileDesaImage_profileDesaId_fkey" FOREIGN KEY ("profileDesaId") REFERENCES "ProfileDesa"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ProfileDesaImage" ADD CONSTRAINT "ProfileDesaImage_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "GalleryFoto" ADD CONSTRAINT "GalleryFoto_imagesId_fkey" FOREIGN KEY ("imagesId") REFERENCES "FileStorage"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PelayananSuratKeterangan" ADD CONSTRAINT "PelayananSuratKeterangan_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Penghargaan" ADD CONSTRAINT "Penghargaan_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Posyandu" ADD CONSTRAINT "Posyandu_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@@ -0,0 +1,78 @@
/*
Warnings:
- You are about to drop the column `profileDesaId` on the `ProfileDesaImage` table. All the data in the column will be lost.
- You are about to drop the `ProfileDesa` table. If the table is not empty, all the data it contains will be lost.
- Added the required column `maskotDesaId` to the `ProfileDesaImage` table without a default value. This is not possible if the table is not empty.
*/
-- DropForeignKey
ALTER TABLE "ProfileDesaImage" DROP CONSTRAINT "ProfileDesaImage_profileDesaId_fkey";
-- AlterTable
ALTER TABLE "ProfilPerbekel" ADD COLUMN "imageId" TEXT;
-- AlterTable
ALTER TABLE "ProfileDesaImage" DROP COLUMN "profileDesaId",
ADD COLUMN "maskotDesaId" TEXT NOT NULL;
-- DropTable
DROP TABLE "ProfileDesa";
-- CreateTable
CREATE TABLE "SejarahDesa" (
"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 "SejarahDesa_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "VisiMisiDesa" (
"id" TEXT NOT NULL,
"visi" TEXT NOT NULL,
"misi" 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 "VisiMisiDesa_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "LambangDesa" (
"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 "LambangDesa_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "MaskotDesa" (
"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 "MaskotDesa_pkey" PRIMARY KEY ("id")
);
-- AddForeignKey
ALTER TABLE "ProfileDesaImage" ADD CONSTRAINT "ProfileDesaImage_maskotDesaId_fkey" FOREIGN KEY ("maskotDesaId") REFERENCES "MaskotDesa"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ProfilPerbekel" ADD CONSTRAINT "ProfilPerbekel_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE SET NULL ON UPDATE CASCADE;

View File

@@ -0,0 +1,139 @@
/*
Warnings:
- The primary key for the `DataKematian_Kelahiran` table will be changed. If it partially fails, the table could be left without primary key constraint.
*/
-- AlterTable
ALTER TABLE "DataKematian_Kelahiran" DROP CONSTRAINT "DataKematian_Kelahiran_pkey",
ALTER COLUMN "id" DROP DEFAULT,
ALTER COLUMN "id" SET DATA TYPE TEXT,
ADD CONSTRAINT "DataKematian_Kelahiran_pkey" PRIMARY KEY ("id");
DROP SEQUENCE "DataKematian_Kelahiran_id_seq";
-- AlterTable
ALTER TABLE "ProfilePPID" ADD COLUMN "imageUrl" TEXT;
-- CreateTable
CREATE TABLE "Puskesmas" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"alamat" TEXT NOT NULL,
"jamId" TEXT NOT NULL,
"imageId" TEXT NOT NULL,
"kontakId" 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 "Puskesmas_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "JamOperasional" (
"id" TEXT NOT NULL,
"workDays" TEXT NOT NULL,
"weekDays" TEXT NOT NULL,
"holiday" 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 "JamOperasional_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "KontakPuskesmas" (
"id" TEXT NOT NULL,
"kontakPuskesmas" TEXT NOT NULL,
"email" TEXT NOT NULL,
"facebook" TEXT NOT NULL,
"kontakUGD" 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 "KontakPuskesmas_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ProgramKesehatan" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"deskripsiSingkat" 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 "ProgramKesehatan_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "PenangananDarurat" (
"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 "PenangananDarurat_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "KontakDarurat" (
"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 "KontakDarurat_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "InfoWabahPenyakit" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"deskripsiSingkat" TEXT NOT NULL,
"deskripsiLengkap" 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 "InfoWabahPenyakit_pkey" PRIMARY KEY ("id")
);
-- AddForeignKey
ALTER TABLE "Puskesmas" ADD CONSTRAINT "Puskesmas_jamId_fkey" FOREIGN KEY ("jamId") REFERENCES "JamOperasional"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Puskesmas" ADD CONSTRAINT "Puskesmas_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Puskesmas" ADD CONSTRAINT "Puskesmas_kontakId_fkey" FOREIGN KEY ("kontakId") REFERENCES "KontakPuskesmas"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ProgramKesehatan" ADD CONSTRAINT "ProgramKesehatan_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PenangananDarurat" ADD CONSTRAINT "PenangananDarurat_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "KontakDarurat" ADD CONSTRAINT "KontakDarurat_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "InfoWabahPenyakit" ADD CONSTRAINT "InfoWabahPenyakit_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@@ -0,0 +1,12 @@
/*
Warnings:
- The primary key for the `GrafikKepuasan` table will be changed. If it partially fails, the table could be left without primary key constraint.
*/
-- AlterTable
ALTER TABLE "GrafikKepuasan" DROP CONSTRAINT "GrafikKepuasan_pkey",
ALTER COLUMN "id" DROP DEFAULT,
ALTER COLUMN "id" SET DATA TYPE TEXT,
ADD CONSTRAINT "GrafikKepuasan_pkey" PRIMARY KEY ("id");
DROP SEQUENCE "GrafikKepuasan_id_seq";

View File

@@ -0,0 +1,96 @@
/*
Warnings:
- You are about to drop the `_DokterdanTenagaMedisToFasilitasKesehatan` table. If the table is not empty, all the data it contains will be lost.
- You are about to drop the `_FasilitasKesehatanToFasilitasPendukung` table. If the table is not empty, all the data it contains will be lost.
- You are about to drop the `_FasilitasKesehatanToInformasiUmum` table. If the table is not empty, all the data it contains will be lost.
- You are about to drop the `_FasilitasKesehatanToLayananUnggulan` table. If the table is not empty, all the data it contains will be lost.
- You are about to drop the `_FasilitasKesehatanToProsedurPendaftaran` table. If the table is not empty, all the data it contains will be lost.
- You are about to drop the `_FasilitasKesehatanToTarifDanLayanan` table. If the table is not empty, all the data it contains will be lost.
- Added the required column `dokterdanTenagaMedisId` to the `FasilitasKesehatan` table without a default value. This is not possible if the table is not empty.
- Added the required column `fasilitasPendukungId` to the `FasilitasKesehatan` table without a default value. This is not possible if the table is not empty.
- Added the required column `informasiUmumId` to the `FasilitasKesehatan` table without a default value. This is not possible if the table is not empty.
- Added the required column `layananUnggulanId` to the `FasilitasKesehatan` table without a default value. This is not possible if the table is not empty.
- Added the required column `prosedurPendaftaranId` to the `FasilitasKesehatan` table without a default value. This is not possible if the table is not empty.
- Added the required column `tarifDanLayananId` to the `FasilitasKesehatan` table without a default value. This is not possible if the table is not empty.
*/
-- DropForeignKey
ALTER TABLE "_DokterdanTenagaMedisToFasilitasKesehatan" DROP CONSTRAINT "_DokterdanTenagaMedisToFasilitasKesehatan_A_fkey";
-- DropForeignKey
ALTER TABLE "_DokterdanTenagaMedisToFasilitasKesehatan" DROP CONSTRAINT "_DokterdanTenagaMedisToFasilitasKesehatan_B_fkey";
-- DropForeignKey
ALTER TABLE "_FasilitasKesehatanToFasilitasPendukung" DROP CONSTRAINT "_FasilitasKesehatanToFasilitasPendukung_A_fkey";
-- DropForeignKey
ALTER TABLE "_FasilitasKesehatanToFasilitasPendukung" DROP CONSTRAINT "_FasilitasKesehatanToFasilitasPendukung_B_fkey";
-- DropForeignKey
ALTER TABLE "_FasilitasKesehatanToInformasiUmum" DROP CONSTRAINT "_FasilitasKesehatanToInformasiUmum_A_fkey";
-- DropForeignKey
ALTER TABLE "_FasilitasKesehatanToInformasiUmum" DROP CONSTRAINT "_FasilitasKesehatanToInformasiUmum_B_fkey";
-- DropForeignKey
ALTER TABLE "_FasilitasKesehatanToLayananUnggulan" DROP CONSTRAINT "_FasilitasKesehatanToLayananUnggulan_A_fkey";
-- DropForeignKey
ALTER TABLE "_FasilitasKesehatanToLayananUnggulan" DROP CONSTRAINT "_FasilitasKesehatanToLayananUnggulan_B_fkey";
-- DropForeignKey
ALTER TABLE "_FasilitasKesehatanToProsedurPendaftaran" DROP CONSTRAINT "_FasilitasKesehatanToProsedurPendaftaran_A_fkey";
-- DropForeignKey
ALTER TABLE "_FasilitasKesehatanToProsedurPendaftaran" DROP CONSTRAINT "_FasilitasKesehatanToProsedurPendaftaran_B_fkey";
-- DropForeignKey
ALTER TABLE "_FasilitasKesehatanToTarifDanLayanan" DROP CONSTRAINT "_FasilitasKesehatanToTarifDanLayanan_A_fkey";
-- DropForeignKey
ALTER TABLE "_FasilitasKesehatanToTarifDanLayanan" DROP CONSTRAINT "_FasilitasKesehatanToTarifDanLayanan_B_fkey";
-- AlterTable
ALTER TABLE "FasilitasKesehatan" ADD COLUMN "dokterdanTenagaMedisId" TEXT NOT NULL,
ADD COLUMN "fasilitasPendukungId" TEXT NOT NULL,
ADD COLUMN "informasiUmumId" TEXT NOT NULL,
ADD COLUMN "layananUnggulanId" TEXT NOT NULL,
ADD COLUMN "prosedurPendaftaranId" TEXT NOT NULL,
ADD COLUMN "tarifDanLayananId" TEXT NOT NULL;
-- DropTable
DROP TABLE "_DokterdanTenagaMedisToFasilitasKesehatan";
-- DropTable
DROP TABLE "_FasilitasKesehatanToFasilitasPendukung";
-- DropTable
DROP TABLE "_FasilitasKesehatanToInformasiUmum";
-- DropTable
DROP TABLE "_FasilitasKesehatanToLayananUnggulan";
-- DropTable
DROP TABLE "_FasilitasKesehatanToProsedurPendaftaran";
-- DropTable
DROP TABLE "_FasilitasKesehatanToTarifDanLayanan";
-- AddForeignKey
ALTER TABLE "FasilitasKesehatan" ADD CONSTRAINT "FasilitasKesehatan_informasiUmumId_fkey" FOREIGN KEY ("informasiUmumId") REFERENCES "InformasiUmum"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "FasilitasKesehatan" ADD CONSTRAINT "FasilitasKesehatan_layananUnggulanId_fkey" FOREIGN KEY ("layananUnggulanId") REFERENCES "LayananUnggulan"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "FasilitasKesehatan" ADD CONSTRAINT "FasilitasKesehatan_dokterdanTenagaMedisId_fkey" FOREIGN KEY ("dokterdanTenagaMedisId") REFERENCES "DokterdanTenagaMedis"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "FasilitasKesehatan" ADD CONSTRAINT "FasilitasKesehatan_fasilitasPendukungId_fkey" FOREIGN KEY ("fasilitasPendukungId") REFERENCES "FasilitasPendukung"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "FasilitasKesehatan" ADD CONSTRAINT "FasilitasKesehatan_prosedurPendaftaranId_fkey" FOREIGN KEY ("prosedurPendaftaranId") REFERENCES "ProsedurPendaftaran"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "FasilitasKesehatan" ADD CONSTRAINT "FasilitasKesehatan_tarifDanLayananId_fkey" FOREIGN KEY ("tarifDanLayananId") REFERENCES "TarifDanLayanan"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@@ -0,0 +1,32 @@
/*
Warnings:
- The primary key for the `DataKematian_Kelahiran` table will be changed. If it partially fails, the table could be left without primary key constraint.
- The `id` column on the `DataKematian_Kelahiran` table would be dropped and recreated. This will lead to data loss if there is data in the column.
- The primary key for the `GrafikKepuasan` table will be changed. If it partially fails, the table could be left without primary key constraint.
- The `id` column on the `GrafikKepuasan` table would be dropped and recreated. This will lead to data loss if there is data in the column.
- A unique constraint covering the columns `[uuid]` on the table `DataKematian_Kelahiran` will be added. If there are existing duplicate values, this will fail.
- A unique constraint covering the columns `[uuid]` on the table `GrafikKepuasan` will be added. If there are existing duplicate values, this will fail.
- The required column `uuid` was added to the `DataKematian_Kelahiran` table with a prisma-level default value. This is not possible if the table is not empty. Please add this column as optional, then populate it before making it required.
- The required column `uuid` was added to the `GrafikKepuasan` table with a prisma-level default value. This is not possible if the table is not empty. Please add this column as optional, then populate it before making it required.
*/
-- AlterTable
ALTER TABLE "DataKematian_Kelahiran" DROP CONSTRAINT "DataKematian_Kelahiran_pkey",
ADD COLUMN "uuid" TEXT NOT NULL,
DROP COLUMN "id",
ADD COLUMN "id" SERIAL NOT NULL,
ADD CONSTRAINT "DataKematian_Kelahiran_pkey" PRIMARY KEY ("id");
-- AlterTable
ALTER TABLE "GrafikKepuasan" DROP CONSTRAINT "GrafikKepuasan_pkey",
ADD COLUMN "uuid" TEXT NOT NULL,
DROP COLUMN "id",
ADD COLUMN "id" SERIAL NOT NULL,
ADD CONSTRAINT "GrafikKepuasan_pkey" PRIMARY KEY ("id");
-- CreateIndex
CREATE UNIQUE INDEX "DataKematian_Kelahiran_uuid_key" ON "DataKematian_Kelahiran"("uuid");
-- CreateIndex
CREATE UNIQUE INDEX "GrafikKepuasan_uuid_key" ON "GrafikKepuasan"("uuid");

View File

@@ -0,0 +1,92 @@
/*
Warnings:
- The primary key for the `ArtikelKesehatan` table will be changed. If it partially fails, the table could be left without primary key constraint.
- The primary key for the `DoctorSign` table will be changed. If it partially fails, the table could be left without primary key constraint.
- The primary key for the `FirstAid` table will be changed. If it partially fails, the table could be left without primary key constraint.
- The primary key for the `Introduction` table will be changed. If it partially fails, the table could be left without primary key constraint.
- The primary key for the `MythVsFact` table will be changed. If it partially fails, the table could be left without primary key constraint.
- The primary key for the `Prevention` table will be changed. If it partially fails, the table could be left without primary key constraint.
- The primary key for the `Symptom` table will be changed. If it partially fails, the table could be left without primary key constraint.
- Added the required column `deskripsiJadwalKegiatanId` to the `JadwalKegiatan` table without a default value. This is not possible if the table is not empty.
- Added the required column `dokumenJadwalKegiatanId` to the `JadwalKegiatan` table without a default value. This is not possible if the table is not empty.
- Added the required column `informasiJadwalKegiatanId` to the `JadwalKegiatan` table without a default value. This is not possible if the table is not empty.
- Added the required column `layananJadwalKegiatanId` to the `JadwalKegiatan` table without a default value. This is not possible if the table is not empty.
- Added the required column `pendaftaranJadwalKegiatanId` to the `JadwalKegiatan` table without a default value. This is not possible if the table is not empty.
- Added the required column `syaratKetentuanJadwalKegiatanId` to the `JadwalKegiatan` table without a default value. This is not possible if the table is not empty.
*/
-- AlterTable
ALTER TABLE "ArtikelKesehatan" DROP CONSTRAINT "ArtikelKesehatan_pkey",
ALTER COLUMN "id" DROP DEFAULT,
ALTER COLUMN "id" SET DATA TYPE TEXT,
ADD CONSTRAINT "ArtikelKesehatan_pkey" PRIMARY KEY ("id");
DROP SEQUENCE "ArtikelKesehatan_id_seq";
-- AlterTable
ALTER TABLE "DoctorSign" DROP CONSTRAINT "DoctorSign_pkey",
ALTER COLUMN "id" DROP DEFAULT,
ALTER COLUMN "id" SET DATA TYPE TEXT,
ADD CONSTRAINT "DoctorSign_pkey" PRIMARY KEY ("id");
DROP SEQUENCE "DoctorSign_id_seq";
-- AlterTable
ALTER TABLE "FirstAid" DROP CONSTRAINT "FirstAid_pkey",
ALTER COLUMN "id" DROP DEFAULT,
ALTER COLUMN "id" SET DATA TYPE TEXT,
ADD CONSTRAINT "FirstAid_pkey" PRIMARY KEY ("id");
DROP SEQUENCE "FirstAid_id_seq";
-- AlterTable
ALTER TABLE "Introduction" DROP CONSTRAINT "Introduction_pkey",
ALTER COLUMN "id" DROP DEFAULT,
ALTER COLUMN "id" SET DATA TYPE TEXT,
ADD CONSTRAINT "Introduction_pkey" PRIMARY KEY ("id");
DROP SEQUENCE "Introduction_id_seq";
-- AlterTable
ALTER TABLE "JadwalKegiatan" ADD COLUMN "deskripsiJadwalKegiatanId" TEXT NOT NULL,
ADD COLUMN "dokumenJadwalKegiatanId" TEXT NOT NULL,
ADD COLUMN "informasiJadwalKegiatanId" TEXT NOT NULL,
ADD COLUMN "layananJadwalKegiatanId" TEXT NOT NULL,
ADD COLUMN "pendaftaranJadwalKegiatanId" TEXT NOT NULL,
ADD COLUMN "syaratKetentuanJadwalKegiatanId" TEXT NOT NULL;
-- AlterTable
ALTER TABLE "MythVsFact" DROP CONSTRAINT "MythVsFact_pkey",
ALTER COLUMN "id" DROP DEFAULT,
ALTER COLUMN "id" SET DATA TYPE TEXT,
ADD CONSTRAINT "MythVsFact_pkey" PRIMARY KEY ("id");
DROP SEQUENCE "MythVsFact_id_seq";
-- AlterTable
ALTER TABLE "Prevention" DROP CONSTRAINT "Prevention_pkey",
ALTER COLUMN "id" DROP DEFAULT,
ALTER COLUMN "id" SET DATA TYPE TEXT,
ADD CONSTRAINT "Prevention_pkey" PRIMARY KEY ("id");
DROP SEQUENCE "Prevention_id_seq";
-- AlterTable
ALTER TABLE "Symptom" DROP CONSTRAINT "Symptom_pkey",
ALTER COLUMN "id" DROP DEFAULT,
ALTER COLUMN "id" SET DATA TYPE TEXT,
ADD CONSTRAINT "Symptom_pkey" PRIMARY KEY ("id");
DROP SEQUENCE "Symptom_id_seq";
-- AddForeignKey
ALTER TABLE "JadwalKegiatan" ADD CONSTRAINT "JadwalKegiatan_informasiJadwalKegiatanId_fkey" FOREIGN KEY ("informasiJadwalKegiatanId") REFERENCES "InformasiJadwalKegiatan"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "JadwalKegiatan" ADD CONSTRAINT "JadwalKegiatan_deskripsiJadwalKegiatanId_fkey" FOREIGN KEY ("deskripsiJadwalKegiatanId") REFERENCES "DeskripsiJadwalKegiatan"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "JadwalKegiatan" ADD CONSTRAINT "JadwalKegiatan_layananJadwalKegiatanId_fkey" FOREIGN KEY ("layananJadwalKegiatanId") REFERENCES "LayananJadwalKegiatan"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "JadwalKegiatan" ADD CONSTRAINT "JadwalKegiatan_syaratKetentuanJadwalKegiatanId_fkey" FOREIGN KEY ("syaratKetentuanJadwalKegiatanId") REFERENCES "SyaratKetentuanJadwalKegiatan"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "JadwalKegiatan" ADD CONSTRAINT "JadwalKegiatan_dokumenJadwalKegiatanId_fkey" FOREIGN KEY ("dokumenJadwalKegiatanId") REFERENCES "DokumenJadwalKegiatan"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "JadwalKegiatan" ADD CONSTRAINT "JadwalKegiatan_pendaftaranJadwalKegiatanId_fkey" FOREIGN KEY ("pendaftaranJadwalKegiatanId") REFERENCES "PendaftaranJadwalKegiatan"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@@ -0,0 +1,68 @@
/*
Warnings:
- The primary key for the `DataKematian_Kelahiran` table will be changed. If it partially fails, the table could be left without primary key constraint.
- You are about to drop the column `uuid` on the `DataKematian_Kelahiran` table. All the data in the column will be lost.
- The primary key for the `GrafikKepuasan` table will be changed. If it partially fails, the table could be left without primary key constraint.
- You are about to drop the column `uuid` on the `GrafikKepuasan` table. All the data in the column will be lost.
- A unique constraint covering the columns `[id]` on the table `DataKematian_Kelahiran` will be added. If there are existing duplicate values, this will fail.
- A unique constraint covering the columns `[id]` on the table `GrafikKepuasan` will be added. If there are existing duplicate values, this will fail.
- Added the required column `doctorSignId` to the `ArtikelKesehatan` table without a default value. This is not possible if the table is not empty.
- Added the required column `firstAidId` to the `ArtikelKesehatan` table without a default value. This is not possible if the table is not empty.
- Added the required column `introductionId` to the `ArtikelKesehatan` table without a default value. This is not possible if the table is not empty.
- Added the required column `mythVsFactId` to the `ArtikelKesehatan` table without a default value. This is not possible if the table is not empty.
- Added the required column `preventionId` to the `ArtikelKesehatan` table without a default value. This is not possible if the table is not empty.
- Added the required column `symptomId` to the `ArtikelKesehatan` table without a default value. This is not possible if the table is not empty.
*/
-- DropIndex
DROP INDEX "DataKematian_Kelahiran_uuid_key";
-- DropIndex
DROP INDEX "GrafikKepuasan_uuid_key";
-- AlterTable
ALTER TABLE "ArtikelKesehatan" ADD COLUMN "doctorSignId" TEXT NOT NULL,
ADD COLUMN "firstAidId" TEXT NOT NULL,
ADD COLUMN "introductionId" TEXT NOT NULL,
ADD COLUMN "mythVsFactId" TEXT NOT NULL,
ADD COLUMN "preventionId" TEXT NOT NULL,
ADD COLUMN "symptomId" TEXT NOT NULL;
-- AlterTable
ALTER TABLE "DataKematian_Kelahiran" DROP CONSTRAINT "DataKematian_Kelahiran_pkey",
DROP COLUMN "uuid",
ALTER COLUMN "id" DROP DEFAULT,
ALTER COLUMN "id" SET DATA TYPE TEXT;
DROP SEQUENCE "DataKematian_Kelahiran_id_seq";
-- AlterTable
ALTER TABLE "GrafikKepuasan" DROP CONSTRAINT "GrafikKepuasan_pkey",
DROP COLUMN "uuid",
ALTER COLUMN "id" DROP DEFAULT,
ALTER COLUMN "id" SET DATA TYPE TEXT;
DROP SEQUENCE "GrafikKepuasan_id_seq";
-- CreateIndex
CREATE UNIQUE INDEX "DataKematian_Kelahiran_id_key" ON "DataKematian_Kelahiran"("id");
-- CreateIndex
CREATE UNIQUE INDEX "GrafikKepuasan_id_key" ON "GrafikKepuasan"("id");
-- AddForeignKey
ALTER TABLE "ArtikelKesehatan" ADD CONSTRAINT "ArtikelKesehatan_introductionId_fkey" FOREIGN KEY ("introductionId") REFERENCES "Introduction"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ArtikelKesehatan" ADD CONSTRAINT "ArtikelKesehatan_symptomId_fkey" FOREIGN KEY ("symptomId") REFERENCES "Symptom"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ArtikelKesehatan" ADD CONSTRAINT "ArtikelKesehatan_preventionId_fkey" FOREIGN KEY ("preventionId") REFERENCES "Prevention"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ArtikelKesehatan" ADD CONSTRAINT "ArtikelKesehatan_firstAidId_fkey" FOREIGN KEY ("firstAidId") REFERENCES "FirstAid"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ArtikelKesehatan" ADD CONSTRAINT "ArtikelKesehatan_mythVsFactId_fkey" FOREIGN KEY ("mythVsFactId") REFERENCES "MythVsFact"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ArtikelKesehatan" ADD CONSTRAINT "ArtikelKesehatan_doctorSignId_fkey" FOREIGN KEY ("doctorSignId") REFERENCES "DoctorSign"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@@ -0,0 +1,201 @@
-- CreateEnum
CREATE TYPE "StatusLaporan" AS ENUM ('SELESAI', 'PROSES', 'GAGAL');
-- AlterTable
ALTER TABLE "DataKematian_Kelahiran" ADD CONSTRAINT "DataKematian_Kelahiran_pkey" PRIMARY KEY ("id");
-- DropIndex
DROP INDEX "DataKematian_Kelahiran_id_key";
-- AlterTable
ALTER TABLE "GrafikKepuasan" ADD CONSTRAINT "GrafikKepuasan_pkey" PRIMARY KEY ("id");
-- DropIndex
DROP INDEX "GrafikKepuasan_id_key";
-- CreateTable
CREATE TABLE "KeamananLingkungan" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"deskripsi" 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 "KeamananLingkungan_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "PolsekTerdekat" (
"id" TEXT NOT NULL,
"nama" TEXT NOT NULL,
"jarakKeDesa" TEXT NOT NULL,
"alamat" TEXT NOT NULL,
"nomorTelepon" TEXT NOT NULL,
"jamOperasional" TEXT NOT NULL,
"embedMapUrl" TEXT NOT NULL,
"namaTempatMaps" TEXT NOT NULL,
"alamatMaps" TEXT NOT NULL,
"linkPetunjukArah" TEXT NOT NULL,
"layananPolsekId" 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 "PolsekTerdekat_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "LayananPolsek" (
"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 "LayananPolsek_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "KontakDaruratKeamanan" (
"id" TEXT NOT NULL,
"nama" TEXT NOT NULL,
"kontak" TEXT NOT NULL,
"icon" 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 "KontakDaruratKeamanan_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "PencegahanKriminalitas" (
"id" TEXT NOT NULL,
"programKeamananId" TEXT NOT NULL,
"tipsKeamananId" TEXT NOT NULL,
"videoKeamananId" 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 "PencegahanKriminalitas_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ProgramKeamanan" (
"id" TEXT NOT NULL,
"nama" TEXT NOT NULL,
"deskripsi" TEXT,
"slug" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "ProgramKeamanan_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "TipsKeamanan" (
"id" TEXT NOT NULL,
"judul" TEXT NOT NULL,
"konten" TEXT NOT NULL,
"slug" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "TipsKeamanan_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "VideoKeamanan" (
"id" TEXT NOT NULL,
"judul" TEXT NOT NULL,
"deskripsi" TEXT,
"videoUrl" TEXT NOT NULL,
"slug" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "VideoKeamanan_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "LaporanPublik" (
"id" TEXT NOT NULL,
"judul" TEXT NOT NULL,
"lokasi" TEXT NOT NULL,
"tanggalWaktu" TIMESTAMP(3) NOT NULL,
"status" "StatusLaporan" NOT NULL,
"kronologi" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "LaporanPublik_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "PenangananLaporanPublik" (
"id" TEXT NOT NULL,
"laporanId" TEXT NOT NULL,
"deskripsi" TEXT NOT NULL,
CONSTRAINT "PenangananLaporanPublik_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Pelapor" (
"id" TEXT NOT NULL,
"nama" TEXT NOT NULL,
"alamat" TEXT NOT NULL,
"nomorTelepon" TEXT NOT NULL,
"imageId" TEXT NOT NULL,
CONSTRAINT "Pelapor_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "MenuTipsKeamanan" (
"id" TEXT NOT NULL,
"judul" TEXT NOT NULL,
"deskripsi" 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 "MenuTipsKeamanan_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "ProgramKeamanan_slug_key" ON "ProgramKeamanan"("slug");
-- AddForeignKey
ALTER TABLE "KeamananLingkungan" ADD CONSTRAINT "KeamananLingkungan_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PolsekTerdekat" ADD CONSTRAINT "PolsekTerdekat_layananPolsekId_fkey" FOREIGN KEY ("layananPolsekId") REFERENCES "LayananPolsek"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PencegahanKriminalitas" ADD CONSTRAINT "PencegahanKriminalitas_programKeamananId_fkey" FOREIGN KEY ("programKeamananId") REFERENCES "ProgramKeamanan"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PencegahanKriminalitas" ADD CONSTRAINT "PencegahanKriminalitas_tipsKeamananId_fkey" FOREIGN KEY ("tipsKeamananId") REFERENCES "TipsKeamanan"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PencegahanKriminalitas" ADD CONSTRAINT "PencegahanKriminalitas_videoKeamananId_fkey" FOREIGN KEY ("videoKeamananId") REFERENCES "VideoKeamanan"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PenangananLaporanPublik" ADD CONSTRAINT "PenangananLaporanPublik_laporanId_fkey" FOREIGN KEY ("laporanId") REFERENCES "LaporanPublik"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Pelapor" ADD CONSTRAINT "Pelapor_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "MenuTipsKeamanan" ADD CONSTRAINT "MenuTipsKeamanan_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE SET NULL ON UPDATE CASCADE;

View File

@@ -0,0 +1,3 @@
-- AlterTable
ALTER TABLE "LayananPolsek" ALTER COLUMN "deletedAt" DROP NOT NULL,
ALTER COLUMN "deletedAt" DROP DEFAULT;

View File

@@ -0,0 +1,75 @@
/*
Warnings:
- You are about to drop the column `deletedAt` on the `KontakDarurat` table. All the data in the column will be lost.
- You are about to drop the column `deskripsi` on the `KontakDarurat` table. All the data in the column will be lost.
- You are about to drop the column `imageId` on the `KontakDarurat` table. All the data in the column will be lost.
- You are about to drop the column `isActive` on the `KontakDarurat` table. All the data in the column will be lost.
- You are about to drop the column `name` on the `KontakDarurat` table. All the data in the column will be lost.
- Added the required column `nama` to the `KontakDarurat` table without a default value. This is not possible if the table is not empty.
*/
-- DropForeignKey
ALTER TABLE "KontakDarurat" DROP CONSTRAINT "KontakDarurat_imageId_fkey";
-- AlterTable
ALTER TABLE "KontakDarurat" DROP COLUMN "deletedAt",
DROP COLUMN "deskripsi",
DROP COLUMN "imageId",
DROP COLUMN "isActive",
DROP COLUMN "name",
ADD COLUMN "icon" TEXT,
ADD COLUMN "nama" TEXT NOT NULL,
ADD COLUMN "urutan" INTEGER;
-- CreateTable
CREATE TABLE "KontakItem" (
"id" TEXT NOT NULL,
"nama" TEXT NOT NULL,
"nomorTelepon" TEXT NOT NULL,
"icon" TEXT,
"kategoriId" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "KontakItem_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "PasarDesa" (
"id" TEXT NOT NULL,
"nama" TEXT NOT NULL,
"harga" INTEGER NOT NULL,
"satuan" TEXT NOT NULL,
"alamat" TEXT NOT NULL,
"imageId" TEXT NOT NULL,
"rating" 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,
"kategoriId" TEXT NOT NULL,
CONSTRAINT "PasarDesa_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "KategoriMakanan" (
"id" TEXT NOT NULL,
"nama" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3),
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "KategoriMakanan_pkey" PRIMARY KEY ("id")
);
-- AddForeignKey
ALTER TABLE "KontakItem" ADD CONSTRAINT "KontakItem_kategoriId_fkey" FOREIGN KEY ("kategoriId") REFERENCES "KontakDarurat"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PasarDesa" ADD CONSTRAINT "PasarDesa_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PasarDesa" ADD CONSTRAINT "PasarDesa_kategoriId_fkey" FOREIGN KEY ("kategoriId") REFERENCES "KategoriMakanan"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@@ -0,0 +1,38 @@
/*
Warnings:
- You are about to drop the column `icon` on the `KontakDarurat` table. All the data in the column will be lost.
- You are about to drop the column `nama` on the `KontakDarurat` table. All the data in the column will be lost.
- You are about to drop the column `urutan` on the `KontakDarurat` table. All the data in the column will be lost.
- You are about to drop the column `deletedAt` on the `KontakDaruratKeamanan` table. All the data in the column will be lost.
- You are about to drop the column `isActive` on the `KontakDaruratKeamanan` table. All the data in the column will be lost.
- You are about to drop the column `kontak` on the `KontakDaruratKeamanan` table. All the data in the column will be lost.
- Added the required column `deskripsi` to the `KontakDarurat` table without a default value. This is not possible if the table is not empty.
- Added the required column `imageId` to the `KontakDarurat` table without a default value. This is not possible if the table is not empty.
- Added the required column `name` to the `KontakDarurat` table without a default value. This is not possible if the table is not empty.
*/
-- DropForeignKey
ALTER TABLE "KontakItem" DROP CONSTRAINT "KontakItem_kategoriId_fkey";
-- AlterTable
ALTER TABLE "KontakDarurat" DROP COLUMN "icon",
DROP COLUMN "nama",
DROP COLUMN "urutan",
ADD COLUMN "deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
ADD COLUMN "deskripsi" TEXT NOT NULL,
ADD COLUMN "imageId" TEXT NOT NULL,
ADD COLUMN "isActive" BOOLEAN NOT NULL DEFAULT true,
ADD COLUMN "name" TEXT NOT NULL;
-- AlterTable
ALTER TABLE "KontakDaruratKeamanan" DROP COLUMN "deletedAt",
DROP COLUMN "isActive",
DROP COLUMN "kontak",
ADD COLUMN "urutan" INTEGER;
-- AddForeignKey
ALTER TABLE "KontakDarurat" ADD CONSTRAINT "KontakDarurat_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "KontakItem" ADD CONSTRAINT "KontakItem_kategoriId_fkey" FOREIGN KEY ("kategoriId") REFERENCES "KontakDaruratKeamanan"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@@ -0,0 +1,85 @@
/*
Warnings:
- The values [SELESAI,PROSES,GAGAL] on the enum `StatusLaporan` will be removed. If these variants are still used in the database, this will fail.
- You are about to drop the column `icon` on the `KontakDaruratKeamanan` table. All the data in the column will be lost.
- You are about to drop the column `urutan` on the `KontakDaruratKeamanan` table. All the data in the column will be lost.
- You are about to drop the column `icon` on the `KontakItem` table. All the data in the column will be lost.
*/
-- AlterEnum
BEGIN;
CREATE TYPE "StatusLaporan_new" AS ENUM ('Selesai', 'Proses', 'Gagal');
ALTER TABLE "LaporanPublik" ALTER COLUMN "status" TYPE "StatusLaporan_new" USING ("status"::text::"StatusLaporan_new");
ALTER TYPE "StatusLaporan" RENAME TO "StatusLaporan_old";
ALTER TYPE "StatusLaporan_new" RENAME TO "StatusLaporan";
DROP TYPE "StatusLaporan_old";
COMMIT;
-- AlterTable
ALTER TABLE "KontakDaruratKeamanan" DROP COLUMN "icon",
DROP COLUMN "urutan",
ADD COLUMN "imageId" TEXT;
-- AlterTable
ALTER TABLE "KontakItem" DROP COLUMN "icon",
ADD COLUMN "imageId" TEXT;
-- CreateTable
CREATE TABLE "LowonganPekerjaan" (
"id" TEXT NOT NULL,
"posisi" TEXT NOT NULL,
"namaPerusahaan" TEXT NOT NULL,
"lokasi" TEXT NOT NULL,
"tipePekerjaan" TEXT NOT NULL,
"gaji" TEXT NOT NULL,
"deskripsi" TEXT NOT NULL,
"kualifikasi" TEXT NOT NULL,
"tanggalPosting" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "LowonganPekerjaan_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ProgramKemiskinan" (
"id" TEXT NOT NULL,
"nama" TEXT NOT NULL,
"deskripsi" TEXT NOT NULL,
"ikonUrl" TEXT,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"statistikId" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "ProgramKemiskinan_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "StatistikKemiskinan" (
"id" TEXT NOT NULL,
"tahun" INTEGER NOT NULL,
"jumlah" INTEGER NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "StatistikKemiskinan_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "ProgramKemiskinan_statistikId_key" ON "ProgramKemiskinan"("statistikId");
-- CreateIndex
CREATE UNIQUE INDEX "StatistikKemiskinan_tahun_key" ON "StatistikKemiskinan"("tahun");
-- AddForeignKey
ALTER TABLE "KontakDaruratKeamanan" ADD CONSTRAINT "KontakDaruratKeamanan_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "KontakItem" ADD CONSTRAINT "KontakItem_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ProgramKemiskinan" ADD CONSTRAINT "ProgramKemiskinan_statistikId_fkey" FOREIGN KEY ("statistikId") REFERENCES "StatistikKemiskinan"("id") ON DELETE SET NULL ON UPDATE CASCADE;

View File

@@ -0,0 +1,59 @@
/*
Warnings:
- You are about to drop the column `alamat` on the `PasarDesa` table. All the data in the column will be lost.
- You are about to drop the column `deletedAt` on the `PasarDesa` table. All the data in the column will be lost.
- You are about to drop the column `isActive` on the `PasarDesa` table. All the data in the column will be lost.
- You are about to drop the column `kategoriId` on the `PasarDesa` table. All the data in the column will be lost.
- You are about to drop the column `satuan` on the `PasarDesa` table. All the data in the column will be lost.
- You are about to drop the `KategoriMakanan` table. If the table is not empty, all the data it contains will be lost.
- Added the required column `alamatUsaha` to the `PasarDesa` table without a default value. This is not possible if the table is not empty.
*/
-- DropForeignKey
ALTER TABLE "PasarDesa" DROP CONSTRAINT "PasarDesa_imageId_fkey";
-- DropForeignKey
ALTER TABLE "PasarDesa" DROP CONSTRAINT "PasarDesa_kategoriId_fkey";
-- AlterTable
ALTER TABLE "PasarDesa" DROP COLUMN "alamat",
DROP COLUMN "deletedAt",
DROP COLUMN "isActive",
DROP COLUMN "kategoriId",
DROP COLUMN "satuan",
ADD COLUMN "alamatUsaha" TEXT NOT NULL,
ALTER COLUMN "imageId" DROP NOT NULL;
-- DropTable
DROP TABLE "KategoriMakanan";
-- CreateTable
CREATE TABLE "KategoriProduk" (
"id" TEXT NOT NULL,
"nama" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "KategoriProduk_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "_ProdukToKategori" (
"A" TEXT NOT NULL,
"B" TEXT NOT NULL,
CONSTRAINT "_ProdukToKategori_AB_pkey" PRIMARY KEY ("A","B")
);
-- CreateIndex
CREATE INDEX "_ProdukToKategori_B_index" ON "_ProdukToKategori"("B");
-- AddForeignKey
ALTER TABLE "PasarDesa" ADD CONSTRAINT "PasarDesa_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_ProdukToKategori" ADD CONSTRAINT "_ProdukToKategori_A_fkey" FOREIGN KEY ("A") REFERENCES "KategoriProduk"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_ProdukToKategori" ADD CONSTRAINT "_ProdukToKategori_B_fkey" FOREIGN KEY ("B") REFERENCES "PasarDesa"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@@ -0,0 +1,41 @@
/*
Warnings:
- You are about to drop the `_ProdukToKategori` table. If the table is not empty, all the data it contains will be lost.
*/
-- DropForeignKey
ALTER TABLE "_ProdukToKategori" DROP CONSTRAINT "_ProdukToKategori_A_fkey";
-- DropForeignKey
ALTER TABLE "_ProdukToKategori" DROP CONSTRAINT "_ProdukToKategori_B_fkey";
-- AlterTable
ALTER TABLE "KategoriProduk" ADD COLUMN "deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
ADD COLUMN "isActive" BOOLEAN NOT NULL DEFAULT true;
-- AlterTable
ALTER TABLE "PasarDesa" ADD COLUMN "deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
ADD COLUMN "isActive" BOOLEAN NOT NULL DEFAULT true;
-- DropTable
DROP TABLE "_ProdukToKategori";
-- CreateTable
CREATE TABLE "KategoriToPasar" (
"id" TEXT NOT NULL,
"kategoriId" TEXT NOT NULL,
"pasarDesaId" 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 "KategoriToPasar_pkey" PRIMARY KEY ("id")
);
-- AddForeignKey
ALTER TABLE "KategoriToPasar" ADD CONSTRAINT "KategoriToPasar_kategoriId_fkey" FOREIGN KEY ("kategoriId") REFERENCES "KategoriProduk"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "KategoriToPasar" ADD CONSTRAINT "KategoriToPasar_pasarDesaId_fkey" FOREIGN KEY ("pasarDesaId") REFERENCES "PasarDesa"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@@ -0,0 +1,11 @@
/*
Warnings:
- Added the required column `kategoriProdukId` to the `PasarDesa` table without a default value. This is not possible if the table is not empty.
*/
-- AlterTable
ALTER TABLE "PasarDesa" ADD COLUMN "kategoriProdukId" TEXT NOT NULL;
-- AddForeignKey
ALTER TABLE "PasarDesa" ADD CONSTRAINT "PasarDesa_kategoriProdukId_fkey" FOREIGN KEY ("kategoriProdukId") REFERENCES "KategoriProduk"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@@ -0,0 +1,78 @@
-- CreateTable
CREATE TABLE "posisi_organisasi" (
"id" VARCHAR(50) NOT NULL,
"nama" VARCHAR(100) NOT NULL,
"deskripsi" TEXT,
"hierarki" INTEGER NOT NULL,
CONSTRAINT "posisi_organisasi_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "pegawai" (
"id" UUID NOT NULL,
"namaLengkap" VARCHAR(255) NOT NULL,
"gelarAkademik" VARCHAR(100),
"imageId" TEXT,
"tanggalMasuk" DATE,
"email" VARCHAR(255),
"telepon" VARCHAR(20),
"alamat" TEXT,
"posisiId" VARCHAR(50) NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "pegawai_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "hubungan_organisasi" (
"id" UUID NOT NULL,
"atasanId" UUID NOT NULL,
"bawahanId" UUID NOT NULL,
"tipe" VARCHAR(50),
CONSTRAINT "hubungan_organisasi_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "struktur_organisasi" (
"id" TEXT NOT NULL,
"posisiOrganisasiId" VARCHAR(50) NOT NULL,
"pegawaiId" UUID NOT NULL,
"hubunganOrganisasiId" UUID NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3),
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "struktur_organisasi_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "pegawai_email_key" ON "pegawai"("email");
-- CreateIndex
CREATE UNIQUE INDEX "hubungan_organisasi_atasanId_bawahanId_key" ON "hubungan_organisasi"("atasanId", "bawahanId");
-- AddForeignKey
ALTER TABLE "pegawai" ADD CONSTRAINT "pegawai_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "pegawai" ADD CONSTRAINT "pegawai_posisiId_fkey" FOREIGN KEY ("posisiId") REFERENCES "posisi_organisasi"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "hubungan_organisasi" ADD CONSTRAINT "hubungan_organisasi_atasanId_fkey" FOREIGN KEY ("atasanId") REFERENCES "pegawai"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "hubungan_organisasi" ADD CONSTRAINT "hubungan_organisasi_bawahanId_fkey" FOREIGN KEY ("bawahanId") REFERENCES "pegawai"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "struktur_organisasi" ADD CONSTRAINT "struktur_organisasi_posisiOrganisasiId_fkey" FOREIGN KEY ("posisiOrganisasiId") REFERENCES "posisi_organisasi"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "struktur_organisasi" ADD CONSTRAINT "struktur_organisasi_pegawaiId_fkey" FOREIGN KEY ("pegawaiId") REFERENCES "pegawai"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "struktur_organisasi" ADD CONSTRAINT "struktur_organisasi_hubunganOrganisasiId_fkey" FOREIGN KEY ("hubunganOrganisasiId") REFERENCES "hubungan_organisasi"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@@ -0,0 +1,77 @@
-- CreateTable
CREATE TABLE "GrafikMenganggurBerdasarkanUsia" (
"id" TEXT NOT NULL,
"usia18_25" TEXT NOT NULL,
"usia26_35" TEXT NOT NULL,
"usia36_45" TEXT NOT NULL,
"usia46_keatas" 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 "GrafikMenganggurBerdasarkanUsia_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "GrafikMenganggurBerdasarkanPendidikan" (
"id" TEXT NOT NULL,
"SD" TEXT NOT NULL,
"SMP" TEXT NOT NULL,
"SMA" TEXT NOT NULL,
"D3" TEXT NOT NULL,
"S1" 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 "GrafikMenganggurBerdasarkanPendidikan_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "GrafikJumlahPendudukMiskin" (
"id" UUID NOT NULL,
"year" INTEGER NOT NULL,
"totalPoorPopulation" INTEGER 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 "GrafikJumlahPendudukMiskin_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "SektorUnggulanDesa" (
"id" UUID NOT NULL,
"name" VARCHAR(100) NOT NULL,
"description" TEXT,
"value" 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 "SektorUnggulanDesa_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "DataDemografiPekerjaan" (
"id" TEXT NOT NULL,
"pekerjaan" TEXT NOT NULL,
"lakiLaki" INTEGER NOT NULL,
"perempuan" INTEGER 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 "DataDemografiPekerjaan_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "GrafikJumlahPendudukMiskin_year_key" ON "GrafikJumlahPendudukMiskin"("year");
-- CreateIndex
CREATE UNIQUE INDEX "SektorUnggulanDesa_name_key" ON "SektorUnggulanDesa"("name");

File diff suppressed because it is too large Load Diff

View File

@@ -6,13 +6,21 @@ import caraMemperolehSalinanInformasi from "./data/list-caraMemperolehSalinanInf
import jenisInformasiDiminta from "./data/list-jenisInfromasi.json";
import layanan from "./data/list-layanan.json";
import potensi from "./data/list-potensi.json";
import visiMisiPPID from "./data/ppid/visi-misi-ppid/visimisiPPID.json";
import dasarHukumPPID from "./data/ppid/dasar-hukum-ppid/dasarhukumPPID.json";
import profilePPID from "./data/ppid/profile-ppid/profilePPid.json";
import path from "path";
import fs from "fs";
import { mkdir, writeFile } from "fs/promises";
import { v4 as uuid } from "uuid";
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";
import visiMisiDesa from "./data/desa/profile/visi_misi_desa.json";
import lambangDesa from "./data/desa/profile/lambang_desa.json";
import maskotDesa from "./data/desa/profile/maskot_desa.json";
import profilPerbekel from "./data/desa/profile/profil_perbekel.json";
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";
(async () => {
for (const l of layanan) {
@@ -31,6 +39,159 @@ import { v4 as uuid } from "uuid";
console.log("layanan success ...");
for (const l of sejarahDesa) {
await prisma.sejarahDesa.upsert({
where: {
id: l.id,
},
update: {
judul: l.judul,
deskripsi: l.deskripsi,
},
create: {
id: l.id,
judul: l.judul,
deskripsi: l.deskripsi,
},
});
}
console.log("sejarah desa success ...");
for (const l of maskotDesa) {
await prisma.maskotDesa.upsert({
where: {
id: l.id,
},
update: {
judul: l.judul,
deskripsi: l.deskripsi,
},
create: {
id: l.id,
judul: l.judul,
deskripsi: l.deskripsi,
},
});
}
console.log("maskot desa success ...");
for (const l of lambangDesa) {
await prisma.lambangDesa.upsert({
where: {
id: l.id,
},
update: {
judul: l.judul,
deskripsi: l.deskripsi,
},
create: {
id: l.id,
judul: l.judul,
deskripsi: l.deskripsi,
},
});
}
console.log("lambang desa success ...");
for (const c of profilPerbekel) {
await prisma.profilPerbekel.upsert({
where: { id: c.id },
update: {
biodata: c.biodata,
pengalaman: c.pengalaman,
pengalamanOrganisasi: c.pengalamanOrganisasi,
programUnggulan: c.programUnggulan,
// imageId tidak di-update
},
create: {
id: c.id,
biodata: c.biodata,
pengalaman: c.pengalaman,
pengalamanOrganisasi: c.pengalamanOrganisasi,
programUnggulan: c.programUnggulan,
// imageId tidak di-create
},
});
}
console.log("✅ profilePerbekel seeded without imageId (editable later via UI)");
for (const l of visiMisiDesa) {
await prisma.visiMisiDesa.upsert({
where: {
id: l.id,
},
update: {
visi: l.visi,
misi: l.misi,
},
create: {
id: l.id,
visi: l.visi,
misi: l.misi,
},
});
}
console.log("visi misi desa success ...");
for (const l of pelayananPerizinanBerusaha) {
await prisma.pelayananPerizinanBerusaha.upsert({
where: {
id: l.id,
},
update: {
name: l.name,
deskripsi: l.deskripsi,
link: l.link,
},
create: {
id: l.id,
name: l.name,
deskripsi: l.deskripsi,
link: l.link,
},
});
}
console.log("pelayanan perizinan berusaha success ...");
for (const l of pelayananPendudukNonPermanen) {
await prisma.pelayananPendudukNonPermanen.upsert({
where: {
id: l.id,
},
update: {
name: l.name,
deskripsi: l.deskripsi,
},
create: {
id: l.id,
name: l.name,
deskripsi: l.deskripsi,
},
});
}
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: {
@@ -124,56 +285,29 @@ import { v4 as uuid } from "uuid";
}
console.log("cara memperoleh salinan informasi success ...");
const seedProfilePPID = async () => {
const targetDir = path.resolve("public", "uploads", "seeded-images", "profile-ppid")
// Buat folder hanya jika belum ada
if (!fs.existsSync(targetDir)) {
await mkdir(targetDir, { recursive: true })
}
for (const c of profilePPID) {
let finalImageUrl = c.imageUrl
if (c.imageUrl.startsWith("/uploads/seeded-images/")) {
const filename = path.basename(c.imageUrl)
const seedImagePath = path.resolve("prisma", "seed-images", filename)
const targetFilename = `${uuid()}_${filename}`
const targetPath = path.join(targetDir, targetFilename)
const buffer = fs.readFileSync(seedImagePath)
await writeFile(targetPath, buffer)
finalImageUrl = `/uploads/seeded-images/profile-ppid/${targetFilename}`
}
await prisma.profilePPID.upsert({
where: { id: c.id },
update: {
name: c.name,
biodata: c.biodata,
riwayat: c.riwayat,
pengalaman: c.pengalaman,
unggulan: c.unggulan,
imageUrl: finalImageUrl,
},
create: {
id: c.id,
name: c.name,
biodata: c.biodata,
riwayat: c.riwayat,
pengalaman: c.pengalaman,
unggulan: c.unggulan,
imageUrl: finalImageUrl,
},
})
}
console.log("✅ profilePPID seeded from JSON with image copying")
for (const c of profilePPID) {
await prisma.profilePPID.upsert({
where: { id: c.id },
update: {
name: c.name,
biodata: c.biodata,
riwayat: c.riwayat,
pengalaman: c.pengalaman,
unggulan: c.unggulan,
// imageId tidak di-update
},
create: {
id: c.id,
name: c.name,
biodata: c.biodata,
riwayat: c.riwayat,
pengalaman: c.pengalaman,
unggulan: c.unggulan,
// imageId tidak di-create
},
});
}
await seedProfilePPID()
console.log("✅ profilePPID seeded without imageId (editable later via UI)");
for (const v of visiMisiPPID) {
await prisma.visiMisiPPID.upsert({
@@ -211,7 +345,92 @@ import { v4 as uuid } from "uuid";
}
console.log("dasar hukum PPID success ...");
for (const k of kategoriProduk) {
await prisma.kategoriProduk.upsert({
where: {
id: k.id,
},
update: {
nama: k.nama,
},
create: {
id: k.id,
nama: k.nama,
},
});
}
console.log("kategori produk success ...");
for (const p of posisiOrganisasi) {
await prisma.posisiOrganisasi.upsert({
where: {
id: p.id,
},
update: {
nama: p.nama,
deskripsi: p.deskripsi,
hierarki: p.hierarki,
},
create: {
id: p.id,
nama: p.nama,
deskripsi: p.deskripsi,
hierarki: p.hierarki,
},
});
}
console.log("posisi organisasi success ...");
for (const p of pegawai) {
await prisma.pegawai.upsert({
where: {
id: p.id,
},
update: {
namaLengkap: p.namaLengkap,
gelarAkademik: p.gelarAkademik,
tanggalMasuk: new Date(p.tanggalMasuk),
email: p.email,
telepon: p.telepon,
alamat: p.alamat,
posisiId: p.posisiId,
isActive: p.isActive,
},
create: {
id: p.id,
namaLengkap: p.namaLengkap,
gelarAkademik: p.gelarAkademik,
tanggalMasuk: new Date(p.tanggalMasuk),
email: p.email,
telepon: p.telepon,
alamat: p.alamat,
posisiId: p.posisiId,
isActive: p.isActive,
},
});
}
console.log("pegawai success ...");
for (const p of hubunganOrganisasi) {
await prisma.hubunganOrganisasi.upsert({
where: {
atasanId_bawahanId: {
atasanId: p.atasanId,
bawahanId: p.bawahanId,
},
},
update: {
tipe: p.tipe,
},
create: {
atasanId: p.atasanId,
bawahanId: p.bawahanId,
tipe: p.tipe,
},
});
}
console.log("hubungan organisasi success ...");
})()
.then(() => prisma.$disconnect())
.catch((e) => {

BIN
public/Share.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

BIN
public/bagikanPostingan.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

BIN
public/bungapudak.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 KiB

BIN
public/darmasaba-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

BIN
public/klimakstari.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 KiB

BIN
public/pohonpudak.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 KiB

View File

Before

Width:  |  Height:  |  Size: 195 KiB

After

Width:  |  Height:  |  Size: 195 KiB

BIN
public/sematkan.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

BIN
public/struktur_ppid.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

BIN
public/tarisekar.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

View File

Before

Width:  |  Height:  |  Size: 275 KiB

After

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 KiB

BIN
public/video.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

View File

@@ -1,22 +1,37 @@
import React from 'react';
import { Grid, GridCol, Paper, TextInput, Title } from '@mantine/core';
import { IconSearch } from '@tabler/icons-react'; // Sesuaikan jika kamu pakai icon lain
import { IconSearch } from '@tabler/icons-react';
import colors from '@/con/colors';
type HeaderSearchProps = {
title: string;
placeholder?: string;
searchIcon?: React.ReactNode;
value?: string;
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
};
const HeaderSearch = ({ title = "", placeholder = "pencarian", searchIcon = <IconSearch size={20} /> }: { title: string, placeholder?: string, searchIcon?: React.ReactNode }) => {
const HeaderSearch = ({
title = "",
placeholder = "pencarian",
searchIcon = <IconSearch size={20} />,
value,
onChange,
}: HeaderSearchProps) => {
return (
<Grid>
<GridCol span={{ base: 12, md: 9 }}>
<Title order={3}>{title}</Title>
</GridCol>
<GridCol span={{ base: 12, md: 3 }}>
<Paper radius={"lg"} bg={colors['white-1']}>
<Paper radius="lg" bg={colors['white-1']}>
<TextInput
radius="lg"
placeholder={placeholder}
leftSection={searchIcon}
w="100%"
value={value}
onChange={onChange}
/>
</Paper>
</GridCol>
@@ -24,4 +39,4 @@ const HeaderSearch = ({ title = "", placeholder = "pencarian", searchIcon = <Ico
);
};
export default HeaderSearch;
export default HeaderSearch;

View File

@@ -5,31 +5,48 @@ import { IconCircleDashedPlus, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import React from 'react';
const JudulListTab = ({ title = "", href = "#", placeholder = "pencarian", searchIcon = <IconSearch size={20} /> }) => {
type JudulListTabProps = {
title: string;
href: string;
placeholder: string;
searchIcon: React.ReactNode;
value?: string;
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
}
const JudulListTab = ({
title = "",
href = "#",
placeholder = "pencarian",
searchIcon = <IconSearch size={20} />,
value,
onChange
}: JudulListTabProps) => {
const router = useRouter();
const handleNavigate = () => {
router.push(href);
};
return (
<Grid mb={10}>
<GridCol span={{ base: 12, md: 8 }}>
<Text fz={{base: "md", md: "xl"}} fw={"bold"}>{title}</Text>
<Text fz={{ base: "md", md: "xl" }} fw={"bold"}>{title}</Text>
</GridCol>
<GridCol span={{ base: 9, md: 3}} ta="right">
<GridCol span={{ base: 9, md: 3 }} ta="right">
<Paper radius={"lg"} bg={colors['white-1']}>
<TextInput
radius="lg"
placeholder={placeholder}
leftSection={searchIcon}
w="100%"
value={value}
onChange={onChange}
/>
</Paper>
</GridCol>
<GridCol span={{ base: 3, md: 1}} ta="right">
<GridCol span={{ base: 3, md: 1 }} ta="right">
<Button onClick={handleNavigate} bg={colors['blue-button']}>
<IconCircleDashedPlus size={25} />
</Button>

View File

@@ -21,7 +21,7 @@ export function ModalKonfirmasiHapus({
<Modal
opened={opened}
onClose={onClose}
title="Konfirmasi Hapus"
title={<Text fw={"bold"} fz={"xl"}>Konfirmasi Hapus</Text>}
centered
>
<Text mb="md">{text}</Text>

View File

@@ -0,0 +1,415 @@
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 fotoForm = z.object({
name: z.string().min(1, { message: "Name is required" }),
deskripsi: z.string().min(1, { message: "Deskripsi is required" }),
imagesId: z.string().nonempty(),
});
const videoForm = z.object({
name: z.string().min(1, { message: "Name is required" }),
deskripsi: z.string().min(1, { message: "Deskripsi is required" }),
linkVideo: z.string().min(1, { message: "Link video is required" }),
});
const defaultFormFoto = {
name: "",
deskripsi: "",
imagesId: "",
};
const defaultFormVideo = {
name: "",
deskripsi: "",
linkVideo: "",
};
const foto = proxy({
create: {
form: { ...defaultFormFoto },
loading: false,
async create() {
const cek = fotoForm.safeParse(foto.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
foto.create.loading = true;
const res = await ApiFetch.api.desa.gallery.foto["create"].post(
foto.create.form
);
if (res.status === 200) {
foto.findMany.load();
return toast.success("Foto berhasil disimpan!");
}
return toast.error("Gagal menyimpan foto");
} catch (error) {
console.log((error as Error).message);
} finally {
foto.create.loading = false;
}
},
resetForm() {
foto.create.form = { ...defaultFormFoto };
},
},
findMany: {
data: null as
| Prisma.GalleryFotoGetPayload<{
include: {
imageGalleryFoto: true;
};
}>[]
| null,
async load() {
const res = await ApiFetch.api.desa.gallery.foto["find-many"].get();
if (res.status === 200) {
foto.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.GalleryFotoGetPayload<{
include: {
imageGalleryFoto: true;
};
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/desa/gallery/foto/${id}`);
if (res.ok) {
const data = await res.json();
foto.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch foto:", res.statusText);
foto.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching foto:", error);
foto.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
foto.delete.loading = true;
const response = await fetch(`/api/desa/gallery/foto/del/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
const result = await response.json();
if (response.ok) {
toast.success(result.message || "Foto berhasil dihapus");
await foto.findMany.load(); // refresh list
} else {
toast.error(result.message || "Gagal menghapus foto");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus foto");
} finally {
foto.delete.loading = false;
}
},
},
update: {
id: "",
form: { ...defaultFormFoto },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(`/api/desa/gallery/foto/${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,
imagesId: data.imagesId || "",
};
return data;
} else {
throw new Error(result.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error loading foto:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async update() {
const cek = fotoForm.safeParse(foto.update.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return false;
}
try {
foto.update.loading = true;
const response = await fetch(`/api/desa/gallery/foto/${this.id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: this.form.name,
deskripsi: this.form.deskripsi,
imagesId: this.form.imagesId,
}),
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(
errorData.message || `HTTP error! status: ${response.status}`
);
}
const result = await response.json();
if (result.success) {
toast.success(result.message || "Foto berhasil diupdate");
await foto.findMany.load(); // refresh list
return true;
} else {
throw new Error(result.message || "Gagal mengupdate foto");
}
} catch (error) {
console.error("Error updating foto:", error);
toast.error(
error instanceof Error ? error.message : "Gagal mengupdate foto"
);
return false;
} finally {
foto.update.loading = false;
}
},
reset() {
foto.update.id = "";
foto.update.form = { ...defaultFormFoto };
},
},
});
const video = proxy({
create: {
form: { ...defaultFormVideo },
loading: false,
async create() {
const cek = videoForm.safeParse(video.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
video.create.loading = true;
const res = await ApiFetch.api.desa.gallery.video["create"].post(
video.create.form
);
if (res.status === 200) {
video.findMany.load();
return toast.success("Video berhasil disimpan!");
}
return toast.error("Gagal menyimpan video");
} catch (error) {
console.log((error as Error).message);
} finally {
video.create.loading = false;
}
},
resetForm() {
video.create.form = { ...defaultFormVideo };
},
},
findMany: {
data: null as
| Prisma.GalleryVideoGetPayload<{
omit: {
isActive: true;
};
}>[]
| null,
async load() {
const res = await ApiFetch.api.desa.gallery.video["find-many"].get();
if (res.status === 200) {
video.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.GalleryVideoGetPayload<{
omit: {
isActive: true;
};
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/desa/gallery/video/${id}`);
if (res.ok) {
const data = await res.json();
video.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch video:", res.statusText);
video.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching video:", error);
video.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
video.delete.loading = true;
const response = await fetch(`/api/desa/gallery/video/del/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
const result = await response.json();
if (response.ok) {
toast.success(result.message || "Video berhasil dihapus");
await video.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus video");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus video");
} finally {
video.delete.loading = false;
}
},
},
update: {
id: "",
form: { ...defaultFormVideo },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(`/api/desa/gallery/video/${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,
linkVideo: data.linkVideo,
};
return data;
} else {
throw new Error(result.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error loading video:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async update() {
const cek = videoForm.safeParse(video.update.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return false;
}
try {
video.update.loading = true;
const response = await fetch(`/api/desa/gallery/video/${this.id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: this.form.name,
deskripsi: this.form.deskripsi,
linkVideo: this.form.linkVideo,
}),
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(
errorData.message || `HTTP error! status: ${response.status}`
);
}
const result = await response.json();
if (result.success) {
toast.success(result.message || "Video berhasil diupdate");
await video.findMany.load(); // refresh list
return true;
} else {
throw new Error(result.message || "Gagal mengupdate video");
}
} catch (error) {
console.error("Error updating video:", error);
toast.error(
error instanceof Error ? error.message : "Gagal mengupdate video"
);
return false;
} finally {
video.update.loading = false;
}
},
reset() {
video.update.id = "";
video.update.form = { ...defaultFormVideo };
},
},
});
const stateGallery = proxy({
foto,
video,
});
export default stateGallery;

View File

@@ -0,0 +1,718 @@
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 templateSuratKeteranganForm = z.object({
name: z.string().min(3, "Nama minimal 3 karakter"),
deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"),
imageId: z.string().nonempty(),
});
const suratKeteranganForm = {
name: "",
deskripsi: "",
imageId: "",
};
const telunjukSaktiDesaForm = {
name: "",
deskripsi: "",
link: "",
};
const templateTelunjukSaktiDesaForm = z.object({
name: z.string().min(3, "Nama minimal 3 karakter"),
deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"),
});
const templatePelayananPerizinanBerusaha = z.object({
name: z.string().min(3, "Nama minimal 3 karakter"),
deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"),
link: z.string().min(3, "Link minimal 3 karakter"),
});
type pelayananPerizinanBerusahaForm =
Prisma.PelayananPerizinanBerusahaGetPayload<{
select: {
id: true;
name: true;
deskripsi: true;
link: true;
};
}>;
const pelayananPerizinanBerusahaForm = {
name: "",
deskripsi: "",
link: "",
};
const templatePelayananPendudukNonPermanen = z.object({
name: z.string().min(3, "Nama minimal 3 karakter"),
deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"),
});
type pelayananPendudukNonPermanenForm =
Prisma.PelayananPendudukNonPermanenGetPayload<{
select: {
id: true;
name: true;
deskripsi: true;
};
}>;
const pelayananPendudukNonPermanenForm = {
name: "",
deskripsi: "",
};
const suratKeterangan = proxy({
create: {
form: { ...suratKeteranganForm },
loading: false,
async create() {
const cek = templateSuratKeteranganForm.safeParse(
suratKeterangan.create.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
suratKeterangan.create.loading = true;
const res = await ApiFetch.api.desa.layanan.pelayanansuratketerangan[
"create"
].post(suratKeterangan.create.form);
if (res.status === 200) {
suratKeterangan.findMany.load();
return toast.success("Surat Keterangan berhasil disimpan!");
}
return toast.error("Gagal menyimpan surat keterangan");
} catch (error) {
console.log((error as Error).message);
} finally {
suratKeterangan.create.loading = false;
}
},
resetForm() {
suratKeterangan.create.form = { ...suratKeteranganForm };
},
},
findMany: {
data: [] as Prisma.PelayananSuratKeteranganGetPayload<{
include: { image: true };
}>[],
async load() {
const res = await ApiFetch.api.desa.layanan.pelayanansuratketerangan[
"find-many"
].get();
if (res.status === 200) {
suratKeterangan.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.PelayananSuratKeteranganGetPayload<{
include: {
image: true;
};
}> | null,
async load(id: string) {
try {
const res = await fetch(
`/api/desa/layanan/pelayanansuratketerangan/${id}`
);
if (res.ok) {
const data = await res.json();
suratKeterangan.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch surat keterangan:", res.statusText);
suratKeterangan.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching surat keterangan:", error);
suratKeterangan.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
suratKeterangan.delete.loading = true;
const response = await fetch(
`/api/desa/layanan/pelayanansuratketerangan/del/${id}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
);
const result = await response.json();
if (response.ok) {
toast.success(result.message || "Surat Keterangan berhasil dihapus");
await suratKeterangan.findMany.load(); // refresh list
} else {
toast.error(result.message || "Gagal menghapus surat keterangan");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus surat keterangan");
} finally {
suratKeterangan.delete.loading = false;
}
},
},
edit: {
id: "",
form: { ...suratKeteranganForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(
`/api/desa/layanan/pelayanansuratketerangan/${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 fetching surat keterangan:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async update() {
const cek = templateSuratKeteranganForm.safeParse(
suratKeterangan.edit.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
suratKeterangan.edit.loading = true;
const response = await fetch(
`/api/desa/layanan/pelayanansuratketerangan/${this.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(result.message || "Surat Keterangan berhasil diupdate");
await suratKeterangan.findMany.load(); // refresh list
return true;
} else {
throw new Error(
result.message || "Gagal mengupdate surat keterangan"
);
}
} catch (error) {
console.error("Error updating surat keterangan:", error);
toast.error(
error instanceof Error
? error.message
: "Terjadi kesalahan saat update surat keterangan"
);
return false;
} finally {
suratKeterangan.edit.loading = false;
}
},
},
});
const pelayananTelunjukSaktiDesa = proxy({
create: {
form: { ...telunjukSaktiDesaForm },
loading: false,
async create() {
const cek = templateTelunjukSaktiDesaForm.safeParse(
pelayananTelunjukSaktiDesa.create.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
pelayananTelunjukSaktiDesa.create.loading = true;
const res = await ApiFetch.api.desa.layanan.pelayanantelunjuksaktidesa[
"create"
].post(pelayananTelunjukSaktiDesa.create.form);
if (res.status === 200) {
pelayananTelunjukSaktiDesa.findMany.load();
return toast.success("Telunjuk Sakti Desa berhasil disimpan!");
}
return toast.error("Gagal menyimpan telunjuk sakti desa");
} catch (error) {
console.log((error as Error).message);
} finally {
pelayananTelunjukSaktiDesa.create.loading = false;
}
},
resetForm() {
pelayananTelunjukSaktiDesa.create.form = { ...telunjukSaktiDesaForm };
},
},
findMany: {
data: [] as Prisma.PelayananTelunjukSaktiDesaGetPayload<{
omit: { isActive: true };
}>[],
async load() {
const res = await ApiFetch.api.desa.layanan.pelayanantelunjuksaktidesa[
"find-many"
].get();
if (res.status === 200) {
pelayananTelunjukSaktiDesa.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.PelayananTelunjukSaktiDesaGetPayload<{
omit: { isActive: true };
}> | null,
async load(id: string) {
try {
const res = await fetch(
`/api/desa/layanan/pelayanantelunjuksaktidesa/${id}`
);
if (res.ok) {
const data = await res.json();
pelayananTelunjukSaktiDesa.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch telunjuk sakti desa:", res.statusText);
pelayananTelunjukSaktiDesa.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching telunjuk sakti desa:", error);
pelayananTelunjukSaktiDesa.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
pelayananTelunjukSaktiDesa.delete.loading = true;
const response = await fetch(
`/api/desa/layanan/pelayanantelunjuksaktidesa/del/${id}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
);
const result = await response.json();
if (response.ok) {
toast.success(result.message || "Telunjuk Sakti Desa berhasil dihapus");
await pelayananTelunjukSaktiDesa.findMany.load(); // refresh list
} else {
toast.error(result.message || "Gagal menghapus telunjuk sakti desa");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus telunjuk sakti desa");
} finally {
pelayananTelunjukSaktiDesa.delete.loading = false;
}
},
},
edit: {
id: "",
form: { ...telunjukSaktiDesaForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(
`/api/desa/layanan/pelayanantelunjuksaktidesa/${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,
link: data.link,
};
return data;
} else {
throw new Error(result.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error fetching telunjuk sakti desa:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async update() {
const cek = templateTelunjukSaktiDesaForm.safeParse(
pelayananTelunjukSaktiDesa.edit.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
pelayananTelunjukSaktiDesa.edit.loading = true;
const response = await fetch(
`/api/desa/layanan/pelayanantelunjuksaktidesa/${this.id}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: this.form.name,
deskripsi: this.form.deskripsi,
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(result.message || "Telunjuk Sakti Desa berhasil diupdate");
await pelayananTelunjukSaktiDesa.findMany.load(); // refresh list
return true;
} else {
throw new Error(
result.message || "Gagal mengupdate telunjuk sakti desa"
);
}
} catch (error) {
console.error("Error updating telunjuk sakti desa:", error);
toast.error(
error instanceof Error
? error.message
: "Terjadi kesalahan saat update telunjuk sakti desa"
);
return false;
} finally {
pelayananTelunjukSaktiDesa.edit.loading = false;
}
},
},
})
const pelayananPerizinanBerusaha = proxy({
findById: {
data: null as pelayananPerizinanBerusahaForm | null,
loading: false,
initialize() {
pelayananPerizinanBerusaha.findById.data = {
id: "",
name: "",
deskripsi: "",
link: "",
} as pelayananPerizinanBerusahaForm;
},
async load(id: string) {
try {
pelayananPerizinanBerusaha.findById.loading = true;
const res = await fetch(
`/api/desa/layanan/pelayananperizinanberusaha/${id}`
);
if (res.ok) {
const data = await res.json();
pelayananPerizinanBerusaha.findById.data = data.data ?? null;
} else {
console.error(
"Failed to fetch pelayanan perizinan berusaha:",
res.statusText
);
pelayananPerizinanBerusaha.findById.data = null;
}
} catch (error) {
console.error("Error fetching pelayanan perizinan berusaha:", error);
pelayananPerizinanBerusaha.findById.data = null;
}
},
},
update: {
id: "",
form: { ...pelayananPerizinanBerusahaForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak boleh kosong");
return null;
}
try {
const response = await fetch(
`/api/desa/layanan/pelayananperizinanberusaha/${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;
pelayananPerizinanBerusaha.update.id = data.id;
pelayananPerizinanBerusaha.update.form = {
name: data.name,
deskripsi: data.deskripsi,
link: data.link,
};
return data;
} else {
throw new Error(result.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error fetching pelayanan perizinan berusaha:", error);
toast.error(
error instanceof Error
? error.message
: "Gagal memuat data"
);
return null;
}
},
async update(data: pelayananPerizinanBerusahaForm) {
const cek = templatePelayananPerizinanBerusaha.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 {
pelayananPerizinanBerusaha.update.loading = true;
const res = await fetch(
`/api/desa/layanan/pelayananperizinanberusaha/${data.id}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
}
);
if (res.ok) {
toast.success("Pelayanan perizinan berusaha berhasil diupdate");
await pelayananPerizinanBerusaha.findById.load(data.id);
} else {
toast.error("Gagal mengupdate pelayanan perizinan berusaha");
}
} catch (error) {
console.error("Error updating pelayanan perizinan berusaha:", error);
toast.error(
"Terjadi kesalahan saat mengupdate pelayanan perizinan berusaha"
);
} finally {
pelayananPerizinanBerusaha.update.loading = false;
}
},
},
});
const pelayananPendudukNonPermanen = proxy({
findById: {
data: null as pelayananPendudukNonPermanenForm | null,
loading: false,
initialize() {
pelayananPendudukNonPermanen.findById.data = {
id: "",
name: "",
deskripsi: "",
} as pelayananPendudukNonPermanenForm;
},
async load(id: string) {
try {
pelayananPendudukNonPermanen.findById.loading = true;
const res = await fetch(
`/api/desa/layanan/pelayananpenduduknonpermanen/${id}`
);
if (res.ok) {
const data = await res.json();
pelayananPendudukNonPermanen.findById.data = data.data ?? null;
} else {
console.error(
"Failed to fetch pelayanan penduduk non permanen:",
res.statusText
);
pelayananPendudukNonPermanen.findById.data = null;
}
} catch (error) {
console.error("Error fetching pelayanan penduduk non permanen:", error);
pelayananPendudukNonPermanen.findById.data = null;
}
},
},
update: {
id: "",
form: { ...pelayananPendudukNonPermanenForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak boleh kosong");
return null;
}
try {
const response = await fetch(
`/api/desa/layanan/pelayananpenduduknonpermanen/${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;
pelayananPendudukNonPermanen.update.id = data.id;
pelayananPendudukNonPermanen.update.form = {
name: data.name,
deskripsi: data.deskripsi,
};
return data;
} else {
throw new Error(result.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error fetching pelayanan penduduk non permanen:", error);
toast.error(
error instanceof Error
? error.message
: "Gagal memuat data"
);
return null;
}
},
async update(data: pelayananPendudukNonPermanenForm) {
const cek = templatePelayananPendudukNonPermanen.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 {
pelayananPendudukNonPermanen.update.loading = true;
const res = await fetch(
`/api/desa/layanan/pelayananpenduduknonpermanen/${data.id}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
}
);
if (res.ok) {
toast.success("Pelayanan penduduk non permanen berhasil diupdate");
await pelayananPendudukNonPermanen.findById.load(data.id);
} else {
toast.error("Gagal mengupdate pelayanan penduduk non permanen");
}
} catch (error) {
console.error("Error updating pelayanan penduduk non permanen:", error);
toast.error(
"Terjadi kesalahan saat mengupdate pelayanan penduduk non permanen"
);
} finally {
pelayananPendudukNonPermanen.update.loading = false;
}
},
},
});
const stateLayananDesa = proxy({
suratKeterangan,
pelayananPerizinanBerusaha,
pelayananTelunjukSaktiDesa,
pelayananPendudukNonPermanen,
});
export default stateLayananDesa;

View File

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

View File

@@ -6,7 +6,7 @@ import { z } from "zod";
const templateForm = z.object({
name: z.string().min(1).max(50),
deskripsi: z.string().min(1).max(50),
deskripsi: z.string().min(1).max(5000),
kategori: z.string().min(1).max(50),
imageId: z.string().min(1).max(50),
content: z.string().min(1).max(5000),

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,197 @@
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 templateDemografiPekerjaan = z.object({
pekerjaan: z.string().min(1, "Pekerjaan harus diisi"),
lakiLaki: z.number().min(1, "Laki - Laki harus diisi"),
perempuan: z.number().min(1, "Perempuan harus diisi"),
});
type DemografiPekerjaan = Prisma.DataDemografiPekerjaanGetPayload<{
select: {
pekerjaan: true;
lakiLaki: true;
perempuan: true;
};
}>;
const defaultForm: DemografiPekerjaan = {
pekerjaan: "",
lakiLaki: 0,
perempuan: 0,
};
const demografiPekerjaan = proxy({
create: {
form: defaultForm,
loading: false,
async create() {
const cek = templateDemografiPekerjaan.safeParse(
demografiPekerjaan.create.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return null;
}
try {
demografiPekerjaan.create.loading = true;
const res = await ApiFetch.api.ekonomi.demografipekerjaan[
"create"
].post(demografiPekerjaan.create.form);
if (res.status === 200) {
const id = res.data?.data?.id;
if (id) {
toast.success("Success create");
demografiPekerjaan.create.form = { ...defaultForm };
demografiPekerjaan.findMany.load();
return id;
}
}
toast.error("failed create");
return null;
} catch (error) {
console.log((error as Error).message);
return null;
} finally {
demografiPekerjaan.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.DataDemografiPekerjaanGetPayload<{
omit: { isActive: true };
}>[]
| null,
async load() {
const res = await ApiFetch.api.ekonomi.demografipekerjaan[
"find-many"
].get();
if (res.status === 200) {
demografiPekerjaan.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.DataDemografiPekerjaanGetPayload<{
omit: { isActive: true };
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/ekonomi/demografipekerjaan/${id}`);
if (res.ok) {
const data = await res.json();
demografiPekerjaan.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch demografiPekerjaan:", res.statusText);
demografiPekerjaan.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching demografiPekerjaan:", error);
demografiPekerjaan.findUnique.data = null;
}
},
},
update: {
id: "",
form: { ...defaultForm },
loading: false,
async submit() {
const id = this.id;
if (!id) {
toast.warn("ID tidak valid");
return null;
}
const formData = {
pekerjaan: this.form.pekerjaan,
lakiLaki: this.form.lakiLaki,
perempuan: this.form.perempuan,
};
const cek = templateDemografiPekerjaan.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/demografipekerjaan/${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 demografiPekerjaan.findMany.load();
return result.data;
} catch (error) {
console.error("Update error:", error);
toast.error("Gagal update data demografi pekerjaan");
throw error;
} finally {
this.loading = false;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
demografiPekerjaan.delete.loading = true;
const response = await fetch(
`/api/ekonomi/demografipekerjaan/del/${id}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
);
const result = await response.json();
if (response.ok && result?.success) {
toast.success(
result.message || "Demografi pekerjaan berhasil dihapus"
);
await demografiPekerjaan.findMany.load();
} else {
toast.error(result?.message || "Gagal menghapus demografi pekerjaan");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus persentase kelahiran");
} finally {
demografiPekerjaan.delete.loading = false;
}
},
},
});
export default demografiPekerjaan

View File

@@ -0,0 +1,180 @@
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 templateJumlahPendudukMiskin = z.object({
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;
};
}>;
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;
}
},
}
})
export default jumlahPendudukMiskin

View File

@@ -0,0 +1,225 @@
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({
posisi: z.string(),
namaPerusahaan: z.string(),
lokasi: z.string(),
tipePekerjaan: z.string(),
gaji: z.string(),
deskripsi: z.string(),
kualifikasi: z.string(),
});
const defaultForm = {
posisi: "",
namaPerusahaan: "",
lokasi: "",
tipePekerjaan: "",
gaji: "",
deskripsi: "",
kualifikasi: "",
};
const lowonganKerjaState = proxy({
create: {
form: { ...defaultForm },
loading: false,
async create() {
const cek = templateForm.safeParse(lowonganKerjaState.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
lowonganKerjaState.create.loading = true;
const res = await ApiFetch.api.ekonomi.lowongankerja["create"].post(
lowonganKerjaState.create.form
);
if (res.status === 200) {
lowonganKerjaState.create.loading = false;
return toast.success("success create");
}
console.log(res);
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
lowonganKerjaState.create.loading = false;
}
},
resetForm() {
lowonganKerjaState.create.form = { ...defaultForm };
},
},
findMany: {
data: null as
| Prisma.LowonganPekerjaanGetPayload<{
omit: { isActive: true };
}>[]
| null,
async load() {
const res = await ApiFetch.api.ekonomi.lowongankerja["find-many"].get();
if (res.status === 200) {
lowonganKerjaState.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.LowonganPekerjaanGetPayload<{
omit: { isActive: true };
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/ekonomi/lowongankerja/${id}`);
if (res.ok) {
const data = await res.json();
lowonganKerjaState.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
lowonganKerjaState.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching data:", error);
lowonganKerjaState.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
lowonganKerjaState.delete.loading = true;
const response = await fetch(`/api/ekonomi/lowongankerja/del/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Lowongan kerja berhasil dihapus");
await lowonganKerjaState.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus lowongan kerja");
}
} catch (error) {
console.error("Error deleting data:", error);
toast.error("Terjadi kesalahan saat menghapus lowongan kerja");
} finally {
lowonganKerjaState.delete.loading = false;
}
},
},
update: {
id: "",
form: { ...defaultForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(`/api/ekonomi/lowongankerja/${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 = {
posisi: data.posisi,
namaPerusahaan: data.namaPerusahaan,
lokasi: data.lokasi,
tipePekerjaan: data.tipePekerjaan,
gaji: data.gaji,
deskripsi: data.deskripsi,
kualifikasi: data.kualifikasi,
};
return data;
} else {
throw new Error(
result?.message || "Gagal memuat data lowongan kerja"
);
}
} catch (error) {
console.error("Error fetching data:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async update() {
const cek = templateForm.safeParse(lowonganKerjaState.update.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
lowonganKerjaState.update.loading = true;
const response = await fetch(`/api/ekonomi/lowongankerja/${this.id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
posisi: this.form.posisi,
namaPerusahaan: this.form.namaPerusahaan,
lokasi: this.form.lokasi,
tipePekerjaan: this.form.tipePekerjaan,
gaji: this.form.gaji,
deskripsi: this.form.deskripsi,
kualifikasi: this.form.kualifikasi,
}),
});
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 lowongan kerja");
await lowonganKerjaState.findMany.load(); // refresh list
return true;
} else {
throw new Error(result.message || "Gagal update lowongan kerja");
}
} catch (error) {
console.error("Error updating data:", error);
toast.error(error instanceof Error ? error.message : "Terjadi kesalahan saat mengupdate lowongan kerja");
return false;
} finally {
lowonganKerjaState.update.loading = false;
}
},
reset() {
lowonganKerjaState.update.id = "";
lowonganKerjaState.update.form = { ...defaultForm };
},
},
});
export default lowonganKerjaState;

View File

@@ -0,0 +1,459 @@
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 templatePasarDesaForm = z.object({
nama: z.string().min(1, "Nama minimal 1 karakter"),
harga: z.number().min(1, "Harga minimal 1"),
alamatUsaha: z.string().min(1, "Alamat minimal 1 karakter"),
imageId: z.string().min(1, "Gambar wajib dipilih"),
rating: z.number().min(1, "Rating minimal 1"),
kategoriId: z.array(z.string()).min(1, "Minimal pilih satu kategori"),
});
const defaultPasarDesaForm = {
nama: "",
harga: 0,
alamatUsaha: "",
imageId: "",
rating: 0,
kategoriId: [] as string[],
};
const pasarDesa = proxy({
create: {
form: { ...defaultPasarDesaForm },
loading: false,
async create() {
const cek = templatePasarDesaForm.safeParse(pasarDesa.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
pasarDesa.create.loading = true;
const res = await ApiFetch.api.ekonomi.pasardesa["create"].post(
pasarDesa.create.form
);
if (res.status === 200) {
pasarDesa.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 {
pasarDesa.create.loading = false;
}
},
},
findMany: {
data: null as Array<
Prisma.PasarDesaGetPayload<{
include: {
image: true;
KategoriToPasar: {
include: {
kategori: true;
};
};
};
}>
> | null,
async load() {
const res = await ApiFetch.api.ekonomi.pasardesa["find-many"].get();
if (res.status === 200) {
pasarDesa.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.PasarDesaGetPayload<{
include: {
image: true;
KategoriToPasar: {
include: {
kategori: true;
};
};
};
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/ekonomi/pasardesa/${id}`);
if (res.ok) {
const data = await res.json();
pasarDesa.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
pasarDesa.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching data:", error);
pasarDesa.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
pasarDesa.delete.loading = true;
const response = await fetch(`/api/ekonomi/pasardesa/del/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Pasar desa berhasil dihapus");
await pasarDesa.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 {
pasarDesa.delete.loading = false;
}
},
},
edit: {
id: "",
form: { ...defaultPasarDesaForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(`/api/ekonomi/pasardesa/${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,
harga: data.harga,
alamatUsaha: data.alamatUsaha,
imageId: data.imageId,
rating: data.rating,
kategoriId: data.kategoriId,
};
return data;
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error loading pasar desa:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async update() {
const cek = templatePasarDesaForm.safeParse(pasarDesa.edit.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
pasarDesa.edit.loading = true;
const response = await fetch(`/api/ekonomi/pasardesa/${this.id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
nama: this.form.nama,
harga: this.form.harga,
alamatUsaha: this.form.alamatUsaha,
imageId: this.form.imageId,
rating: this.form.rating,
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 pasar desa");
await pasarDesa.findMany.load(); // refresh list
return true;
} else {
throw new Error(result.message || "Gagal mengupdate pasar desa");
}
} catch (error) {
console.error("Error updating pasar desa:", error);
toast.error(
error instanceof Error ? error.message : "Gagal mengupdate pasar desa"
);
return false;
} finally {
pasarDesa.edit.loading = false;
}
},
reset() {
pasarDesa.edit.id = "";
pasarDesa.edit.form = { ...defaultPasarDesaForm };
},
},
});
// ========================================= KATEGORI PRODUK ========================================= //
const kategoriProdukForm = z.object({
nama: z.string().min(1, "Nama minimal 1 karakter"),
});
const kategoriProdukDefaultForm = {
nama: "",
};
const kategoriProduk = proxy({
create: {
form: { ...kategoriProdukDefaultForm },
loading: false,
async create() {
const cek = kategoriProdukForm.safeParse(kategoriProduk.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
kategoriProduk.create.loading = true;
const res = await ApiFetch.api.ekonomi.kategoriproduk["create"].post(
kategoriProduk.create.form
);
if (res.status === 200) {
kategoriProduk.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 {
kategoriProduk.create.loading = false;
}
},
},
findMany: {
data: null as Array<{
id: string;
nama: string;
}>
| null,
async load() {
const res = await ApiFetch.api.ekonomi.kategoriproduk["find-many"].get();
if (res.status === 200) {
kategoriProduk.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.KategoriProdukGetPayload<{
omit: { isActive: true };
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/ekonomi/kategoriproduk/${id}`);
if (res.ok) {
const data = await res.json();
kategoriProduk.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
kategoriProduk.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching data:", error);
kategoriProduk.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
kategoriProduk.delete.loading = true;
const response = await fetch(`/api/ekonomi/kategoriproduk/del/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Kategori produk berhasil dihapus");
await kategoriProduk.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus kategori produk");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus kategori produk");
} finally {
kategoriProduk.delete.loading = false;
}
},
},
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}`);
}
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",
},
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');
}
}
} 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
});
export default pasarDesaState;

View File

@@ -0,0 +1,249 @@
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({
nama: z.string().min(1, "Nama minimal 1 karakter"),
deskripsi: z.string().min(1, "Deskripsi minimal 1 karakter"),
ikonUrl: z.string().optional(),
statistik: z.object({
tahun: z.string().min(1, "Tahun minimal 1 karakter"),
jumlah: z.string().min(1, "Jumlah minimal 1 karakter"),
})
});
const defaultForm = {
nama: "",
deskripsi: "",
ikonUrl: "",
statistik: {
tahun: "",
jumlah: ""
}
};
const programKemiskinanState = proxy({
create: {
form: { ...defaultForm },
loading: false,
async create() {
const cek = templateForm.safeParse(programKemiskinanState.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
programKemiskinanState.create.loading = true;
const res = await ApiFetch.api.ekonomi.programkemiskinan["create"].post(
programKemiskinanState.create.form
);
if (res.status === 200) {
programKemiskinanState.findMany.load();
return toast.success("success create");
}
console.log(res);
return toast.error("failed create");
} catch (error) {
console.log(error);
return toast.error("failed create");
} finally {
programKemiskinanState.create.loading = false;
}
},
},
findMany: {
data: [] as Prisma.ProgramKemiskinanGetPayload<{
include: {
statistik: true;
};
}>[],
loading: false,
async load() {
const res = await ApiFetch.api.ekonomi.programkemiskinan[
"find-many"
].get();
if (res.status === 200) {
programKemiskinanState.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.ProgramKemiskinanGetPayload<{
include: {
statistik: true;
};
}> | null,
loading: false,
async load(id: string) {
try {
const res = await fetch(`/api/ekonomi/programkemiskinan/${id}`);
if (res.ok) {
const data = await res.json();
programKemiskinanState.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
programKemiskinanState.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching data:", error);
programKemiskinanState.findUnique.data = null;
}
},
},
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/ekonomi/programkemiskinan/${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,
ikonUrl: data.ikonUrl || "",
statistik: {
tahun: data.statistik.tahun,
jumlah: data.statistik.jumlah,
},
};
return data; // Return the loaded data
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error loading program kemiskinan:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async update() {
const cek = templateForm.safeParse(programKemiskinanState.update.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return false;
}
try {
programKemiskinanState.update.loading = true;
const response = await fetch(
`/api/ekonomi/programkemiskinan/${this.id}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
nama: this.form.nama,
deskripsi: this.form.deskripsi,
ikonUrl: this.form.ikonUrl,
statistik: {
tahun: this.form.statistik.tahun,
jumlah: this.form.statistik.jumlah,
},
}),
}
);
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 kemiskinan");
await programKemiskinanState.findMany.load(); // refresh list
return true;
} else {
throw new Error(result.message || "Gagal update program kemiskinan");
}
} catch (error) {
console.error("Error updating program kemiskinan:", error);
toast.error(
error instanceof Error
? error.message
: "Terjadi kesalahan saat update program kemiskinan"
);
return false;
} finally {
programKemiskinanState.update.loading = false;
}
},
reset() {
programKemiskinanState.update.id = "";
programKemiskinanState.update.form = { ...defaultForm };
},
},
delete: {
loading: false,
async delete(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
programKemiskinanState.delete.loading = true;
const response = await fetch(
`/api/ekonomi/programkemiskinan/del/${id}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
);
const result = await response.json();
if (response.ok && result?.success) {
toast.success(
result.message || "Program kemiskinan berhasil dihapus"
);
await programKemiskinanState.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus program kemiskinan");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus program kemiskinan");
} finally {
programKemiskinanState.delete.loading = false;
}
},
},
});
export default programKemiskinanState;

View File

@@ -0,0 +1,202 @@
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 templateGrafikSektorUnggulan = z.object({
name: z.string().min(2, "Nama harus diisi"),
description: z.string().min(2, "Deskripsi harus diisi"),
value: z.number().min(1, "Nilai harus diisi"),
});
interface SektorUnggulanForm {
id?: string;
name: string;
description: string;
value: number;
}
const defaultForm: SektorUnggulanForm = {
name: "",
description: "",
value: 0,
};
const grafikSektorUnggulan = proxy({
create: {
form: defaultForm,
loading: false,
async create() {
const cek = templateGrafikSektorUnggulan.safeParse(
grafikSektorUnggulan.create.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
grafikSektorUnggulan.create.loading = true;
const res = await ApiFetch.api.ekonomi.sektourunggulandesa[
"create"
].post(grafikSektorUnggulan.create.form);
if (res.status === 200) {
const id = res.data?.data?.id;
if (id) {
toast.success("Success create");
grafikSektorUnggulan.create.form = {
name: "",
description: "",
value: 0,
};
grafikSektorUnggulan.findMany.load();
return id;
}
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
grafikSektorUnggulan.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.SektorUnggulanDesaGetPayload<{
select: {
id: true;
name: true;
description: true;
value: true;
createdAt: true;
updatedAt: true;
};
}>[]
| null,
loading: false,
async load() {
const res = await ApiFetch.api.ekonomi.sektourunggulandesa[
"find-many"
].get();
if (res.status === 200) {
grafikSektorUnggulan.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.SektorUnggulanDesaGetPayload<{
select: {
id: true;
name: true;
description: true;
value: true;
createdAt: true;
updatedAt: true;
};
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/ekonomi/sektourunggulandesa/${id}`);
if (res.ok) {
const data = await res.json();
grafikSektorUnggulan.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
grafikSektorUnggulan.findUnique.data = null;
}
} catch (error) {
console.error("Error loading grafik sektor unggulan desa:", error);
grafikSektorUnggulan.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 = templateGrafikSektorUnggulan.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/ekonomi/sektourunggulandesa/${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 grafikSektorUnggulan.findMany.load();
return result.data;
} catch (error) {
console.error("Error update data:", error);
toast.error("Gagal update data grafik sektor unggulan desa");
} finally {
this.loading = false;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
grafikSektorUnggulan.delete.loading = true;
const response = await fetch(
`/api/ekonomi/sektourunggulandesa/del/${id}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
);
const result = await response.json();
if (response.ok && result?.success) {
toast.success(
result.message || "Grafik sektor unggulan desa berhasil dihapus"
);
await grafikSektorUnggulan.findMany.load(); // refresh list
} else {
toast.error(
result?.message || "Gagal menghapus grafik sektor unggulan desa"
);
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error(
"Terjadi kesalahan saat menghapus grafik sektor unggulan desa"
);
} finally {
grafikSektorUnggulan.delete.loading = false;
}
},
},
});
export default grafikSektorUnggulan;

View File

@@ -0,0 +1,720 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { proxy } from "valtio";
import { z } from "zod";
import { toast } from "react-toastify";
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
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.ekonomi["struktur-organisasi"]["posisi-organisasi"]["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 };
},
},
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/ekonomi/struktur-organisasi/posisi-organisasi/${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/ekonomi/struktur-organisasi/posisi-organisasi/${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.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 ?? [];
}
} 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/ekonomi/struktur-organisasi/posisi-organisasi/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().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 };
},
},
});
// Schema Zod untuk form validasi
const templateHubunganOrganisasiForm = z.object({
atasanId: z.string().min(1, "Atasan wajib dipilih"),
bawahanId: z.string().min(1, "Bawahan wajib dipilih"),
tipe: z.string().optional(),
});
// Default form state
const defaultHubunganOrganisasiForm = {
atasanId: "",
bawahanId: "",
tipe: "",
};
// ====================== STATE ===========================
const hubunganOrganisasi = proxy({
create: {
form: { ...defaultHubunganOrganisasiForm },
loading: false,
async create() {
const cek = templateHubunganOrganisasiForm.safeParse(
hubunganOrganisasi.create.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}: ${v.message}`)
.join("\n")}]`;
return toast.error(err);
}
try {
hubunganOrganisasi.create.loading = true;
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();
return toast.success("Berhasil menambahkan hubungan organisasi");
} else {
return toast.error(res.data?.message || "Gagal menambahkan data");
}
} catch (error) {
console.error("Gagal create:", error);
toast.error("Terjadi kesalahan saat menambahkan");
} finally {
hubunganOrganisasi.create.loading = false;
}
},
},
findMany: {
data: null as Array<{
id: string;
atasanId: string;
bawahanId: string;
tipe?: string | null;
atasan: {
id: string;
namaLengkap: string;
gelarAkademik: string | null;
imageId: string | null;
tanggalMasuk: Date | null;
email: string | null;
telepon: string | null;
alamat: string | null;
posisiId: string;
isActive: boolean;
createdAt: Date;
updatedAt: Date;
};
bawahan: {
id: string;
namaLengkap: string;
gelarAkademik: string | null;
imageId: string | null;
tanggalMasuk: Date | null;
email: string | null;
telepon: string | null;
alamat: string | null;
posisiId: string;
isActive: boolean;
createdAt: Date;
updatedAt: Date;
};
}> | null,
async load() {
try {
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
}));
} else {
hubunganOrganisasi.findMany.data = [];
}
} catch (error) {
console.error("Fetch list error:", error);
toast.error("Gagal memuat data hubungan organisasi");
hubunganOrganisasi.findMany.data = [];
}
},
},
findUnique: {
data: null as {
id: string;
atasanId: string;
bawahanId: string;
tipe?: string | null;
atasan?: {
id: string;
namaLengkap: string;
gelarAkademik: string | null;
imageId: string;
tanggalMasuk: Date | null;
email: string | null;
telepon: string | null;
alamat: string | null;
posisiId: string;
aktif: boolean;
createdAt: Date;
updatedAt: Date;
};
bawahan?: {
id: string;
namaLengkap: string;
gelarAkademik: string | null;
imageId: string;
tanggalMasuk: Date | null;
email: string | null;
telepon: string | null;
alamat: string | null;
posisiId: string;
aktif: boolean;
createdAt: Date;
updatedAt: Date;
};
} | null,
async load(id: string) {
try {
const res = await fetch(`/api/ekonomi/struktur-organisasi/hubungan-organisasi/${id}`);
const result = await res.json();
if (res.ok && result?.success) {
hubunganOrganisasi.findUnique.data = result.data;
} else {
hubunganOrganisasi.findUnique.data = null;
toast.error(result?.message || "Gagal mengambil data");
}
} catch (error) {
console.error("Find unique error:", error);
hubunganOrganisasi.findUnique.data = null;
}
},
},
edit: {
id: "",
form: { ...defaultHubunganOrganisasiForm },
loading: false,
async load(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
const res = await fetch(`/api/ekonomi/struktur-organisasi/hubungan-organisasi/${id}`);
const result = await res.json();
if (res.ok && result?.success) {
const data = result.data;
this.id = data.id;
this.form = {
atasanId: data.atasanId,
bawahanId: data.bawahanId,
tipe: data.tipe || "",
};
return data;
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error loading:", error);
toast.error(error instanceof Error ? error.message : "Gagal memuat data");
return null;
}
},
async update() {
const cek = templateHubunganOrganisasiForm.safeParse(this.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}: ${v.message}`)
.join("\n")}]`;
return toast.error(err);
}
try {
this.loading = true;
const res = await fetch(
`/api/ekonomi/struktur-organisasi/hubungan-organisasi/${this.id}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(this.form),
}
);
const result = await res.json();
if (res.ok && result.success) {
await hubunganOrganisasi.findMany.load();
toast.success("Berhasil mengupdate hubungan organisasi");
return true;
} else {
throw new Error(result?.message || "Gagal mengupdate");
}
} catch (error) {
console.error("Update error:", error);
toast.error(error instanceof Error ? error.message : "Gagal update");
return false;
} finally {
this.loading = false;
}
},
reset() {
hubunganOrganisasi.edit.id = "";
hubunganOrganisasi.edit.form = { ...defaultHubunganOrganisasiForm };
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
hubunganOrganisasi.delete.loading = true;
const res = await fetch(`/api/ekonomi/struktur-organisasi/hubungan-organisasi/del/${id}`, {
method: "DELETE",
});
const result = await res.json();
if (res.ok && result?.success) {
toast.success("Hubungan organisasi berhasil dihapus");
hubunganOrganisasi.findMany.load();
} else {
toast.error(result?.message || "Gagal menghapus hubungan organisasi");
}
} catch (error) {
console.error("Delete error:", error);
toast.error("Terjadi kesalahan saat menghapus");
} finally {
hubunganOrganisasi.delete.loading = false;
}
},
},
});
const strukturorganisasiState = proxy({
posisiOrganisasi,
pegawai,
hubunganOrganisasi,
});
export default strukturorganisasiState;

View File

@@ -0,0 +1,376 @@
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 templateGrafikUsiaKerjaYangMenganggur = z.object({
usia18_25: z.string().min(1, "Data usia 18-25 harus diisi"),
usia26_35: z.string().min(1, "Data usia 26-35 harus diisi"),
usia36_45: z.string().min(1, "Data usia 36-45 harus diisi"),
usia46_keatas: z.string().min(1, "Data usia 46 keatas harus diisi"),
});
type GrafikUsiaKerjaYangMenganggur = Prisma.GrafikMenganggurBerdasarkanUsiaGetPayload<{
select: {
id: true;
usia18_25: true;
usia26_35: true;
usia36_45: true;
usia46_keatas: true;
};
}>;
const defaultForm: Omit<GrafikUsiaKerjaYangMenganggur, 'id'> & { id?: string } = {
usia18_25: "",
usia26_35: "",
usia36_45: "",
usia46_keatas: "",
};
const grafikBerdasarkanUsiaKerjaNganggur = proxy({
create: {
form: defaultForm,
loading: false,
async create() {
const cek = templateGrafikUsiaKerjaYangMenganggur.safeParse(
grafikBerdasarkanUsiaKerjaNganggur.create.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
grafikBerdasarkanUsiaKerjaNganggur.create.loading = true;
const res = await ApiFetch.api.ekonomi.grafikusiakerjayangmenganggur[
"create"
].post(grafikBerdasarkanUsiaKerjaNganggur.create.form);
if (res.status === 200) {
const id = res.data?.data?.id;
if (id) {
toast.success("Success create");
grafikBerdasarkanUsiaKerjaNganggur.create.form = {
usia18_25: "",
usia26_35: "",
usia36_45: "",
usia46_keatas: "",
};
grafikBerdasarkanUsiaKerjaNganggur.findMany.load();
return id;
}
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
grafikBerdasarkanUsiaKerjaNganggur.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.GrafikMenganggurBerdasarkanUsiaGetPayload<{
omit: { isActive: true };
}>[]
| null,
loading: false,
async load() {
const res = await ApiFetch.api.ekonomi.grafikusiakerjayangmenganggur[
"find-many"
].get();
if (res.status === 200) {
grafikBerdasarkanUsiaKerjaNganggur.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.GrafikMenganggurBerdasarkanUsiaGetPayload<{
omit: { isActive: true };
}> | null,
async load(id: string) {
try {
const res = await fetch(
`/api/ekonomi/grafikusiakerjayangmenganggur/${id}`
);
if (res.ok) {
const data = await res.json();
grafikBerdasarkanUsiaKerjaNganggur.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
grafikBerdasarkanUsiaKerjaNganggur.findUnique.data = null;
}
} catch (error) {
console.error("Error loading grafik berdasarkan usia kerja yang menganggur:", error);
grafikBerdasarkanUsiaKerjaNganggur.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 = templateGrafikUsiaKerjaYangMenganggur.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/ekonomi/grafikusiakerjayangmenganggur/${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 grafikBerdasarkanUsiaKerjaNganggur.findMany.load();
return result.data;
} catch (error) {
console.error("Error update data:", error);
toast.error("Gagal update data grafik berdasarkan usia kerja yang menganggur");
} finally {
this.loading = false;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
grafikBerdasarkanUsiaKerjaNganggur.delete.loading = true;
const response = await fetch(`/api/ekonomi/grafikusiakerjayangmenganggur/del/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Grafik berdasarkan usia kerja yang menganggur berhasil dihapus");
await grafikBerdasarkanUsiaKerjaNganggur.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus grafik berdasarkan usia kerja yang menganggur");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus grafik berdasarkan usia kerja yang menganggur");
} finally {
grafikBerdasarkanUsiaKerjaNganggur.delete.loading = false;
}
},
}
});
const templateGrafikBerpendidikanYangMenganggur = z.object({
SD: z.string().min(1, "Data SD harus diisi"),
SMP: z.string().min(1, "Data SMP harus diisi"),
SMA: z.string().min(1, "Data SMA harus diisi"),
D3: z.string().min(1, "Data D3 harus diisi"),
S1: z.string().min(1, "Data S1 harus diisi"),
});
type GrafikBerpendidikanYangMenganggur = Prisma.GrafikMenganggurBerdasarkanPendidikanGetPayload<{
select: {
id: true;
SD: true;
SMP: true;
SMA: true;
D3: true;
S1: true;
};
}>;
const defaultFormBerpendidikan: Omit<GrafikBerpendidikanYangMenganggur, 'id'> & { id?: string } = {
SD: "",
SMP: "",
SMA: "",
D3: "",
S1: "",
};
const grafikBerdasarkanPendidikan = proxy({
create: {
form: defaultFormBerpendidikan,
loading: false,
async create() {
const cek = templateGrafikBerpendidikanYangMenganggur.safeParse(
grafikBerdasarkanPendidikan.create.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
grafikBerdasarkanPendidikan.create.loading = true;
const res = await ApiFetch.api.ekonomi.grafikmenganggurberdasarkanpendidikan[
"create"
].post(grafikBerdasarkanPendidikan.create.form);
if (res.status === 200) {
const id = res.data?.data?.id;
if (id) {
toast.success("Success create");
grafikBerdasarkanPendidikan.create.form = {
SD: "",
SMP: "",
SMA: "",
D3: "",
S1: "",
};
grafikBerdasarkanPendidikan.findMany.load();
return id;
}
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
grafikBerdasarkanPendidikan.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.GrafikMenganggurBerdasarkanPendidikanGetPayload<{
omit: { isActive: true };
}>[]
| null,
loading: false,
async load() {
const res = await ApiFetch.api.ekonomi.grafikmenganggurberdasarkanpendidikan[
"find-many"
].get();
if (res.status === 200) {
grafikBerdasarkanPendidikan.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.GrafikMenganggurBerdasarkanPendidikanGetPayload<{
omit: { isActive: true };
}> | null,
async load(id: string) {
try {
const res = await fetch(
`/api/ekonomi/grafikmenganggurberdasarkanpendidikan/${id}`
);
if (res.ok) {
const data = await res.json();
grafikBerdasarkanPendidikan.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
grafikBerdasarkanPendidikan.findUnique.data = null;
}
} catch (error) {
console.error("Error loading grafik berdasarkan usia kerja yang menganggur:", error);
grafikBerdasarkanPendidikan.findUnique.data = null;
}
},
},
update: {
id: "",
form: {...defaultFormBerpendidikan},
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 = templateGrafikBerpendidikanYangMenganggur.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/ekonomi/grafikmenganggurberdasarkanpendidikan/${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 grafikBerdasarkanPendidikan.findMany.load();
return result.data;
} catch (error) {
console.error("Error update data:", error);
toast.error("Gagal update data grafik berdasarkan pendidikan yang menganggur");
} finally {
this.loading = false;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
grafikBerdasarkanPendidikan.delete.loading = true;
const response = await fetch(`/api/ekonomi/grafikmenganggurberdasarkanpendidikan/del/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Grafik berdasarkan pendidikan yang menganggur berhasil dihapus");
await grafikBerdasarkanPendidikan.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus grafik berdasarkan pendidikan yang menganggur");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus grafik berdasarkan pendidikan yang menganggur");
} finally {
grafikBerdasarkanPendidikan.delete.loading = false;
}
},
}
});
const grafikNganggur = proxy({
grafikBerdasarkanUsiaKerjaNganggur,
grafikBerdasarkanPendidikan
})
export default grafikNganggur;

View File

@@ -0,0 +1,232 @@
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"),
deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"),
imageId: z.string().nonempty(),
});
const defaultForm = {
name: "",
deskripsi: "",
imageId: "",
};
const keamananLingkunganState = proxy({
create: {
form: { ...defaultForm },
loading: false,
async create() {
const cek = templateForm.safeParse(keamananLingkunganState.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
keamananLingkunganState.create.loading = true;
const res = await ApiFetch.api.keamanan.keamananlingkungan[
"create"
].post(keamananLingkunganState.create.form);
if (res.status === 200) {
keamananLingkunganState.findMany.load();
return toast.success("success create");
}
console.log(res);
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
keamananLingkunganState.create.loading = false;
}
},
resetForm() {
keamananLingkunganState.create.form = { ...defaultForm };
},
},
findMany: {
data: null as
| Prisma.KeamananLingkunganGetPayload<{
include: { image: true };
}>[]
| null,
async load() {
const res = await ApiFetch.api.keamanan.keamananlingkungan[
"find-many"
].get();
if (res.status === 200) {
keamananLingkunganState.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.KeamananLingkunganGetPayload<{
include: { image: true };
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/keamanan/keamananlingkungan/${id}`);
if (res.ok) {
const data = await res.json();
keamananLingkunganState.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
keamananLingkunganState.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching data:", error);
keamananLingkunganState.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
keamananLingkunganState.delete.loading = true;
const response = await fetch(
`/api/keamanan/keamananlingkungan/del/${id}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
);
const result = await response.json();
if (response.ok && result?.success) {
toast.success(
result.message || "Keamanan ingkungan berhasil dihapus"
);
await keamananLingkunganState.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus keamanan ingkungan");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus keamanan ingkungan");
} finally {
keamananLingkunganState.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/keamanan/keamananlingkungan/${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; // Return the loaded data
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error loading keamanan lingkungan:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async update() {
const cek = templateForm.safeParse(keamananLingkunganState.edit.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return false;
}
try {
keamananLingkunganState.edit.loading = true;
const response = await fetch(
`/api/keamanan/keamananlingkungan/${this.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 keamanan lingkungan");
await keamananLingkunganState.findMany.load(); // refresh list
return true;
} else {
throw new Error(result.message || "Gagal update keamanan lingkungan");
}
} catch (error) {
console.error("Error updating keamanan lingkungan:", error);
toast.error(
error instanceof Error
? error.message
: "Terjadi kesalahan saat update keamanan lingkungan"
);
return false;
} finally {
keamananLingkunganState.edit.loading = false;
}
},
reset() {
keamananLingkunganState.edit.id = "";
keamananLingkunganState.edit.form = { ...defaultForm };
},
},
});
export default keamananLingkunganState;

View File

@@ -0,0 +1,259 @@
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({
nama: z.string().min(1, "Nama minimal 1 karakter"),
imageId: z.string().nonempty(),
kontakItems: z.array(
z.object({
nama: z.string().min(1, "Nama minimal 1 karakter"),
nomorTelepon: z.string().min(1, "Nomor Telepon minimal 1 karakter"),
imageId: z.string().nonempty(),
})
),
});
const defaultForm = {
nama: "",
imageId: "",
kontakItems: [
{
nama: "",
nomorTelepon: "",
imageId: "",
},
],
};
const kontakDaruratKeamananState = proxy({
create: {
form: { ...defaultForm },
loading: false,
async create() {
const cek = templateForm.safeParse(
kontakDaruratKeamananState.create.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
kontakDaruratKeamananState.create.loading = true;
const res = await ApiFetch.api.keamanan.kontakdaruratkeamanan[
"create"
].post(kontakDaruratKeamananState.create.form);
if (res.status === 200) {
kontakDaruratKeamananState.findMany.load();
return toast.success("success create");
}
console.log(res);
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
kontakDaruratKeamananState.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.KontakDaruratKeamananGetPayload<{
include: {
kontakItems: true;
image: true;
};
}>[]
| null,
async load() {
const res = await ApiFetch.api.keamanan.kontakdaruratkeamanan[
"find-many"
].get();
if (res.status === 200) {
kontakDaruratKeamananState.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.KontakDaruratKeamananGetPayload<{
include: {
kontakItems: {
include: {
image: true;
};
};
image: true;
};
}> | null,
loading: false,
async load(id: string) {
try {
const res = await fetch(`/api/keamanan/kontakdaruratkeamanan/${id}`);
if (res.ok) {
const data = await res.json();
kontakDaruratKeamananState.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
kontakDaruratKeamananState.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching data:", error);
kontakDaruratKeamananState.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
kontakDaruratKeamananState.delete.loading = true;
const response = await fetch(
`/api/keamanan/kontakdaruratkeamanan/del/${id}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
);
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Kontak darurat berhasil dihapus");
await kontakDaruratKeamananState.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus kontak darurat");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus kontak darurat");
} finally {
kontakDaruratKeamananState.delete.loading = false;
}
},
},
update: {
id: "",
form: { ...defaultForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(
`/api/keamanan/kontakdaruratkeamanan/${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,
imageId: data.imageId,
kontakItems: [
{
nama: data.kontakItems.nama,
nomorTelepon: data.kontakItems.nomorTelepon,
imageId: data.kontakItems.imageId,
},
],
};
return data;
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error loading kontak darurat:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async update() {
const cek = templateForm.safeParse(
kontakDaruratKeamananState.update.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
kontakDaruratKeamananState.update.loading = true;
const response = await fetch(
`/api/keamanan/kontakdaruratkeamanan/${this.id}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
nama: this.form.nama,
imageId: this.form.imageId,
kontakItems: [
{
nama: this.form.kontakItems[0].nama,
nomorTelepon: this.form.kontakItems[0].nomorTelepon,
imageId: this.form.kontakItems[0].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 kontak darurat");
await kontakDaruratKeamananState.findMany.load(); // refresh list
return true;
} else {
throw new Error(result.message || "Gagal mengupdate kontak darurat");
}
} catch (error) {
console.error("Error updating kontak darurat:", error);
toast.error(
error instanceof Error
? error.message
: "Gagal mengupdate kontak darurat"
);
return false;
} finally {
kontakDaruratKeamananState.update.loading = false;
}
},
reset() {
kontakDaruratKeamananState.update.id = "";
kontakDaruratKeamananState.update.form = { ...defaultForm };
},
},
});
export default kontakDaruratKeamananState;

View File

@@ -0,0 +1,273 @@
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
import { proxy } from "valtio";
import { z } from "zod";
export type Status = "Selesai" | "Proses" | "Gagal";
const templateForm = z.object({
judul: z.string().min(3, "Judul minimal 3 karakter"),
lokasi: z.string().min(3, "Lokasi minimal 3 karakter"),
tanggalWaktu: z.string().min(3, "Tanggal Waktu minimal 3 karakter"),
status: z.enum(["Selesai", "Proses", "Gagal"]),
penanganan: z.string(),
kronologi: z.string().optional(),
});
interface FormData {
judul: string;
lokasi: string;
tanggalWaktu: string;
status: Status;
penanganan: string;
kronologi: string;
}
const defaultForm: FormData = {
judul: "",
lokasi: "",
tanggalWaktu: new Date().toISOString(),
status: "Proses",
penanganan: "",
kronologi: "",
};
const laporanPublikState = proxy({
create: {
form: { ...defaultForm },
loading: false,
async create() {
const cek = templateForm.safeParse(laporanPublikState.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
laporanPublikState.create.loading = true;
// Ensure we have a valid date
if (!laporanPublikState.create.form.tanggalWaktu) {
return toast.error("Tanggal laporan harus diisi");
}
// Format the data before sending
const formData = {
...laporanPublikState.create.form,
// Ensure the date is in the correct format for the API
tanggalWaktu: new Date(laporanPublikState.create.form.tanggalWaktu).toISOString()
};
console.log("Sending form data:", formData); // Debug log
const res = await ApiFetch.api.keamanan.laporanpublik["create"].post(
formData
);
if (res.error) {
console.error("API Error:", res.error);
throw new Error("Failed to create laporan publik");
}
if (res.status === 200) {
laporanPublikState.findMany.load();
return toast.success("success create");
}
console.log(res);
return toast.error("failed create");
} catch (error) {
console.error("Error creating laporan publik:", error);
toast.error(error instanceof Error ? error.message : "Gagal membuat laporan publik");
throw error; // Re-throw to be handled by the caller
} finally {
laporanPublikState.create.loading = false;
}
},
resetForm() {
laporanPublikState.create.form = { ...defaultForm };
},
},
findMany: {
data: null as
| Prisma.LaporanPublikGetPayload<{
include: { penanganan: true };
}>[]
| null,
async load() {
const res = await ApiFetch.api.keamanan.laporanpublik["find-many"].get();
if (res.status === 200) {
laporanPublikState.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.LaporanPublikGetPayload<{
include: { penanganan: true };
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/keamanan/laporanpublik/${id}`);
if (res.ok) {
const data = await res.json();
laporanPublikState.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
laporanPublikState.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching data:", error);
laporanPublikState.findUnique.data = null;
}
},
resetForm() {
laporanPublikState.findUnique.data = null;
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
laporanPublikState.delete.loading = true;
const response = await fetch(`/api/keamanan/laporanpublik/del/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
const result = await response.json();
if (response.ok && result?.success) {
toast.success(
result.message || "Laporan publik berhasil dihapus"
);
await laporanPublikState.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus laporan publik");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus laporan publik");
} finally {
laporanPublikState.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/keamanan/laporanpublik/${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,
lokasi: data.lokasi,
tanggalWaktu: data.tanggalWaktu,
status: data.status,
penanganan: data.penanganan,
kronologi: data.kronologi,
};
return data; // Return the loaded data
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error loading keamanan lingkungan:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async update() {
const cek = templateForm.safeParse(laporanPublikState.edit.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return false;
}
try {
laporanPublikState.edit.loading = true;
const response = await fetch(
`/api/keamanan/laporanpublik/${this.id}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
judul: this.form.judul,
lokasi: this.form.lokasi,
tanggalWaktu: this.form.tanggalWaktu,
status: this.form.status,
penanganan: this.form.penanganan,
kronologi: this.form.kronologi,
}),
}
);
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 laporan publik");
await laporanPublikState.findMany.load(); // refresh list
return true;
} else {
throw new Error(result.message || "Gagal update laporan publik");
}
} catch (error) {
console.error("Error updating laporan publik:", error);
toast.error(
error instanceof Error
? error.message
: "Terjadi kesalahan saat update laporan publik"
);
return false;
} finally {
laporanPublikState.edit.loading = false;
}
},
reset() {
laporanPublikState.edit.id = "";
laporanPublikState.edit.form = { ...defaultForm };
},
}
});
export default laporanPublikState;

View File

@@ -0,0 +1,314 @@
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({
pencegahanKriminalitas: z.object({
programKeamanan: z.object({
nama: z.string().min(1, "Nama minimal 1 karakter"),
deskripsi: z.string().min(1, "Deskripsi minimal 1 karakter"),
slug: z.string().min(1, "Slug minimal 1 karakter"),
}),
tipsKeamanan: z.object({
judul: z.string().min(1, "Judul minimal 1 karakter"),
konten: z.string().min(1, "Konten minimal 1 karakter"),
slug: z.string().min(1, "Slug minimal 1 karakter"),
}),
videoKeamanan: z.object({
judul: z.string().min(1, "Judul minimal 1 karakter"),
deskripsi: z.string().min(1, "Deskripsi minimal 1 karakter"),
videoUrl: z.string().min(1, "Video URL minimal 1 karakter"),
slug: z.string().min(1, "Slug minimal 1 karakter"),
}),
}),
});
const defaultForm = {
pencegahanKriminalitas: {
programKeamanan: {
nama: "",
deskripsi: "",
slug: "",
},
tipsKeamanan: {
judul: "",
konten: "",
slug: "",
},
videoKeamanan: {
judul: "",
deskripsi: "",
videoUrl: "",
slug: "",
},
},
};
const pencegahanKriminalitasState = proxy({
create: {
form: { ...defaultForm },
loading: false,
async create() {
const cek = templateForm.safeParse(
pencegahanKriminalitasState.create.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
pencegahanKriminalitasState.create.loading = true;
const res = await ApiFetch.api.keamanan.pencegahankriminalitas[
"create"
].post(pencegahanKriminalitasState.create.form.pencegahanKriminalitas);
if (res.status === 200) {
pencegahanKriminalitasState.findMany.load();
return toast.success("success create");
}
console.log(res);
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
pencegahanKriminalitasState.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.PencegahanKriminalitasGetPayload<{
include: {
programKeamanan: true;
tipsKeamanan: true;
videoKeamanan: true;
};
}>[]
| null,
async load() {
const res = await ApiFetch.api.keamanan.pencegahankriminalitas[
"find-many"
].get();
if (res.status === 200) {
pencegahanKriminalitasState.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.PencegahanKriminalitasGetPayload<{
include: {
programKeamanan: true;
tipsKeamanan: true;
videoKeamanan: true;
};
}> | null,
loading: false,
async load(id: string) {
try {
const res = await fetch(`/api/keamanan/pencegahankriminalitas/${id}`);
if (res.ok) {
const data = await res.json();
pencegahanKriminalitasState.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
pencegahanKriminalitasState.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching data:", error);
pencegahanKriminalitasState.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
pencegahanKriminalitasState.delete.loading = true;
const response = await fetch(
`/api/keamanan/pencegahankriminalitas/del/${id}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
);
const result = await response.json();
if (response.ok && result?.success) {
toast.success(
result.message || "Pencegahan kriminalitas berhasil dihapus"
);
await pencegahanKriminalitasState.findMany.load(); // refresh list
} else {
toast.error(
result?.message || "Gagal menghapus pencegahan kriminalitas"
);
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus pencegahan kriminalitas");
} finally {
pencegahanKriminalitasState.delete.loading = false;
}
},
},
update: {
id: "",
form: { ...defaultForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
pencegahanKriminalitasState.update.loading = true;
const response = await fetch(
`/api/keamanan/pencegahankriminalitas/${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;
pencegahanKriminalitasState.update.id = data.id;
pencegahanKriminalitasState.update.form = {
pencegahanKriminalitas: {
programKeamanan: {
nama: data.programKeamanan.nama,
deskripsi: data.programKeamanan.deskripsi,
slug: data.programKeamanan.slug,
},
tipsKeamanan: {
judul: data.tipsKeamanan.judul,
konten: data.tipsKeamanan.konten,
slug: data.tipsKeamanan.slug,
},
videoKeamanan: {
judul: data.videoKeamanan.judul,
deskripsi: data.videoKeamanan.deskripsi,
videoUrl: data.videoKeamanan.videoUrl,
slug: data.videoKeamanan.slug,
},
},
};
return data;
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Gagal update:", error);
toast.error(
"Terjadi kesalahan saat mengupdate pencegahan kriminalitas"
);
return null;
}
},
async update() {
const cek = templateForm.safeParse(
pencegahanKriminalitasState.update.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
pencegahanKriminalitasState.update.loading = true;
const response = await fetch(
`/api/keamanan/pencegahankriminalitas/${pencegahanKriminalitasState.update.id}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
pencegahanKriminalitas: {
programKeamanan: {
nama: pencegahanKriminalitasState.update.form
.pencegahanKriminalitas.programKeamanan.nama,
deskripsi:
pencegahanKriminalitasState.update.form
.pencegahanKriminalitas.programKeamanan.deskripsi,
slug: pencegahanKriminalitasState.update.form
.pencegahanKriminalitas.programKeamanan.slug,
},
tipsKeamanan: {
judul:
pencegahanKriminalitasState.update.form
.pencegahanKriminalitas.tipsKeamanan.judul,
konten:
pencegahanKriminalitasState.update.form
.pencegahanKriminalitas.tipsKeamanan.konten,
slug: pencegahanKriminalitasState.update.form
.pencegahanKriminalitas.tipsKeamanan.slug,
},
videoKeamanan: {
judul:
pencegahanKriminalitasState.update.form
.pencegahanKriminalitas.videoKeamanan.judul,
deskripsi:
pencegahanKriminalitasState.update.form
.pencegahanKriminalitas.videoKeamanan.deskripsi,
videoUrl:
pencegahanKriminalitasState.update.form
.pencegahanKriminalitas.videoKeamanan.videoUrl,
slug: pencegahanKriminalitasState.update.form
.pencegahanKriminalitas.videoKeamanan.slug,
},
},
}),
}
);
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 pencegahan kriminalitas");
await pencegahanKriminalitasState.findMany.load(); // refresh list
return true;
} else {
throw new Error(
result.message || "Gagal mengupdate pencegahan kriminalitas"
);
}
} catch (error) {
console.error("Gagal update:", error);
toast.error(
error instanceof Error
? error.message
: "Gagal mengupdate pencegahan kriminalitas"
);
return false;
} finally {
pencegahanKriminalitasState.update.loading = false;
}
},
reset() {
pencegahanKriminalitasState.update.id = "";
pencegahanKriminalitasState.update.form = { ...defaultForm };
},
},
});
export default pencegahanKriminalitasState;

View File

@@ -0,0 +1,242 @@
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({
nama: z.string().min(1, "Nama minimal 1 karakter"),
jarakKeDesa: z.string().min(1, "Jarak minimal 1 karakter"),
alamat: z.string().min(1, "Alamat minimal 1 karakter"),
nomorTelepon: z.string().min(1, "Nomor Telepon minimal 1 karakter"),
jamOperasional: z.string().min(1, "Jam Operasional minimal 1 karakter"),
embedMapUrl: z.string().min(1, "Embed Map Url minimal 1 karakter"),
namaTempatMaps: z.string().min(1, "Nama Tempat Maps minimal 1 karakter"),
alamatMaps: z.string().min(1, "Alamat Maps minimal 1 karakter"),
linkPetunjukArah: z.string().min(1, "Link Petunjuk Arah minimal 1 karakter"),
layananPolsekId: z.string().min(1, "Layanan Polsek Id minimal 1 karakter"),
});
const defaultForm = {
nama: "",
jarakKeDesa: "",
alamat: "",
nomorTelepon: "",
jamOperasional: "",
embedMapUrl: "",
namaTempatMaps: "",
alamatMaps: "",
linkPetunjukArah: "",
layananPolsekId: "",
};
const polsekTerdekatState = proxy({
create: {
form: { ...defaultForm },
loading: false,
async create() {
const cek = templateForm.safeParse(polsekTerdekatState.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
polsekTerdekatState.create.loading = true;
const res = await ApiFetch.api.keamanan.polsekterdekat["create"].post(
polsekTerdekatState.create.form
);
if (res.status === 200) {
polsekTerdekatState.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 {
polsekTerdekatState.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.PolsekTerdekatGetPayload<{
include: { layananPolsek: true };
}>[]
| null,
async load() {
const res = await ApiFetch.api.keamanan.polsekterdekat["find-many"].get();
if (res.status === 200) {
polsekTerdekatState.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.PolsekTerdekatGetPayload<{
include: { layananPolsek: true };
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/keamanan/polsekterdekat/${id}`);
if (res.ok) {
const data = await res.json();
polsekTerdekatState.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
polsekTerdekatState.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching data:", error);
polsekTerdekatState.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
polsekTerdekatState.delete.loading = true;
const response = await fetch(`/api/keamanan/polsekterdekat/del/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Polsek terdekat berhasil dihapus");
await polsekTerdekatState.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus polsek terdekat");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus polsek terdekat");
} finally {
polsekTerdekatState.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/keamanan/polsekterdekat/${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,
jarakKeDesa: data.jarakKeDesa,
alamat: data.alamat,
nomorTelepon: data.nomorTelepon,
jamOperasional: data.jamOperasional,
embedMapUrl: data.embedMapUrl,
namaTempatMaps: data.namaTempatMaps,
alamatMaps: data.alamatMaps,
linkPetunjukArah: data.linkPetunjukArah,
layananPolsekId: data.layananPolsekId,
};
return data;
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error loading polsek terdekat:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async update() {
const cek = templateForm.safeParse(polsekTerdekatState.edit.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
polsekTerdekatState.edit.loading = true;
const response = await fetch(
`/api/keamanan/polsekterdekat/${this.id}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
nama: this.form.nama,
jarakKeDesa: this.form.jarakKeDesa,
alamat: this.form.alamat,
nomorTelepon: this.form.nomorTelepon,
jamOperasional: this.form.jamOperasional,
embedMapUrl: this.form.embedMapUrl,
namaTempatMaps: this.form.namaTempatMaps,
alamatMaps: this.form.alamatMaps,
linkPetunjukArah: this.form.linkPetunjukArah,
layananPolsekId: this.form.layananPolsekId,
}),
}
);
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 polsek terdekat");
await polsekTerdekatState.findMany.load(); // refresh list
return true;
} else {
throw new Error(result.message || "Gagal mengupdate polsek terdekat");
}
} catch (error) {
console.error("Error updating polsek terdekat:", error);
toast.error(
error instanceof Error
? error.message
: "Gagal mengupdate polsek terdekat"
);
return false;
} finally {
polsekTerdekatState.edit.loading = false;
}
},
reset() {
polsekTerdekatState.edit.id = "";
polsekTerdekatState.edit.form = { ...defaultForm };
},
},
});
export default polsekTerdekatState;

View File

@@ -0,0 +1,212 @@
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({
judul: z.string().min(3, "Nama minimal 3 karakter"),
deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"),
imageId: z.string().nonempty(),
});
const defaultForm = {
judul: "",
deskripsi: "",
imageId: "",
};
const tipsKeamananState = proxy({
create: {
form: { ...defaultForm },
loading: false,
async create() {
const cek = templateForm.safeParse(tipsKeamananState.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
tipsKeamananState.create.loading = true;
const res = await ApiFetch.api.keamanan.menutipskeamanan["create"].post(
tipsKeamananState.create.form
);
if (res.status === 200) {
tipsKeamananState.findMany.load();
return toast.success("success create");
}
console.log(res);
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
tipsKeamananState.create.loading = false;
}
},
resetForm() {
tipsKeamananState.create.form = { ...defaultForm };
},
},
findMany: {
data: null as
| Prisma.MenuTipsKeamananGetPayload<{
include: { image: true };
}>[]
| null,
async load() {
const res = await ApiFetch.api.keamanan.menutipskeamanan[
"find-many"
].get();
if (res.status === 200) {
tipsKeamananState.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.MenuTipsKeamananGetPayload<{
include: { image: true };
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/keamanan/menutipskeamanan/${id}`);
if (res.ok) {
const data = await res.json();
tipsKeamananState.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
tipsKeamananState.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching data:", error);
tipsKeamananState.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
tipsKeamananState.delete.loading = true;
const response = await fetch(
`/api/keamanan/menutipskeamanan/del/${id}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
);
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Tips keamanan berhasil dihapus");
await tipsKeamananState.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus tips keamanan");
}
} catch (error) {
toast.error("Terjadi kesalahan saat menghapus tips keamanan");
console.error("Gagal delete:", error);
} finally {
tipsKeamananState.delete.loading = false;
}
},
},
update: {
id: "",
loading: false,
form: { ...defaultForm },
async load(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
tipsKeamananState.update.loading = true;
const response = await fetch(`/api/keamanan/menutipskeamanan/${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 || "",
};
return data; // Return the loaded data
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error fetching data:", error);
toast.error("Gagal memuat data");
return null;
}
},
async update() {
const cek = templateForm.safeParse(tipsKeamananState.update.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
tipsKeamananState.update.loading = true;
const response = await fetch(
`/api/keamanan/menutipskeamanan/${tipsKeamananState.update.id}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
judul: this.form.judul,
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 tips keamanan");
await tipsKeamananState.findMany.load(); // refresh list
return true;
} else {
throw new Error(result.message || "Gagal update tips keamanan");
}
} catch (error) {
console.error("Error updating data:", error);
toast.error("Gagal update data");
return false;
} finally {
tipsKeamananState.update.loading = false;
}
},
reset() {
tipsKeamananState.update.id = "";
tipsKeamananState.update.form = { ...defaultForm };
},
},
});
export default tipsKeamananState;

View File

@@ -1,339 +1,306 @@
/* 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";
/* Introduction */
const templateIntroduction = z.object({
content: z.string().min(3, "Content minimal 3 karakter"),
})
const templateForm = z.object({
title: z.string().min(1, "Judul harus diisi"),
content: z.string().min(1, "Content harus diisi"),
introduction: z.object({
content: z.string().min(1, "Content harus diisi"),
}),
symptom: z.object({
title: z.string().min(1, "Judul harus diisi"),
content: z.string().min(1, "Content harus diisi"),
}),
prevention: z.object({
title: z.string().min(1, "Judul harus diisi"),
content: z.string().min(1, "Content harus diisi"),
}),
firstAid: z.object({
title: z.string().min(1, "Judul harus diisi"),
content: z.string().min(1, "Content harus diisi"),
}),
mythVsFact: z.object({
title: z.string().min(1, "Judul harus diisi"),
mitos: z.string().min(1, "Mitos harus diisi"),
fakta: z.string().min(1, "Fakta harus diisi"),
}),
doctorSign: z.object({
content: z.string().min(1, "Content harus diisi"),
}),
});
type Introduction = Prisma.IntroductionGetPayload<{
select: {
content: true;
};
}>;
const defaultForm = {
title: "",
content: "",
introduction: {
content: "",
},
symptom: {
title: "",
content: "",
},
prevention: {
title: "",
content: "",
},
firstAid: {
title: "",
content: "",
},
mythVsFact: {
title: "",
mitos: "",
fakta: "",
},
doctorSign: {
content: "",
},
};
const introduction = proxy({
const artikelKesehatanState = proxy({
create: {
form: {} as Introduction,
form: { ...defaultForm },
loading: false,
async create() {
const cek = templateIntroduction.safeParse(introduction.create.form);
async submit() {
const cek = templateForm.safeParse(this.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
const errMsg = cek.error.issues
.map((v) => `${v.path.join(".")}: ${v.message}`)
.join("\n");
toast.error(errMsg);
return null;
}
try {
introduction.create.loading = true;
const res = await ApiFetch.api.kesehatan.introduction["create"].post(introduction.create.form);
this.loading = true;
const payload = { ...this.form };
const res = await (ApiFetch.api.kesehatan as any)[
"artikel-kesehatan"
].create.post(payload);
if (res.status === 200) {
introduction.findMany.load();
return toast.success("success create");
toast.success("Berhasil menambahkan artikel kesehatan");
this.resetForm();
await artikelKesehatanState.findMany.load();
return res.data;
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} catch (err: any) {
const msg = err?.message || "Terjadi kesalahan saat mengirim data";
toast.error(msg);
console.error("SUBMIT ERROR:", err);
return null;
} finally {
introduction.create.loading = false;
this.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.IntroductionGetPayload<{ omit: { isActive: true } }>[]
| null,
async load() {
const res = await ApiFetch.api.kesehatan.introduction["find-many"].get();
if (res.status === 200) {
introduction.findMany.data = res.data?.data ?? [];
}
}
}
});
/* ======================================================================= */
/* symptom */
const templateSymptom = z.object({
title: z.string().min(3, "Title minimal 3 karakter"),
content: z.string().min(3, "Content minimal 3 karakter"),
})
type Symptom = Prisma.SymptomGetPayload<{
select: {
title: true;
content: true;
};
}>;
const symptom = proxy({
create: {
form: {} as Symptom,
loading: false,
async create() {
const cek = templateSymptom.safeParse(symptom.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
symptom.create.loading = true;
const res = await ApiFetch.api.kesehatan.symptom["create"].post(symptom.create.form);
if (res.status === 200) {
symptom.findMany.load();
return toast.success("success create");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
symptom.create.loading = false;
}
resetForm() {
this.form = { ...defaultForm };
},
},
findMany: {
data: null as
| Prisma.SymptomGetPayload<{ omit: { isActive: true } }>[]
| Prisma.ArtikelKesehatanGetPayload<{
include: {
introduction: true;
symptom: true;
prevention: true;
firstaid: true;
mythvsfact: true;
doctorsign: true;
};
}>[]
| null,
loading: false,
async load() {
const res = await ApiFetch.api.kesehatan.symptom["find-many"].get();
if (res.status === 200) {
symptom.findMany.data = res.data?.data ?? [];
try {
this.loading = true;
const res = await (ApiFetch.api.kesehatan as any)["artikel-kesehatan"][
"find-many"
].get();
if (res.status === 200) {
this.data = res.data?.data ?? [];
} else {
toast.error("Gagal memuat data artikel kesehatan");
}
return res;
} catch (err) {
toast.error("Terjadi error saat load data");
console.error("LOAD ERROR:", err);
throw err;
} finally {
this.loading = false;
}
},
},
findUnique: {
data: null as Prisma.ArtikelKesehatanGetPayload<{
include: {
introduction: true;
symptom: true;
prevention: true;
firstaid: true;
mythvsfact: true;
doctorsign: true;
};
}> | null,
loading: false,
async load(id: string) {
const res = await fetch(`/api/kesehatan/artikel-kesehatan/${id}`);
if (res.ok) {
const data = await res.json();
artikelKesehatanState.findUnique.data = data.data ?? null;
} else {
toast.error("Gagal load data artikel kesehatan");
}
},
},
edit: {
id: "",
form: { ...defaultForm },
loading: false,
async load(id: string) {
const res = await fetch(`/api/kesehatan/artikel-kesehatan/${id}`);
if (!res.ok) {
toast.error("Gagal load data artikel kesehatan");
return;
}
const result = await res.json();
const data = result.data;
artikelKesehatanState.edit.id = data.id;
artikelKesehatanState.edit.form = {
title: data.title,
content: data.content,
introduction: {
content: data.introduction.content,
},
symptom: {
title: data.symptom.title,
content: data.symptom.content,
},
prevention: {
title: data.prevention.title,
content: data.prevention.content,
},
firstAid: {
title: data.firstaid.title,
content: data.firstaid.content,
},
mythVsFact: {
title: data.mythvsfact.title,
mitos: data.mythvsfact.mitos,
fakta: data.mythvsfact.fakta,
},
doctorSign: {
content: data.doctorsign.content,
},
};
},
async submit() {
const cek = templateForm.safeParse(artikelKesehatanState.edit.form);
if (!cek.success) {
const errMsg = cek.error.issues
.map((v) => `${v.path.join(".")}: ${v.message}`)
.join("\n");
toast.error(errMsg);
return null;
}
try {
artikelKesehatanState.edit.loading = true;
const payload = {
title: artikelKesehatanState.edit.form.title,
content: artikelKesehatanState.edit.form.content,
introduction: {
content: artikelKesehatanState.edit.form.introduction.content,
},
symptom: {
title: artikelKesehatanState.edit.form.symptom.title,
content: artikelKesehatanState.edit.form.symptom.content,
},
prevention: {
title: artikelKesehatanState.edit.form.prevention.title,
content: artikelKesehatanState.edit.form.prevention.content,
},
firstAid: {
title: artikelKesehatanState.edit.form.firstAid.title,
content: artikelKesehatanState.edit.form.firstAid.content,
},
mythVsFact: {
title: artikelKesehatanState.edit.form.mythVsFact.title,
mitos: artikelKesehatanState.edit.form.mythVsFact.mitos,
fakta: artikelKesehatanState.edit.form.mythVsFact.fakta,
},
doctorSign: {
content: artikelKesehatanState.edit.form.doctorSign.content,
},
};
const res = await fetch(
`/api/kesehatan/artikel-kesehatan/${artikelKesehatanState.edit.id}`,
{
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
}
);
if (!res.ok) {
const error = await res.json();
throw new Error(error.message || "Update gagal");
}
toast.success("Berhasil update artikel kesehatan");
await artikelKesehatanState.findMany.load();
return true;
} catch (err) {
toast.error(
err instanceof Error ? err.message : "Terjadi kesalahan saat update"
);
return false;
} finally {
artikelKesehatanState.edit.loading = false;
}
},
resetForm() {
artikelKesehatanState.edit.id = "";
artikelKesehatanState.edit.form = { ...defaultForm };
},
},
delete: {
loading: false,
async byId(id: string) {
try {
artikelKesehatanState.delete.loading = true;
const res = await fetch(
`/api/kesehatan/artikel-kesehatan/del/${id}`,
{
method: "DELETE",
}
);
const result = await res.json();
if (res.ok && result.success) {
toast.success("Artikel kesehatan berhasil dihapus");
await artikelKesehatanState.findMany.load();
} else {
toast.error(result.message || "Gagal menghapus");
}
} catch {
toast.error("Terjadi kesalahan saat menghapus");
} finally {
artikelKesehatanState.delete.loading = false;
}
},
},
});
/* ======================================================================= */
/* Prevention */
const templatePrevention = z.object({
title: z.string().min(3, "Title minimal 3 karakter"),
content: z.string().min(3, "Content minimal 3 karakter"),
})
type Prevention = Prisma.PreventionGetPayload<{
select: {
title: true;
content: true;
};
}>;
const prevention = proxy({
create: {
form: {} as Prevention,
loading: false,
async create() {
const cek = templatePrevention.safeParse(prevention.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
prevention.create.loading = true;
const res = await ApiFetch.api.kesehatan.prevention["create"].post(prevention.create.form);
if (res.status === 200) {
prevention.findMany.load();
return toast.success("success create");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
prevention.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.PreventionGetPayload<{ omit: { isActive: true } }>[]
| null,
async load() {
const res = await ApiFetch.api.kesehatan.prevention["find-many"].get();
if (res.status === 200) {
prevention.findMany.data = res.data?.data ?? [];
}
},
},
});
/* ======================================================================= */
/* First Aid */
const templateFirstAid = z.object({
title: z.string().min(3, "Title minimal 3 karakter"),
content: z.string().min(3, "Content minimal 3 karakter"),
})
type FirstAid = Prisma.FirstAidGetPayload<{
select: {
title: true;
content: true;
};
}>;
const firstAid = proxy({
create: {
form: {} as FirstAid,
loading: false,
async create() {
const cek = templateFirstAid.safeParse(firstAid.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
firstAid.create.loading = true;
const res = await ApiFetch.api.kesehatan.firstaid["create"].post(firstAid.create.form);
if (res.status === 200) {
firstAid.findMany.load();
return toast.success("success create");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
firstAid.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.FirstAidGetPayload<{ omit: { isActive: true } }>[]
| null,
async load() {
const res = await ApiFetch.api.kesehatan.firstaid["find-many"].get();
if (res.status === 200) {
firstAid.findMany.data = res.data?.data ?? [];
}
},
},
})
/* ======================================================================= */
/* Myth vs Fact */
const templateMythFact = z.object({
title: z.string().min(3, "Title minimal 3 karakter"),
mitos: z.string().min(3, "Mitos minimal 3 karakter"),
fakta: z.string().min(3, "Fakta minimal 3 karakter"),
})
type MythFact = Prisma.MythVsFactGetPayload<{
select: {
title: true;
mitos: true;
fakta: true;
};
}>;
const mythFact = proxy({
create: {
form: {} as MythFact,
loading: false,
async create() {
const cek = templateMythFact.safeParse(mythFact.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
mythFact.create.loading = true;
const res = await ApiFetch.api.kesehatan.mythvsfact["create"].post(mythFact.create.form);
if (res.status === 200) {
mythFact.findMany.load();
return toast.success("success create");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
mythFact.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.MythVsFactGetPayload<{ omit: { isActive: true } }>[]
| null,
async load() {
const res = await ApiFetch.api.kesehatan.mythvsfact["find-many"].get();
if (res.status === 200) {
mythFact.findMany.data = res.data?.data ?? [];
}
},
},
})
/* ======================================================================= */
/* Doctor Sign */
const templateDoctorSign = z.object({
content: z.string().min(3, "Content minimal 3 karakter"),
})
type DoctorSign = Prisma.DoctorSignGetPayload<{
select: {
content: true
}
}>
const doctorSign = proxy({
create: {
form: {} as DoctorSign,
loading: false,
async create() {
const cek = templateDoctorSign.safeParse(doctorSign.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
doctorSign.create.loading = true;
const res = await ApiFetch.api.kesehatan.doctor_sign["create"].post(doctorSign.create.form);
if (res.status === 200) {
doctorSign.findMany.load();
return toast.success("success create");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
doctorSign.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.DoctorSignGetPayload<{ omit: { isActive: true } }>[]
| null,
async load() {
const res = await ApiFetch.api.kesehatan.doctor_sign["find-many"].get();
if (res.status === 200) {
doctorSign.findMany.data = res.data?.data ?? [];
}
},
},
})
/* ======================================================================= */
const stateArtikelKesehatan = proxy({
introduction,
symptom,
prevention,
firstAid,
mythFact,
doctorSign
})
export default stateArtikelKesehatan
export default artikelKesehatanState;

View File

@@ -1,333 +1,308 @@
/* 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";
/* Informasi Umum */
const templateInformasiUmum = z.object({
fasilitas: z.string().min(3, "Fasilitas minimal 3 karakter"),
alamat: z.string().min(3, "Alamat minimal 3 karakter"),
jamOperasional: z.string().min(3, "Jam Operasional minimal 3 karakter"),
// Validasi form
const templateForm = z.object({
name: z.string().min(1, "Nama harus diisi"),
informasiUmum: z.object({
fasilitas: z.string().min(1, "Fasilitas harus diisi"),
alamat: z.string().min(1, "Alamat harus diisi"),
jamOperasional: z.string().min(1, "Jam operasional harus diisi"),
}),
layananUnggulan: z.object({
content: z.string().min(1, "Layanan unggulan harus diisi"),
}),
dokterdanTenagaMedis: z.object({
name: z.string().min(1, "Nama dokter harus diisi"),
specialist: z.string().min(1, "Spesialis harus diisi"),
jadwal: z.string().min(1, "Jadwal harus diisi"),
}),
fasilitasPendukung: z.object({
content: z.string().min(1, "Fasilitas pendukung harus diisi"),
}),
prosedurPendaftaran: z.object({
content: z.string().min(1, "Prosedur pendaftaran harus diisi"),
}),
tarifDanLayanan: z.object({
layanan: z.string().min(1, "Layanan harus diisi"),
tarif: z.string().min(1, "Tarif harus diisi"),
}),
});
type InfromasiUmum = Prisma.InformasiUmumGetPayload<{
select: {
fasilitas: true;
alamat: true;
jamOperasional: true;
};
}>;
// Default form kosong
const defaultForm = {
name: "",
informasiUmum: {
fasilitas: "",
alamat: "",
jamOperasional: "",
},
layananUnggulan: {
content: "",
},
dokterdanTenagaMedis: {
name: "",
specialist: "",
jadwal: "",
},
fasilitasPendukung: {
content: "",
},
prosedurPendaftaran: {
content: "",
},
tarifDanLayanan: {
layanan: "",
tarif: "",
},
};
const informasiumum = proxy({
const fasilitasKesehatanState = proxy({
create: {
form: {} as InfromasiUmum,
form: { ...defaultForm },
loading: false,
async create() {
const cek = templateInformasiUmum.safeParse(informasiumum.create.form);
async submit() {
const cek = templateForm.safeParse(this.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
const errMsg = cek.error.issues
.map((v) => `${v.path.join(".")}: ${v.message}`)
.join("\n");
toast.error(errMsg);
return null;
}
try {
informasiumum.create.loading = true;
const res = await ApiFetch.api.kesehatan.informasiumum["create"].post(
informasiumum.create.form
this.loading = true;
const payload = { ...this.form };
const res = await (ApiFetch.api.kesehatan as any)[
"fasilitas-kesehatan"
].create.post(payload);
if (res.status === 200) {
toast.success("Berhasil menambahkan fasilitas kesehatan");
this.resetForm();
await fasilitasKesehatanState.findMany.load();
return res.data;
}
} catch (err: any) {
const msg = err?.message || "Terjadi kesalahan saat mengirim data";
toast.error(msg);
console.error("SUBMIT ERROR:", err);
return null;
} finally {
this.loading = false;
}
},
resetForm() {
this.form = { ...defaultForm };
},
},
findMany: {
data: null as
| Prisma.FasilitasKesehatanGetPayload<{
include: {
informasiumum: true;
layananunggulan: true;
dokterdantenagamedis: true;
fasilitaspendukung: true;
prosedurpendaftaran: true;
tarifdanlayanan: true;
};
}>[]
| null,
loading: false,
async load() {
try {
this.loading = true;
const res = await (ApiFetch.api.kesehatan as any)[
"fasilitas-kesehatan"
]["find-many"].get();
if (res.status === 200) {
this.data = res.data?.data ?? [];
} else {
toast.error("Gagal memuat data fasilitas kesehatan");
}
return res;
} catch (err) {
toast.error("Terjadi error saat load data");
console.error("LOAD ERROR:", err);
throw err;
} finally {
this.loading = false;
}
},
},
findUnique: {
data: null as Prisma.FasilitasKesehatanGetPayload<{
include: {
informasiumum: true;
layananunggulan: true;
dokterdantenagamedis: true;
fasilitaspendukung: true;
prosedurpendaftaran: true;
tarifdanlayanan: true;
};
}> | null,
loading: false,
async load(id: string) {
const res = await fetch(`/api/kesehatan/fasilitas-kesehatan/${id}`);
if (res.ok) {
const data = await res.json();
fasilitasKesehatanState.findUnique.data = data.data ?? null;
} else {
toast.error("Gagal load data fasilitas kesehatan");
}
},
},
edit: {
id: "",
form: { ...defaultForm },
loading: false,
async load(id: string) {
const res = await fetch(`/api/kesehatan/fasilitas-kesehatan/${id}`);
if (!res.ok) {
toast.error("Gagal load data fasilitas kesehatan");
return;
}
const result = await res.json();
const data = result.data;
fasilitasKesehatanState.edit.id = data.id;
fasilitasKesehatanState.edit.form = {
name: data.name,
informasiUmum: {
fasilitas: data.informasiumum.fasilitas,
alamat: data.informasiumum.alamat,
jamOperasional: data.informasiumum.jamOperasional,
},
layananUnggulan: {
content: data.layananunggulan.content,
},
dokterdanTenagaMedis: {
name: data.dokterdantenagamedis.name,
specialist: data.dokterdantenagamedis.specialist,
jadwal: data.dokterdantenagamedis.jadwal,
},
fasilitasPendukung: {
content: data.fasilitaspendukung.content,
},
prosedurPendaftaran: {
content: data.prosedurpendaftaran.content,
},
tarifDanLayanan: {
layanan: data.tarifdanlayanan.layanan,
tarif: data.tarifdanlayanan.tarif,
},
};
},
async submit() {
const cek = templateForm.safeParse(fasilitasKesehatanState.edit.form);
if (!cek.success) {
const errMsg = cek.error.issues
.map((v) => `${v.path.join(".")}: ${v.message}`)
.join("\n");
toast.error(errMsg);
return null;
}
try {
fasilitasKesehatanState.edit.loading = true;
const payload = {
name: fasilitasKesehatanState.edit.form.name,
informasiUmum: {
fasilitas:
fasilitasKesehatanState.edit.form.informasiUmum.fasilitas,
alamat: fasilitasKesehatanState.edit.form.informasiUmum.alamat,
jamOperasional:
fasilitasKesehatanState.edit.form.informasiUmum.jamOperasional,
},
layananUnggulan: {
content: fasilitasKesehatanState.edit.form.layananUnggulan.content,
},
dokterdanTenagaMedis: {
name: fasilitasKesehatanState.edit.form.dokterdanTenagaMedis.name,
specialist:
fasilitasKesehatanState.edit.form.dokterdanTenagaMedis.specialist,
jadwal:
fasilitasKesehatanState.edit.form.dokterdanTenagaMedis.jadwal,
},
fasilitasPendukung: {
content:
fasilitasKesehatanState.edit.form.fasilitasPendukung.content,
},
prosedurPendaftaran: {
content:
fasilitasKesehatanState.edit.form.prosedurPendaftaran.content,
},
tarifDanLayanan: {
layanan: fasilitasKesehatanState.edit.form.tarifDanLayanan.layanan,
tarif: fasilitasKesehatanState.edit.form.tarifDanLayanan.tarif,
},
};
const res = await fetch(
`/api/kesehatan/fasilitas-kesehatan/${fasilitasKesehatanState.edit.id}`,
{
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
}
);
if (res.status === 200) {
informasiumum.findMany.load();
return toast.success("success create");
if (!res.ok) {
const error = await res.json();
throw new Error(error.message || "Update gagal");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
informasiumum.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.InformasiUmumGetPayload<{ omit: { isActive: true } }>[]
| null,
async load() {
const res = await ApiFetch.api.kesehatan.
informasiumum["find-many"].get();
if (res.status === 200) {
informasiumum.findMany.data = res.data?.data ?? [];
}
},
},
});
/* ======================================================================= */
/* Layanan Unggulan */
const templateLayananUnggulanForm = z.object({
content: z.string().min(3, "Content minimal 3 karakter"),
});
type LayananUnggulan = Prisma.LayananUnggulanGetPayload<{
select: {
content: true;
};
}>;
const layananunggulan = proxy({
create: {
form: {} as LayananUnggulan,
loading: false,
async create() {
const cek = templateLayananUnggulanForm.safeParse(layananunggulan.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
layananunggulan.create.loading = true;
const res = await ApiFetch.api.kesehatan.layananunggulan["create"].post(
layananunggulan.create.form
toast.success("Berhasil update fasilitas kesehatan");
await fasilitasKesehatanState.findMany.load();
return true;
} catch (err) {
toast.error(
err instanceof Error ? err.message : "Terjadi kesalahan saat update"
);
if (res.status === 200) {
layananunggulan.findMany.load();
return toast.success("success create");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
return false;
} finally {
layananunggulan.create.loading = false;
fasilitasKesehatanState.edit.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.LayananUnggulanGetPayload<{ omit: { isActive: true } }>[]
| null,
async load() {
const res = await ApiFetch.api.kesehatan.
layananunggulan["find-many"].get();
if (res.status === 200) {
layananunggulan.findMany.data = res.data?.data ?? [];
}
resetForm() {
fasilitasKesehatanState.edit.id = "";
fasilitasKesehatanState.edit.form = { ...defaultForm };
},
},
})
/* ======================================================================= */
/* Dokter dan Tenaga Medis */
const templateDokterdanTenagaMedis = z.object({
name: z.string().min(3, "Name minimal 3 karakter"),
specialist: z.string().min(3, "Specialist minimal 3 karakter"),
jadwal: z.string().min(3, "Jadwal minimal 3 karakter"),
})
type DokterdanTenagaMedis = Prisma.DokterdanTenagaMedisGetPayload<{
select: {
name: true;
specialist: true;
jadwal: true;
};
}>;
const dokterdantenagamedis = proxy({
create: {
form: {} as DokterdanTenagaMedis,
delete: {
loading: false,
async create() {
const cek = templateDokterdanTenagaMedis.safeParse(dokterdantenagamedis.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
async byId(id: string){
try {
dokterdantenagamedis.create.loading = true;
const res = await ApiFetch.api.kesehatan.dokterdantenagamedis["create"].post(dokterdantenagamedis.create.form);
if (res.status === 200) {
dokterdantenagamedis.findMany.load();
return toast.success("success create");
fasilitasKesehatanState.delete.loading = true;
const res = await fetch(`/api/kesehatan/fasilitas-kesehatan/del/${id}`, {
method: "DELETE",
});
const result = await res.json();
if (res.ok && result.success) {
toast.success("Fasilitas kesehatan berhasil dihapus");
await fasilitasKesehatanState.findMany.load();
} else {
toast.error(result.message || "Gagal menghapus");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} catch {
toast.error("Terjadi kesalahan saat menghapus");
} finally {
dokterdantenagamedis.create.loading = false;
fasilitasKesehatanState.delete.loading = false;
}
},
}
},
findMany: {
data: null as
| Prisma.DokterdanTenagaMedisGetPayload<{ omit: { isActive: true } }>[]
| null,
async load() {
const res = await ApiFetch.api.kesehatan.dokterdantenagamedis["find-many"].get();
if (res.status === 200) {
dokterdantenagamedis.findMany.data = res.data?.data ?? [];
}
},
},
})
/* ======================================================================= */
});
/* Fasilitas Pendukung */
const templateFasilitasPendukung = z.object({
content: z.string().min(3, "Content minimal 3 karakter"),
})
type FasilitasPendukung = Prisma.FasilitasPendukungGetPayload<{
select: {
content: true;
};
}>;
const fasilitaspendukung = proxy({
create: {
form: {} as FasilitasPendukung,
loading: false,
async create() {
const cek = templateFasilitasPendukung.safeParse(fasilitaspendukung.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
fasilitaspendukung.create.loading = true;
const res = await ApiFetch.api.kesehatan.fasilitaspendukung["create"].post(fasilitaspendukung.create.form);
if (res.status === 200) {
fasilitaspendukung.findMany.load();
return toast.success("success create");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
fasilitaspendukung.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.FasilitasPendukungGetPayload<{ omit: { isActive: true } }>[]
| null,
async load() {
const res = await ApiFetch.api.kesehatan.
fasilitaspendukung["find-many"].get();
if (res.status === 200) {
fasilitaspendukung.findMany.data = res.data?.data ?? [];
}
},
},
})
/* ======================================================================= */
/* Tarif dan Layanan */
const templateTarifDanLayanan = z.object({
layanan: z.string().min(3, "Layanan minimal 3 karakter"),
tarif: z.string().min(3, "Tarif minimal 3 karakter"),
})
const tarifdanlayanan = proxy({
create: {
form: {} as Prisma.TarifDanLayananGetPayload<{ select: { layanan: true; tarif: true } }>,
loading: false,
async create() {
const cek = templateTarifDanLayanan.safeParse(tarifdanlayanan.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
tarifdanlayanan.create.loading = true;
const res = await ApiFetch.api.kesehatan.tarifdanlayanan["create"].post(tarifdanlayanan.create.form);
if (res.status === 200) {
tarifdanlayanan.findMany.load();
return toast.success("success create");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
tarifdanlayanan.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.TarifDanLayananGetPayload<{ omit: { isActive: true } }>[]
| null,
async load() {
const res = await ApiFetch.api.kesehatan.
tarifdanlayanan["find-many"].get();
if (res.status === 200) {
tarifdanlayanan.findMany.data = res.data?.data ?? [];
}
},
},
})
/* ======================================================================= */
/* Prosedur Pendaftaran */
const templateProsedurPendaftaran = z.object({
content: z.string().min(3, "Content minimal 3 karakter"),
})
const prosedurpendaftaran = proxy({
create: {
form: {} as Prisma.ProsedurPendaftaranGetPayload<{ select: { content: true } }>,
loading: false,
async create() {
const cek = templateProsedurPendaftaran.safeParse(prosedurpendaftaran.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
prosedurpendaftaran.create.loading = true;
const res = await ApiFetch.api.kesehatan.prosedurpendaftaran["create"].post(prosedurpendaftaran.create.form);
if (res.status === 200) {
prosedurpendaftaran.findMany.load();
return toast.success("success create");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
prosedurpendaftaran.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.ProsedurPendaftaranGetPayload<{ omit: { isActive: true } }>[]
| null,
async load() {
const res = await ApiFetch.api.kesehatan.
prosedurpendaftaran["find-many"].get();
if (res.status === 200) {
prosedurpendaftaran.findMany.data = res.data?.data ?? [];
}
},
},
})
const stateFasilitasKesehatan = proxy({
informasiumum,
layananunggulan,
dokterdantenagamedis,
fasilitaspendukung,
tarifdanlayanan,
prosedurpendaftaran
})
export default stateFasilitasKesehatan
export default fasilitasKesehatanState;

View File

@@ -6,7 +6,7 @@ import { z } from "zod";
const templateGrafikKepuasan = z.object({
label: z.string().min(2, "Label harus diisi"),
jumlah: z.string().min(2, "Jumlah harus diisi"),
jumlah: z.string().min(1, "Jumlah harus diisi"),
});
type GrafikKepuasan = Prisma.GrafikKepuasanGetPayload<{
@@ -31,24 +31,30 @@ const grafikkepuasan = proxy({
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
toast.error(err);
return null;
}
try {
grafikkepuasan.create.loading = true;
const res = await ApiFetch.api.kesehatan.grafikkepuasan["create"].post(
grafikkepuasan.create.form
);
const res = await ApiFetch.api.kesehatan.grafikkepuasan["create"].post(grafikkepuasan.create.form);
if (res.status === 200) {
grafikkepuasan.create.form = {
label: "",
jumlah: ""
};
grafikkepuasan.findMany.load();
return toast.success("success create");
const id = res.data?.data?.id;
if (id) {
toast.success("Success create");
grafikkepuasan.create.form = {
label: "",
jumlah: "",
};
grafikkepuasan.findMany.load();
return id;
}
}
return toast.error("failed create");
toast.error("failed create");
return null;
} catch (error) {
console.log((error as Error).message);
return null;
} finally {
grafikkepuasan.create.loading = false;
}
@@ -67,10 +73,110 @@ const grafikkepuasan = proxy({
}
},
},
findUnique: {
data: null as Prisma.GrafikKepuasanGetPayload<{
omit: { isActive: true }
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/kesehatan/grafikkepuasan/${id}`);
if (res.ok) {
const data = await res.json();
grafikkepuasan.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch grafikkepuasan:", res.statusText);
grafikkepuasan.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching grafikkepuasan:", error);
grafikkepuasan.findUnique.data = null;
}
},
},
update: {
id: "",
form: {...defaultForm},
loading: false,
async byId() {
},
async submit() {
const id = this.id;
if (!id) {
toast.warn("ID tidak valid");
return null;
}
const cek = templateGrafikKepuasan.safeParse(grafikkepuasan.update.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/kesehatan/grafikkepuasan/${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!");
// ✅ Optional: refresh list kalau kamu langsung ke halaman list
await grafikkepuasan.findMany.load();
return result.data;
} catch (error) {
console.error("Error update data:", error);
toast.error("Gagal update data grafik kepuasan");
} finally {
this.loading = false;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) {
return toast.warn("ID tidak valid");
}
try {
grafikkepuasan.delete.loading = true;
const response = await fetch(`/api/kesehatan/grafikkepuasan/del/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
const result = await response.json();
if (response.ok && result?.success) {
toast.success(
result.message || "Grafik kepuasan berhasil dihapus"
);
await grafikkepuasan.findMany.load(); // refresh list
} else {
toast.error(
result?.message || "Gagal menghapus grafik kepuasan"
);
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus grafik kepuasan");
} finally {
grafikkepuasan.delete.loading = false;
}
}
}
});
const stategrafikKepuasan = proxy({
grafikkepuasan,
});
export default stategrafikKepuasan;
export default grafikkepuasan;

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
@@ -5,362 +6,307 @@ import { proxy } from "valtio";
import { z } from "zod";
/* Informasi Kegiatan */
const templateInformasiKegiatan = z.object({
name: z.string().min(3, "Name minimal 3 karakter"),
tanggal: z.string().min(3, "Tanggal minimal 3 karakter"),
waktu: z.string().min(3, "Waktu minimal 3 karakter"),
lokasi: z.string().min(3, "Lokasi minimal 3 karakter"),
const templateForm = z.object({
content: z.string().min(1, "Content minimal 1 karakter"),
informasiJadwalKegiatan: z.object({
name: z.string().min(1, "Name minimal 1 karakter"),
tanggal: z.string().min(1, "Tanggal minimal 1 karakter"),
waktu: z.string().min(1, "Waktu minimal 1 karakter"),
lokasi: z.string().min(1, "Lokasi minimal 1 karakter"),
}),
deskripsiJadwalKegiatan: z.object({
deskripsi: z.string().min(1, "Deskripsi minimal 1 karakter"),
}),
layananJadwalKegiatan: z.object({
content: z.string().min(1, "Content minimal 1 karakter"),
}),
syaratKetentuanJadwalKegiatan: z.object({
content: z.string().min(1, "Content minimal 1 karakter"),
}),
dokumenJadwalKegiatan: z.object({
content: z.string().min(1, "Content minimal 1 karakter"),
}),
pendaftaranJadwalKegiatan: z.object({
name: z.string().min(1, "Name minimal 1 karakter"),
tanggal: z.string().min(1, "Tanggal minimal 1 karakter"),
namaOrangtua: z.string().min(1, "Nama Orangtua minimal 1 karakter"),
nomor: z.string().min(1, "Nomor minimal 1 karakter"),
alamat: z.string().min(1, "Alamat minimal 1 karakter"),
catatan: z.string().min(1, "Catatan minimal 1 karakter"),
}),
});
type InformasiKegiatan = Prisma.InformasiJadwalKegiatanGetPayload<{
select: {
name: true;
tanggal: true;
waktu: true;
lokasi: true;
};
}>;
const defaultForm = {
content: "",
informasiJadwalKegiatan: {
name: "",
tanggal: "",
waktu: "",
lokasi: "",
},
deskripsiJadwalKegiatan: {
deskripsi: "",
},
layananJadwalKegiatan: {
content: "",
},
syaratKetentuanJadwalKegiatan: {
content: "",
},
dokumenJadwalKegiatan: {
content: "",
},
pendaftaranJadwalKegiatan: {
name: "",
tanggal: "",
namaOrangtua: "",
nomor: "",
alamat: "",
catatan: "",
},
};
const informasiKegiatan = proxy({
const jadwalkegiatanState = proxy({
create: {
form: {} as InformasiKegiatan,
form: { ...defaultForm },
loading: false,
async create() {
const cek = templateInformasiKegiatan.safeParse(
informasiKegiatan.create.form
);
async submit() {
const cek = templateForm.safeParse(this.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
const errMsg = cek.error.issues
.map((v) => `${v.path.join(".")}: ${v.message}`)
.join("\n");
toast.error(errMsg);
return null;
}
try {
informasiKegiatan.create.loading = true;
const res = await ApiFetch.api.kesehatan.informasiJadwalKegiatan[
"create"
].post(informasiKegiatan.create.form);
this.loading = true;
const payload = { ...this.form };
const res = await (ApiFetch.api.kesehatan as any)[
"jadwal-kegiatan"
].create.post(payload);
if (res.status === 200) {
informasiKegiatan.findMany.load();
return toast.success("success create");
toast.success("Berhasil menambahkan jadwal kegiatan");
this.resetForm();
await jadwalkegiatanState.findMany.load();
return res.data;
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} catch (err: any) {
const msg = err?.message || "Terjadi kesalahan saat mengirim data";
toast.error(msg);
console.error("SUBMIT ERROR:", err);
return null;
} finally {
informasiKegiatan.create.loading = false;
this.loading = false;
}
},
resetForm() {
this.form = { ...defaultForm };
},
},
findMany: {
data: null as
| Prisma.InformasiJadwalKegiatanGetPayload<{ omit: { isActive: true } }>[]
| Prisma.JadwalKegiatanGetPayload<{
include: {
informasijadwalkegiatan: true;
deskripsijadwalkegiatan: true;
layananjadwalkegiatan: true;
dokumenjadwalkegiatan: true;
pendaftaranjadwalkegiatan: true;
};
}>[]
| null,
async load() {
const res = await ApiFetch.api.kesehatan.informasiJadwalKegiatan[
"find-many"
].get();
if (res.status === 200) {
informasiKegiatan.findMany.data = res.data?.data ?? [];
}
},
},
});
/* ======================================================================= */
/* Deskripsi Kegiatan */
const templateDeskripsiKegiatan = z.object({
deskripsi: z.string().min(3, "Content minimal 3 karakter"),
});
type DeskripsiKegiatan = Prisma.DeskripsiJadwalKegiatanGetPayload<{
select: { deskripsi: true };
}>;
const deskripsiKegiatan = proxy({
create: {
form: {} as DeskripsiKegiatan,
loading: false,
async create() {
const cek = templateDeskripsiKegiatan.safeParse(
deskripsiKegiatan.create.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
async load() {
try {
deskripsiKegiatan.create.loading = true;
const res = await ApiFetch.api.kesehatan.deskripsikegiatan[
"create"
].post(deskripsiKegiatan.create.form);
this.loading = true;
const res = await (ApiFetch.api.kesehatan as any)[
"jadwal-kegiatan"
]["find-many"].get();
if (res.status === 200) {
deskripsiKegiatan.findMany.load();
return toast.success("success create");
this.data = res.data?.data ?? [];
} else {
toast.error("Gagal memuat data jadwal kegiatan");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
return res;
} catch (err) {
toast.error("Terjadi error saat load data");
console.error("LOAD ERROR:", err);
throw err;
} finally {
deskripsiKegiatan.create.loading = false;
this.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.DeskripsiJadwalKegiatanGetPayload<{ omit: { isActive: true } }>[]
| null,
async load() {
const res = await ApiFetch.api.kesehatan.deskripsikegiatan[
"find-many"
].get();
if (res.status === 200) {
deskripsiKegiatan.findMany.data = res.data?.data ?? [];
}
},
},
});
/* ======================================================================= */
/* Layanan Tersedia */
const templateLayananTersedia = z.object({
content: z.string().min(3, "Content minimal 3 karakter"),
});
type LayananTersedia = Prisma.LayananJadwalKegiatanGetPayload<{
select: { content: true };
}>;
const layanantersedia = proxy({
create: {
form: {} as LayananTersedia,
findUnique: {
data: null as Prisma.JadwalKegiatanGetPayload<{
include: {
informasijadwalkegiatan: true;
deskripsijadwalkegiatan: true;
layananjadwalkegiatan: true;
syaratketentuanjadwalkegiatan: true;
dokumenjadwalkegiatan: true;
pendaftaranjadwalkegiatan: true;
};
}> | null,
loading: false,
async create() {
const cek = templateLayananTersedia.safeParse(
layanantersedia.create.form
);
async load(id: string) {
const res = await fetch(`/api/kesehatan/jadwal-kegiatan/${id}`);
if (res.ok) {
const data = await res.json();
jadwalkegiatanState.findUnique.data = data.data ?? null;
} else {
toast.error("Gagal load data jadwal kegiatan");
}
},
},
edit: {
id: "",
form: { ...defaultForm },
loading: false,
async load(id: string) {
const res = await fetch(`/api/kesehatan/jadwal-kegiatan/${id}`);
if (!res.ok) {
toast.error("Gagal load data jadwal kegiatan");
return;
}
const result = await res.json();
const data = result.data;
jadwalkegiatanState.edit.id = data.id;
jadwalkegiatanState.edit.form = {
content: data.content,
informasiJadwalKegiatan: {
name: data.informasijadwalkegiatan.name,
tanggal: data.informasijadwalkegiatan.tanggal,
waktu: data.informasijadwalkegiatan.waktu,
lokasi: data.informasijadwalkegiatan.lokasi,
},
layananJadwalKegiatan: {
content: data.layananjadwalkegiatan.content,
},
deskripsiJadwalKegiatan: {
deskripsi: data.deskripsijadwalkegiatan.deskripsi,
},
syaratKetentuanJadwalKegiatan: {
content: data.syaratketentuanjadwalkegiatan.content,
},
dokumenJadwalKegiatan: {
content: data.dokumenjadwalkegiatan.content,
},
pendaftaranJadwalKegiatan: {
name: data.pendaftaranjadwalkegiatan.name,
tanggal: data.pendaftaranjadwalkegiatan.tanggal,
namaOrangtua: data.pendaftaranjadwalkegiatan.namaOrangtua,
nomor: data.pendaftaranjadwalkegiatan.nomor,
alamat: data.pendaftaranjadwalkegiatan.alamat,
catatan: data.pendaftaranjadwalkegiatan.catatan,
},
};
},
async submit() {
const cek = templateForm.safeParse(jadwalkegiatanState.edit.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
const errMsg = cek.error.issues
.map((v) => `${v.path.join(".")}: ${v.message}`)
.join("\n");
toast.error(errMsg);
return null;
}
try {
layanantersedia.create.loading = true;
const res = await ApiFetch.api.kesehatan.layanantersedia["create"].post(
layanantersedia.create.form
);
if (res.status === 200) {
layanantersedia.findMany.load();
return toast.success("success create");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
layanantersedia.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.LayananJadwalKegiatanGetPayload<{ omit: { isActive: true } }>[]
| null,
async load() {
const res = await ApiFetch.api.kesehatan.layanantersedia[
"find-many"
].get();
if (res.status === 200) {
layanantersedia.findMany.data = res.data?.data ?? [];
}
},
},
});
/* ======================================================================= */
jadwalkegiatanState.edit.loading = true;
const payload = {
content: jadwalkegiatanState.edit.form.content,
informasiJadwalKegiatan: {
name: jadwalkegiatanState.edit.form.informasiJadwalKegiatan.name,
tanggal: jadwalkegiatanState.edit.form.informasiJadwalKegiatan.tanggal,
waktu: jadwalkegiatanState.edit.form.informasiJadwalKegiatan.waktu,
lokasi: jadwalkegiatanState.edit.form.informasiJadwalKegiatan.lokasi,
},
layananJadwalKegiatan: {
content: jadwalkegiatanState.edit.form.layananJadwalKegiatan.content,
},
deskripsiJadwalKegiatan: {
deskripsi: jadwalkegiatanState.edit.form.deskripsiJadwalKegiatan.deskripsi,
},
syaratKetentuanJadwalKegiatan: {
content: jadwalkegiatanState.edit.form.syaratKetentuanJadwalKegiatan.content,
},
dokumenJadwalKegiatan: {
content: jadwalkegiatanState.edit.form.dokumenJadwalKegiatan.content,
},
pendaftaranJadwalKegiatan: {
name: jadwalkegiatanState.edit.form.pendaftaranJadwalKegiatan.name,
tanggal: jadwalkegiatanState.edit.form.pendaftaranJadwalKegiatan.tanggal,
namaOrangtua: jadwalkegiatanState.edit.form.pendaftaranJadwalKegiatan.namaOrangtua,
nomor: jadwalkegiatanState.edit.form.pendaftaranJadwalKegiatan.nomor,
alamat: jadwalkegiatanState.edit.form.pendaftaranJadwalKegiatan.alamat,
catatan: jadwalkegiatanState.edit.form.pendaftaranJadwalKegiatan.catatan,
},
};
/* Syarat dan Ketentuan */
const templateSyaratKetentuan = z.object({
content: z.string().min(3, "Content minimal 3 karakter"),
});
type SyaratKetentuan = Prisma.SyaratKetentuanJadwalKegiatanGetPayload<{
select: { content: true };
}>;
const syaratketentuan = proxy({
create: {
form: {} as SyaratKetentuan,
loading: false,
async create() {
const cek = templateSyaratKetentuan.safeParse(
syaratketentuan.create.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
syaratketentuan.create.loading = true;
const res = await ApiFetch.api.kesehatan.syaratketentuan[
"create"
].post(syaratketentuan.create.form);
if (res.status === 200) {
syaratketentuan.findMany.load();
return toast.success("success create");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
syaratketentuan.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.SyaratKetentuanJadwalKegiatanGetPayload<{ omit: { isActive: true } }>[]
| null,
async load() {
const res = await ApiFetch.api.kesehatan.syaratketentuan[
"find-many"
].get();
if (res.status === 200) {
syaratketentuan.findMany.data = res.data?.data ?? [];
}
},
},
});
/* ======================================================================= */
/* Dokumen Yang Diperlukan */
const templateDokumenDiperlukan = z.object({
content: z.string().min(3, "Content minimal 3 karakter"),
});
type DokumenDiperlukan = Prisma.DokumenJadwalKegiatanGetPayload<{
select: { content: true };
}>;
const dokumenjadwalkegiatan = proxy({
create: {
form: {} as DokumenDiperlukan,
loading: false,
async create() {
const cek = templateDokumenDiperlukan.safeParse(
dokumenjadwalkegiatan.create.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
dokumenjadwalkegiatan.create.loading = true;
const res = await ApiFetch.api.kesehatan.dokumendiperlukan[
"create"
].post(dokumenjadwalkegiatan.create.form);
if (res.status === 200) {
dokumenjadwalkegiatan.findMany.load();
return toast.success("success create");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
dokumenjadwalkegiatan.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.DokumenJadwalKegiatanGetPayload<{ omit: { isActive: true } }>[]
| null,
async load() {
const res = await ApiFetch.api.kesehatan.dokumendiperlukan[
"find-many"
].get();
if (res.status === 200) {
dokumenjadwalkegiatan.findMany.data = res.data?.data ?? [];
}
},
},
});
/* ======================================================================= */
/* Pendaftaran */
const templatePendaftaran = z.object({
name: z.string().min(3, "Nama minimal 3 karakter"),
tanggal: z.string().min(1),
namaOrangtua: z.string().min(3, "Nama minimal 3 karakter"),
nomor: z.string().min(9, "Nama minimal 9 karakter"),
alamat: z.string().min(7, "Alamat minimal 7 karakter"),
catatan: z.string().min(3, "Catatan minimal 3 karakter"),
})
type Pendaftaran = Prisma.PendaftaranJadwalKegiatanGetPayload<{
select: {
name: true;
tanggal: true;
namaOrangtua: true;
nomor: true;
alamat: true;
catatan: true;
}
}>
const pendaftaranjadwal = proxy({
create: {
form: {} as Pendaftaran,
loading: false,
async create() {
const cek = templatePendaftaran.safeParse(pendaftaranjadwal.create.form);
if(!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
pendaftaranjadwal.create.loading = true;
const res = await ApiFetch.api.kesehatan.pendaftaranJadwalKegiatan["create"].post(
pendaftaranjadwal.create.form);
if (res.status === 200) {
pendaftaranjadwal.findMany.load();
return toast.success("success create")
const res = await fetch(
`/api/kesehatan/jadwal-kegiatan/${jadwalkegiatanState.edit.id}`,
{
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
}
return toast.error("failed create")
} catch (error) {
console.log((error as Error).message)
);
if (!res.ok) {
const error = await res.json();
throw new Error(error.message || "Update gagal");
}
toast.success("Berhasil update jadwal kegiatan");
await jadwalkegiatanState.findMany.load();
return true;
} catch (err) {
toast.error(
err instanceof Error ? err.message : "Terjadi kesalahan saat update"
);
return false;
} finally {
pendaftaranjadwal.create.loading = false;
jadwalkegiatanState.edit.loading = false;
}
},
resetForm() {
jadwalkegiatanState.edit.id = "";
jadwalkegiatanState.edit.form = { ...defaultForm };
},
},
delete: {
loading: false,
async byId(id: string){
try {
jadwalkegiatanState.delete.loading = true;
const res = await fetch(`/api/kesehatan/jadwal-kegiatan/del/${id}`, {
method: "DELETE",
});
const result = await res.json();
if (res.ok && result.success) {
toast.success("Jadwal kegiatan berhasil dihapus");
await jadwalkegiatanState.findMany.load();
} else {
toast.error(result.message || "Gagal menghapus");
}
} catch {
toast.error("Terjadi kesalahan saat menghapus");
} finally {
jadwalkegiatanState.delete.loading = false;
}
}
},
findMany: {
data: null as
| Prisma.PendaftaranJadwalKegiatanGetPayload<{omit: {isActive: true}}>[]
| null,
async load() {
const res = await ApiFetch.api.kesehatan.
pendaftaranJadwalKegiatan["find-many"].get();
if(res.status === 200) {
pendaftaranjadwal.findMany.data = res.data?.data ?? [];
}
}
}
})
const stateJadwalKegiatan = proxy({
informasiKegiatan,
deskripsiKegiatan,
layanantersedia,
syaratketentuan,
dokumenjadwalkegiatan,
pendaftaranjadwal
});
export default stateJadwalKegiatan;
export default jadwalkegiatanState;

View File

@@ -6,9 +6,9 @@ import { z } from "zod";
const templatePersentase = z.object({
tahun: z.string().min(4, "Tahun harus diisi"),
kematianKasar: z.string().min(2, "Kematian kasar harus diisi"),
kelahiranKasar: z.string().min(2, "Kelahiran kasar harus diisi"),
kematianBayi: z.string().min(2, "Kematian bayi harus diisi"),
kematianKasar: z.string().min(1, "Kematian kasar harus diisi"),
kelahiranKasar: z.string().min(1, "Kelahiran kasar harus diisi"),
kematianBayi: z.string().min(1, "Kematian bayi harus diisi"),
});
type Persentase = Prisma.DataKematian_KelahiranGetPayload<{
@@ -37,34 +37,40 @@ const persentasekelahiran = proxy({
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
toast.error(err);
return null;
}
try {
persentasekelahiran.create.loading = true;
const res = await ApiFetch.api.kesehatan.persentasekelahiran[
"create"
].post(persentasekelahiran.create.form);
if (res.status === 200) {
persentasekelahiran.create.form = {
tahun: "",
kematianKasar: "",
kelahiranKasar: "",
kematianBayi: "",
};
persentasekelahiran.findMany.load();
return toast.success("success create");
const id = res.data?.data?.id;
if (id) {
toast.success("Success create");
persentasekelahiran.create.form = { ...defaultForm };
persentasekelahiran.findMany.load();
return id;
}
}
return toast.error("failed create");
toast.error("failed create");
return null;
} catch (error) {
console.log((error as Error).message);
return null;
} finally {
persentasekelahiran.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.DataKematian_KelahiranGetPayload<{ omit: { isActive: true } }>[]
| Prisma.DataKematian_KelahiranGetPayload<{
omit: { isActive: true };
}>[]
| null,
async load() {
const res = await ApiFetch.api.kesehatan.persentasekelahiran[
@@ -75,10 +81,122 @@ const persentasekelahiran = proxy({
}
},
},
findUnique: {
data: null as Prisma.DataKematian_KelahiranGetPayload<{
omit: { isActive: true };
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/kesehatan/persentasekelahiran/${id}`);
if (res.ok) {
const data = await res.json();
persentasekelahiran.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch persentasekelahiran:", res.statusText);
persentasekelahiran.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching persentasekelahiran:", error);
persentasekelahiran.findUnique.data = null;
}
},
},
update: {
id: "",
form: { ...defaultForm },
loading: false,
async submit() {
const id = this.id;
if (!id) {
toast.warn("ID tidak valid");
return null;
}
const formData = {
tahun: this.form.tahun,
kematianKasar: this.form.kematianKasar,
kelahiranKasar: this.form.kelahiranKasar,
kematianBayi: this.form.kematianBayi,
};
const cek = templatePersentase.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/kesehatan/persentasekelahiran/${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 persentasekelahiran.findMany.load();
return result.data;
} catch (error) {
console.error("Update error:", error);
toast.error("Gagal update data persentase kelahiran");
throw error;
} finally {
this.loading = false;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
persentasekelahiran.delete.loading = true;
const response = await fetch(
`/api/kesehatan/persentasekelahiran/del/${id}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
);
const result = await response.json();
if (response.ok && result?.success) {
toast.success(
result.message || "Persentase kelahiran berhasil dihapus"
);
await persentasekelahiran.findMany.load();
} else {
toast.error(
result?.message || "Gagal menghapus persentase kelahiran"
);
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus persentase kelahiran");
} finally {
persentasekelahiran.delete.loading = false;
}
},
},
});
const statePersentase = proxy({
persentasekelahiran,
});
export default statePersentase;
export default persentasekelahiran;

View File

@@ -0,0 +1,214 @@
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, "Judul minimal 3 karakter"),
deskripsiSingkat: z.string().min(3, "Deskripsi singkat minimal 3 karakter"),
deskripsiLengkap: z.string().min(3, "Deskripsi lengkap minimal 3 karakter"),
imageId: z.string().nonempty(),
});
const defaultForm = {
name: "",
deskripsiSingkat: "",
deskripsiLengkap: "",
imageId: "",
};
const infoWabahPenyakit = proxy({
findMany: {
data: [] as Prisma.InfoWabahPenyakitGetPayload<{
include: {
image: true;
};
}>[],
async load() {
const res = await ApiFetch.api.kesehatan.infowabahpenyakit[
"find-many"
].get();
if (res.status === 200) {
infoWabahPenyakit.findMany.data = res.data?.data ?? [];
}
},
},
create: {
form: { ...defaultForm },
loading: false,
async create() {
const cek = templateForm.safeParse(infoWabahPenyakit.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
infoWabahPenyakit.create.loading = true;
const res = await ApiFetch.api.kesehatan.infowabahpenyakit[
"create"
].post(infoWabahPenyakit.create.form);
if (res.status === 200) {
infoWabahPenyakit.findMany.load();
return toast.success("Info wabah penyakit berhasil disimpan!");
}
return toast.error("Gagal menyimpan info wabah penyakit");
} catch (error) {
console.log((error as Error).message);
} finally {
infoWabahPenyakit.create.loading = false;
}
},
resetForm() {
infoWabahPenyakit.create.form = { ...defaultForm };
},
},
findUnique: {
data: null as Prisma.InfoWabahPenyakitGetPayload<{
include: {
image: true;
};
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/kesehatan/infowabahpenyakit/${id}`);
if (res.ok) {
const data = await res.json();
infoWabahPenyakit.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch info wabah penyakit:", res.statusText);
infoWabahPenyakit.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching info wabah penyakit:", error);
infoWabahPenyakit.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
infoWabahPenyakit.delete.loading = true;
const response = await fetch(`/api/kesehatan/infowabahpenyakit/del/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Info wabah penyakit berhasil dihapus");
await infoWabahPenyakit.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus info wabah penyakit");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus info wabah penyakit");
} finally {
infoWabahPenyakit.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/kesehatan/infowabahpenyakit/${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,
deskripsiSingkat: data.deskripsiSingkat,
deskripsiLengkap: data.deskripsiLengkap,
imageId: data.imageId,
};
return data; // Return the loaded data
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error fetching info wabah penyakit:", error);
toast.error(error instanceof Error ? error.message : "Gagal memuat data");
return null;
}
},
async update() {
const cek = templateForm.safeParse(infoWabahPenyakit.edit.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
infoWabahPenyakit.edit.loading = true;
const response = await fetch(`/api/kesehatan/infowabahpenyakit/${this.id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: this.form.name,
deskripsiSingkat: this.form.deskripsiSingkat,
deskripsiLengkap: this.form.deskripsiLengkap,
imageId: this.form.imageId,
}),
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.message || `HTTP error! status: ${response.status}`);
}
const result = await response.json();
if (result.success) {
toast.success(result.message || "Info wabah penyakit berhasil diupdate");
await infoWabahPenyakit.findMany.load();
return true;
} else {
throw new Error(result.message || "Gagal update info wabah penyakit");
}
} catch (error) {
console.error("Gagal update:", error);
toast.error(error instanceof Error ? error.message : "Terjadi kesalahan saat mengupdate info wabah penyakit");
return false;
} finally {
infoWabahPenyakit.edit.loading = false;
}
},
reset() {
infoWabahPenyakit.edit.id = "";
infoWabahPenyakit.edit.form = { ...defaultForm };
},
},
});
export default infoWabahPenyakit;

View File

@@ -0,0 +1,208 @@
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, "Judul minimal 3 karakter"),
deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"),
imageId: z.string().nonempty(),
})
const defaultForm = {
name: "",
deskripsi: "",
imageId: "",
}
const kontakDarurat = proxy({
findMany: {
data: [] as Prisma.KontakDaruratGetPayload<{
include: {
image: true;
};
}>[],
async load() {
const res = await ApiFetch.api.kesehatan.kontakdarurat[
"find-many"
].get();
if (res.status === 200) {
kontakDarurat.findMany.data = res.data?.data ?? [];
}
},
},
create:{
form: {...defaultForm},
loading: false,
async create() {
const cek = templateForm.safeParse(kontakDarurat.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
kontakDarurat.create.loading = true;
const res = await ApiFetch.api.kesehatan.kontakdarurat[
"create"
].post(kontakDarurat.create.form);
if (res.status === 200) {
kontakDarurat.findMany.load();
return toast.success("Kontak Darurat berhasil disimpan!");
}
return toast.error("Gagal menyimpan kontak darurat");
} catch (error) {
console.log((error as Error).message);
} finally {
kontakDarurat.create.loading = false;
}
},
resetForm() {
kontakDarurat.create.form = {...defaultForm};
}
},
findUnique: {
data: null as Prisma.KontakDaruratGetPayload<{
include: {
image: true;
};
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/kesehatan/kontakdarurat/${id}`);
if (res.ok) {
const data = await res.json();
kontakDarurat.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
kontakDarurat.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching data:", error);
kontakDarurat.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
try {
kontakDarurat.delete.loading = true;
const response = await fetch(`/api/kesehatan/kontakdarurat/del/${id}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
},
});
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Kontak darurat berhasil dihapus");
await kontakDarurat.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus kontak darurat");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus kontak darurat");
} finally {
kontakDarurat.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/kesehatan/kontakdarurat/${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; // Return the loaded data
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error fetching kontak darurat:", error);
toast.error(error instanceof Error ? error.message : "Gagal memuat data");
return null;
}
},
async update() {
const cek = templateForm.safeParse(kontakDarurat.edit.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
kontakDarurat.edit.loading = true;
const response = await fetch(`/api/kesehatan/kontakdarurat/${this.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(result.message || "Kontak darurat berhasil diupdate");
await kontakDarurat.findMany.load();
return true;
} else {
throw new Error(result.message || "Gagal update kontak darurat");
}
} catch (error) {
console.error("Gagal update:", error);
toast.error(error instanceof Error ? error.message : "Terjadi kesalahan saat mengupdate kontak darurat");
return false;
} finally {
kontakDarurat.edit.loading = false;
}
},
reset() {
kontakDarurat.edit.id = "";
kontakDarurat.edit.form = { ...defaultForm };
},
},
});
export default kontakDarurat

View File

@@ -0,0 +1,208 @@
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, "Judul minimal 3 karakter"),
deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"),
imageId: z.string().nonempty(),
})
const defaultForm = {
name: "",
deskripsi: "",
imageId: "",
}
const penangananDarurat = proxy({
findMany: {
data: [] as Prisma.PenangananDaruratGetPayload<{
include: {
image: true;
};
}>[],
async load() {
const res = await ApiFetch.api.kesehatan.penanganandarurat[
"find-many"
].get();
if (res.status === 200) {
penangananDarurat.findMany.data = res.data?.data ?? [];
}
},
},
create:{
form: {...defaultForm},
loading: false,
async create() {
const cek = templateForm.safeParse(penangananDarurat.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
penangananDarurat.create.loading = true;
const res = await ApiFetch.api.kesehatan.penanganandarurat[
"create"
].post(penangananDarurat.create.form);
if (res.status === 200) {
penangananDarurat.findMany.load();
return toast.success("Penanganan Darurat berhasil disimpan!");
}
return toast.error("Gagal menyimpan penanganan darurat");
} catch (error) {
console.log((error as Error).message);
} finally {
penangananDarurat.create.loading = false;
}
},
resetForm() {
penangananDarurat.create.form = {...defaultForm};
}
},
findUnique: {
data: null as Prisma.PenangananDaruratGetPayload<{
include: {
image: true;
};
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/kesehatan/penanganandarurat/${id}`);
if (res.ok) {
const data = await res.json();
penangananDarurat.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
penangananDarurat.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching data:", error);
penangananDarurat.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
try {
penangananDarurat.delete.loading = true;
const response = await fetch(`/api/kesehatan/penanganandarurat/del/${id}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
},
});
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Penanganan darurat berhasil dihapus");
await penangananDarurat.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus penanganan darurat");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus penanganan darurat");
} finally {
penangananDarurat.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/kesehatan/penanganandarurat/${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; // Return the loaded data
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error fetching penanganan darurat:", error);
toast.error(error instanceof Error ? error.message : "Gagal memuat data");
return null;
}
},
async update() {
const cek = templateForm.safeParse(penangananDarurat.edit.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
penangananDarurat.edit.loading = true;
const response = await fetch(`/api/kesehatan/penanganandarurat/${this.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(result.message || "Penanganan darurat berhasil diupdate");
await penangananDarurat.findMany.load();
return true;
} else {
throw new Error(result.message || "Gagal update penanganan darurat");
}
} catch (error) {
console.error("Gagal update:", error);
toast.error(error instanceof Error ? error.message : "Terjadi kesalahan saat mengupdate penanganan darurat");
return false;
} finally {
penangananDarurat.edit.loading = false;
}
},
reset() {
penangananDarurat.edit.id = "";
penangananDarurat.edit.form = { ...defaultForm };
},
},
});
export default penangananDarurat

View File

@@ -0,0 +1,217 @@
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, { message: "Name is required" }),
nomor: z.string().min(1, { message: "Nomor is required" }),
deskripsi: z.string().min(1, { message: "Deskripsi is required" }),
imageId: z.string().nonempty(),
});
const defaultForm = {
name: "",
nomor: "",
deskripsi: "",
imageId: "",
};
const posyandustate = proxy({
create: {
form: { ...defaultForm },
loading: false,
async create() {
const cek = templateForm.safeParse(posyandustate.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
posyandustate.create.loading = true;
const res = await ApiFetch.api.kesehatan.posyandu["create"].post(posyandustate.create.form);
if (res.status === 200) {
posyandustate.findMany.load();
return toast.success("Posyandu berhasil disimpan!");
}
return toast.error("Gagal menyimpan posyandu");
} catch (error) {
console.log((error as Error).message);
} finally {
posyandustate.create.loading = false;
}
},
resetForm(){
posyandustate.create.form = { ...defaultForm };
}
},
findMany: {
data: null as
| Prisma.PosyanduGetPayload<{
include: {
image: true;
}
}>[]
| null,
async load() {
const res = await ApiFetch.api.kesehatan.posyandu["find-many"].get();
if (res.status === 200) {
posyandustate.findMany.data = res.data?.data ?? [];
}
}
},
findUnique: {
data: null as
| Prisma.PosyanduGetPayload<{
include: {
image: true;
}
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/kesehatan/posyandu/${id}`);
if (res.ok) {
const data = await res.json();
posyandustate.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch posyandu:", res.statusText);
posyandustate.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching posyandu:", error);
posyandustate.findUnique.data = null;
}
}
},
delete: {
loading: false,
async byId(id: string) {
try {
posyandustate.delete.loading = true;
const response = await fetch(`/api/kesehatan/posyandu/del/${id}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
},
});
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Posyandu berhasil dihapus");
await posyandustate.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus posyandu");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus posyandu");
} finally {
posyandustate.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/kesehatan/posyandu/${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,
nomor: data.nomor,
deskripsi: data.deskripsi,
imageId: data.imageId || "",
};
return data;
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error fetching posyandu:", error);
toast.error(error instanceof Error ? error.message : "Gagal memuat data");
return null;
}
},
async update() {
const cek = templateForm.safeParse(posyandustate.edit.form);
if(!cek.success){
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return false;
}
try {
posyandustate.edit.loading = true;
const response = await fetch(`/api/kesehatan/posyandu/${this.id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: this.form.name,
nomor: this.form.nomor,
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(result.message || "Posyandu berhasil diperbarui");
await posyandustate.findMany.load(); // refresh list
return true;
} else {
throw new Error(result.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error fetching posyandu:", error);
toast.error(error instanceof Error ? error.message : "Gagal memuat data");
return false;
} finally {
posyandustate.edit.loading = false;
}
},
reset() {
posyandustate.edit.id = "";
posyandustate.edit.form = {...defaultForm};
}
}
})
export default posyandustate;

View File

@@ -0,0 +1,214 @@
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, "Judul minimal 3 karakter"),
deskripsiSingkat: z.string().min(3, "Deskripsi singkat minimal 3 karakter"),
deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"),
imageId: z.string().nonempty(),
});
const defaultForm = {
name: "",
deskripsiSingkat: "",
deskripsi: "",
imageId: "",
};
const programKesehatan = proxy({
findMany: {
data: [] as Prisma.ProgramKesehatanGetPayload<{
include: {
image: true;
};
}>[],
async load() {
const res = await ApiFetch.api.kesehatan.programkesehatan[
"find-many"
].get();
if (res.status === 200) {
programKesehatan.findMany.data = res.data?.data ?? [];
}
},
},
create: {
form: { ...defaultForm },
loading: false,
async create() {
const cek = templateForm.safeParse(programKesehatan.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
programKesehatan.create.loading = true;
const res = await ApiFetch.api.kesehatan.programkesehatan[
"create"
].post(programKesehatan.create.form);
if (res.status === 200) {
programKesehatan.findMany.load();
return toast.success("Program Kesehatan berhasil disimpan!");
}
return toast.error("Gagal menyimpan program kesehatan");
} catch (error) {
console.log((error as Error).message);
} finally {
programKesehatan.create.loading = false;
}
},
resetForm() {
programKesehatan.create.form = { ...defaultForm };
},
},
findUnique: {
data: null as Prisma.ProgramKesehatanGetPayload<{
include: {
image: true;
};
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/kesehatan/programkesehatan/${id}`);
if (res.ok) {
const data = await res.json();
programKesehatan.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch program kesehatan:", res.statusText);
programKesehatan.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching program kesehatan:", error);
programKesehatan.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
programKesehatan.delete.loading = true;
const response = await fetch(`/api/kesehatan/programkesehatan/del/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Program kesehatan berhasil dihapus");
await programKesehatan.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus program kesehatan");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus program kesehatan");
} finally {
programKesehatan.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/kesehatan/programkesehatan/${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,
deskripsiSingkat: data.deskripsiSingkat,
deskripsi: data.deskripsi,
imageId: data.imageId,
};
return data; // Return the loaded data
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error fetching program kesehatan:", error);
toast.error(error instanceof Error ? error.message : "Gagal memuat data");
return null;
}
},
async update() {
const cek = templateForm.safeParse(programKesehatan.edit.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
programKesehatan.edit.loading = true;
const response = await fetch(`/api/kesehatan/programkesehatan/${this.id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: this.form.name,
deskripsiSingkat: this.form.deskripsiSingkat,
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(result.message || "Program kesehatan berhasil diupdate");
await programKesehatan.findMany.load();
return true;
} else {
throw new Error(result.message || "Gagal update program kesehatan");
}
} catch (error) {
console.error("Gagal update:", error);
toast.error(error instanceof Error ? error.message : "Terjadi kesalahan saat mengupdate program kesehatan");
return false;
} finally {
programKesehatan.edit.loading = false;
}
},
reset() {
programKesehatan.edit.id = "";
programKesehatan.edit.form = { ...defaultForm };
},
},
});
export default programKesehatan;

View File

@@ -0,0 +1,298 @@
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
import { proxy } from "valtio";
import { z } from "zod";
// Validasi form
const templateForm = z.object({
name: z.string().min(1),
alamat: z.string().min(1),
imageId: z.string().min(1),
jam: z.object({
workDays: z.string().min(1),
weekDays: z.string().min(1),
holiday: z.string().min(1),
}),
kontak: z.object({
kontakPuskesmas: z.string().min(1),
email: z.string().min(1),
facebook: z.string().min(1),
kontakUGD: z.string().min(1),
}),
});
// Default form
const defaultForm = {
name: "",
alamat: "",
imageId: "",
jam: {
workDays: "",
weekDays: "",
holiday: "",
},
kontak: {
kontakPuskesmas: "",
email: "",
facebook: "",
kontakUGD: "",
},
image: undefined,
};
const puskesmasState = proxy({
create: {
form: { ...defaultForm },
loading: false,
async submit() {
const cek = templateForm.safeParse(puskesmasState.create.form);
if (!cek.success) {
const err = `[${cek.error.issues.map((v) => v.path.join(".")).join(", ")}] required`;
return toast.error(err);
}
try {
puskesmasState.create.loading = true;
console.log('Form data:', puskesmasState.create.form);
interface ErrorResponse {
message?: string;
error?: string;
errors?: Record<string, string[]>;
}
const payload = {
name: puskesmasState.create.form.name,
alamat: puskesmasState.create.form.alamat,
imageId: puskesmasState.create.form.imageId,
jam: {
workDays: puskesmasState.create.form.jam.workDays,
weekDays: puskesmasState.create.form.jam.weekDays,
holiday: puskesmasState.create.form.jam.holiday,
},
kontak: {
kontakPuskesmas: puskesmasState.create.form.kontak.kontakPuskesmas,
email: puskesmasState.create.form.kontak.email,
facebook: puskesmasState.create.form.kontak.facebook,
kontakUGD: puskesmasState.create.form.kontak.kontakUGD,
},
};
console.log('Sending payload:', JSON.stringify(payload, null, 2));
try {
const res = await ApiFetch.api.kesehatan.puskesmas.create.post(payload);
console.log('API Response:', res);
if (res.status === 200) {
await puskesmasState.findMany.load();
toast.success("Berhasil menambahkan puskesmas");
return res;
} else {
console.error('API Error Response:', {
status: res.status,
data: res.data
});
const errorData = res.data as ErrorResponse;
let errorMessage = 'Gagal menambahkan puskesmas';
if (errorData?.message) {
errorMessage = errorData.message;
} else if (errorData?.error) {
errorMessage = errorData.error;
} else if (errorData?.errors) {
errorMessage = Object.entries(errorData.errors)
.map(([field, messages]) => `${field}: ${Array.isArray(messages) ? messages.join(', ') : messages}`)
.join('; ');
}
console.error('Extracted error message:', errorMessage);
toast.error(errorMessage);
throw new Error(errorMessage);
}
} catch (error) {
console.error('Error in API call:', {
error,
errorString: String(error),
jsonError: error instanceof Error ? {
name: error.name,
message: error.message,
stack: error.stack
} : 'Not an Error instance'
});
throw error;
}
} catch (error) {
console.error("Error in puskesmas submit:", {
error,
errorString: String(error),
errorType: typeof error,
isErrorInstance: error instanceof Error,
errorDetails: error instanceof Error ? {
name: error.name,
message: error.message,
stack: error.stack,
cause: error.cause
} : null
});
let errorMessage = "Terjadi kesalahan saat menambahkan puskesmas";
if (error instanceof Error) {
errorMessage = error.message || errorMessage;
} else if (error && typeof error === 'object' && 'message' in error) {
errorMessage = String((error as { message: unknown }).message);
} else if (typeof error === 'string') {
errorMessage = error;
}
console.error('Displaying error to user:', errorMessage);
toast.error(errorMessage);
throw error;
} finally {
puskesmasState.create.loading = false;
}
},
resetForm() {
puskesmasState.create.form = { ...defaultForm };
}
},
findMany: {
data: null as Prisma.PuskesmasGetPayload<{
include: { image: true; jam: true; kontak: true };
}>[] | null,
async load() {
const res = await ApiFetch.api.kesehatan.puskesmas["find-many"].get();
if (res.status === 200) {
puskesmasState.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.PuskesmasGetPayload<{
include: { image: true; jam: true; kontak: true };
}> | null,
async load(id: string) {
const res = await fetch(`/api/kesehatan/puskesmas/${id}`);
if (res.ok) {
const data = await res.json();
puskesmasState.findUnique.data = data.data ?? null;
} else {
toast.error("Gagal load data puskesmas");
}
},
},
edit: {
id: "",
form: { ...defaultForm },
loading: false,
async load(id: string) {
const res = await fetch(`/api/kesehatan/puskesmas/${id}`);
if (!res.ok) {
toast.error("Gagal memuat data");
return;
}
const result = await res.json();
const data = result.data;
puskesmasState.edit.id = data.id;
puskesmasState.edit.form = {
name: data.name,
alamat: data.alamat,
imageId: data.imageId,
jam: {
workDays: data.jam.workDays,
weekDays: data.jam.weekDays,
holiday: data.jam.holiday,
},
kontak: {
kontakPuskesmas: data.kontak.kontakPuskesmas,
email: data.kontak.email,
facebook: data.kontak.facebook,
kontakUGD: data.kontak.kontakUGD,
},
image: data.image,
};
},
async submit() {
const cek = templateForm.safeParse(puskesmasState.edit.form);
if (!cek.success) {
const err = `[${cek.error.issues.map((v) => v.path.join(".")).join(", ")}] required`;
toast.error(err);
return false;
}
try {
puskesmasState.edit.loading = true;
const payload = {
name: puskesmasState.edit.form.name,
alamat: puskesmasState.edit.form.alamat,
imageId: puskesmasState.edit.form.imageId,
jam: { ...puskesmasState.edit.form.jam },
kontak: { ...puskesmasState.edit.form.kontak }
};
const res = await fetch(`/api/kesehatan/puskesmas/${puskesmasState.edit.id}`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
if (!res.ok) {
const error = await res.json();
throw new Error(error.message || "Update gagal");
}
toast.success("Berhasil update puskesmas");
await puskesmasState.findMany.load();
return true;
} catch (err) {
toast.error(err instanceof Error ? err.message : "Terjadi kesalahan saat update");
return false;
} finally {
puskesmasState.edit.loading = false;
}
},
reset() {
puskesmasState.edit.id = "";
puskesmasState.edit.form = { ...defaultForm };
}
},
delete: {
loading: false,
async byId(id: string) {
try {
puskesmasState.delete.loading = true;
const res = await fetch(`/api/kesehatan/puskesmas/del/${id}`, {
method: "DELETE",
});
const result = await res.json();
if (res.ok && result.success) {
toast.success("Puskesmas berhasil dihapus");
await puskesmasState.findMany.load();
} else {
toast.error(result.message || "Gagal menghapus");
}
} catch {
toast.error("Terjadi kesalahan saat menghapus");
} finally {
puskesmasState.delete.loading = false;
}
}
}
});
export default puskesmasState;

View File

@@ -5,18 +5,19 @@ import { proxy } from "valtio";
import { z } from "zod";
const templateGrafikJenisKelamin = z.object({
laki: z.string().min(2, "Data laki-laki harus diisi"),
perempuan: z.string().min(2, "Data perempuan harus diisi"),
});
laki: z.string().min(1, "Data laki-laki harus diisi"),
perempuan: z.string().min(1, "Data perempuan harus diisi"),
});
type GrafikJenisKelamin = Prisma.GrafikBerdasarkanJenisKelaminGetPayload<{
select: {
id: true;
laki: true;
perempuan: true;
};
}>;
const defaultForm: GrafikJenisKelamin = {
const defaultForm: Omit<GrafikJenisKelamin, 'id'> & { id?: string } = {
laki: "",
perempuan: "",
};
@@ -41,9 +42,16 @@ const grafikBerdasarkanJenisKelamin = proxy({
"create"
].post(grafikBerdasarkanJenisKelamin.create.form);
if (res.status === 200) {
grafikBerdasarkanJenisKelamin.create.form = defaultForm;
grafikBerdasarkanJenisKelamin.findMany.load();
return toast.success("success create");
const id = res.data?.data?.id;
if (id) {
toast.success("Success create");
grafikBerdasarkanJenisKelamin.create.form = {
laki: "",
perempuan: "",
};
grafikBerdasarkanJenisKelamin.findMany.load();
return id;
}
}
return toast.error("failed create");
} catch (error) {
@@ -69,9 +77,103 @@ const grafikBerdasarkanJenisKelamin = proxy({
}
},
},
findUnique: {
data: null as Prisma.GrafikBerdasarkanJenisKelaminGetPayload<{
omit: { isActive: true };
}> | null,
async load(id: string) {
try {
const res = await fetch(
`/api/ppid/grafikberdasarkanjeniskelamin/${id}`
);
if (res.ok) {
const data = await res.json();
grafikBerdasarkanJenisKelamin.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
grafikBerdasarkanJenisKelamin.findUnique.data = null;
}
} catch (error) {
console.error("Error loading grafik berdasarkan jenis kelamin:", error);
grafikBerdasarkanJenisKelamin.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 = templateGrafikJenisKelamin.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/ppid/grafikberdasarkanjeniskelamin/${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 grafikBerdasarkanJenisKelamin.findMany.load();
return result.data;
} catch (error) {
console.error("Error update data:", error);
toast.error("Gagal update data grafik berdasarkan jenis kelamin");
} finally {
this.loading = false;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
grafikBerdasarkanJenisKelamin.delete.loading = true;
const response = await fetch(`/api/ppid/grafikberdasarkanjeniskelamin/del/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Grafik berdasarkan jenis kelamin berhasil dihapus");
await grafikBerdasarkanJenisKelamin.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus grafik berdasarkan jenis kelamin");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus grafik berdasarkan jenis kelamin");
} finally {
grafikBerdasarkanJenisKelamin.delete.loading = false;
}
},
}
});
const stateGrafikBerdasarkanJenisKelamin = proxy({
grafikBerdasarkanJenisKelamin,
});
export default stateGrafikBerdasarkanJenisKelamin;
export default grafikBerdasarkanJenisKelamin;

View File

@@ -13,6 +13,7 @@ const templateGrafikResponden = z.object({
type GrafikResponden = Prisma.GrafikBerdasarkanRespondenGetPayload<{
select: {
id: true;
sangatbaik: true;
baik: true;
kurangbaik: true;
@@ -20,7 +21,7 @@ type GrafikResponden = Prisma.GrafikBerdasarkanRespondenGetPayload<{
};
}>;
const defaultForm: GrafikResponden = {
const defaultForm: Omit<GrafikResponden, 'id'> & { id?: string } = {
sangatbaik: "",
baik: "",
kurangbaik: "",
@@ -47,9 +48,18 @@ const grafikBerdasarkanResponden = proxy({
"create"
].post(grafikBerdasarkanResponden.create.form);
if (res.status === 200) {
grafikBerdasarkanResponden.create.form = defaultForm;
grafikBerdasarkanResponden.findMany.load();
return toast.success("success create");
const id = res.data?.data?.id;
if (id) {
toast.success("Success create");
grafikBerdasarkanResponden.create.form = {
sangatbaik: "",
baik: "",
kurangbaik: "",
tidakbaik: "",
};
grafikBerdasarkanResponden.findMany.load();
return id;
}
}
return toast.error("failed create");
} catch (error) {
@@ -75,10 +85,112 @@ const grafikBerdasarkanResponden = proxy({
}
},
},
findUnique: {
data: null as Prisma.GrafikBerdasarkanRespondenGetPayload<{
omit: { isActive: true };
}> | null,
async load(id: string) {
try {
const res = await fetch(
`/api/ppid/grafikberdasarkanresponden/${id}`
);
if (res.ok) {
const data = await res.json();
grafikBerdasarkanResponden.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
grafikBerdasarkanResponden.findUnique.data = null;
}
} catch (error) {
console.error("Error loading grafik berdasarkan responden:", error);
grafikBerdasarkanResponden.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 = templateGrafikResponden.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/ppid/grafikberdasarkanresponden/${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 grafikBerdasarkanResponden.findMany.load();
return result.data;
} catch (error) {
console.error("Error update data:", error);
toast.error("Gagal update data grafik berdasarkan responden");
} finally {
this.loading = false;
}
}
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
grafikBerdasarkanResponden.delete.loading = true;
const response = await fetch(`/api/ppid/grafikberdasarkanresponden/del/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Grafik berdasarkan responden berhasil dihapus");
await grafikBerdasarkanResponden.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus grafik berdasarkan responden");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus grafik berdasarkan responden");
} finally {
grafikBerdasarkanResponden.delete.loading = false;
}
}
}
});
const stateGrafikResponden = proxy({
grafikBerdasarkanResponden,
});
export default stateGrafikResponden;
export default grafikBerdasarkanResponden;

View File

@@ -5,80 +5,182 @@ import { proxy } from "valtio";
import { z } from "zod";
const templateGrafikUmur = z.object({
remaja: z.string().min(2, "Data remaja harus diisi"),
dewasa: z.string().min(2, "Data dewasa harus diisi"),
orangtua: z.string().min(2, "Data orangtua harus diisi"),
lansia: z.string().min(2, "Data lansia harus diisi"),
remaja: z.string().min(1, "Data remaja harus diisi"),
dewasa: z.string().min(1, "Data dewasa harus diisi"),
orangtua: z.string().min(1, "Data orangtua harus diisi"),
lansia: z.string().min(1, "Data lansia harus diisi"),
});
type GrafikUmur = Prisma.GrafikBerdasarkanUmurGetPayload<{
select: {
remaja: true;
dewasa: true;
orangtua: true;
lansia: true;
};
select: {
id: true;
remaja: true;
dewasa: true;
orangtua: true;
lansia: true;
};
}>;
const defaultForm: GrafikUmur = {
remaja: "",
dewasa: "",
orangtua: "",
lansia: "",
const defaultForm: Omit<GrafikUmur, "id"> & { id?: string } = {
remaja: "",
dewasa: "",
orangtua: "",
lansia: "",
};
const grafikBerdasarkanUmur = proxy({
create: {
form: defaultForm,
loading: false,
async create() {
const cek = templateGrafikUmur.safeParse(
grafikBerdasarkanUmur.create.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
grafikBerdasarkanUmur.create.loading = true;
const res = await ApiFetch.api.ppid.grafikberdasarkanumur[
"create"
].post(grafikBerdasarkanUmur.create.form);
if (res.status === 200) {
grafikBerdasarkanUmur.create.form = defaultForm;
grafikBerdasarkanUmur.findMany.load();
return toast.success("success create");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
grafikBerdasarkanUmur.create.loading = false;
}
},
create: {
form: defaultForm,
loading: false,
async create() {
const cek = templateGrafikUmur.safeParse(
grafikBerdasarkanUmur.create.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
grafikBerdasarkanUmur.create.loading = true;
const res = await ApiFetch.api.ppid.grafikberdasarkanumur[
"create"
].post(grafikBerdasarkanUmur.create.form);
if (res.status === 200) {
const id = res.data?.data?.id;
if (id) {
toast.success("Success create");
grafikBerdasarkanUmur.create.form = {
remaja: "",
dewasa: "",
orangtua: "",
lansia: "",
};
grafikBerdasarkanUmur.findMany.load();
return id;
}
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
grafikBerdasarkanUmur.create.loading = false;
}
},
findMany: {
data: null as
| Prisma.GrafikBerdasarkanUmurGetPayload<{
omit: { isActive: true };
},
findMany: {
data: null as
| Prisma.GrafikBerdasarkanUmurGetPayload<{
omit: { isActive: true };
}>[]
| null,
loading: false,
async load() {
const res = await ApiFetch.api.ppid.grafikberdasarkanumur[
"find-many"
].get();
if (res.status === 200) {
grafikBerdasarkanUmur.findMany.data = res.data?.data ?? [];
}
},
| null,
loading: false,
async load() {
const res = await ApiFetch.api.ppid.grafikberdasarkanumur[
"find-many"
].get();
if (res.status === 200) {
grafikBerdasarkanUmur.findMany.data = res.data?.data ?? [];
}
},
})
},
findUnique: {
data: null as Prisma.GrafikBerdasarkanUmurGetPayload<{
omit: { isActive: true };
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/ppid/grafikberdasarkanumur/${id}`);
if (res.ok) {
const data = await res.json();
grafikBerdasarkanUmur.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
grafikBerdasarkanUmur.findUnique.data = null;
}
} catch (error) {
console.error("Error loading grafik berdasarkan umur:", error);
grafikBerdasarkanUmur.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 = templateGrafikUmur.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/ppid/grafikberdasarkanumur/${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 grafikBerdasarkanUmur.findMany.load();
return result.data;
} catch (error) {
console.error("Error update data:", error);
toast.error("Gagal update data grafik berdasarkan umur");
} finally {
this.loading = false;
}
}
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
const stateGrafikBerdasarkanUmur = proxy({
grafikBerdasarkanUmur,
})
try {
grafikBerdasarkanUmur.delete.loading = true;
export default stateGrafikBerdasarkanUmur;
const response = await fetch(`/api/ppid/grafikberdasarkanumur/del/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Grafik berdasarkan umur berhasil dihapus");
await grafikBerdasarkanUmur.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus grafik berdasarkan umur");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus grafik berdasarkan umur");
} finally {
grafikBerdasarkanUmur.delete.loading = false;
}
}
}
});
export default grafikBerdasarkanUmur;

View File

@@ -6,17 +6,18 @@ import { z } from "zod";
const templateGrafikHasilKepuasanMasyarakat = z.object({
label: z.string().min(2, "Label harus diisi"),
kepuasan: z.string().min(2, "Kepuasan harus diisi"),
kepuasan: z.string().min(1, "Kepuasan harus diisi"),
});
type GrafikHasilKepuasanMasyarakat = Prisma.IndeksKepuasanMasyarakatGetPayload<{
select: {
id: true;
label: true;
kepuasan: true;
};
}>;
const defaultForm: GrafikHasilKepuasanMasyarakat = {
const defaultForm: Omit<GrafikHasilKepuasanMasyarakat, 'id'> & { id?: string } = {
label: "",
kepuasan: "",
};
@@ -41,13 +42,18 @@ const grafikHasilKepuasanMasyarakat = proxy({
grafikHasilKepuasanMasyarakat.create.form
);
if (res.status === 200) {
grafikHasilKepuasanMasyarakat.create.form = {
label: "",
kepuasan: ""
};
grafikHasilKepuasanMasyarakat.findMany.load();
return toast.success("success create");
const id = res.data?.data?.id;
if (id) {
toast.success("Success create");
grafikHasilKepuasanMasyarakat.create.form = {
label: "",
kepuasan: "",
};
grafikHasilKepuasanMasyarakat.findMany.load();
return id;
}
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
@@ -58,19 +64,127 @@ const grafikHasilKepuasanMasyarakat = proxy({
},
findMany: {
data: null as
| Prisma.IndeksKepuasanMasyarakatGetPayload<{ omit: { isActive: true } }>[]
| null,
| 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 ?? [];
const res = await ApiFetch.api.ppid.grafikhasilkepuasamanmasyarakat[
"find-many"
].get();
if (res.status === 200) {
grafikHasilKepuasanMasyarakat.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.IndeksKepuasanMasyarakatGetPayload<{
omit: { isActive: true };
}> | null,
async load(id: string) {
try {
const res = await fetch(
`/api/ppid/grafikhasilkepuasamanmasyarakat/${id}`
);
if (res.ok) {
const data = await res.json();
grafikHasilKepuasanMasyarakat.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
grafikHasilKepuasanMasyarakat.findUnique.data = null;
}
} catch (error) {
console.error("Error loading grafik hasil kepuasan masyarakat:", error);
grafikHasilKepuasanMasyarakat.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;
}
// ✅ Validasi pakai Zod
const cek = templateGrafikHasilKepuasanMasyarakat.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/ppid/grafikhasilkepuasamanmasyarakat/${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!");
// ✅ Optional: refresh list kalau kamu langsung ke halaman list
await grafikHasilKepuasanMasyarakat.findMany.load();
return result.data;
} catch (error) {
console.error("Error update data:", error);
toast.error("Gagal update data grafik hasil kepuasan masyarakat");
} finally {
this.loading = false;
}
}
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
grafikHasilKepuasanMasyarakat.delete.loading = true;
const response = await fetch(`/api/ppid/grafikhasilkepuasamanmasyarakat/del/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Grafik hasil kepuasan masyarakat berhasil dihapus");
await grafikHasilKepuasanMasyarakat.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus grafik hasil kepuasan masyarakat");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus grafik hasil kepuasan masyarakat");
} finally {
grafikHasilKepuasanMasyarakat.delete.loading = false;
}
},
}
});
const stateGrafikHasilKepuasanMasyarakat = proxy({
grafikHasilKepuasanMasyarakat,
});
export default stateGrafikHasilKepuasanMasyarakat;
export default grafikHasilKepuasanMasyarakat;

View File

@@ -1,4 +1,3 @@
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
import { proxy } from "valtio";
@@ -13,11 +12,18 @@ const templateForm = z.object({
riwayat: z.string().min(3, "Riwayat minimal 3 karakter"),
pengalaman: z.string().min(3, "Pengalaman minimal 3 karakter"),
unggulan: z.string().min(3, "Unggulan minimal 3 karakter"),
imageId: z.string().min(1, "Gambar wajib dipilih"),
});
/**
* Tipe data ProfilePPID yang digunakan dalam form dan API, berdasarkan Prisma schema.
*/
const defaultForm = {
name: "",
biodata: "",
riwayat: "",
pengalaman: "",
unggulan: "",
imageId: "",
};
type ProfilePPIDForm = Prisma.ProfilePPIDGetPayload<{
select: {
id: true;
@@ -26,147 +32,170 @@ type ProfilePPIDForm = Prisma.ProfilePPIDGetPayload<{
riwayat: true;
pengalaman: true;
unggulan: true;
imageUrl: true;
imageId: true;
image?: {
select: {
link: true;
};
};
};
}>;
/**
* State utama ProfilePPID yang mencakup fitur:
* - Ambil data berdasarkan ID
* - Update data
* - Upload gambar
* Improved State Management - Consolidated and more robust
*/
const stateProfilePPID = proxy({
/**
* Bagian untuk ambil data berdasarkan ID
*/
findById: {
// Consolidated data management
profile: {
data: null as ProfilePPIDForm | null,
loading: false,
error: null as string | null,
/**
* Inisialisasi data kosong ke dalam state.
*/
initialize() {
stateProfilePPID.findById.data = {
id: '',
name: '',
biodata: '',
riwayat: '',
pengalaman: '',
unggulan: '',
imageUrl: ''
} as ProfilePPIDForm;
},
/**
* Mengambil data profil berdasarkan ID.
* @param {string} id - ID dari profile yang ingin diambil.
*/
// Single method to load profile data
async load(id: string) {
try {
stateProfilePPID.findById.loading = true;
const res = await ApiFetch.api.ppid.profileppid["find-by-id"].get({
query: { id },
});
if (!id) {
toast.warn("ID tidak valid");
return null;
}
if (res.status === 200) {
stateProfilePPID.findById.data = res.data?.data ?? null;
this.loading = true;
this.error = null;
try {
const response = await fetch(`/api/ppid/profileppid/${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 {
toast.error("Gagal mengambil data profile");
throw new Error(result.message || "Gagal mengambil data profile");
}
} catch (error) {
console.error((error as Error).message);
const errorMessage = (error as Error).message;
this.error = errorMessage;
console.error("Load profile error:", errorMessage);
toast.error("Terjadi kesalahan saat mengambil data profile");
return null;
} finally {
stateProfilePPID.findById.loading = false;
this.loading = false;
}
},
// Reset profile data
reset() {
this.data = null;
this.error = null;
this.loading = false;
}
},
/**
* Bagian untuk update data profile
*/
update: {
// Edit form management
editForm: {
id: "",
form: { ...defaultForm },
loading: false,
error: null as string | null,
isReadOnly: false, // Flag untuk data yang tidak bisa diedit
/**
* Melakukan validasi dan menyimpan perubahan data profile ke server.
* @param {ProfilePPIDForm} data - Data profil yang akan disimpan.
*/
async save(data: ProfilePPIDForm) {
const cek = templateForm.safeParse(data);
// Initialize form with profile data
initialize(profileData: ProfilePPIDForm) {
this.id = profileData.id;
this.isReadOnly = false; // Semua data bisa diedit
this.form = {
name: profileData.name || "",
biodata: profileData.biodata || "",
riwayat: profileData.riwayat || "",
pengalaman: profileData.pengalaman || "",
unggulan: profileData.unggulan || "",
imageId: profileData.imageId || "",
};
},
if (!cek.success) {
const errors = cek.error.issues
// Update form field
updateField(field: keyof typeof defaultForm, value: string) {
this.form[field] = value;
},
// Submit form
async submit() {
// Validate form
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;
return false;
}
try {
stateProfilePPID.update.loading = true;
const res = await ApiFetch.api.ppid.profileppid["update"].post(data);
this.loading = true;
this.error = null;
if (res.status === 200) {
try {
const response = await fetch(`/api/ppid/profileppid/${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");
await stateProfilePPID.findById.load(data.id);
// Refresh profile data
await stateProfilePPID.profile.load(this.id);
return true;
} else {
toast.error("Gagal update profile");
throw new Error(result.message || "Gagal update profile");
}
} catch (error) {
console.error((error as Error).message);
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 {
stateProfilePPID.update.loading = false;
this.loading = false;
}
},
// Reset form
reset() {
this.id = "";
this.form = { ...defaultForm };
this.error = null;
this.loading = false;
this.isReadOnly = false;
}
},
/**
* Bagian untuk upload gambar profil
*/
uploadImage: {
loading: false,
/**
* Mengunggah gambar profil berdasarkan ID.
* @param {File} file - File gambar yang akan diunggah.
* @param {string} id - ID dari profil yang akan diperbarui gambarnya.
*/
async save(file: File, id: string) {
if (!file || !id) {
toast.error("File atau ID harus disertakan");
return;
}
try {
stateProfilePPID.uploadImage.loading = true;
const form = new FormData();
form.append("file", file);
form.append("id", id);
const res = await ApiFetch.api.ppid.profileppid["edit-img"].post(form);
if (res.status === 200) {
toast.success("Berhasil mengunggah gambar");
await stateProfilePPID.findById.load(id);
} else {
toast.error("Gagal mengunggah gambar");
}
} catch (error) {
console.error((error as Error).message);
toast.error("Terjadi kesalahan saat mengunggah gambar");
} finally {
stateProfilePPID.uploadImage.loading = false;
}
},
// Helper methods
async loadForEdit(id: string) {
const profileData = await this.profile.load(id);
if (profileData) {
this.editForm.initialize(profileData);
}
return profileData;
},
reset() {
this.profile.reset();
this.editForm.reset();
}
});
/**
* Ekspor state utama ProfilePPID untuk digunakan di komponen lain.
*/
export default stateProfilePPID;
export default stateProfilePPID;

View File

@@ -0,0 +1,169 @@
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"),
})
const defaultForm = {
name: "",
imageId: "",
};
type StrukturPPIDForm = Prisma.StrukturPPIDGetPayload<{
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,
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/ppid/strukturppid/${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 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;
}
},
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 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;
}
},
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();
}
})
export default stateStrukturPPID;

View File

@@ -0,0 +1,72 @@
/* 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 LayoutTabsLayanan({ children }: { children: React.ReactNode }) {
const router = useRouter()
const pathname = usePathname()
const tabs = [
{
label: "Pelayanan Surat Keterangan",
value: "pelayanansuratketerangan",
href: "/admin/desa/layanan/pelayanan_surat_keterangan"
},
{
label: "Pelayanan Perizinan Berusaha",
value: "pelayananperizinanusaha",
href: "/admin/desa/layanan/pelayanan_perizinan_berusaha"
},
{
label: "Pelayanan Telunjuk Sakti Desa",
value: "pelayanantelunjuksaktidesa",
href: "/admin/desa/layanan/pelayanan_telunjuk_sakti_desa"
},
{
label: "Pelayanan Penduduk Non-Permanent",
value: "pelayanantelunjuknonpermanent",
href: "/admin/desa/layanan/pelayanan_penduduk_non_permanent"
}
];
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}>Layanan</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 LayoutTabsLayanan;

View File

@@ -125,16 +125,6 @@ function EditBerita() {
placeholder="masukkan judul"
/>
<SelectCategory
value={formData.kategoriBeritaId}
onChange={(val) => {
setFormData({
...formData,
kategoriBeritaId: val?.id || ''
});
}}
/>
<TextInput
value={formData.deskripsi}
onChange={(e) => setFormData({ ...formData, deskripsi: e.target.value })}
@@ -174,6 +164,16 @@ function EditBerita() {
/>
</Box>
<SelectCategory
value={formData.kategoriBeritaId}
onChange={(val) => {
setFormData({
...formData,
kategoriBeritaId: val?.id || ''
});
}}
/>
<Button onClick={handleSubmit}>Edit Berita</Button>
</Stack>
</Paper>

View File

@@ -35,9 +35,7 @@ function DetailBerita() {
if (!beritaState.berita.findUnique.data) {
return (
<Stack py={10}>
{Array.from({ length: 10 }).map((_, k) => (
<Skeleton key={k} h={40} />
))}
<Skeleton h={40} />
</Stack>
)
}

View File

@@ -63,9 +63,9 @@ export default function CreateBerita() {
return (
<Box>
<Box mb={10}>
<Button variant="subtle" onClick={() => router.back()}>
<IconArrowBack color={colors['blue-button']} size={25} />
</Button>
<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"}>
@@ -79,8 +79,9 @@ export default function CreateBerita() {
placeholder="masukkan judul"
/>
<SelectCategory
value={beritaState.berita.create.form.kategoriBeritaId}
onChange={(val) => {
beritaState.berita.create.form.kategoriBeritaId = val.id;
beritaState.berita.create.form.kategoriBeritaId = val?.id || "";
}}
/>
<TextInput
@@ -114,10 +115,10 @@ export default function CreateBerita() {
<Box>
<Text fz={"sm"} fw={"bold"}>Konten</Text>
<CreateEditor
value={beritaState.berita.create.form.content}
onChange={(htmlContent) => {
beritaState.berita.create.form.content = htmlContent;
}}
value={beritaState.berita.create.form.content}
onChange={(htmlContent) => {
beritaState.berita.create.form.content = htmlContent;
}}
/>
</Box>
<Button bg={colors['blue-button']} onClick={handleSubmit}>Simpan Berita</Button>
@@ -126,26 +127,37 @@ export default function CreateBerita() {
</Box>
);
function SelectCategory({
onChange,
}: {
interface SelectCategoryProps {
onChange: (value: Prisma.KategoriBeritaGetPayload<{
select: {
name: true;
id: true;
};
}>) => void;
}) {
}> | null) => void;
value?: string | null;
defaultValue?: string | null;
}
function SelectCategory({
onChange,
value,
defaultValue,
}: SelectCategoryProps) {
const categoryState = useProxy(stateDashboardBerita.category);
useShallowEffect(() => {
categoryState.findMany.load();
categoryState.findMany.load().then(() => {
console.log("Kategori berhasil dimuat:", categoryState.findMany.data);
});
}, []);
if (!categoryState.findMany.data) {
return <Skeleton height={38} />;
}
const selectedValue = value || defaultValue;
return (
<Select
label={<Text fz={"sm"} fw={"bold"}>Kategori</Text>}
@@ -154,13 +166,19 @@ export default function CreateBerita() {
label: item.name,
value: item.id,
}))}
onChange={(val) => {
const selected = categoryState.findMany.data?.find((item) => item.id === val);
if (selected) {
onChange(selected);
value={selectedValue || null}
onChange={(val: string | null) => {
if (val) {
const selected = categoryState.findMany.data?.find((item) => item.id === val);
if (selected) {
onChange(selected);
}
} else {
onChange(null);
}
}}
searchable
clearable
nothingFoundMessage="Tidak ditemukan"
/>
);

View File

@@ -1,43 +1,47 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Grid, GridCol, Image, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, TextInput, Title } from '@mantine/core';
import { Box, Button, Grid, GridCol, Image, 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';
import { useState } from 'react';
import { useProxy } from 'valtio/utils';
import HeaderSearch from '../../_com/header';
import stateDashboardBerita from '../../_state/desa/berita';
function Page() {
function Berita() {
const [search, setSearch] = useState("");
return (
<Box>
<Grid>
<GridCol span={{ base: 12, md: 9 }}>
<Title order={3}>Berita</Title>
</GridCol>
<GridCol span={{ base: 12, md: 3 }}>
<Paper radius={"lg"} bg={colors['white-1']}>
<TextInput
radius={"lg"}
placeholder='pencarian'
leftSection={<IconSearch size={20} />}
/>
</Paper>
</GridCol>
</Grid>
<BeritaList />
<HeaderSearch
title='Berita'
placeholder='pencarian'
searchIcon={<IconSearch size={20} />}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<ListBerita search={search} />
</Box>
);
}
function BeritaList() {
function ListBerita({ search }: { search: string }) {
const beritaState = useProxy(stateDashboardBerita)
const router = useRouter()
useShallowEffect(() => {
beritaState.berita.findMany.load()
}, [])
const router = useRouter()
const filteredData = (beritaState.berita.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.judul.toLowerCase().includes(keyword) ||
item.kategoriBerita?.name.toLowerCase().includes(keyword)
);
});
if (!beritaState.berita.findMany.data) {
return (
@@ -62,7 +66,7 @@ function BeritaList() {
</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>
@@ -73,7 +77,7 @@ function BeritaList() {
</TableTr>
</TableThead>
<TableTbody >
{beritaState.berita.findMany.data?.map((item) => (
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd >
<Box w={100}>
@@ -101,4 +105,4 @@ function BeritaList() {
)
}
export default Page;
export default Berita;

View File

@@ -0,0 +1,119 @@
'use client'
/* eslint-disable react-hooks/exhaustive-deps */
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
import stateGallery from '@/app/admin/(dashboard)/_state/desa/gallery';
import colors from '@/con/colors';
import ApiFetch from '@/lib/api-fetch';
import { Box, Button, Center, FileInput, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
import { useParams, useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { toast } from 'react-toastify';
import { useProxy } from 'valtio/utils';
function EditFoto() {
const fotoState = useProxy(stateGallery.foto)
const router = useRouter();
const params = useParams();
const [previewImage, setPreviewImage] = useState<string | null>(null);
const [file, setFile] = useState<File | null>(null);
useEffect(() => {
const loadFoto = async () => {
const id = params?.id as string;
if (!id) return;
try {
const data = await fotoState.update.load(id);
if (data) {
if (data?.imageGalleryFoto?.link) {
setPreviewImage(data.imageGalleryFoto.link);
}
}
} catch (error) {
console.error('Error loading foto:', error);
toast.error('Gagal memuat data foto');
}
};
loadFoto();
}, [params?.id]);
const handleSubmit = async () => {
try {
if (file) {
const res = await ApiFetch.api.fileStorage.create.post({
file,
name: file.name,
});
const uploaded = res.data?.data;
if (!uploaded?.id) {
return toast.error("Gagal upload gambar");
}
fotoState.update.form.imagesId = uploaded.id;
}
await fotoState.update.update();
toast.success('Foto berhasil diperbarui!');
router.push('/admin/desa/gallery/foto');
} catch (error) {
console.error('Error updating foto:', error);
toast.error('Terjadi kesalahan saat memperbarui foto');
}
};
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 Foto</Title>
<TextInput
label={<Text fw={"bold"} fz={"sm"}>Judul Foto</Text>}
placeholder='Masukkan judul foto'
value={fotoState.update.form.name}
onChange={(e) =>
(fotoState.update.form.name = e.target.value)
}
/>
<FileInput
label={<Text fz={"sm"} fw={"bold"}>Upload Gambar</Text>}
value={file}
onChange={async (e) => {
if (!e) return;
setFile(e);
const base64 = await e.arrayBuffer().then((buf) =>
"data:image/png;base64," + Buffer.from(buf).toString("base64")
);
setPreviewImage(base64);
}}
/>
{previewImage ? (
<Image alt="" src={previewImage} w={200} h={200} />
) : (
<Center w={200} h={200} bg={"gray"}>
<IconImageInPicture />
</Center>
)}
<Box>
<Text fw={"bold"} fz={"sm"}>Deskripsi Foto</Text>
<EditEditor
value={fotoState.update.form.deskripsi}
onChange={(val) => {
fotoState.update.form.deskripsi = val;
}}
/>
</Box>
<Group>
<Button onClick={handleSubmit} bg={colors['blue-button']}>Submit</Button>
</Group>
</Stack>
</Paper>
</Box>
);
}
export default EditFoto;

View File

@@ -0,0 +1,112 @@
'use client'
import stateGallery from '@/app/admin/(dashboard)/_state/desa/gallery';
import React from 'react';
import { useProxy } from 'valtio/utils';
import { useState } from 'react';
import { useParams, useRouter } from 'next/navigation';
import { useShallowEffect } from '@mantine/hooks';
import { Box, Button, Flex, Image, Paper, Skeleton, Stack, Text } from '@mantine/core';
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
import colors from '@/con/colors';
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
function DetailFoto() {
const fotoState = useProxy(stateGallery.foto)
const [modalHapus, setModalHapus] = useState(false);
const [selectedId, setSelectedId] = useState<string | null>(null)
const params = useParams()
const router = useRouter()
useShallowEffect(() => {
fotoState.findUnique.load(params?.id as string)
}, [])
const handleHapus = () => {
if (selectedId) {
fotoState.delete.byId(selectedId)
setModalHapus(false)
setSelectedId(null)
router.push("/admin/desa/gallery/foto")
}
}
if (!fotoState.findUnique.data) {
return (
<Stack py={10}>
<Skeleton h={500} />
</Stack>
)
}
return (
<Box>
<Box mb={10}>
<Button variant="subtle" onClick={() => router.back()}>
<IconArrowBack color={colors['blue-button']} size={25} />
</Button>
</Box>
<Paper bg={colors['white-1']} w={{ base: "100%", md: "100%", lg: "50%" }} p={'md'}>
<Stack>
<Text fz={"xl"} fw={"bold"}>Detail Foto</Text>
{fotoState.findUnique.data ? (
<Paper key={fotoState.findUnique.data.id} bg={colors['BG-trans']} p={'md'}>
<Stack gap={"xs"}>
<Box>
<Text fw={"bold"} fz={"lg"}>Judul</Text>
<Text fz={"lg"}>{fotoState.findUnique.data?.name}</Text>
</Box>
<Box>
<Text fw={"bold"} fz={"lg"}>Tanggal Foto</Text>
<Text fz={"lg"}>{new Date(fotoState.findUnique.data?.createdAt).toDateString()}</Text>
</Box>
<Box>
<Text fw={"bold"} fz={"lg"}>Deskripsi</Text>
<Text fz={"lg"} dangerouslySetInnerHTML={{ __html: fotoState.findUnique.data?.deskripsi }} />
</Box>
<Box>
<Text fw={"bold"} fz={"lg"}>Gambar</Text>
<Image w={{ base: 300, md: 350}} src={fotoState.findUnique.data?.imageGalleryFoto?.link} alt="gambar" />
</Box>
<Flex gap={"xs"} mt={10}>
<Button
onClick={() => {
if (fotoState.findUnique.data) {
setSelectedId(fotoState.findUnique.data.id);
setModalHapus(true);
}
}}
disabled={fotoState.delete.loading || !fotoState.findUnique.data}
color={"red"}
>
<IconX size={20} />
</Button>
<Button
onClick={() => {
if (fotoState.findUnique.data) {
router.push(`/admin/desa/gallery/foto/${fotoState.findUnique.data.id}/edit`);
}
}}
disabled={!fotoState.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 berita ini?'
/>
</Box>
);
}
export default DetailFoto;

View File

@@ -1,45 +1,108 @@
'use client'
import { KeamananEditor } from '@/app/admin/(dashboard)/keamanan/_com/keamananEditor';
import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
import stateGallery from '@/app/admin/(dashboard)/_state/desa/gallery';
import colors from '@/con/colors';
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
import { IconArrowBack } from '@tabler/icons-react';
import ApiFetch from '@/lib/api-fetch';
import { Box, Button, Center, FileInput, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import { toast } from 'react-toastify';
import { useProxy } from 'valtio/utils';
function CreateFoto() {
const fotoState = useProxy(stateGallery.foto)
const router = useRouter();
const [previewImage, setPreviewImage] = useState<string | null>(null);
const [file, setFile] = useState<File | null>(null);
const resetForm = () => {
fotoState.create.form = {
name: "",
deskripsi: "",
imagesId: "",
};
setPreviewImage(null)
setFile(null)
};
const handleSubmit = async () => {
if (!file) {
return toast.warn("Pilih file gambar terlebih dahulu");
}
const res = await ApiFetch.api.fileStorage.create.post({
file,
name: file.name,
});
const uploaded = res.data?.data;
if (!uploaded?.id) {
return toast.error("Gagal upload gambar");
}
fotoState.create.form.imagesId = uploaded.id;
await fotoState.create.create();
resetForm();
router.push("/admin/desa/gallery/foto")
};
return (
<Box>
<Box mb={10}>
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
<IconArrowBack color={colors['blue-button']} size={25}/>
<IconArrowBack color={colors['blue-button']} size={25} />
</Button>
</Box>
<Paper w={{base: '100%', md: '50%'}} bg={colors['white-1']} p={'md'}>
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
<Stack gap={"xs"}>
<Title order={4}>Create Foto</Title>
<TextInput
label={<Text fw={"bold"} fz={"sm"}>Judul Foto</Text>}
placeholder='Masukkan judul foto'
label={<Text fw={"bold"} fz={"sm"}>Judul Foto</Text>}
placeholder='Masukkan judul foto'
value={fotoState.create.form.name}
onChange={(val) => {
fotoState.create.form.name = val.target.value;
}}
/>
<TextInput
label={<Text fw={"bold"} fz={"sm"}>Tanggal Foto</Text>}
placeholder='Masukkan tanggal foto'
<FileInput
label={<Text fz={"sm"} fw={"bold"}>Upload Gambar</Text>}
value={file}
onChange={async (e) => {
if (!e) return;
setFile(e);
const base64 = await e.arrayBuffer().then((buf) =>
"data:image/png;base64," + Buffer.from(buf).toString("base64")
);
setPreviewImage(base64);
}}
/>
{previewImage ? (
<Image alt="" src={previewImage} w={200} h={200} />
) : (
<Center w={200} h={200} bg={"gray"}>
<IconImageInPicture />
</Center>
)}
<Box>
<Text fw={"bold"} fz={"sm"}>Deskripsi Foto</Text>
<KeamananEditor
showSubmit={false}
<CreateEditor
value={fotoState.create.form.deskripsi}
onChange={(val) => {
fotoState.create.form.deskripsi = val;
}}
/>
</Box>
<Group>
<Button bg={colors['blue-button']}>Submit</Button>
<Button onClick={handleSubmit} bg={colors['blue-button']}>Submit</Button>
</Group>
</Stack>
</Stack>
</Paper>
</Box>
</Box>
);
}

View File

@@ -1,62 +0,0 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Flex, Paper, Stack, Text } from '@mantine/core';
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
// import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
function DetailFoto() {
const router = useRouter();
return (
<Box>
<Box mb={10}>
<Button variant="subtle" onClick={() => router.back()}>
<IconArrowBack color={colors['blue-button']} size={25} />
</Button>
</Box>
<Paper w={{ base: "100%", md: "50%" }} bg={colors['white-1']} p={'md'}>
<Stack>
<Text fz={"xl"} fw={"bold"}>Detail Foto</Text>
<Paper bg={colors['BG-trans']} p={'md'}>
<Stack gap={"xs"}>
<Box>
<Text fw={"bold"}>Judul Foto</Text>
<Text>Foto 1</Text>
</Box>
<Box>
<Text fw={"bold"}>Tanggal Foto</Text>
<Text>2022-01-01</Text>
</Box>
<Box>
<Text fw={"bold"}>Deskripsi Foto</Text>
<Text>Deskripsi Foto 1</Text>
</Box>
<Box>
<Flex gap={"xs"}>
<Button color="red">
<IconX size={20} />
</Button>
<Button onClick={() => router.push('/admin/desa/gallery/foto/edit')} color="green">
<IconEdit size={20} />
</Button>
</Flex>
</Box>
</Stack>
</Paper>
</Stack>
</Paper>
{/* Modal Hapus
<ModalKonfirmasiHapus
opened={modalHapus}
onClose={() => setModalHapus(false)}
onConfirm={handleHapus}
text="Apakah anda yakin ingin menghapus potensi ini?"
/> */}
</Box>
);
}
export default DetailFoto;

View File

@@ -1,46 +0,0 @@
'use client'
import { KeamananEditor } from '@/app/admin/(dashboard)/keamanan/_com/keamananEditor';
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';
function EditFoto() {
const router = useRouter();
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 Foto</Title>
<TextInput
label={<Text fw={"bold"} fz={"sm"}>Judul Foto</Text>}
placeholder='Masukkan judul foto'
/>
<TextInput
label={<Text fw={"bold"} fz={"sm"}>Tanggal Foto</Text>}
placeholder='Masukkan tanggal foto'
/>
<Box>
<Text fw={"bold"} fz={"sm"}>Deskripsi Foto</Text>
<KeamananEditor
showSubmit={false}
/>
</Box>
<Group>
<Button bg={colors['blue-button']}>Submit</Button>
</Group>
</Stack>
</Paper>
</Box>
);
}
export default EditFoto;

View File

@@ -1,12 +1,55 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Paper, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
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 { useProxy } from 'valtio/utils';
import stateGallery from '../../../_state/desa/gallery';
import { useShallowEffect } from '@mantine/hooks';
import HeaderSearch from '../../../_com/header';
import { useState } from 'react';
function Foto() {
const [search, setSearch] = useState("");
return (
<Box>
<HeaderSearch
title='Posisi Organisasi'
placeholder='pencarian'
searchIcon={<IconSearch size={20} />}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<ListFoto search={search} />
</Box>
);
}
function ListFoto({ search }: { search: string }) {
const fotoState = useProxy(stateGallery.foto)
const router = useRouter();
useShallowEffect(() => {
fotoState.findMany.load()
}, [])
const filteredData = (fotoState.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.name.toLowerCase().includes(keyword) ||
item.deskripsi.toLowerCase().includes(keyword)
);
});
if (!fotoState.findMany.data) {
return (
<Box py={10}>
<Skeleton h={500} />
</Box>
)
}
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
@@ -26,16 +69,20 @@ function Foto() {
</TableTr>
</TableThead>
<TableTbody>
<TableTr>
<TableTd>Foto 1</TableTd>
<TableTd>2022-01-01</TableTd>
<TableTd>Deskripsi Foto 1</TableTd>
<TableTd>
<Button onClick={() => router.push('/admin/desa/gallery/foto/detail')}>
<IconDeviceImac size={20} />
</Button>
</TableTd>
</TableTr>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>{item.name}</TableTd>
<TableTd>{new Date(item.createdAt).toDateString()}</TableTd>
<TableTd>
<Text dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
</TableTd>
<TableTd>
<Button onClick={() => router.push(`/admin/desa/gallery/foto/${item.id}`)}>
<IconDeviceImac size={20} />
</Button>
</TableTd>
</TableTr>
))}
</TableTbody>
</Table>
</Paper>

View File

@@ -0,0 +1,10 @@
'use client'
import LayoutTabsGallery from "../../ppid/_com/layoutTabsGallery"
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<LayoutTabsGallery>
{children}
</LayoutTabsGallery>
)
}

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