Compare commits

...

95 Commits

Author SHA1 Message Date
ac2fc1a705 Fix QC Kak Inno 8 Des
Fix QC Kak Ayu 8 Des
Fix QC Pak Jun 8 Des
2025-12-09 17:27:23 +08:00
9dbe172165 Fix QC Kak Inno Tgl 4 & 5 Desember
Fix QC Kak Ayu Tgl 4 & 5 Desember
Fix QC Pak Jun Tgl 5 Desember
2025-12-09 12:00:27 +08:00
cc318d4d54 Fix QC Kak Inno Tgl 4 & 5 Desember
Fix QC Kak Ayu Tgl 4 & 5 Desember
Fix QC Pak Jun Tgl 5 Desember
2025-12-09 10:28:17 +08:00
dcb8017594 Fix undefined ke detail berita terbaru 2025-12-05 17:42:04 +08:00
ec3ad12531 Fix Notifikasi saat ada berita atau pengumuman baru, notifikasi baru muncul. Ga setiap masuk landing page ada notifikasi 2025-12-05 14:30:53 +08:00
dad44c0537 Fix Menu Gallery : Gallery Foto
Fix detail berita
2025-12-05 10:56:03 +08:00
867dce42f0 Fix Error Build Staging 2025-12-04 11:58:47 +08:00
7bb17ddf22 Menambahkan menu dokter dan tenaga medis, admin bisa create, edit, delet dokter
Menambahkan menu tarif dan layanan, admin bisa create, edit, delete tarif dan layanan
Dibagian fasilitas kesehatan admin bisa multiselect bagian dokter dan tarif layanan
Di tampilan user juga sudah disesuaikan dengan datanya bisa muncul lebih dari 1 dokter dan 1 tarif layanan
2025-12-03 17:24:03 +08:00
a4069d3cba Fix UI Sosial Media Landing Page in User 2025-12-02 16:45:55 +08:00
ffe5e6dd9f Fix menu admin landing page, submenu sosial media 2025-12-02 16:06:14 +08:00
dcf195f54f Tambahan filter data sesuai tahun, di landing page apbdes 2025-12-01 17:11:24 +08:00
c03a6b3aed Tambah Term of Service di Registrasi 2025-12-01 14:01:03 +08:00
1bb9f239db Tambah Term of Service di Registrasi 2025-12-01 13:50:25 +08:00
a213ff7d37 Tambah Term of Service di Registrasi 2025-12-01 12:10:22 +08:00
0018bdc251 Fix Ganti Role, ganti role menunya sudah menyesuaikan 2025-11-28 15:03:18 +08:00
83fb39a957 Fix Ganti Role, ganti role menunya sudah menyesuaikan 2025-11-28 15:00:09 +08:00
7238692dd0 Push WebDesaDarmasabaSatging 2025-11-28 13:56:40 +08:00
8b50139d79 Push Staging 2025-11-28 12:03:07 +08:00
066180fc0e Fix registrasi, waitong-room, & tampilan layout sesuai id 2025-11-28 11:13:20 +08:00
67f29aabef Balik ke awal 2025-11-27 18:53:33 +08:00
dbf7c34228 Fix eror registrasi 2 2025-11-27 17:08:17 +08:00
036fc86fed Fix eror registrasi 1 2025-11-27 16:45:47 +08:00
2cecec733e Tambah cookies di bagian verifikasi, agar kedeteksi user sudah regis apa belom 2025-11-27 14:46:49 +08:00
c64a2e5457 Fix Seeder User, dan role 2025-11-27 12:18:15 +08:00
757911d7dd Fix Seeder 2025-11-26 15:32:49 +08:00
54232e4465 Menambahkan seed user
Fix Infinite reload di page ikm dan landing page
2025-11-26 15:01:34 +08:00
29a9a59bca saat tampilan user sudah diubah dan login ulan sudah menyesuaikan untuk menunya 2025-11-26 11:01:23 +08:00
2fb3666e57 User yang sudah registrasi sudah langsung diarahkan ke layout sesuai dengan roleIdnya
Superadmin sudah bisa menambah atau mengurangkan menu pad user yang diinginkan
Next-------------------------------
Ada bug saat tampilan menu sudah di edit superamin berhasil namun saat user logout tampilan menunya balik ke sebelumnya
2025-11-26 10:14:05 +08:00
e30b27f7a4 Fix Search 2025-11-25 17:30:41 +08:00
e941ed3893 Sudah fix menunya, superadmin bisa memilihkan menu untuk user 2025-11-25 16:21:15 +08:00
ace5aff1b6 Fix Kondisi Verify Otp Registrasi dan Login
Next mau fix eror saat user sudah terdaftar tetapi di redirect ke login, seharusnya redirect sesuai roleIdnya
2025-11-25 15:03:27 +08:00
716db0adca Fix Middleware
Fix Layout sesuai role, dan superadmin bisa menambahkan menu ke user jika diperlukan
Penambahan menu di user & role : menu access
2025-11-24 16:02:13 +08:00
a291bdfb51 Tampilan Layout sudah sesuai dengan roleIdnya
Sudah sessionnya
Sudah disesuaikan juga semisal superadmin ngubah role admin, maka admin tersebut akan logOut dan diarahkan ke halama login
sudah bisa logOut
2025-11-21 17:26:38 +08:00
0dff8f3254 Nico 20 Nov 25
Dibagian layout admin sudah disesuaikan dengan rolenya : supadmin, admin desa, admin kesehatan, admin pendidikan
Fix API User & Role Admin
2025-11-20 16:42:36 +08:00
78b8aa74cd Saat user baru registrasi maka akan diarahkan ke page waiting-room dan menunggu validasi admin 2025-11-20 14:07:26 +08:00
a0537810e8 Login, Register, Verifkasi Code Admin V1 2025-11-20 02:42:39 +08:00
b3c169a2d4 Fix create admin & progress bar persentase 2025-11-18 17:23:38 +08:00
2608a5ffdd Fix Edit di Admin APbdes, dan fix data real di apbdes user 2025-11-18 16:26:09 +08:00
6c32f3ebdb Fix Route APBdes 2025-11-18 14:27:53 +08:00
0feeb4de93 Fix SDGs Desa Barchart sudah responsive, tabel dan bar progress di menu apbdes sudah sesuai dengan data 2025-11-18 11:56:16 +08:00
9622eb5a9a Fix QC Kak Inno Admin, Fix QC Keano UI User, Fix QC Pak jun tabel apbdes 2025-11-12 17:42:31 +08:00
417a8937f5 Semua tooltips di admin sudah dihilangkan 2025-11-07 14:38:32 +08:00
db8909b9ed Fix Text to Speech Menu Landing Page && Add barchart Landing Page APBDes 2025-11-06 11:35:04 +08:00
f66a46f645 QC ToolTip Admin Keano Masih di Menu Landing Page - Keamanan, QC Dari Darmasaba Pop Up Notifikasi 2025-11-05 14:32:38 +08:00
fb57698dc9 Add Menu Musik
Add News Reader for Difable
Add Running text news / announcement
2025-11-04 15:08:48 +08:00
d128313e71 Fix QC Keano FrontEnd
Fix QC Kak Ayu Admin 29 Okt
2025-11-03 17:36:00 +08:00
7b4bb1e58e QC Kak Inno FrontEnd Done
QC Kak Ayu FrontEnd Done
QC Keano 31 Okt
2025-11-03 10:28:03 +08:00
0befe6a3f2 QC Kak Inno 28 Okt
QC Kak Ayu 28 Okt
QC Keano 28 Okt
2025-10-30 15:51:12 +08:00
a6663bbcee QC Kak Inno 27 Oct
QC Kak Ayu 27 Oct
QC Keano 27 Oct
QC Pak Jun 27 Oct
2025-10-28 17:34:38 +08:00
ed371bd0d9 Fix QC Kak Inno 24 Okt 25
Fix QC Kak Ayu 24 Okt 25
Fix QC Keano 24 Okt 25
Fix Detail Lowongan Kerja
2025-10-27 22:15:55 +08:00
f82c7b86e0 27 Oct 2025-10-27 10:54:50 +08:00
b5d6585cd5 27 Oct 2025-10-27 10:54:01 +08:00
aa98359ef7 Fix Revisi Kak Inno 22 Oktober && Fix Revisi Kak Ayu 22 Oktober 2025-10-23 17:45:45 +08:00
0ff0d5234a Fix QC Kak Inno 21 Oktober, QC Kak Ayu 21 Oktober, QC Keano, && QC Pak Jun 21 Oktober 2025-10-22 17:00:12 +08:00
827c1c191a Revisi QC Kak Inno tanggal 20 2025-10-22 09:58:16 +08:00
fb596f9033 Fix QC Kak Inno 17 Okt 25, Fix QC Kak Ayu 17 Okt 25, & Fix Qc Pak Jun 17 Okt 25 2025-10-21 12:17:30 +08:00
9055b40769 Fix navbar mobile add active page 2025-10-19 18:08:49 +08:00
bbf13c1cf7 Mengerjakan QC Kak Inno & Kak Ayu Tanggal 16 Oktober
Fix Search
2025-10-17 17:45:56 +08:00
75bf0652b1 Fix QC Kak Inno & Kak Ayu Tanggal 15 Oct 2025-10-17 10:03:03 +08:00
0b574406e2 Fix QC Kak Inno : tanggal 14 Oktober
Fitur Search bisa digunakan di 6 Menu, sisa 3 Menu Lagi
2025-10-15 17:29:57 +08:00
ccf39bc778 Penambahan fungsi search disetiap menu & submenu,
Menu Landing Page
Menu PPID
Menu Desa
2025-10-15 10:13:02 +08:00
3c21f7742c Yang sudh dikerjakan:
- Saat Mau minjam muncul modal data diri peminjam buku V
- Ada Status Peminjamannya ( status buku bisa engga otomatis dipinjemnya), kalau dikembalikan statusnya otomatis
)
Yang Mau Dikerjakan:
Cek fungsi menu yang kompleks
2025-10-14 10:38:55 +08:00
a158241c0b - QC User & Admin Menu Pendidikan V
- Fix SubMenu :
- Beasiswa Desa ( Baca Selengkapnya terdapatkan konten ) V
- Info Sekolah ( Kategori Menyesuaikan Dengan Datanya ) V
- Perpustakaan Digital (  V
- Kategori Menyesuaikan Dengan Datanya V
- Saat Mau minjam muncul modal data diri peminjam buku V
- Ada Status Peminjamannya V
)
2025-10-13 11:20:38 +08:00
80c5dc6361 - QC User & Admin Menu Lingkungan
- Fix SubMenu : Edukasi Lingkungan & Konservasi Adat Bali dibagian User
- Fix SUbMenu : Gotong Royong User ( Tabs kategori menyesuaikan dengan data kategori kegiatan )
2025-10-08 17:06:21 +08:00
8ad38fc907 - QC User & Admin Menu Lingkungan
- Fix SubMenu : Edukasi Lingkungan & Konservasi Adat Bali dibagian User
- Fix SUbMenu : Gotong Royong User ( Tabs kategori menyesuaikan dengan data kategori kegiatan )
2025-10-08 14:02:11 +08:00
d601b2fee3 Fix Bug SubMenu Struktur PPID, SubMenu Struktur Organisasi BumDes 2025-10-07 14:38:20 +08:00
cee0957e07 QC - User & Admin Menu Ekonomi SubMenu Pasar Desa
Fix bug kategori produk
2025-10-06 10:26:59 +08:00
5c66eccf23 Fix Menu Ekonomi :
Pasar Desa : Kategorinya ga tampil,
Bug inputan edit di submenu : Demografi pekerjaa
2025-10-04 21:34:31 +08:00
f7fd9be255 QC User & Admin Responsive : Menu Kesehatan - Ekonomi 2025-10-03 10:17:06 +08:00
8a6d8ed8db QC User & Admin Responsive : Menu Landing Page - Desa 2025-10-02 00:10:33 +08:00
63054cedf0 fix inputan edit menu: desa, ekonomi, inovasi, keamanan, kesehatan, landing-page, & lingkungan 2025-09-30 21:41:26 +08:00
c2f1ab8179 Fix Menu Desa Admin & User 2025-09-30 17:13:06 +08:00
295d6f7d63 Fix tampilan admin pertama kali 2025-09-29 14:33:25 +08:00
dbd56a1493 Fix All Text Input User & Admin, fix deskripsi detail break word 2025-09-29 14:06:04 +08:00
2a26db6e17 Tambahan Fix Form Di Menu Landing Page & PPID IKM 2025-09-25 16:21:44 +08:00
33fc472472 Fix Tampilan Mobile Penghargaan Landing Page 2025-09-25 11:41:43 +08:00
d8fa56d923 Tambahan fix menu prestasi desa 2025-09-25 11:14:38 +08:00
cac146471a Fix UI Mobile User & Admin Menu Kesehatan, QC Menu Kesehatan 2025-09-25 10:40:47 +08:00
3e4a7a1c0a Fix Ui Admin & User to Mobile && QC Menu Landing Page, PPID, Desa 2025-09-24 14:50:53 +08:00
b5c044df6e Fix Tampilan User & Admin Menu Inovasi & Lingkungan 2025-09-22 17:15:11 +08:00
0fc47c28ff fix tampilan admin menu inovasi, sisa menu lingkungan 2025-09-22 10:53:48 +08:00
8e25c91e85 Fix Tampilab DesaAntiKorupsi Landing Page Mobile 2025-09-20 03:49:20 +08:00
068d8b1077 Fix All Image Add Lazy Loading 2025-09-19 10:41:18 +08:00
9f72e94557 Fix Admin - User Menu Keamanan, Submenu Pencegahan Kriminalitas 2025-09-17 17:54:03 +08:00
79ad39fc55 Fix Admin - User Menu Keamanan, Submenu Laporan Kontak Darurat, Laporan Publik 2025-09-17 14:59:46 +08:00
39e1e7b575 QC Tampilan Admin & User, Api berfungsi 2025-09-16 16:47:12 +08:00
4ceea5203f QC Admin - User Menu Ekonomi : Jumlah Pengangguran 2025-09-16 10:11:54 +08:00
a5d841bb6b Fix Compres Gambar && seed gambar profile - landing page 2025-09-12 11:55:40 +08:00
6a7bd386ae Add Layout Kontak Darurat - Admin Menu Keamanan 2025-09-11 12:15:40 +08:00
a9d98895bb Fix Admin Menu SDGs Desa & APBdes Desa, Fix UI IMage Layanan Landing Page & Layanan Desa 2025-09-09 17:14:28 +08:00
75475dc62e Fix Package.json Bun 2025-09-08 21:52:17 +08:00
b39800a475 Fix UI Admin Keamanan Lingkungan 2025-09-08 15:45:56 +08:00
797713ef49 Fix UI Admin Menu Kesehatan, Login Admin, OTP 2025-09-08 14:02:21 +08:00
8817b937b1 API Auth 2025-09-04 11:46:08 +08:00
2adf60f9eb Fix UI Admin menu desa 2025-09-03 15:30:02 +08:00
1105 changed files with 80415 additions and 36222 deletions

5
.gitignore vendored
View File

@@ -41,6 +41,9 @@ next-env.d.ts
# uploads # uploads
/uploads /uploads
# download
/download
# cache # cache
/cache /cache
@@ -48,3 +51,5 @@ next-env.d.ts
.env.* .env.*
*.tar.gz

BIN
bun.lockb

Binary file not shown.

View File

@@ -1,6 +1,11 @@
import type { NextConfig } from "next"; import type { NextConfig } from "next";
const nextConfig: NextConfig = { const nextConfig: NextConfig = {
experimental: {},
allowedDevOrigins: [
"http://192.168.1.82:3000", // buat akses dari HP/device lain
"http://localhost:3000", // akses lokal
],
async headers() { async headers() {
return [ return [
{ {

View File

@@ -3,11 +3,9 @@
"version": "0.1.5", "version": "0.1.5",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "next dev --turbopack", "dev": "next dev",
"build": "next build", "build": "next build",
"start": "next start", "start": "next start"
"lint": "next lint",
"prisma:seed": "bun run prisma/seed.ts"
}, },
"prisma": { "prisma": {
"seed": "bun run prisma/seed.ts" "seed": "bun run prisma/seed.ts"
@@ -21,6 +19,7 @@
"@elysiajs/static": "^1.3.0", "@elysiajs/static": "^1.3.0",
"@elysiajs/stream": "^1.1.0", "@elysiajs/stream": "^1.1.0",
"@elysiajs/swagger": "^1.2.0", "@elysiajs/swagger": "^1.2.0",
"@emotion/react": "^11.14.0",
"@mantine/carousel": "^7.16.2", "@mantine/carousel": "^7.16.2",
"@mantine/charts": "^7.17.1", "@mantine/charts": "^7.17.1",
"@mantine/core": "^7.17.4", "@mantine/core": "^7.17.4",
@@ -28,6 +27,7 @@
"@mantine/dropzone": "^8.1.1", "@mantine/dropzone": "^8.1.1",
"@mantine/form": "^8.1.0", "@mantine/form": "^8.1.0",
"@mantine/hooks": "^7.17.4", "@mantine/hooks": "^7.17.4",
"@mantine/modals": "^8.3.6",
"@mantine/tiptap": "^7.17.4", "@mantine/tiptap": "^7.17.4",
"@paljs/types": "^8.1.0", "@paljs/types": "^8.1.0",
"@prisma/client": "^6.3.1", "@prisma/client": "^6.3.1",
@@ -41,22 +41,32 @@
"@tiptap/pm": "^2.11.7", "@tiptap/pm": "^2.11.7",
"@tiptap/react": "^2.11.7", "@tiptap/react": "^2.11.7",
"@tiptap/starter-kit": "^2.11.7", "@tiptap/starter-kit": "^2.11.7",
"@types/adm-zip": "^0.5.7",
"@types/bun": "^1.2.2", "@types/bun": "^1.2.2",
"@types/leaflet": "^1.9.20", "@types/leaflet": "^1.9.20",
"@types/lodash": "^4.17.16", "@types/lodash": "^4.17.16",
"@types/nodemailer": "^7.0.2",
"add": "^2.0.6", "add": "^2.0.6",
"adm-zip": "^0.5.16",
"animate.css": "^4.1.1", "animate.css": "^4.1.1",
"bcryptjs": "^3.0.2", "bcryptjs": "^3.0.2",
"bun": "^1.2.2", "bun": "^1.2.2",
"chart.js": "^4.4.8", "chart.js": "^4.4.8",
"classnames": "^2.5.1", "classnames": "^2.5.1",
"colors": "^1.4.0",
"date-fns": "^4.1.0",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
"dotenv": "^17.2.3",
"elysia": "^1.3.5", "elysia": "^1.3.5",
"embla-carousel-autoplay": "^8.5.2", "embla-carousel": "^8.6.0",
"embla-carousel-react": "^7.1.0", "embla-carousel-autoplay": "^8.6.0",
"embla-carousel-react": "^8.6.0",
"extract-zip": "^2.0.1",
"form-data": "^4.0.2", "form-data": "^4.0.2",
"framer-motion": "^12.23.5", "framer-motion": "^12.23.5",
"get-port": "^7.1.0", "get-port": "^7.1.0",
"iron-session": "^8.0.4",
"jose": "^6.1.0",
"jotai": "^2.12.3", "jotai": "^2.12.3",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"leaflet": "^1.9.4", "leaflet": "^1.9.4",
@@ -64,24 +74,30 @@
"lodash": "^4.17.21", "lodash": "^4.17.21",
"motion": "^12.4.1", "motion": "^12.4.1",
"nanoid": "^5.1.5", "nanoid": "^5.1.5",
"next": "15.1.6", "next": "^15.5.2",
"next-view-transitions": "^0.3.4", "next-view-transitions": "^0.3.4",
"node-fetch": "^3.3.2", "node-fetch": "^3.3.2",
"nodemailer": "^7.0.10",
"p-limit": "^6.2.0", "p-limit": "^6.2.0",
"primeicons": "^7.0.0", "primeicons": "^7.0.0",
"primereact": "^10.9.6", "primereact": "^10.9.6",
"prisma": "^6.3.1", "prisma": "^6.3.1",
"react": "^19.0.0", "react": "^19.0.0",
"react-dom": "^19.0.0", "react-dom": "^19.0.0",
"react-exif-orientation-img": "^0.1.5",
"react-international-phone": "^4.6.0",
"react-leaflet": "^5.0.0", "react-leaflet": "^5.0.0",
"react-simple-toasts": "^6.1.0", "react-simple-toasts": "^6.1.0",
"react-toastify": "^11.0.5", "react-toastify": "^11.0.5",
"react-transition-group": "^4.4.5", "react-transition-group": "^4.4.5",
"react-zoom-pan-pinch": "^3.7.0",
"readdirp": "^4.1.1", "readdirp": "^4.1.1",
"recharts": "^2.15.3", "recharts": "^2.15.3",
"sharp": "^0.34.3",
"swr": "^2.3.2", "swr": "^2.3.2",
"uuid": "^11.1.0", "uuid": "^11.1.0",
"valtio": "^2.1.3", "valtio": "^2.1.3",
"zlib": "^1.0.5",
"zod": "^3.24.3" "zod": "^3.24.3"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -1,5 +1,4 @@
[ [
{ "name": "Semua" },
{ "name": "Pemerintahan" }, { "name": "Pemerintahan" },
{ "name": "Pembangunan" }, { "name": "Pembangunan" },
{ "name": "Ekonomi" }, { "name": "Ekonomi" },

View File

@@ -1,6 +1,6 @@
[ [
{ {
"id": "1", "id": "edit",
"name": "Pelayanan Penduduk Non-Permanent", "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>" "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

@@ -1,6 +1,6 @@
[ [
{ {
"id": "1", "id": "edit",
"name": "Pelayanan Perizinan Berusaha Berbasis Risiko Melalui Sistem ONLINE SINGLE SUBMISSION (OSS)", "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>", "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/" "link" : "https://oss.go.id/"

View File

@@ -1,51 +1,99 @@
[ [
{ {
"month": "Jan", "month": "Jan",
"year": 2025, "year": 2025,
"totalUnemployment": 160, "totalUnemployment": 160,
"educatedUnemployment": 95, "educatedUnemployment": 95,
"uneducatedUnemployment": 65, "uneducatedUnemployment": 65,
"percentageChange": null "percentageChange": 0.0
}, },
{ {
"month": "Feb", "month": "Feb",
"year": 2025, "year": 2025,
"totalUnemployment": 155, "totalUnemployment": 158,
"educatedUnemployment": 90, "educatedUnemployment": 93,
"uneducatedUnemployment": 65, "uneducatedUnemployment": 65,
"percentageChange": -3.1 "percentageChange": -1.25
}, },
{ {
"month": "Mar", "month": "Mar",
"year": 2025, "year": 2025,
"totalUnemployment": 150, "totalUnemployment": 155,
"educatedUnemployment": 88, "educatedUnemployment": 91,
"uneducatedUnemployment": 62, "uneducatedUnemployment": 64,
"percentageChange": -3.2 "percentageChange": -1.90
}, },
{ {
"month": "Apr", "month": "Apr",
"year": 2025, "year": 2025,
"totalUnemployment": 148, "totalUnemployment": 152,
"educatedUnemployment": 85, "educatedUnemployment": 89,
"uneducatedUnemployment": 63, "uneducatedUnemployment": 63,
"percentageChange": -1.3 "percentageChange": -1.94
}, },
{ {
"month": "Mei", "month": "Mei",
"year": 2025, "year": 2025,
"totalUnemployment": 145, "totalUnemployment": 150,
"educatedUnemployment": 82, "educatedUnemployment": 88,
"uneducatedUnemployment": 63, "uneducatedUnemployment": 62,
"percentageChange": -2.0 "percentageChange": -1.32
}, },
{ {
"month": "Jun", "month": "Jun",
"year": 2025, "year": 2025,
"totalUnemployment": 140, "totalUnemployment": 148,
"educatedUnemployment": 80, "educatedUnemployment": 87,
"uneducatedUnemployment": 60, "uneducatedUnemployment": 61,
"percentageChange": -3.4 "percentageChange": -1.33
} },
] {
"month": "Jul",
"year": 2025,
"totalUnemployment": 145,
"educatedUnemployment": 85,
"uneducatedUnemployment": 60,
"percentageChange": -2.03
},
{
"month": "Agu",
"year": 2025,
"totalUnemployment": 142,
"educatedUnemployment": 84,
"uneducatedUnemployment": 58,
"percentageChange": -2.07
},
{
"month": "Sep",
"year": 2025,
"totalUnemployment": 140,
"educatedUnemployment": 83,
"uneducatedUnemployment": 57,
"percentageChange": -1.41
},
{
"month": "Okt",
"year": 2025,
"totalUnemployment": 138,
"educatedUnemployment": 82,
"uneducatedUnemployment": 56,
"percentageChange": -1.43
},
{
"month": "Nov",
"year": 2025,
"totalUnemployment": 135,
"educatedUnemployment": 80,
"uneducatedUnemployment": 55,
"percentageChange": -2.17
},
{
"month": "Des",
"year": 2025,
"totalUnemployment": 132,
"educatedUnemployment": 78,
"uneducatedUnemployment": 54,
"percentageChange": -2.22
}
]

View File

@@ -1,8 +0,0 @@
[
{
"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,91 @@
[
{
"id": "cmgewz4gt000704ib91i3f169",
"namaLengkap": "Ida Bagus Surya Prabhawa Manuaba, S.H.,M.H., NL.P.",
"gelarAkademik": "S.H.,M.H.,NL.P.",
"tanggalMasuk": "2020-01-01T00:00:00.000Z",
"email": "bagus@desa.id",
"telepon": "081234567891",
"alamat": "Jl. Raya Desa No. 1",
"posisiId": "kepala_desa",
"isActive": true
},
{
"id": "cmgewxfvw000004ibee5013f4",
"namaLengkap": "I Ketut Suwanta",
"gelarAkademik": "S.Pt",
"tanggalMasuk": "2020-02-01T00:00:00.000Z",
"email": "suwanta@desa.id",
"telepon": "081234567892",
"alamat": "Jl. Raya Desa No. 2",
"posisiId": "sekretaris_desa",
"isActive": true
},
{
"id": "cmgewxvqw000104ibgm5l8fzs",
"namaLengkap": "Ni Wayan Supardiati",
"gelarAkademik": "S.Pd",
"tanggalMasuk": "2020-02-01T00:00:00.000Z",
"email": "supardiati@desa.id",
"telepon": "081234567892",
"alamat": "Jl. Raya Desa No. 2",
"posisiId": "kaur_keuangan",
"isActive": true
},
{
"id": "cmgewy1g9000204ib2n7hbx0i",
"namaLengkap": "I Wayan Agus Juni Artha Saputra",
"gelarAkademik": "S.T.",
"tanggalMasuk": "2020-02-01T00:00:00.000Z",
"email": "agus@desa.id",
"telepon": "081234567892",
"alamat": "Jl. Raya Desa No. 2",
"posisiId": "kadus_banjar_dinas_menesa",
"isActive": true
},
{
"id": "cmgewybah000304ibgqhn1gm2",
"namaLengkap": "I Wayan Sueca",
"gelarAkademik": "S.H.",
"tanggalMasuk": "2020-02-01T00:00:00.000Z",
"email": "sueca@desa.id",
"telepon": "081234567893",
"alamat": "Jl. Raya Desa No. 2",
"posisiId": "kadus_banjar_dinas_darmasaba",
"isActive": true
},
{
"id": "cmgewygqz000404ib20sv8nvg",
"namaLengkap": "Si Gede Ketut Astawa",
"gelarAkademik": "S.T.",
"tanggalMasuk": "2020-02-01T00:00:00.000Z",
"email": "astawa@desa.id",
"telepon": "081234567893",
"alamat": "Jl. Raya Desa No. 2",
"posisiId": "kadus_banjar_dinas_bucu",
"isActive": true
},
{
"id": "cmgewyos1000504ibcu8o2gyk",
"namaLengkap": "I Kadek Arya Minarta",
"gelarAkademik": "S.T.",
"tanggalMasuk": "2020-02-01T00:00:00.000Z",
"email": "minarta@desa.id",
"telepon": "081234567893",
"alamat": "Jl. Raya Desa No. 2",
"posisiId": "kadus_banjar_dinas_gulingan",
"isActive": true
},
{
"id": "cmgewyxk7000604ib8djs3i6c",
"namaLengkap": "I Gede Andika Pradnya Diputra",
"gelarAkademik": "S.E.",
"tanggalMasuk": "2020-02-01T00:00:00.000Z",
"email": "diputra@desa.id",
"telepon": "081234567893",
"alamat": "Jl. Raya Desa No. 2",
"posisiId": "kadus_banjar_dinas_taman",
"isActive": true
}
]

View File

@@ -1,24 +0,0 @@
[
{
"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,159 @@
[
[
{
"id": "kepala_desa",
"nama": "Kepala Desa",
"deskripsi": "Pemimpin desa Darmasaba",
"hierarki": 1,
"parentId": null
},
{
"id": "kepala_urusan",
"nama": "Kepala Urusan",
"deskripsi": "Pemimpin urusan desa Darmasaba",
"hierarki": 2,
"parentId": "kepala_desa"
},
{
"id": "sekretaris_desa",
"nama": "Sekretaris Desa",
"deskripsi": "Pengelola administrasi desa",
"hierarki": 2,
"parentId": "kepala_desa"
},
{
"id": "kaur_keuangan",
"nama": "Kaur Keuangan",
"deskripsi": "Pengelola keuangan desa",
"hierarki": 3,
"parentId": "kaur_umum"
},
{
"id": "kaur_perencanaan",
"nama": "Kaur Perencanaan",
"deskripsi": "Penyusun program kerja desa",
"hierarki": 3,
"parentId": "kaur_umum"
},
{
"id": "kaur_umum",
"nama": "Kaur Umum & TU",
"deskripsi": "Pelayanan umum dan administrasi",
"hierarki": 2,
"parentId": "kepala_desa"
},
{
"id": "kasi_pemerintahan",
"nama": "Kasi Pemerintahan",
"deskripsi": "Urusan pemerintahan dan keamanan",
"hierarki": 2,
"parentId": "kepala_desa"
},
{
"id": "kasi_pelayanan",
"nama": "Kasi Pelayanan",
"deskripsi": "Urusan pelayanan masyarakat",
"hierarki": 2,
"parentId": "kepala_desa"
},
{
"id": "kasi_kesejahteraan",
"nama": "Kasi Kesejahteraan",
"deskripsi": "Urusan sosial dan kesejahteraan",
"hierarki": 2,
"parentId": "kepala_desa"
},
{
"id": "kadus_banjar_dinas_cabe",
"nama": "Kepala Dusun Banjar Dinas Cabe",
"deskripsi": "Pimpinan wilayah Banjar Dinas Cabe",
"hierarki": 3,
"parentId": "sekretaris_desa"
},
{
"id": "kadus_banjar_dinas_menesa",
"nama": "Kepala Dusun Banjar Dinas Menesa",
"deskripsi": "Pimpinan wilayah Banjar Menesa",
"hierarki": 3,
"parentId": "sekretaris_desa"
},
{
"id": "kadus_banjar_dinas_penenjoan",
"nama": "Kepala Dusun Banjar Dinas Penenjoan",
"deskripsi": "Pimpinan wilayah Banjar Dinas Penenjoan",
"hierarki": 3,
"parentId": "sekretaris_desa"
},
{
"id": "kadus_banjar_dinas_telanga",
"nama": "Kepala Dusun Banjar Dinas Telanga",
"deskripsi": "Pimpinan wilayah Banjar Dinas Telanga",
"hierarki": 3,
"parentId": "sekretaris_desa"
},
{
"id": "kadus_banjar_dinas_tengah",
"nama": "Kepala Dusun Banjar Dinas Tengah",
"deskripsi": "Pimpinan wilayah Banjar Dinas Tengah",
"hierarki": 3,
"parentId": "sekretaris_desa"
},
{
"id": "kadus_banjar_dinas_baler_pasar",
"nama": "Kepala Dusun Banjar Dinas Baler Pasar",
"deskripsi": "Pimpinan wilayah Banjar Dinas Baler Pasar",
"hierarki": 3,
"parentId": "sekretaris_desa"
},
{
"id": "kadus_banjar_dinas_bucu",
"nama": "Kepala Dusun Banjar Dinas Bucu",
"deskripsi": "Pimpinan wilayah Banjar Dinas Bucu",
"hierarki": 3,
"parentId": "sekretaris_desa"
},
{
"id": "kadus_banjar_dinas_gulingan",
"nama": "Kepala Dusun Banjar Dinas Gulingan",
"deskripsi": "Pimpinan wilayah Banjar Dinas Gulingan",
"hierarki": 3,
"parentId": "sekretaris_desa"
},
{
"id": "kadus_banjar_dinas_bersih",
"nama": "Kepala Dusun Banjar Dinas Bersih",
"deskripsi": "Pimpinan wilayah Banjar Dinas Bersih",
"hierarki": 3,
"parentId": "sekretaris_desa"
},
{
"id": "kadus_banjar_dinas_umahanyar",
"nama": "Kepala Dusun Banjar Dinas Umahanyar",
"deskripsi": "Pimpinan wilayah Banjar Dinas Umahanyar",
"hierarki": 3,
"parentId": "sekretaris_desa"
},
{
"id": "kadus_banjar_dinas_taman",
"nama": "Kepala Dusun Banjar Dinas Taman",
"deskripsi": "Pimpinan wilayah Banjar Dinas Taman",
"hierarki": 3,
"parentId": "sekretaris_desa"
},
{
"id": "kadus_banjar_dinas_darmasaba",
"nama": "Kepala Dusun Banjar Dinas Darmasaba",
"deskripsi": "Pimpinan wilayah Banjar Dinas Darmasaba",
"hierarki": 3,
"parentId": "sekretaris_desa"
},
{
"id": "staf_desa",
"nama": "Staf Desa",
"deskripsi": "Staf Desa",
"hierarki": 3,
"parentId": "sekretaris_desa"
}
]
]

View File

@@ -1,27 +0,0 @@
[
{
"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

@@ -0,0 +1,137 @@
[
{
"id": "cmff0rr4z0002vn0twp333m2",
"name": "S6RIjFaPvdQm3oq4rM4X9-desktop.webp",
"realName": "bares.png",
"path": "uploads/images",
"mimeType": "image/webp",
"link": "/api/fileStorage/findUnique/S6RIjFaPvdQm3oq4rM4X9-desktop.webp",
"category": "image"
},
{
"id": "cmff0tnf00003vn0t3kgzi0u0",
"name": "_pVNEmThU5ICGa8gv3gh_-desktop.webp",
"realName": "bicara-darma.png",
"path": "uploads/images",
"mimeType": "image/webp",
"link": "/api/fileStorage/findUnique/_pVNEmThU5ICGa8gv3gh_-desktop.webp",
"category": "image"
},
{
"id": "cmff0uykf0004vn0trmmxpgfh",
"name": "bv6rdKvjxkkjUSGLQ0lvB-desktop.webp",
"realName": "daves.png",
"path": "uploads/images",
"mimeType": "image/webp",
"link": "/api/fileStorage/findUnique/bv6rdKvjxkkjUSGLQ0lvB-desktop.webp",
"category": "image"
},
{
"id": "cmff0z34f0005vn0tjtvq519p",
"name": "Z4hWaV04CvoE20MjccQsV-desktop.webp",
"realName": "mangan.png",
"path": "uploads/images",
"mimeType": "image/webp",
"link": "/api/fileStorage/findUnique/Z4hWaV04CvoE20MjccQsV-desktop.webp",
"category": "image"
},
{
"id": "cmff38cyq000bvn0t9f01cz3f",
"name": "LvLAtOqWojx4sn6NjJWB9-desktop.webp",
"realName": "gelah-melah.png",
"path": "uploads/images",
"mimeType": "image/webp",
"link": "/api/fileStorage/findUnique/LvLAtOqWojx4sn6NjJWB9-desktop.webp",
"category": "image"
},
{
"id": "cmff0zqvd0007vn0tv6o5hjcq",
"name": "gR2mcvAQVgJ2-rM5coYJj-desktop.webp",
"realName": "inovasi-desa-darmasaba.png",
"path": "uploads/images",
"mimeType": "image/webp",
"link": "/api/fileStorage/findUnique/gR2mcvAQVgJ2-rM5coYJj-desktop.webp",
"category": "image"
},
{
"id": "cmff1013m0008vn0th7t0d64d",
"name": "JpL-9F8-IGztMn8E2ce02-desktop.webp",
"realName": "pdkt.png",
"path": "uploads/images",
"mimeType": "image/webp",
"link": "/api/fileStorage/findUnique/JpL-9F8-IGztMn8E2ce02-desktop.webp",
"category": "image"
},
{
"id": "cmff10cwq0009vn0tse8dzu3j",
"name": "bxAk4AsGbJTC705_IVdes-desktop.webp",
"realName": "sajjiana-dharma-raksaka.png",
"path": "uploads/images",
"mimeType": "image/webp",
"link": "/api/fileStorage/findUnique/bxAk4AsGbJTC705_IVdes-desktop.webp",
"category": "image"
},
{
"id": "cmff2w5ly000avn0telhct71k",
"name": "Vbj_osnMJUkGEQGDTLwV--desktop.webp",
"realName": "perbekel.png",
"path": "uploads/images",
"mimeType": "image/webp",
"link": "/api/fileStorage/findUnique/Vbj_osnMJUkGEQGDTLwV--desktop.webp",
"category": "image"
},
{
"id": "cmff3joae0000vn6h8sgs0ilg",
"name": "7hox9spUxj56hY_EBYLnj-desktop.webp",
"realName": "youtube.png",
"path": "uploads/images",
"mimeType": "image/webp",
"link": "/api/fileStorage/findUnique/7hox9spUxj56hY_EBYLnj-desktop.webp",
"category": "image"
},
{
"id": "cmff3ll130001vn6hkhls3f5y",
"name": "ChihV7_1eS-AGtSg9UwMv-desktop.webp",
"realName": "gmail.png",
"path": "uploads/images",
"mimeType": "image/webp",
"link": "/api/fileStorage/findUnique/ChihV7_1eS-AGtSg9UwMv-desktop.webp",
"category": "image"
},
{
"id": "cmff3mtat0002vn6hs8vyyhdd",
"name": "z8v9ZREwOJHKGIRYauROt-desktop.webp",
"realName": "facebook.png",
"path": "uploads/images",
"mimeType": "image/webp",
"link": "/api/fileStorage/findUnique/z8v9ZREwOJHKGIRYauROt-desktop.webp",
"category": "image"
},
{
"id": "cmff3nv180003vn6h5jvedidq",
"name": "BLjMxTKoCNE31uOURR3IU-desktop.webp",
"realName": "telephone-call.png",
"path": "uploads/images",
"mimeType": "image/webp",
"link": "/api/fileStorage/findUnique/BLjMxTKoCNE31uOURR3IU-desktop.webp",
"category": "image"
},
{
"id": "cmff3oouh0004vn6hd94brzv9",
"name": "hkJYAeTNWK_vYaYS20w3I-desktop.webp",
"realName": "instagram.png",
"path": "uploads/images",
"mimeType": "image/webp",
"link": "/api/fileStorage/findUnique/hkJYAeTNWK_vYaYS20w3I-desktop.webp",
"category": "image"
},
{
"id": "cmff3q12g0005vn6h5ojov2qa",
"name": "6XEoZ9SFu59COpil03Gya-desktop.webp",
"realName": "tiktok.png",
"path": "uploads/images",
"mimeType": "image/webp",
"link": "/api/fileStorage/findUnique/6XEoZ9SFu59COpil03Gya-desktop.webp",
"category": "image"
}
]

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,6 @@
[
{ "nama": "Kebersihan" },
{ "nama": "Infrastruktur" },
{ "nama": "Sosial" },
{ "nama": "Lingkungan" }
]

View File

@@ -0,0 +1,9 @@
[
{ "id": "cmghqwjs4000404l8c5uvc300", "nama": "PAUD" },
{ "id": "cmghqwjs4000404l8c5uvc301", "nama": "TK" },
{ "id": "cmghqwjs4000404l8c5uvc302", "nama": "SD" },
{ "id": "cmghqwjs4000404l8c5uvc303", "nama": "SMP" },
{ "id": "cmghqwjs4000404l8c5uvc304", "nama": "SMA" },
{ "id": "cmghqwjs4000404l8c5uvc305", "nama": "SMK" }
]

View File

@@ -1,6 +1,6 @@
[ [
{ {
"id": "550e8400-e29b-41d4-a716-446655440001", "id": "cmgewz4gt000704ib91i3f169",
"namaLengkap": "Ida Bagus Surya Prabhawa Manuaba, S.H.,M.H., NL.P.", "namaLengkap": "Ida Bagus Surya Prabhawa Manuaba, S.H.,M.H., NL.P.",
"gelarAkademik": "S.H.,M.H.,NL.P.", "gelarAkademik": "S.H.,M.H.,NL.P.",
"tanggalMasuk": "2020-01-01T00:00:00.000Z", "tanggalMasuk": "2020-01-01T00:00:00.000Z",
@@ -11,7 +11,7 @@
"isActive": true "isActive": true
}, },
{ {
"id": "550e8400-e29b-41d4-a716-446655440002", "id": "cmgewxfvw000004ibee5013f4",
"namaLengkap": "I Ketut Suwanta", "namaLengkap": "I Ketut Suwanta",
"gelarAkademik": "S.Pt", "gelarAkademik": "S.Pt",
"tanggalMasuk": "2020-02-01T00:00:00.000Z", "tanggalMasuk": "2020-02-01T00:00:00.000Z",
@@ -22,7 +22,7 @@
"isActive": true "isActive": true
}, },
{ {
"id": "550e8400-e29b-41d4-a716-446655440006", "id": "cmgewxvqw000104ibgm5l8fzs",
"namaLengkap": "Ni Wayan Supardiati", "namaLengkap": "Ni Wayan Supardiati",
"gelarAkademik": "S.Pd", "gelarAkademik": "S.Pd",
"tanggalMasuk": "2020-02-01T00:00:00.000Z", "tanggalMasuk": "2020-02-01T00:00:00.000Z",
@@ -33,7 +33,7 @@
"isActive": true "isActive": true
}, },
{ {
"id": "550e8400-e29b-41d4-a716-446655440011", "id": "cmgewy1g9000204ib2n7hbx0i",
"namaLengkap": "I Wayan Agus Juni Artha Saputra", "namaLengkap": "I Wayan Agus Juni Artha Saputra",
"gelarAkademik": "S.T.", "gelarAkademik": "S.T.",
"tanggalMasuk": "2020-02-01T00:00:00.000Z", "tanggalMasuk": "2020-02-01T00:00:00.000Z",
@@ -44,7 +44,7 @@
"isActive": true "isActive": true
}, },
{ {
"id": "550e8400-e29b-41d4-a716-446655440012", "id": "cmgewybah000304ibgqhn1gm2",
"namaLengkap": "I Wayan Sueca", "namaLengkap": "I Wayan Sueca",
"gelarAkademik": "S.H.", "gelarAkademik": "S.H.",
"tanggalMasuk": "2020-02-01T00:00:00.000Z", "tanggalMasuk": "2020-02-01T00:00:00.000Z",
@@ -55,7 +55,7 @@
"isActive": true "isActive": true
}, },
{ {
"id": "550e8400-e29b-41d4-a716-446655440017", "id": "cmgewygqz000404ib20sv8nvg",
"namaLengkap": "Si Gede Ketut Astawa", "namaLengkap": "Si Gede Ketut Astawa",
"gelarAkademik": "S.T.", "gelarAkademik": "S.T.",
"tanggalMasuk": "2020-02-01T00:00:00.000Z", "tanggalMasuk": "2020-02-01T00:00:00.000Z",
@@ -66,7 +66,7 @@
"isActive": true "isActive": true
}, },
{ {
"id": "550e8400-e29b-41d4-a716-446655440018", "id": "cmgewyos1000504ibcu8o2gyk",
"namaLengkap": "I Kadek Arya Minarta", "namaLengkap": "I Kadek Arya Minarta",
"gelarAkademik": "S.T.", "gelarAkademik": "S.T.",
"tanggalMasuk": "2020-02-01T00:00:00.000Z", "tanggalMasuk": "2020-02-01T00:00:00.000Z",
@@ -77,7 +77,7 @@
"isActive": true "isActive": true
}, },
{ {
"id": "550e8400-e29b-41d4-a716-446655440021", "id": "cmgewyxk7000604ib8djs3i6c",
"namaLengkap": "I Gede Andika Pradnya Diputra", "namaLengkap": "I Gede Andika Pradnya Diputra",
"gelarAkademik": "S.E.", "gelarAkademik": "S.E.",
"tanggalMasuk": "2020-02-01T00:00:00.000Z", "tanggalMasuk": "2020-02-01T00:00:00.000Z",

View File

@@ -1,29 +1,32 @@
[ [
{ {
"id": "role_admin_desa", "id": "0",
"name": "ADMIN_DESA", "name": "DEVELOPER",
"description": "Administrator Desa", "description": "Developer",
"permissions": ["manage_users", "manage_content", "view_reports"], "isActive": true
"isActive": true, },
"createdAt": "2025-09-01T00:00:00.000Z", {
"updatedAt": "2025-09-01T00:00:00.000Z" "id": "1",
}, "name": "SUPER ADMIN",
{ "description": "Administrator",
"id": "role_admin_kesehatan", "isActive": true
"name": "ADMIN_KESEHATAN", },
"description": "Administrator Bidang Kesehatan", {
"permissions": ["manage_health_data", "view_reports"], "id": "2",
"isActive": true, "name": "ADMIN DESA",
"createdAt": "2025-09-01T00:00:00.000Z", "description": "Administrator Desa",
"updatedAt": "2025-09-01T00:00:00.000Z" "isActive": true
}, },
{ {
"id": "role_admin_sekolah", "id": "3",
"name": "ADMIN_SEKOLAH", "name": "ADMIN KESEHATAN",
"description": "Administrator Sekolah", "description": "Administrator Bidang Kesehatan",
"permissions": ["manage_school_data", "view_reports"], "isActive": true
"isActive": true, },
"createdAt": "2025-09-01T00:00:00.000Z", {
"updatedAt": "2025-09-01T00:00:00.000Z" "id": "4",
} "name": "ADMIN PENDIDIKAN",
] "description": "Administrator Bidang Pendidikan",
"isActive": true
}
]

View File

@@ -1,35 +1,10 @@
[ [
{ {
"id": "user_admin_desa", "id": "cmie1o0zh0002vn132vtzg7hh",
"nama": "Admin Desa", "username": "SuperAdmin-Nico",
"email": "admin.desa@example.com", "nomor": "6289647037426",
"password": "$2b$10$XFDWYOJFxQ7ZM5bA0N4Z0O8u0eKYv58wLsaR7h6XK9bqWJ1YQJQ9q", "roleId": 0,
"roleId": "role_admin_desa", "isActive": true,
"isActive": true, "sessionInvalid": false
"lastLogin": "2025-08-31T10:00:00.000Z", }
"createdAt": "2025-09-01T00:00:00.000Z", ]
"updatedAt": "2025-09-01T00:00:00.000Z"
},
{
"id": "user_admin_puskesmas",
"nama": "Admin Kesehatan",
"email": "admin.kesehatan@example.com",
"password": "$2b$10$XFDWYOJFxQ7ZM5bA0N4Z0O8u0eKYv58wLsaR7h6XK9bqWJ1YQJQ9q",
"roleId": "role_admin_kesehatan",
"isActive": true,
"lastLogin": null,
"createdAt": "2025-09-01T00:00:00.000Z",
"updatedAt": "2025-09-01T00:00:00.000Z"
},
{
"id": "user_admin_sekolah",
"nama": "Admin Sekolah",
"email": "admin.sekolah@example.com",
"password": "$2b$10$XFDWYOJFxQ7ZM5bA0N4Z0O8u0eKYv58wLsaR7h6XK9bqWJ1YQJQ9q",
"roleId": "role_admin_sekolah",
"isActive": true,
"lastLogin": null,
"createdAt": "2025-09-01T00:00:00.000Z",
"updatedAt": "2025-09-01T00:00:00.000Z"
}
]

File diff suppressed because it is too large Load Diff

30
prisma/safeseedUnique.ts Normal file
View File

@@ -0,0 +1,30 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
// helpers/safeSeedUnique.ts
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
/**
* Helper generic buat seed dengan upsert aman
*/
export async function safeSeedUnique<T extends keyof PrismaClient>(
model: T,
where: Record<string, any>,
data: Record<string, any>
) {
const m = prisma[model];
if (!m) throw new Error(`Model ${String(model)} tidak ditemukan di PrismaClient`);
try {
// @ts-expect-error upsert dynamic
await m.upsert({
where,
update: data,
create: { ...where, ...data },
});
console.log(`✅ Seeded ${String(model)} -> ${JSON.stringify(where)}`);
} catch (err) {
console.error(`❌ Gagal seed ${String(model)} -> ${JSON.stringify(where)}`, err);
}
}

View File

@@ -81,9 +81,7 @@ model FileStorage {
PelayananSuratKeteranganImage PelayananSuratKeterangan[] @relation("PelayananSuratKeteranganImage") PelayananSuratKeteranganImage PelayananSuratKeterangan[] @relation("PelayananSuratKeteranganImage")
PelayananSuratKeteranganImage2 PelayananSuratKeterangan[] @relation("PelayananSuratKeteranganImage2") PelayananSuratKeteranganImage2 PelayananSuratKeterangan[] @relation("PelayananSuratKeteranganImage2")
PasarDesa PasarDesa[] PasarDesa PasarDesa[]
KontakDaruratKeamanan KontakDaruratKeamanan[] PegawaiBumDes PegawaiBumDes[]
KontakItem KontakItem[]
Pegawai Pegawai[]
DesaDigital DesaDigital[] DesaDigital DesaDigital[]
InfoTekno InfoTekno[] InfoTekno InfoTekno[]
PengaduanMasyarakat PengaduanMasyarakat[] PengaduanMasyarakat PengaduanMasyarakat[]
@@ -92,7 +90,7 @@ model FileStorage {
PejabatDesa PejabatDesa[] PejabatDesa PejabatDesa[]
MediaSosial MediaSosial[] MediaSosial MediaSosial[]
DesaAntiKorupsi DesaAntiKorupsi[] DesaAntiKorupsi DesaAntiKorupsi[]
SDGSDesa SDGSDesa[] SDGSDesa SdgsDesa[]
APBDesImage APBDes[] @relation("APBDesImage") APBDesImage APBDes[] @relation("APBDesImage")
APBDesFile APBDes[] @relation("APBDesFile") APBDesFile APBDes[] @relation("APBDesFile")
PrestasiDesa PrestasiDesa[] PrestasiDesa PrestasiDesa[]
@@ -101,6 +99,9 @@ model FileStorage {
PerbekelDariMasaKeMasa PerbekelDariMasaKeMasa[] PerbekelDariMasaKeMasa PerbekelDariMasaKeMasa[]
MitraKolaborasi MitraKolaborasi[] MitraKolaborasi MitraKolaborasi[]
ArtikelKesehatan ArtikelKesehatan[]
StrukturBumDes StrukturBumDes[]
} }
//========================================= MENU LANDING PAGE ========================================= // //========================================= MENU LANDING PAGE ========================================= //
@@ -135,6 +136,7 @@ model MediaSosial {
name String name String
image FileStorage? @relation(fields: [imageId], references: [id]) image FileStorage? @relation(fields: [imageId], references: [id])
imageId String? imageId String?
icon String?
iconUrl String? @db.VarChar(255) iconUrl String? @db.VarChar(255)
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
@@ -142,7 +144,7 @@ model MediaSosial {
isActive Boolean @default(true) isActive Boolean @default(true)
} }
//========================================= PROFILE ========================================= // //========================================= DESA ANTI KORUPSI ========================================= //
model DesaAntiKorupsi { model DesaAntiKorupsi {
id String @id @default(cuid()) id String @id @default(cuid())
name String @unique name String @unique
@@ -168,9 +170,9 @@ model KategoriDesaAntiKorupsi {
} }
//========================================= SDGS Desa ========================================= // //========================================= SDGS Desa ========================================= //
model SDGSDesa { model SdgsDesa {
id String @id @default(cuid()) id String @id @default(cuid())
name String @unique name String
jumlah String jumlah String
image FileStorage? @relation(fields: [imageId], references: [id]) image FileStorage? @relation(fields: [imageId], references: [id])
imageId String? imageId String?
@@ -183,22 +185,50 @@ model SDGSDesa {
//========================================= APBDes ========================================= // //========================================= APBDes ========================================= //
model APBDes { model APBDes {
id String @id @default(cuid()) id String @id @default(cuid())
name String @unique tahun Int?
jumlah String name String? // misalnya: "APBDes Tahun 2025"
deskripsi String?
jumlah String? // total keseluruhan (opsional, bisa juga dihitung dari items)
items APBDesItem[]
image FileStorage? @relation("APBDesImage", fields: [imageId], references: [id]) image FileStorage? @relation("APBDesImage", fields: [imageId], references: [id])
imageId String? imageId String?
file FileStorage? @relation("APBDesFile", fields: [fileId], references: [id]) file FileStorage? @relation("APBDesFile", fields: [fileId], references: [id])
fileId String? fileId String?
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
deletedAt DateTime @default(now()) deletedAt DateTime? // opsional, tidak perlu default now()
isActive Boolean @default(true) isActive Boolean @default(true)
} }
model APBDesItem {
id String @id @default(cuid())
kode String // contoh: "4", "4.1", "4.1.2"
uraian String // nama item, contoh: "Pendapatan Asli Desa", "Hasil Usaha"
anggaran Float // dalam satuan Rupiah (bisa DECIMAL di DB, tapi Float umum di TS/JS)
realisasi Float
selisih Float // realisasi - anggaran
persentase Float
tipe String? // (realisasi / anggaran) * 100
level Int // 1 = kelompok utama, 2 = sub-kelompok, 3 = detail
parentId String? // untuk relasi hierarki
parent APBDesItem? @relation("APBDesItemParent", fields: [parentId], references: [id])
children APBDesItem[] @relation("APBDesItemParent")
apbdesId String
apbdes APBDes @relation(fields: [apbdesId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
isActive Boolean @default(true)
@@index([kode])
@@index([level])
@@index([apbdesId])
}
//========================================= PRESTASI DESA ========================================= // //========================================= PRESTASI DESA ========================================= //
model PrestasiDesa { model PrestasiDesa {
id String @id @default(cuid()) id String @id @default(cuid())
name String @unique name String
deskripsi String @db.Text deskripsi String @db.Text
kategori KategoriPrestasiDesa @relation(fields: [kategoriId], references: [id]) kategori KategoriPrestasiDesa @relation(fields: [kategoriId], references: [id])
kategoriId String kategoriId String
@@ -286,49 +316,51 @@ model StrukturPPID {
} }
model PosisiOrganisasiPPID { model PosisiOrganisasiPPID {
id String @id @default(cuid()) id String @id @default(cuid())
nama String @db.VarChar(100) nama String @db.VarChar(100)
deskripsi String? @db.Text deskripsi String? @db.Text
hierarki Int hierarki Int
pegawai PegawaiPPID[] pegawai PegawaiPPID[]
strukturOrganisasi StrukturPPID[] // Relasi balik strukturOrganisasi StrukturPPID[] // Relasi balik
parentId String? parentId String?
isActive Boolean @default(true) isActive Boolean @default(true)
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
parent PosisiOrganisasiPPID? @relation("Parent", fields: [parentId], references: [id]) parent PosisiOrganisasiPPID? @relation("Parent", fields: [parentId], references: [id])
children PosisiOrganisasiPPID[] @relation("Parent") children PosisiOrganisasiPPID[] @relation("Parent")
StrukturOrganisasiPPID StrukturOrganisasiPPID[]
} }
model PegawaiPPID { model PegawaiPPID {
id String @id @default(cuid()) id String @id @default(cuid())
namaLengkap String @db.VarChar(255) namaLengkap String @db.VarChar(255)
gelarAkademik String? @db.VarChar(100) gelarAkademik String? @db.VarChar(100)
image FileStorage? @relation(fields: [imageId], references: [id]) image FileStorage? @relation(fields: [imageId], references: [id])
imageId String? imageId String?
tanggalMasuk DateTime? @db.Date tanggalMasuk DateTime? @db.Date
email String? @unique @db.VarChar(255) email String? @unique @db.VarChar(255)
telepon String? @db.VarChar(20) telepon String? @db.VarChar(20)
alamat String? @db.Text alamat String? @db.Text
posisiId String @db.VarChar(50) posisiId String @db.VarChar(50)
isActive Boolean @default(true) isActive Boolean @default(true)
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
posisi PosisiOrganisasiPPID @relation(fields: [posisiId], references: [id]) posisi PosisiOrganisasiPPID @relation(fields: [posisiId], references: [id])
strukturOrganisasi StrukturPPID[] // Relasi balik strukturOrganisasi StrukturPPID[] // Relasi balik
StrukturOrganisasiPPID StrukturOrganisasiPPID[]
} }
model StrukturOrganisasiPPID { model StrukturOrganisasiPPID {
id String @id @default(uuid()) id String @id @default(uuid())
posisiOrganisasiId String @db.VarChar(50) posisiOrganisasiId String @db.VarChar(50)
pegawaiId String @db.Uuid pegawaiId String
hubunganOrganisasiId String @db.Uuid hubunganOrganisasiId String
posisiOrganisasi PosisiOrganisasi @relation(fields: [posisiOrganisasiId], references: [id]) posisiOrganisasi PosisiOrganisasiPPID @relation(fields: [posisiOrganisasiId], references: [id])
pegawai Pegawai @relation(fields: [pegawaiId], references: [id]) pegawai PegawaiPPID @relation(fields: [pegawaiId], references: [id])
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
deletedAt DateTime? deletedAt DateTime?
isActive Boolean @default(true) isActive Boolean @default(true)
} }
// ========================================= VISI MISI PPID ========================================= // // ========================================= VISI MISI PPID ========================================= //
@@ -672,17 +704,18 @@ model GalleryVideo {
// ========================================= LAYANAN DESA ========================================= // // ========================================= LAYANAN DESA ========================================= //
model PelayananSuratKeterangan { model PelayananSuratKeterangan {
id String @id @default(cuid()) id String @id @default(cuid())
name String name String
deskripsi String @db.Text deskripsi String @db.Text
image FileStorage? @relation("PelayananSuratKeteranganImage", fields: [imageId], references: [id]) image FileStorage? @relation("PelayananSuratKeteranganImage", fields: [imageId], references: [id])
imageId String? imageId String?
image2 FileStorage? @relation("PelayananSuratKeteranganImage2", fields: [image2Id], references: [id]) image2 FileStorage? @relation("PelayananSuratKeteranganImage2", fields: [image2Id], references: [id])
image2Id String? image2Id String?
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
deletedAt DateTime @default(now()) deletedAt DateTime @default(now())
isActive Boolean @default(true) isActive Boolean @default(true)
AjukanPermohonan AjukanPermohonan[]
} }
model PelayananTelunjukSaktiDesa { model PelayananTelunjukSaktiDesa {
@@ -717,6 +750,20 @@ model PelayananPendudukNonPermanen {
isActive Boolean @default(true) isActive Boolean @default(true)
} }
model AjukanPermohonan {
id String @id @default(cuid())
nama String
nik String
alamat String
nomorKk String
kategori PelayananSuratKeterangan @relation(fields: [kategoriId], references: [id])
kategoriId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
// ========================================= PENGHARGAAN ========================================= // // ========================================= PENGHARGAAN ========================================= //
model Penghargaan { model Penghargaan {
id String @id @default(cuid()) id String @id @default(cuid())
@@ -736,24 +783,22 @@ model Penghargaan {
// ========================================= FASILITAS KESEHATAN ========================================= // // ========================================= FASILITAS KESEHATAN ========================================= //
model FasilitasKesehatan { model FasilitasKesehatan {
id String @id @default(cuid()) id String @id @default(cuid())
name String name String
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
deletedAt DateTime @default(now()) deletedAt DateTime @default(now())
isActive Boolean @default(true) isActive Boolean @default(true)
informasiumum InformasiUmum @relation(fields: [informasiUmumId], references: [id]) informasiumum InformasiUmum @relation(fields: [informasiUmumId], references: [id])
informasiUmumId String informasiUmumId String
layananunggulan LayananUnggulan @relation(fields: [layananUnggulanId], references: [id]) layananunggulan LayananUnggulan @relation(fields: [layananUnggulanId], references: [id])
layananUnggulanId String layananUnggulanId String
dokterdantenagamedis DokterdanTenagaMedis @relation(fields: [dokterdanTenagaMedisId], references: [id]) dokterdantenagamedis DokterdanTenagaMedis[] @relation("Dokter")
dokterdanTenagaMedisId String fasilitaspendukung FasilitasPendukung @relation(fields: [fasilitasPendukungId], references: [id])
fasilitaspendukung FasilitasPendukung @relation(fields: [fasilitasPendukungId], references: [id]) fasilitasPendukungId String
fasilitasPendukungId String prosedurpendaftaran ProsedurPendaftaran @relation(fields: [prosedurPendaftaranId], references: [id])
prosedurpendaftaran ProsedurPendaftaran @relation(fields: [prosedurPendaftaranId], references: [id]) prosedurPendaftaranId String
prosedurPendaftaranId String tarifdanlayanan TarifDanLayanan[] @relation("Tarif")
tarifdanlayanan TarifDanLayanan @relation(fields: [tarifDanLayananId], references: [id])
tarifDanLayananId String
} }
model InformasiUmum { model InformasiUmum {
@@ -779,15 +824,20 @@ model LayananUnggulan {
} }
model DokterdanTenagaMedis { model DokterdanTenagaMedis {
id String @id @default(cuid()) id String @id @default(cuid())
name String name String
specialist String specialist String
jadwal String jadwal String
createdAt DateTime @default(now()) jadwalLibur String?
updatedAt DateTime @updatedAt jamBukaOperasional String?
deletedAt DateTime @default(now()) jamTutupOperasional String?
isActive Boolean @default(true) jamBukaLibur String?
FasilitasKesehatan FasilitasKesehatan[] jamTutupLibur String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
FasilitasKesehatan FasilitasKesehatan[] @relation("Dokter")
} }
model FasilitasPendukung { model FasilitasPendukung {
@@ -818,7 +868,7 @@ model TarifDanLayanan {
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
deletedAt DateTime @default(now()) deletedAt DateTime @default(now())
isActive Boolean @default(true) isActive Boolean @default(true)
FasilitasKesehatan FasilitasKesehatan[] FasilitasKesehatan FasilitasKesehatan[] @relation("Tarif")
} }
// ========================================= JADWAL KEGIATAN ========================================= // // ========================================= JADWAL KEGIATAN ========================================= //
@@ -835,8 +885,8 @@ model JadwalKegiatan {
syaratKetentuanJadwalKegiatanId String syaratKetentuanJadwalKegiatanId String
dokumenjadwalkegiatan DokumenJadwalKegiatan @relation(fields: [dokumenJadwalKegiatanId], references: [id]) dokumenjadwalkegiatan DokumenJadwalKegiatan @relation(fields: [dokumenJadwalKegiatanId], references: [id])
dokumenJadwalKegiatanId String dokumenJadwalKegiatanId String
pendaftaranjadwalkegiatan PendaftaranJadwalKegiatan @relation(fields: [pendaftaranJadwalKegiatanId], references: [id]) pendaftaranjadwalkegiatan PendaftaranJadwalKegiatan? @relation(fields: [pendaftaranJadwalKegiatanId], references: [id])
pendaftaranJadwalKegiatanId String pendaftaranJadwalKegiatanId String?
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
deletedAt DateTime @default(now()) deletedAt DateTime @default(now())
@@ -972,8 +1022,10 @@ model ArtikelKesehatan {
id String @id @default(cuid()) id String @id @default(cuid())
title String title String
content String content String
introduction Introduction @relation(fields: [introductionId], references: [id]) image FileStorage? @relation(fields: [imageId], references: [id])
imageId String?
introductionId String introductionId String
introduction Introduction @relation(fields: [introductionId], references: [id])
symptom Symptom @relation(fields: [symptomId], references: [id]) symptom Symptom @relation(fields: [symptomId], references: [id])
symptomId String symptomId String
prevention Prevention @relation(fields: [preventionId], references: [id]) prevention Prevention @relation(fields: [preventionId], references: [id])
@@ -1150,6 +1202,7 @@ model KontakDarurat {
deskripsi String deskripsi String
image FileStorage @relation(fields: [imageId], references: [id]) image FileStorage @relation(fields: [imageId], references: [id])
imageId String imageId String
whatsapp String
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
deletedAt DateTime @default(now()) deletedAt DateTime @default(now())
@@ -1216,71 +1269,51 @@ model LayananPolsek {
// ========================================= KONTAK DARURAT ========================================= // // ========================================= KONTAK DARURAT ========================================= //
model KontakDaruratKeamanan { model KontakDaruratKeamanan {
id String @id @default(uuid()) id String @id @default(uuid())
nama String // contoh: "Layanan Darurat", "Fasilitas Kesehatan" nama String
image FileStorage? @relation(fields: [imageId], references: [id]) icon String
imageId String? kategori KontakItem @relation(fields: [kategoriId], references: [id])
kontakItems KontakItem[] kategoriId String
createdAt DateTime @default(now()) kontakItems KontakDaruratToItem[]
updatedAt DateTime @updatedAt createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
isActive Boolean @default(true)
} }
model KontakItem { model KontakItem {
id String @id @default(uuid()) id String @id @default(uuid())
nama String // contoh: "Polisi", "Ambulans", "Puskesmas Darmasaba" nama String
nomorTelepon String nomorTelepon String
image FileStorage? @relation(fields: [imageId], references: [id]) icon String
imageId String? createdAt DateTime @default(now())
kategori KontakDaruratKeamanan @relation(fields: [kategoriId], references: [id]) updatedAt DateTime @updatedAt
kategoriId String deletedAt DateTime @default(now())
createdAt DateTime @default(now()) isActive Boolean @default(true)
updatedAt DateTime @updatedAt KontakDaruratToItem KontakDaruratToItem[]
KontakDaruratKeamanan KontakDaruratKeamanan[]
}
model KontakDaruratToItem {
id String @id @default(uuid())
kontakDaruratId String
kontakItemId String
kontakDarurat KontakDaruratKeamanan @relation(fields: [kontakDaruratId], references: [id])
kontakItem KontakItem @relation(fields: [kontakItemId], references: [id])
createdAt DateTime @default(now())
} }
// ========================================= PENCEGAHAN KRIMINALITAS ========================================= // // ========================================= PENCEGAHAN KRIMINALITAS ========================================= //
model PencegahanKriminalitas { model PencegahanKriminalitas {
id String @id @default(cuid()) id String @id @default(cuid())
programKeamanan ProgramKeamanan @relation(fields: [programKeamananId], references: [id]) judul String
programKeamananId String deskripsi String
tipsKeamanan TipsKeamanan @relation(fields: [tipsKeamananId], references: [id]) deskripsiSingkat String
tipsKeamananId String linkVideo String
videoKeamanan VideoKeamanan @relation(fields: [videoKeamananId], references: [id]) createdAt DateTime @default(now())
videoKeamananId String updatedAt DateTime @updatedAt
createdAt DateTime @default(now()) deletedAt DateTime @default(now())
updatedAt DateTime @updatedAt isActive Boolean @default(true)
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
model ProgramKeamanan {
id String @id @default(cuid())
nama String // contoh: "Ronda Malam"
deskripsi String? // jika mau tambahkan info detail
slug String @unique
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
PencegahanKriminalitas PencegahanKriminalitas[]
}
model TipsKeamanan {
id String @id @default(cuid())
judul String
konten String
slug String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
PencegahanKriminalitas PencegahanKriminalitas[]
}
model VideoKeamanan {
id String @id @default(cuid())
judul String
deskripsi String?
videoUrl String // link youtube atau embed url
slug String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
PencegahanKriminalitas PencegahanKriminalitas[]
} }
// ========================================= LAPORAN PUBLIK ========================================= // // ========================================= LAPORAN PUBLIK ========================================= //
@@ -1289,11 +1322,13 @@ model LaporanPublik {
judul String judul String
lokasi String lokasi String
tanggalWaktu DateTime tanggalWaktu DateTime
status StatusLaporan status StatusLaporan @default(Proses)
penanganan PenangananLaporanPublik[] penanganan PenangananLaporanPublik[]
kronologi String? // Optional, bisa diisi detail kronologi kronologi String? // Optional, bisa diisi detail kronologi
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
} }
model PenangananLaporanPublik { model PenangananLaporanPublik {
@@ -1341,6 +1376,7 @@ model PasarDesa {
harga Int harga Int
rating Float rating Float
alamatUsaha String alamatUsaha String
kontak String
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
deletedAt DateTime @default(now()) deletedAt DateTime @default(now())
@@ -1383,6 +1419,7 @@ model LowonganPekerjaan {
gaji String gaji String
deskripsi String deskripsi String
kualifikasi String kualifikasi String
notelp String
tanggalPosting DateTime @default(now()) tanggalPosting DateTime @default(now())
isActive Boolean @default(true) isActive Boolean @default(true)
createdAt DateTime @default(now()) createdAt DateTime @default(now())
@@ -1392,76 +1429,67 @@ model LowonganPekerjaan {
// ========================================= STRUKTUR ORGANISASI ========================================= // // ========================================= STRUKTUR ORGANISASI ========================================= //
model PosisiOrganisasi { model StrukturBumDes {
id String @id @default(uuid()) @db.VarChar(50) id String @id @default(cuid())
nama String @db.VarChar(100) name String @db.Text
deskripsi String? @db.Text image FileStorage? @relation(fields: [imageId], references: [id])
hierarki Int imageId String?
createdAt DateTime @default(now())
pegawai Pegawai[] updatedAt DateTime @updatedAt
strukturOrganisasi StrukturOrganisasi[] // Relasi balik deletedAt DateTime @default(now())
StrukturOrganisasiPPID StrukturOrganisasiPPID[] isActive Boolean @default(true)
PosisiOrganisasiBumDes PosisiOrganisasiBumDes? @relation(fields: [posisiOrganisasiBumDesId], references: [id])
@@map("posisi_organisasi") posisiOrganisasiBumDesId String?
PegawaiBumDes PegawaiBumDes? @relation(fields: [pegawaiBumDesId], references: [id])
pegawaiBumDesId String?
} }
model Pegawai { model PosisiOrganisasiBumDes {
id String @id @default(uuid()) @db.Uuid id String @id @default(cuid())
namaLengkap String @db.VarChar(255) nama String @db.VarChar(100)
gelarAkademik String? @db.VarChar(100) deskripsi String? @db.Text
image FileStorage? @relation(fields: [imageId], references: [id]) hierarki Int
imageId String? pegawai PegawaiBumDes[]
tanggalMasuk DateTime? @db.Date strukturOrganisasi StrukturBumDes[] // Relasi balik
email String? @unique @db.VarChar(255) parentId String?
telepon String? @db.VarChar(20) isActive Boolean @default(true)
alamat String? @db.Text createdAt DateTime @default(now())
posisiId String @db.VarChar(50) updatedAt DateTime @updatedAt
isActive Boolean @default(true) parent PosisiOrganisasiBumDes? @relation("Parent", fields: [parentId], references: [id])
createdAt DateTime @default(now()) children PosisiOrganisasiBumDes[] @relation("Parent")
updatedAt DateTime @updatedAt StrukturOrganisasiBumDes StrukturOrganisasiBumDes[]
posisi PosisiOrganisasi @relation(fields: [posisiId], references: [id])
sebagaiAtasan HubunganOrganisasi[] @relation("AtasanToBawahan")
sebagaiBawahan HubunganOrganisasi[] @relation("BawahanToAtasan")
strukturOrganisasi StrukturOrganisasi[] // Relasi balik
StrukturOrganisasiPPID StrukturOrganisasiPPID[]
@@map("pegawai")
} }
model HubunganOrganisasi { model PegawaiBumDes {
id String @id @default(uuid()) @db.Uuid id String @id @default(cuid())
atasanId String @db.Uuid namaLengkap String @db.VarChar(255)
bawahanId String @db.Uuid gelarAkademik String? @db.VarChar(100)
tipe String? @db.VarChar(50) image FileStorage? @relation(fields: [imageId], references: [id])
imageId String?
atasan Pegawai @relation("AtasanToBawahan", fields: [atasanId], references: [id]) tanggalMasuk DateTime? @db.Date
bawahan Pegawai @relation("BawahanToAtasan", fields: [bawahanId], references: [id]) email String? @unique @db.VarChar(255)
telepon String? @db.VarChar(20)
strukturOrganisasi StrukturOrganisasi[] // Relasi balik alamat String? @db.Text
posisiId String @db.VarChar(50)
@@unique([atasanId, bawahanId]) isActive Boolean @default(true)
@@map("hubungan_organisasi") createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
posisi PosisiOrganisasiBumDes @relation(fields: [posisiId], references: [id])
strukturOrganisasi StrukturBumDes[] // Relasi balik
StrukturOrganisasiBumDes StrukturOrganisasiBumDes[]
} }
model StrukturOrganisasi { model StrukturOrganisasiBumDes {
id String @id @default(uuid()) id String @id @default(uuid())
posisiOrganisasiId String @db.VarChar(50) posisiOrganisasiId String @db.VarChar(50)
pegawaiId String @db.Uuid pegawaiId String
hubunganOrganisasiId String @db.Uuid hubunganOrganisasiId String
posisiOrganisasi PosisiOrganisasiBumDes @relation(fields: [posisiOrganisasiId], references: [id])
posisiOrganisasi PosisiOrganisasi @relation(fields: [posisiOrganisasiId], references: [id]) pegawai PegawaiBumDes @relation(fields: [pegawaiId], references: [id])
pegawai Pegawai @relation(fields: [pegawaiId], references: [id]) createdAt DateTime @default(now())
hubunganOrganisasi HubunganOrganisasi @relation(fields: [hubunganOrganisasiId], references: [id]) updatedAt DateTime @updatedAt
deletedAt DateTime?
createdAt DateTime @default(now()) isActive Boolean @default(true)
updatedAt DateTime @updatedAt
deletedAt DateTime?
isActive Boolean @default(true)
@@map("struktur_organisasi")
} }
// ========================================= PROGRAM KEMISKINAN ========================================= // // ========================================= PROGRAM KEMISKINAN ========================================= //
@@ -1469,7 +1497,7 @@ model ProgramKemiskinan {
id String @id @default(uuid()) id String @id @default(uuid())
nama String nama String
deskripsi String deskripsi String
ikonUrl String? icon String
isActive Boolean @default(true) isActive Boolean @default(true)
statistikId String? @unique statistikId String? @unique
statistik StatistikKemiskinan? @relation(fields: [statistikId], references: [id]) statistik StatistikKemiskinan? @relation(fields: [statistikId], references: [id])
@@ -1551,7 +1579,7 @@ model DataDemografiPekerjaan {
model DetailDataPengangguran { model DetailDataPengangguran {
id String @id @default(uuid()) @db.Uuid id String @id @default(uuid()) @db.Uuid
month String @db.VarChar(20) month String @db.VarChar(20)
year DateTime year Int
totalUnemployment Int totalUnemployment Int
educatedUnemployment Int educatedUnemployment Int
uneducatedUnemployment Int uneducatedUnemployment Int
@@ -1610,7 +1638,7 @@ model Pembiayaan {
ApbDesa ApbDesa[] @relation("ApbDesaPembiayaan") ApbDesa ApbDesa[] @relation("ApbDesaPembiayaan")
} }
// ========================================= INOVASI ========================================= // // ========================================= MENU INOVASI ========================================= //
// ========================================= DESA DIGITAL / SMART VILLAGE ========================================= // // ========================================= DESA DIGITAL / SMART VILLAGE ========================================= //
model DesaDigital { model DesaDigital {
id String @id @default(cuid()) id String @id @default(cuid())
@@ -1946,23 +1974,28 @@ model KeunggulanProgram {
} }
model BeasiswaPendaftar { model BeasiswaPendaftar {
id String @id @default(cuid()) id String @id @default(cuid())
namaLengkap String namaLengkap String
nik String @unique nis String?
kelas String?
jenisKelamin JenisKelamin
alamatDomisili String?
tempatLahir String tempatLahir String
tanggalLahir DateTime tanggalLahir DateTime
jenisKelamin JenisKelamin namaOrtu String?
kewarganegaraan String nik String @unique
agama Agama pekerjaanOrtu String?
alamatKTP String penghasilan String?
alamatDomisili String?
noHp String noHp String
email String @unique kewarganegaraan String?
statusPernikahan StatusPernikahan agama Agama?
alamatKTP String?
email String? @unique
statusPernikahan StatusPernikahan?
ukuranBaju UkuranBaju? ukuranBaju UkuranBaju?
isActive Boolean @default(true) isActive Boolean @default(true)
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
} }
enum JenisKelamin { enum JenisKelamin {
@@ -2091,6 +2124,9 @@ model DataPerpustakaan {
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
deletedAt DateTime @default(now()) deletedAt DateTime @default(now())
isActive Boolean @default(true) isActive Boolean @default(true)
// relasi baru ke peminjaman
peminjamanBuku PeminjamanBuku[]
} }
model KategoriBuku { model KategoriBuku {
@@ -2103,26 +2139,56 @@ model KategoriBuku {
DataPerpustakaan DataPerpustakaan[] DataPerpustakaan DataPerpustakaan[]
} }
model PeminjamanBuku {
id String @id @default(cuid())
nama String
noTelp String
alamat String
bukuId String
tanggalPinjam DateTime @default(now())
batasKembali DateTime // tenggat waktu pengembalian
tanggalKembali DateTime? // diisi saat dikembalikan
status StatusPeminjaman @default(Dipinjam)
catatan String? // opsional, misal: kondisi buku
buku DataPerpustakaan @relation(fields: [bukuId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
enum StatusPeminjaman {
Dipinjam
Dikembalikan
Terlambat
Dibatalkan
}
// ========================================= USER ========================================= //
model User { model User {
id String @id @default(cuid()) id String @id @default(cuid())
nama String username String
email String @unique nomor String @unique
password String? roleId String @default("2")
role Role @relation(fields: [roleId], references: [id]) isActive Boolean @default(false)
roleId String sessionInvalid Boolean @default(false)
instansi String? // Nama instansi (Puskesmas, Sekolah, dll) lastLogin DateTime?
isActive Boolean @default(true) createdAt DateTime @default(now())
lastLogin DateTime? updatedAt DateTime @default(now()) @updatedAt
createdAt DateTime @default(now()) permissions Json?
updatedAt DateTime @updatedAt sessions UserSession[] // ✅ Relasi one-to-many
deletedAt DateTime? role Role @relation(fields: [roleId], references: [id])
menuAccesses UserMenuAccess[]
@@map("users")
} }
model Role { model Role {
id String @id @default(cuid()) id String @id @default(cuid())
name String @unique // ADMIN_DESA, ADMIN_KESEHATAN, ADMIN_SEKOLAH name String @unique // ADMIN_DESA, ADMIN_KESEHATAN, ADMIN_SEKOLAH
description String? description String?
permissions Json // Menyimpan permission dalam format JSON permissions Json?
isActive Boolean @default(true) isActive Boolean @default(true)
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
@@ -2132,15 +2198,41 @@ model Role {
@@map("roles") @@map("roles")
} }
// Tabel untuk menyimpan permission model KodeOtp {
model Permission { id String @id @default(cuid())
id String @id @default(cuid()) isActive Boolean @default(true)
name String @unique createdAt DateTime @default(now())
description String? updatedAt DateTime @updatedAt
createdAt DateTime @default(now()) nomor String
updatedAt DateTime @updatedAt otp Int
}
@@map("permissions") model UserSession {
id String @id @default(cuid())
token String @db.Text // ✅ JWT bisa panjang
expiresAt DateTime // ✅ Ubah jadi expiresAt (konsisten)
active Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId String // ✅ HAPUS @unique - user bisa punya multiple sessions
@@index([userId]) // ✅ Index untuk query cepat
@@index([token]) // ✅ Index untuk verify cepat
@@map("user_sessions")
}
model UserMenuAccess {
id String @id @default(cuid())
userId String
menuId String // ID menu (misal: "Landing Page", "Kesehatan")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id])
@@unique([userId, menuId]) // Satu user tidak bisa punya akses menu yang sama dua kali
} }
// ========================================= DATA PENDIDIKAN ========================================= // // ========================================= DATA PENDIDIKAN ========================================= //

View File

@@ -1,3 +1,5 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-unused-vars */
import prisma from "@/lib/prisma"; import prisma from "@/lib/prisma";
import profilePejabatDesa from "./data/landing-page/profile/profile.json"; import profilePejabatDesa from "./data/landing-page/profile/profile.json";
import programInovasi from "./data/landing-page/profile/programInovasi.json"; import programInovasi from "./data/landing-page/profile/programInovasi.json";
@@ -15,7 +17,7 @@ import posisiOrganisasiPPID from "./data/ppid/struktur-ppid/posisi-organisasi-PP
import visiMisiPPID from "./data/ppid/visi-misi-ppid/visimisiPPID.json"; import visiMisiPPID from "./data/ppid/visi-misi-ppid/visimisiPPID.json";
import dasarHukumPPID from "./data/ppid/dasar-hukum-ppid/dasarhukumPPID.json"; import dasarHukumPPID from "./data/ppid/dasar-hukum-ppid/dasarhukumPPID.json";
import jenisKelamin from "./data/ppid/ikm/jenis-kelamin/jenis-kelamin.json"; import jenisKelamin from "./data/ppid/ikm/jenis-kelamin/jenis-kelamin.json";
import daftarInformasiPublik from "./data/ppid/daftar-informasi-publik-desa-darmasaba/daftarInformasi.json" import daftarInformasiPublik from "./data/ppid/daftar-informasi-publik-desa-darmasaba/daftarInformasi.json";
import pilihanRatingResponden from "./data/ppid/ikm/pilihan-rating-responden/rating-responden.json"; import pilihanRatingResponden from "./data/ppid/ikm/pilihan-rating-responden/rating-responden.json";
import umurResponden from "./data/ppid/ikm/umur-responden/umur-responden.json"; import umurResponden from "./data/ppid/ikm/umur-responden/umur-responden.json";
import categoryPengumuman from "./data/category-pengumuman.json"; import categoryPengumuman from "./data/category-pengumuman.json";
@@ -30,14 +32,14 @@ import sejarahDesa from "./data/desa/profile/sejarah_desa.json";
import visiMisiDesa from "./data/desa/profile/visi_misi_desa.json"; import visiMisiDesa from "./data/desa/profile/visi_misi_desa.json";
import detailDataPengangguran from "./data/ekonomi/jumlah-pengangguran/detail-data-pengangguran.json"; import detailDataPengangguran from "./data/ekonomi/jumlah-pengangguran/detail-data-pengangguran.json";
import kategoriProduk from "./data/ekonomi/pasar-desa/kategori-produk.json"; import kategoriProduk from "./data/ekonomi/pasar-desa/kategori-produk.json";
import hubunganOrganisasi from "./data/ekonomi/struktur-organisasi/hubungan-organisasi.json"; import pegawai from "./data/ekonomi/struktur-organisasi/pegawai-bumdes.json";
import pegawai from "./data/ekonomi/struktur-organisasi/pegawai.json"; import posisiOrganisasi from "./data/ekonomi/struktur-organisasi/posisi-organisasi-bumdes.json";
import posisiOrganisasi from "./data/ekonomi/struktur-organisasi/posisi-organisasi.json"; import kategoriBerita from "./data/desa/berita/kategori-berita.json";
import kategoriBerita from "./data/kategori-berita.json";
import contohEdukasiLingkungan from "./data/lingkungan/edukasi-lingkungan/contoh-kegiatan-di-desa-darmasaba.json"; import contohEdukasiLingkungan from "./data/lingkungan/edukasi-lingkungan/contoh-kegiatan-di-desa-darmasaba.json";
import materiEdukasiLingkungan from "./data/lingkungan/edukasi-lingkungan/materi-edukasi-yang-diberikan.json"; import materiEdukasiLingkungan from "./data/lingkungan/edukasi-lingkungan/materi-edukasi-yang-diberikan.json";
import tujuanEdukasiLingkungan from "./data/lingkungan/edukasi-lingkungan/tujuan-edukasi-lingkungan.json"; import tujuanEdukasiLingkungan from "./data/lingkungan/edukasi-lingkungan/tujuan-edukasi-lingkungan.json";
import bentukKonservasiBerdasarkanAdat from "./data/lingkungan/konservasi-adat-bali/bentuk-konservasi.json"; import bentukKonservasiBerdasarkanAdat from "./data/lingkungan/konservasi-adat-bali/bentuk-konservasi.json";
import kategoriKegiatanData from "./data/lingkungan/gotong-royong/kategori-gotong-royong.json";
import filosofiTriHita from "./data/lingkungan/konservasi-adat-bali/filosofi-tri-hita.json"; import filosofiTriHita from "./data/lingkungan/konservasi-adat-bali/filosofi-tri-hita.json";
import nilaiKonservasiAdat from "./data/lingkungan/konservasi-adat-bali/nilai-konservasi-adat.json"; import nilaiKonservasiAdat from "./data/lingkungan/konservasi-adat-bali/nilai-konservasi-adat.json";
import caraMemperolehInformasi from "./data/list-caraMemperolehInformasi.json"; import caraMemperolehInformasi from "./data/list-caraMemperolehInformasi.json";
@@ -53,51 +55,112 @@ import tujuanProgram2 from "./data/pendidikan/pendidikan-non-formal/tujuan-progr
import programUnggulan from "./data/pendidikan/program-pendidikan-anak/program-unggulan.json"; import programUnggulan from "./data/pendidikan/program-pendidikan-anak/program-unggulan.json";
import tujuanProgram from "./data/pendidikan/program-pendidikan-anak/tujuan-program.json"; import tujuanProgram from "./data/pendidikan/program-pendidikan-anak/tujuan-program.json";
import roles from "./data/user/roles.json"; import roles from "./data/user/roles.json";
import fileStorage from "./data/file-storage.json";
import jenjangPendidikan from "./data/pendidikan/info-sekolah/jenjang-pendidikan.json";
import seedAssets from "./seed_assets";
import users from "./data/user/users.json"; import users from "./data/user/users.json";
import { safeSeedUnique } from "./safeseedUnique";
(async () => { (async () => {
// =========== USER & ROLE =========== console.log("🔄 Seeding roles...");
for (const r of roles) { for (const r of roles) {
await prisma.role.upsert({ try {
where: { id: r.id }, // ✅ Destructure to remove permissions if exists
update: { const { permissions, ...roleData } = r as any;
name: r.name,
description: r.description, await safeSeedUnique(
permissions: r.permissions, "role",
isActive: true, { name: roleData.name },
}, {
create: { id: roleData.id,
id: r.id, name: roleData.name,
name: r.name, description: roleData.description,
description: r.description, permissions: roleData.permissions || {}, // ✅ Include permissions
permissions: r.permissions, isActive: roleData.isActive,
isActive: true, }
}, );
}); console.log(`✅ Seeded role -> ${roleData.name}`);
} catch (error: any) {
if (error.code === "P2002") {
console.warn(`⚠️ Role already exists (skipping): ${r.name}`);
} else {
console.error(`❌ Failed to seed role ${r.name}:`, error.message);
}
}
} }
console.log("✅ Roles seeded"); console.log("✅ Roles seeding completed");
//users
// =========== USER ===========
console.log("🔄 Seeding users...");
for (const u of users) { for (const u of users) {
await prisma.user.upsert({ try {
where: { id: u.id }, // Verify role exists first
update: { const roleExists = await prisma.role.findUnique({
nama: u.nama, where: { id: u.roleId.toString() },
email: u.email, select: { id: true }, // Only select id to minimize query
password: u.password, });
roleId: u.roleId,
isActive: true, if (!roleExists) {
}, console.error(
create: { `❌ Role with id ${u.roleId} not found for user ${u.username}`
id: u.id, );
nama: u.nama, continue;
email: u.email, }
password: u.password,
roleId: u.roleId, await safeSeedUnique(
isActive: true, "user",
}, { id: u.id },
}); {
username: u.username,
nomor: u.nomor,
roleId: u.roleId.toString(),
isActive: u.isActive,
sessionInvalid: false,
}
);
console.log(`✅ Seeded user -> ${u.username}`);
} catch (error: any) {
if (error.code === "P2003") {
console.error(
`❌ Foreign key constraint failed for user ${u.username}: Role ${u.roleId} does not exist`
);
} else {
console.error(`❌ Failed to seed user ${u.username}:`, error.message);
}
}
} }
console.log("✅ Users seeded"); console.log("✅ Users seeding completed");
// =========== FILE STORAGE ===========
console.log("🔄 Seeding file storage...");
for (const f of fileStorage) {
try {
await prisma.fileStorage.upsert({
where: { id: f.id },
update: {
name: f.name,
realName: f.realName,
path: f.path,
mimeType: f.mimeType,
link: f.link,
category: f.category,
},
create: {
id: f.id,
name: f.name,
realName: f.realName,
path: f.path,
mimeType: f.mimeType,
link: f.link,
category: f.category,
},
});
} catch (error: any) {
console.error(`❌ Failed to seed file storage ${f.name}:`, error.message);
}
}
console.log("✅ File storage seeded");
// =========== LANDING PAGE =========== // =========== LANDING PAGE ===========
// =========== SUBMENU PROFILE =========== // =========== SUBMENU PROFILE ===========
// =========== PROFILE PEJABAT DESA =========== // =========== PROFILE PEJABAT DESA ===========
@@ -107,11 +170,13 @@ import users from "./data/user/users.json";
update: { update: {
name: p.name, name: p.name,
position: p.position, position: p.position,
imageId: p.imageId,
}, },
create: { create: {
id: p.id, id: p.id,
name: p.name, name: p.name,
position: p.position, position: p.position,
imageId: p.imageId,
}, },
}); });
} }
@@ -121,18 +186,35 @@ import users from "./data/user/users.json";
// =========== PROGRAM INOVASI =========== // =========== PROGRAM INOVASI ===========
for (const p of programInovasi) { for (const p of programInovasi) {
let imageId: string | null = null;
if (p.imageId) {
const imageExists = await prisma.fileStorage.findUnique({
where: { id: p.imageId },
});
if (imageExists) {
imageId = p.imageId;
} else {
console.warn(
`⚠️ imageId ${p.imageId} tidak ditemukan untuk ProgramInovasi ${p.name}`
);
}
}
await prisma.programInovasi.upsert({ await prisma.programInovasi.upsert({
where: { id: p.id }, where: { id: p.id },
update: { update: {
name: p.name, name: p.name,
description: p.description, description: p.description,
link: p.link, link: p.link,
imageId: p.imageId,
}, },
create: { create: {
id: p.id, id: p.id,
name: p.name, name: p.name,
description: p.description, description: p.description,
link: p.link, link: p.link,
imageId: p.imageId,
}, },
}); });
} }
@@ -145,11 +227,13 @@ import users from "./data/user/users.json";
update: { update: {
name: p.name, name: p.name,
iconUrl: p.iconUrl, iconUrl: p.iconUrl,
imageId: p.imageId,
}, },
create: { create: {
id: p.id, id: p.id,
name: p.name, name: p.name,
iconUrl: p.iconUrl, iconUrl: p.iconUrl,
imageId: p.imageId,
}, },
}); });
} }
@@ -295,16 +379,14 @@ import users from "./data/user/users.json";
// =========== SDGSDesa =========== // =========== SDGSDesa ===========
for (const l of sdgsDesa) { for (const l of sdgsDesa) {
await prisma.sDGSDesa.upsert({ await prisma.sdgsDesa.upsert({
where: { where: { id: l.id },
name: l.name,
jumlah: l.jumlah,
},
update: { update: {
name: l.name, name: l.name,
jumlah: l.jumlah, jumlah: l.jumlah,
}, },
create: { create: {
id: l.id,
name: l.name, name: l.name,
jumlah: l.jumlah, jumlah: l.jumlah,
}, },
@@ -317,8 +399,7 @@ import users from "./data/user/users.json";
for (const l of apbdes) { for (const l of apbdes) {
await prisma.aPBDes.upsert({ await prisma.aPBDes.upsert({
where: { where: {
name: l.name, id: l.id,
jumlah: l.jumlah,
}, },
update: { update: {
name: l.name, name: l.name,
@@ -496,15 +577,40 @@ import users from "./data/user/users.json";
console.log("posisi organisasi berhasil"); console.log("posisi organisasi berhasil");
// =========== PEGAWAI PPID =========== // =========== PEGAWAI PPID ===========
console.log("🔄 Seeding pegawai PPID...");
const flattenedPegawai = pegawaiPPID.flat(); const flattenedPegawai = pegawaiPPID.flat();
// Check for duplicate emails
const emails = new Set();
for (const p of flattenedPegawai) { for (const p of flattenedPegawai) {
await prisma.pegawaiPPID.upsert({ if (emails.has(p.email)) {
where: { id: p.id }, console.warn(`⚠️ Duplicate email found in pegawaiPPID: ${p.email}`);
update: p, }
create: p, emails.add(p.email);
});
} }
console.log("pegawai berhasil");
for (const p of flattenedPegawai) {
try {
await prisma.pegawaiPPID.upsert({
where: { id: p.id },
update: p,
create: p,
});
console.log(`✅ Seeded pegawai PPID -> ${p.namaLengkap}`);
} catch (error: any) {
if (error.code === "P2002") {
console.warn(
`⚠️ Pegawai PPID with duplicate email (skipping): ${p.email}`
);
} else {
console.error(
`❌ Failed to seed pegawai PPID ${p.namaLengkap}:`,
error.message
);
}
}
}
console.log("✅ pegawai PPID seeding completed");
// =========== SUBMENU VISI MISI PPID =========== // =========== SUBMENU VISI MISI PPID ===========
@@ -545,29 +651,29 @@ import users from "./data/user/users.json";
} }
console.log("dasar hukum PPID success ..."); console.log("dasar hukum PPID success ...");
// =========== SUBMENU DAFTAR INFORMASI PUBLIK PPID =========== // =========== SUBMENU DAFTAR INFORMASI PUBLIK PPID ===========
for (const v of daftarInformasiPublik) { for (const v of daftarInformasiPublik) {
// Convert string date to Date object // Convert string date to Date object
const tanggal = new Date(v.tanggal); const tanggal = new Date(v.tanggal);
await prisma.daftarInformasiPublik.upsert({ await prisma.daftarInformasiPublik.upsert({
where: { where: {
id: v.id, id: v.id,
}, },
update: { update: {
jenisInformasi: v.jenisInformasi, jenisInformasi: v.jenisInformasi,
deskripsi: v.deskripsi, deskripsi: v.deskripsi,
tanggal: tanggal, tanggal: tanggal,
}, },
create: { create: {
id: v.id, id: v.id,
jenisInformasi: v.jenisInformasi, jenisInformasi: v.jenisInformasi,
deskripsi: v.deskripsi, deskripsi: v.deskripsi,
tanggal: tanggal, tanggal: tanggal,
}, },
}); });
} }
console.log("daftar informasi publik PPID success ..."); console.log("daftar informasi publik PPID success ...");
for (const l of pelayananPerizinanBerusaha) { for (const l of pelayananPerizinanBerusaha) {
await prisma.pelayananPerizinanBerusaha.upsert({ await prisma.pelayananPerizinanBerusaha.upsert({
@@ -765,28 +871,36 @@ import users from "./data/user/users.json";
} }
console.log("kategori produk success ..."); console.log("kategori produk success ...");
for (const p of posisiOrganisasi) { const flattenedPosisiBumdes = posisiOrganisasi.flat();
await prisma.posisiOrganisasi.upsert({
where: { // ✅ Urutkan berdasarkan hierarki
id: p.id, const sortedPosisiBumdes = flattenedPosisiBumdes.sort(
}, (a, b) => a.hierarki - b.hierarki
update: { );
nama: p.nama,
deskripsi: p.deskripsi, for (const p of sortedPosisiBumdes) {
hierarki: p.hierarki, console.log(`Seeding: ${p.nama} (id: ${p.id}, parent: ${p.parentId})`);
},
create: { if (p.parentId) {
id: p.id, const parentExists = flattenedPosisi.some((pos) => pos.id === p.parentId);
nama: p.nama, if (!parentExists) {
deskripsi: p.deskripsi, console.warn(
hierarki: p.hierarki, `⚠️ Parent tidak ditemukan: ${p.parentId} untuk ${p.nama}`
}, );
continue;
}
}
await prisma.posisiOrganisasiBumDes.upsert({
where: { id: p.id },
update: p,
create: p,
}); });
} }
console.log("posisi organisasi success ..."); console.log("posisi organisasi berhasil");
for (const p of pegawai) { for (const p of pegawai) {
await prisma.pegawai.upsert({ await prisma.pegawaiBumDes.upsert({
where: { where: {
id: p.id, id: p.id,
}, },
@@ -815,33 +929,10 @@ import users from "./data/user/users.json";
} }
console.log("pegawai success ..."); 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 ...");
for (const d of detailDataPengangguran) { for (const d of detailDataPengangguran) {
// Convert the year to a Date object (using January 1st of the year as the date)
const yearAsDate = new Date(d.year, 0, 1);
await prisma.detailDataPengangguran.upsert({ await prisma.detailDataPengangguran.upsert({
where: { where: {
month_year: { month: d.month, year: yearAsDate }, month_year: { month: d.month, year: d.year },
}, },
update: { update: {
totalUnemployment: d.totalUnemployment, totalUnemployment: d.totalUnemployment,
@@ -851,7 +942,7 @@ import users from "./data/user/users.json";
}, },
create: { create: {
month: d.month, month: d.month,
year: yearAsDate, year: d.year,
totalUnemployment: d.totalUnemployment, totalUnemployment: d.totalUnemployment,
educatedUnemployment: d.educatedUnemployment, educatedUnemployment: d.educatedUnemployment,
uneducatedUnemployment: d.uneducatedUnemployment, uneducatedUnemployment: d.uneducatedUnemployment,
@@ -861,6 +952,30 @@ import users from "./data/user/users.json";
} }
console.log("📊 detailDataPengangguran success ..."); console.log("📊 detailDataPengangguran success ...");
// =========== KATEGORI GOTONG ROYONG ===========
// Add IDs to the kategoriKegiatan data
const kategoriKegiatan = kategoriKegiatanData.map((k, index) => ({
...k,
id: `kategori-${index + 1}`,
}));
for (const k of kategoriKegiatan) {
await prisma.kategoriKegiatan.upsert({
where: {
id: k.id,
},
update: {
nama: k.nama,
},
create: {
id: k.id,
nama: k.nama,
},
});
}
console.log("kategori kegiatan success ...");
for (const e of tujuanEdukasiLingkungan) { for (const e of tujuanEdukasiLingkungan) {
await prisma.tujuanEdukasiLingkungan.upsert({ await prisma.tujuanEdukasiLingkungan.upsert({
where: { where: {
@@ -1114,6 +1229,25 @@ import users from "./data/user/users.json";
console.log( console.log(
"✅ fasilitas bimbingan belajar desa seeded (editable later via UI)" "✅ fasilitas bimbingan belajar desa seeded (editable later via UI)"
); );
for (const j of jenjangPendidikan) {
await prisma.jenjangPendidikan.upsert({
where: {
id: j.id || undefined,
},
update: {
nama: j.nama,
},
create: {
nama: j.nama,
},
});
}
console.log("✅ Jenjang Pendidikan seeded successfully");
// seed assets
await seedAssets();
})() })()
.then(() => prisma.$disconnect()) .then(() => prisma.$disconnect())
.catch((e) => { .catch((e) => {

118
prisma/seed_assets.ts Normal file
View File

@@ -0,0 +1,118 @@
// prisma/seedAssets.ts
import fs from "fs/promises";
import path from "path";
import sharp from "sharp";
import fetch from "node-fetch";
import AdmZip from "adm-zip";
import prisma from "@/lib/prisma";
const UPLOADS_DIR =
process.env.WIBU_UPLOAD_DIR || path.join(process.cwd(), "uploads");
// --- Helper: deteksi kategori file ---
function detectCategory(filename: string): "image" | "document" | "other" {
const ext = path.extname(filename).toLowerCase();
if ([".jpg", ".jpeg", ".png", ".webp"].includes(ext)) return "image";
if ([".pdf", ".doc", ".docx"].includes(ext)) return "document";
return "other";
}
// --- Helper: recursive walk dir ---
async function walkDir(dir: string, fileList: string[] = []): Promise<string[]> {
const entries = await fs.readdir(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
if (entry.name === "__MACOSX") continue; // skip folder sampah
await walkDir(fullPath, fileList);
} else {
if (entry.name.startsWith(".") || entry.name === ".DS_Store") continue; // skip file sampah
fileList.push(fullPath);
}
}
return fileList;
}
export default async function seedAssets() {
console.log("🚀 Seeding assets...");
// 1. Download zip
const url =
"https://cld-dkr-makuro-seafile.wibudev.com/f/ffd5a548a04f47939474/?dl=1";
const res = await fetch(url);
if (!res.ok) throw new Error(`Gagal download assets: ${res.statusText}`);
const buffer = Buffer.from(await res.arrayBuffer());
// 2. Extract zip ke folder tmp
const extractDir = path.join(process.cwd(), "tmp_assets");
await fs.rm(extractDir, { recursive: true, force: true });
await fs.mkdir(extractDir, { recursive: true });
const zip = new AdmZip(buffer);
zip.extractAllTo(extractDir, true);
// 3. Cari semua file valid (recursive)
const files = await walkDir(extractDir);
// 4. Loop tiap file & simpan
for (const filePath of files) {
const entryName = path.basename(filePath);
const category = detectCategory(entryName);
let finalName = entryName;
let mimeType = "application/octet-stream";
let targetPath = "";
if (category === "image") {
const fileBaseName = path.parse(entryName).name;
finalName = `${fileBaseName}.webp`;
targetPath = path.join(UPLOADS_DIR, "images", finalName);
await fs.mkdir(path.dirname(targetPath), { recursive: true });
await sharp(filePath).webp({ quality: 80 }).toFile(targetPath);
mimeType = "image/webp";
} else if (category === "document") {
targetPath = path.join(UPLOADS_DIR, "documents", entryName);
await fs.mkdir(path.dirname(targetPath), { recursive: true });
await fs.copyFile(filePath, targetPath);
mimeType = "application/pdf";
} else {
targetPath = path.join(UPLOADS_DIR, "other", entryName);
await fs.mkdir(path.dirname(targetPath), { recursive: true });
await fs.copyFile(filePath, targetPath);
}
// 5. Simpan ke DB
await prisma.fileStorage.create({
data: {
name: finalName,
realName: entryName,
path: targetPath,
mimeType,
link: `/uploads/${category}/${finalName}`,
category,
},
});
console.log(`📂 saved: ${category}/${finalName}`);
}
// 6. Cleanup
await fs.rm(extractDir, { recursive: true, force: true });
console.log("✅ Selesai seed assets!");
}
// --- Auto run kalau dipanggil langsung ---
if (import.meta.main) {
seedAssets()
.catch((err) => {
console.error("❌ Error seeding assets:", err);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 275 KiB

BIN
public/beasiswa-siswa.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 446 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 KiB

BIN
public/mangupuraaward.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 275 KiB

After

Width:  |  Height:  |  Size: 378 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 195 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 275 KiB

View File

@@ -23,6 +23,7 @@ export default function SpashScreen() {
<Paper p={"md"} miw={320}> <Paper p={"md"} miw={320}>
<Flex> <Flex>
<Image <Image
loading="lazy"
src={images["darmasaba-icon"]} src={images["darmasaba-icon"]}
alt="darmasaba" alt="darmasaba"
w={100} w={100}

View File

@@ -7,6 +7,7 @@ import Underline from '@tiptap/extension-underline';
import TextAlign from '@tiptap/extension-text-align'; import TextAlign from '@tiptap/extension-text-align';
import Superscript from '@tiptap/extension-superscript'; import Superscript from '@tiptap/extension-superscript';
import SubScript from '@tiptap/extension-subscript'; import SubScript from '@tiptap/extension-subscript';
import { useEffect } from 'react';
type CreateEditorProps = { type CreateEditorProps = {
value: string; value: string;
@@ -32,6 +33,13 @@ export default function CreateEditor({ value, onChange }: CreateEditorProps) {
}, },
}); });
// 👇 Tambahkan efek untuk sinkronisasi value dari luar (resetForm)
useEffect(() => {
if (editor && value !== editor.getHTML()) {
editor.commands.setContent(value || '');
}
}, [value, editor]);
return ( return (
<RichTextEditor editor={editor}> <RichTextEditor editor={editor}>
<RichTextEditor.Toolbar sticky stickyOffset="var(--docs-header-height)"> <RichTextEditor.Toolbar sticky stickyOffset="var(--docs-header-height)">

View File

@@ -47,6 +47,7 @@ export default function EditEditor({ value, onChange }: EditEditorProps) {
editor.off('update', updateHandler); editor.off('update', updateHandler);
}; };
}, [editor, onChange]); }, [editor, onChange]);
return ( return (
<RichTextEditor editor={editor}> <RichTextEditor editor={editor}>

View File

@@ -0,0 +1,100 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
'use client'
import React from 'react'
import {
IconLeaf,
IconTrophy,
IconTent,
IconChartLine,
IconRecycle,
IconTruck,
IconScale,
IconClipboard,
IconTrash,
IconHomeEco,
IconChristmasTreeFilled,
IconTrendingUp,
IconShieldFilled,
IconHome,
IconTree,
IconDroplet,
IconCash,
IconSchool,
IconShoppingCart,
IconHospital,
IconAmbulance,
IconFiretruck,
IconBuilding,
IconAlertTriangle,
} from '@tabler/icons-react'
export type IconKey =
| 'ekowisata'
| 'kompetisi'
| 'wisata'
| 'ekonomi'
| 'sampah'
| 'truck'
| 'scale'
| 'clipboard'
| 'trash'
| 'lingkunganSehat'
| 'sumberOksigen'
| 'ekonomiBerkelanjutan'
| 'mencegahBencana'
| 'rumah'
| 'pohon'
| 'air'
| 'bantuan'
| 'pelatihan'
| 'subsidi'
| 'layananKesehatan'
| 'polisi'
| 'ambulans'
| 'pemadam'
| 'rumahSakit'
| 'bangunan'
| 'darurat'
const iconMap: Record<IconKey, React.FC<any>> = {
ekowisata: IconLeaf,
kompetisi: IconTrophy,
wisata: IconTent,
ekonomi: IconChartLine,
sampah: IconRecycle,
truck: IconTruck,
scale: IconScale,
clipboard: IconClipboard,
trash: IconTrash,
lingkunganSehat: IconHomeEco,
sumberOksigen: IconChristmasTreeFilled,
ekonomiBerkelanjutan: IconTrendingUp,
mencegahBencana: IconShieldFilled,
rumah: IconHome,
pohon: IconTree,
air: IconDroplet,
bantuan: IconCash,
pelatihan: IconSchool,
subsidi: IconShoppingCart,
layananKesehatan: IconHospital,
polisi: IconShieldFilled,
ambulans: IconAmbulance,
pemadam: IconFiretruck,
rumahSakit: IconHospital,
bangunan: IconBuilding,
darurat: IconAlertTriangle
}
type Props = {
name: IconKey
size?: number
color?: string
}
export const IconMapper: React.FC<Props> = ({ name, size = 24, color }) => {
const IconComponent = iconMap[name]
if (!IconComponent) return null
return <IconComponent size={size} color={color} />
}

View File

@@ -3,16 +3,24 @@
import { Box, rem, Select } from '@mantine/core'; import { Box, rem, Select } from '@mantine/core';
import { import {
IconAlertTriangle,
IconAmbulance,
IconBuilding,
IconCash,
IconChartLine, IconChartLine,
IconChristmasTreeFilled, IconChristmasTreeFilled,
IconClipboardTextFilled, IconClipboardTextFilled,
IconDroplet, IconDroplet,
IconFiretruck,
IconHome, IconHome,
IconHomeEco, IconHomeEco,
IconHospital,
IconLeaf, IconLeaf,
IconRecycle, IconRecycle,
IconScale, IconScale,
IconSchool,
IconShieldFilled, IconShieldFilled,
IconShoppingCart,
IconTent, IconTent,
IconTrashFilled, IconTrashFilled,
IconTree, IconTree,
@@ -32,13 +40,23 @@ const iconMap = {
scale: { label: 'Scale', icon: IconScale }, scale: { label: 'Scale', icon: IconScale },
clipboard: { label: 'Clipboard', icon: IconClipboardTextFilled }, clipboard: { label: 'Clipboard', icon: IconClipboardTextFilled },
trash: { label: 'Trash', icon: IconTrashFilled }, trash: { label: 'Trash', icon: IconTrashFilled },
lingkunganSehat: {label: 'Lingkungan Sehat', icon: IconHomeEco}, lingkunganSehat: { label: 'Lingkungan Sehat', icon: IconHomeEco },
sumberOksigen: {label: 'Sumber Oksigen', icon: IconChristmasTreeFilled}, sumberOksigen: { label: 'Sumber Oksigen', icon: IconChristmasTreeFilled },
ekonomiBerkelanjutan: {label: 'Ekonomi Berkelanjutan', icon: IconTrendingUp}, ekonomiBerkelanjutan: { label: 'Ekonomi Berkelanjutan', icon: IconTrendingUp },
mencegahBencana: {label: 'Mencegah Bencana', icon: IconShieldFilled}, mencegahBencana: { label: 'Mencegah Bencana', icon: IconShieldFilled },
rumah: {label: 'Rumah', icon: IconHome}, rumah: { label: 'Rumah', icon: IconHome },
pohon: {label: 'Pohon', icon: IconTree}, pohon: { label: 'Pohon', icon: IconTree },
air: {label: 'Air', icon: IconDroplet} air: { label: 'Air', icon: IconDroplet },
bantuan: { label: 'Bantuan', icon: IconCash },
pelatihan: { label: 'Pelatihan', icon: IconSchool },
subsidi: { label: 'Subsidi', icon: IconShoppingCart },
layananKesehatan: { label: 'Layanan Kesehatan', icon: IconHospital },
polisi: { label: 'Polisi', icon: IconShieldFilled },
ambulans: { label: 'Ambulans', icon: IconAmbulance },
pemadam: { label: 'Pemadam', icon: IconFiretruck },
rumahSakit: { label: 'Rumah Sakit', icon: IconHospital },
bangunan: { label: 'Bangunan', icon: IconBuilding },
darurat: { label: 'Darurat', icon: IconAlertTriangle },
}; };

View File

@@ -1,23 +1,31 @@
'use client' 'use client';
import { Box, rem, Select } from '@mantine/core'; import { Box, Group, rem, Select, SelectProps } from '@mantine/core';
import { import {
IconAmbulance,
IconCash,
IconChartLine, IconChartLine,
IconChristmasTreeFilled, IconChristmasTreeFilled,
IconClipboardTextFilled, IconClipboardTextFilled,
IconDroplet, IconDroplet,
IconFiretruck,
IconHome, IconHome,
IconHomeEco, IconHomeEco,
IconHospital,
IconLeaf, IconLeaf,
IconRecycle, IconRecycle,
IconScale, IconScale,
IconSchool,
IconShieldFilled, IconShieldFilled,
IconShoppingCart,
IconTent, IconTent,
IconTrashFilled, IconTrashFilled,
IconTree, IconTree,
IconTrendingUp, IconTrendingUp,
IconTrophy, IconTrophy,
IconTruckFilled, IconTruckFilled,
IconBuilding,
IconAlertTriangle,
} from '@tabler/icons-react'; } from '@tabler/icons-react';
const iconMap = { const iconMap = {
@@ -30,16 +38,26 @@ const iconMap = {
scale: { label: 'Scale', icon: IconScale }, scale: { label: 'Scale', icon: IconScale },
clipboard: { label: 'Clipboard', icon: IconClipboardTextFilled }, clipboard: { label: 'Clipboard', icon: IconClipboardTextFilled },
trash: { label: 'Trash', icon: IconTrashFilled }, trash: { label: 'Trash', icon: IconTrashFilled },
lingkunganSehat: {label: 'Lingkungan Sehat', icon: IconHomeEco}, lingkunganSehat: { label: 'Lingkungan Sehat', icon: IconHomeEco },
sumberOksigen: {label: 'Sumber Oksigen', icon: IconChristmasTreeFilled}, sumberOksigen: { label: 'Sumber Oksigen', icon: IconChristmasTreeFilled },
ekonomiBerkelanjutan: {label: 'Ekonomi Berkelanjutan', icon: IconTrendingUp}, ekonomiBerkelanjutan: { label: 'Ekonomi Berkelanjutan', icon: IconTrendingUp },
mencegahBencana: {label: 'Mencegah Bencana', icon: IconShieldFilled}, mencegahBencana: { label: 'Mencegah Bencana', icon: IconShieldFilled },
rumah: {label: 'Rumah', icon: IconHome}, rumah: { label: 'Rumah', icon: IconHome },
pohon: {label: 'Pohon', icon: IconTree}, pohon: { label: 'Pohon', icon: IconTree },
air: {label: 'Air', icon: IconDroplet} air: { label: 'Air', icon: IconDroplet },
bantuan: { label: 'Bantuan', icon: IconCash },
pelatihan: { label: 'Pelatihan', icon: IconSchool },
subsidi: { label: 'Subsidi', icon: IconShoppingCart },
layananKesehatan: { label: 'Layanan Kesehatan', icon: IconHospital },
polisi: { label: 'Polisi', icon: IconShieldFilled },
ambulans: { label: 'Ambulans', icon: IconAmbulance },
pemadam: { label: 'Pemadam', icon: IconFiretruck },
rumahSakit: { label: 'Rumah Sakit', icon: IconHospital },
bangunan: { label: 'Bangunan', icon: IconBuilding },
darurat: { label: 'Darurat', icon: IconAlertTriangle },
}; };
type IconKey = keyof typeof iconMap; export type IconKey = keyof typeof iconMap;
const iconList = Object.entries(iconMap).map(([value, data]) => ({ const iconList = Object.entries(iconMap).map(([value, data]) => ({
value, value,
@@ -49,44 +67,52 @@ const iconList = Object.entries(iconMap).map(([value, data]) => ({
export default function SelectIconProgramEdit({ export default function SelectIconProgramEdit({
onChange, onChange,
value, value,
...props
}: { }: {
onChange: (value: IconKey) => void; onChange: (value: IconKey | '') => void;
value: IconKey; value: IconKey | '';
}) { } & Omit<SelectProps, 'onChange' | 'value' | 'data'>) {
const IconComponent = iconMap[value]?.icon || null;
return ( return (
<Box maw={300}> <Box maw={300}>
<Select <Select
placeholder="Pilih ikon" placeholder="Pilih ikon"
value={value} value={value || ''}
onChange={(value) => { onChange={(val: string | null) => {
if (value) onChange(value as IconKey); if (val) {
onChange(val as IconKey);
} else {
onChange('');
}
}} }}
data={iconList} data={iconList}
renderOption={({ option }) => {
const Icon = iconMap[option.value as IconKey]?.icon;
return (
<Group gap="sm">
{Icon && <Icon size={18} stroke={1.5} />}
{option.label}
</Group>
);
}}
leftSection={ leftSection={
IconComponent && ( value && iconMap[value as IconKey] ? (
<Box> <Box ml={-4}>
<IconComponent size={24} stroke={1.5} /> {(() => {
const Icon = iconMap[value as IconKey].icon;
return <Icon size={20} stroke={1.5} />;
})()}
</Box> </Box>
) ) : null
} }
withCheckIcon={false} searchable
searchable={false}
rightSectionWidth={0}
styles={{ styles={{
input: { input: {
textAlign: 'left',
fontSize: rem(16),
paddingLeft: 40, paddingLeft: 40,
}, fontSize: rem(16),
section: {
left: 10,
right: 'auto',
}, },
}} }}
{...props}
/> />
</Box> </Box>
); );
} }

View File

@@ -0,0 +1,76 @@
'use client';
import { Box, Image, Select, rem } from '@mantine/core';
const sosmedMap = {
facebook: { label: 'Facebook', src: '/assets/images/sosmed/facebook.png' },
instagram: { label: 'Instagram', src: '/assets/images/sosmed/instagram.png' },
tiktok: { label: 'Tiktok', src: '/assets/images/sosmed/tiktok.png' },
youtube: { label: 'YouTube', src: '/assets/images/sosmed/youtube.png' },
whatsapp: { label: 'WhatsApp', src: '/assets/images/sosmed/whatsapp.png' },
gmail: { label: 'Gmail', src: '/assets/images/sosmed/gmail.png' },
telegram: { label: 'Telegram', src: '/assets/images/sosmed/telegram.png' },
x: { label: 'X (Twitter)', src: '/assets/images/sosmed/x-twitter.png' },
telephone: { label: 'Telephone', src: '/assets/images/sosmed/telephone-call.png' },
custom: { label: 'Custom Icon', src: null },
};
type SosmedKey = keyof typeof sosmedMap;
const sosmedList = Object.entries(sosmedMap).map(([value, item]) => ({
value,
label: item.label,
}));
export default function SelectSosialMedia({
value,
onChange,
}: {
value: SosmedKey;
onChange: (value: SosmedKey) => void;
}) {
const selected = value;
const selectedImage = sosmedMap[selected]?.src;
return (
<Box maw={300}>
<Select
placeholder="Pilih sosial media"
value={selected}
data={sosmedList}
searchable={false}
withCheckIcon={false}
onChange={(val) => val && onChange(val as SosmedKey)}
styles={{
input: {
textAlign: 'left',
fontSize: rem(16),
paddingLeft: 36,
},
section: {
left: 10,
right: 'auto',
},
}}
/>
{/* 🔥 PREVIEW DIPISAH DI LUAR SELECT */}
{selectedImage && (
<Box mt="md">
<Image
alt=""
src={selectedImage}
radius="md"
style={{
width: 120,
height: 120,
objectFit: 'contain',
border: '1px solid #eee',
padding: 8,
}}
/>
</Box>
)}
</Box>
);
}

View File

@@ -0,0 +1,56 @@
'use client';
import { Box, Select } from '@mantine/core';
import { useEffect, useState } from 'react';
export const sosmedMap = {
facebook: { label: 'Facebook', src: '/assets/images/sosmed/facebook.png' },
instagram: { label: 'Instagram', src: '/assets/images/sosmed/instagram.png' },
tiktok: { label: 'Tiktok', src: '/assets/images/sosmed/tiktok.png' },
youtube: { label: 'YouTube', src: '/assets/images/sosmed/youtube.png' },
whatsapp: { label: 'WhatsApp', src: '/assets/images/sosmed/whatsapp.png' },
gmail: { label: 'Gmail', src: '/assets/images/sosmed/gmail.png' },
telegram: { label: 'Telegram', src: '/assets/images/sosmed/telegram.png' },
x: { label: 'X (Twitter)', src: '/assets/images/sosmed/x-twitter.png' },
telephone: { label: 'Telephone', src: '/assets/images/sosmed/telephone-call.png' },
custom: { label: 'Custom Icon', src: null },
};
type SosmedKey = keyof typeof sosmedMap;
const sosmedList = Object.entries(sosmedMap).map(([value, item]) => ({
value,
label: item.label,
}));
export default function SelectSocialMediaEdit({
value,
onChange,
}: {
value: string;
onChange: (val: SosmedKey) => void;
}) {
const [selected, setSelected] = useState<SosmedKey>('facebook');
useEffect(() => {
if (value && sosmedMap[value as SosmedKey]) {
setSelected(value as SosmedKey);
}
}, [value]);
return (
<Box>
<Select
label="Jenis Media Sosial"
value={selected}
data={sosmedList}
searchable={false}
onChange={(val) => {
if (!val) return;
setSelected(val as SosmedKey);
onChange(val as SosmedKey);
}}
/>
</Box>
);
}

View File

@@ -74,11 +74,12 @@ const berita = proxy({
totalPages: 1, totalPages: 1,
loading: false, loading: false,
search: "", search: "",
load: async (page = 1, limit = 10, search = "", kategori = "") => { load: async (page = 1, limit = 10, search = "", kategori = "") => {
berita.findMany.loading = true; // ✅ Akses langsung via nama path const startTime = Date.now();
berita.findMany.loading = true;
berita.findMany.page = page; berita.findMany.page = page;
berita.findMany.search = search; berita.findMany.search = search;
try { try {
const query: any = { page, limit }; const query: any = { page, limit };
if (search) query.search = search; if (search) query.search = search;
@@ -98,9 +99,16 @@ const berita = proxy({
berita.findMany.data = []; berita.findMany.data = [];
berita.findMany.totalPages = 1; berita.findMany.totalPages = 1;
} finally { } finally {
berita.findMany.loading = false; // pastikan minimal 300ms sebelum loading = false (biar UX smooth)
const elapsed = Date.now() - startTime;
const minDelay = 300;
const delay = elapsed < minDelay ? minDelay - elapsed : 0;
setTimeout(() => {
berita.findMany.loading = false;
}, delay);
} }
}, },
}, },
findUnique: { findUnique: {
@@ -368,11 +376,37 @@ const kategoriBerita = proxy({
isActive: true; isActive: true;
}; };
}>[], }>[],
page: 1,
totalPages: 1,
loading: false, loading: false,
async load() { search: "",
const res = await ApiFetch.api.desa.kategoriberita["findMany"].get(); load: async (page = 1, limit = 10, search = "") => {
if (res.status === 200) { kategoriBerita.findMany.loading = true; // ✅ Akses langsung via nama path
kategoriBerita.findMany.data = res.data?.data ?? []; kategoriBerita.findMany.page = page;
kategoriBerita.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.desa.kategoriberita[
"findMany"
].get({ query });
if (res.status === 200 && res.data?.success) {
kategoriBerita.findMany.data = res.data.data ?? [];
kategoriBerita.findMany.totalPages =
res.data.totalPages ?? 1;
} else {
kategoriBerita.findMany.data = [];
kategoriBerita.findMany.totalPages = 1;
}
} catch (err) {
console.error("Gagal fetch kategori berita paginated:", err);
kategoriBerita.findMany.data = [];
kategoriBerita.findMany.totalPages = 1;
} finally {
kategoriBerita.findMany.loading = false;
} }
}, },
}, },

View File

@@ -30,7 +30,6 @@ const templateTelunjukSaktiDesaForm = z.object({
deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"), deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"),
}); });
const templatePelayananPerizinanBerusaha = z.object({ const templatePelayananPerizinanBerusaha = z.object({
name: z.string().min(3, "Nama minimal 3 karakter"), name: z.string().min(3, "Nama minimal 3 karakter"),
deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"), deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"),
@@ -72,6 +71,21 @@ const pelayananPendudukNonPermanenForm = {
deskripsi: "", deskripsi: "",
}; };
const templateAjukanForm = z.object({
nama: z.string().min(1).max(5000),
nik: z.string().min(1).max(5000),
alamat: z.string().min(1).max(5000),
nomorKk: z.string().min(1).max(5000),
kategoriId: z.string().min(1).max(5000),
});
const defaultAjukanForm = {
nama: "",
nik: "",
alamat: "",
nomorKk: "",
kategoriId: "",
};
const suratKeterangan = proxy({ const suratKeterangan = proxy({
create: { create: {
@@ -113,16 +127,21 @@ const suratKeterangan = proxy({
totalPages: 1, totalPages: 1,
total: 0, total: 0,
loading: false, loading: false,
load: async (page = 1, limit = 10) => { // Change to arrow function search: "",
suratKeterangan.findMany.loading = true; // Use the full path to access the property load: async (page = 1, limit = 10, search = "") => {
// Change to arrow function
suratKeterangan.findMany.loading = true; // Use the full path to access the property
suratKeterangan.findMany.page = page; suratKeterangan.findMany.page = page;
suratKeterangan.findMany.search = search;
try { try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.desa.layanan.pelayanansuratketerangan[ const res = await ApiFetch.api.desa.layanan.pelayanansuratketerangan[
"find-many" "find-many"
].get({ ].get({
query: { page, limit }, query,
}); });
if (res.status === 200 && res.data?.success) { if (res.status === 200 && res.data?.success) {
suratKeterangan.findMany.data = res.data.data || []; suratKeterangan.findMany.data = res.data.data || [];
suratKeterangan.findMany.total = res.data.total || 0; suratKeterangan.findMany.total = res.data.total || 0;
@@ -143,6 +162,30 @@ const suratKeterangan = proxy({
} }
}, },
}, },
findManyAll: {
data: null as Prisma.PelayananSuratKeteranganGetPayload<{
omit: { isActive: true };
}>[] | null,
loading: false,
load: async () => {
suratKeterangan.findManyAll.loading = true;
try {
const res = await ApiFetch.api.desa.layanan.pelayanansuratketerangan["findManyAll"].get();
if (res.status === 200 && res.data?.success) {
suratKeterangan.findManyAll.data = res.data.data || [];
} else {
suratKeterangan.findManyAll.data = [];
console.error("Failed to load surat keterangan all:", res.data?.message);
}
} catch (error) {
console.error("Error loading surat keterangan all:", error);
suratKeterangan.findManyAll.data = [];
} finally {
suratKeterangan.findManyAll.loading = false;
}
},
},
findUnique: { findUnique: {
data: null as Prisma.PelayananSuratKeteranganGetPayload<{ data: null as Prisma.PelayananSuratKeteranganGetPayload<{
include: { include: {
@@ -341,28 +384,34 @@ const pelayananTelunjukSaktiDesa = proxy({
totalPages: 1, totalPages: 1,
total: 0, total: 0,
loading: false, loading: false,
load: async (page = 1, limit = 10) => { // Change to arrow function search: "",
pelayananTelunjukSaktiDesa.findMany.loading = true; // Use the full path to access the property load: async (page = 1, limit = 10, search = "") => {
// Change to arrow function
pelayananTelunjukSaktiDesa.findMany.loading = true; // Use the full path to access the property
pelayananTelunjukSaktiDesa.findMany.page = page; pelayananTelunjukSaktiDesa.findMany.page = page;
pelayananTelunjukSaktiDesa.findMany.search = search;
try { try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.desa.layanan.pelayanantelunjuksaktidesa[ const res = await ApiFetch.api.desa.layanan.pelayanantelunjuksaktidesa[
"find-many" "find-many"
].get({ ].get({
query: { page, limit }, query,
}); });
if (res.status === 200 && res.data?.success) { if (res.status === 200 && res.data?.success) {
pelayananTelunjukSaktiDesa.findMany.data = res.data.data || []; pelayananTelunjukSaktiDesa.findMany.data = res.data.data || [];
pelayananTelunjukSaktiDesa.findMany.total = res.data.total || 0; pelayananTelunjukSaktiDesa.findMany.total = res.data.total || 0;
pelayananTelunjukSaktiDesa.findMany.totalPages = res.data.totalPages || 1; pelayananTelunjukSaktiDesa.findMany.totalPages =
res.data.totalPages || 1;
} else { } else {
console.error("Failed to load telunjuk sakti desa:", res.data?.message); console.error("Failed to load surat keterangan:", res.data?.message);
pelayananTelunjukSaktiDesa.findMany.data = []; pelayananTelunjukSaktiDesa.findMany.data = [];
pelayananTelunjukSaktiDesa.findMany.total = 0; suratKeterangan.findMany.total = 0;
pelayananTelunjukSaktiDesa.findMany.totalPages = 1; suratKeterangan.findMany.totalPages = 1;
} }
} catch (error) { } catch (error) {
console.error("Error loading telunjuk sakti desa:", error); console.error("Error loading surat keterangan:", error);
pelayananTelunjukSaktiDesa.findMany.data = []; pelayananTelunjukSaktiDesa.findMany.data = [];
pelayananTelunjukSaktiDesa.findMany.total = 0; pelayananTelunjukSaktiDesa.findMany.total = 0;
pelayananTelunjukSaktiDesa.findMany.totalPages = 1; pelayananTelunjukSaktiDesa.findMany.totalPages = 1;
@@ -410,7 +459,9 @@ const pelayananTelunjukSaktiDesa = proxy({
); );
const result = await response.json(); const result = await response.json();
if (response.ok) { if (response.ok) {
toast.success(result.message || "Telunjuk Sakti Desa berhasil dihapus"); toast.success(
result.message || "Telunjuk Sakti Desa berhasil dihapus"
);
await pelayananTelunjukSaktiDesa.findMany.load(); // refresh list await pelayananTelunjukSaktiDesa.findMany.load(); // refresh list
} else { } else {
toast.error(result.message || "Gagal menghapus telunjuk sakti desa"); toast.error(result.message || "Gagal menghapus telunjuk sakti desa");
@@ -501,7 +552,9 @@ const pelayananTelunjukSaktiDesa = proxy({
} }
const result = await response.json(); const result = await response.json();
if (result.success) { if (result.success) {
toast.success(result.message || "Telunjuk Sakti Desa berhasil diupdate"); toast.success(
result.message || "Telunjuk Sakti Desa berhasil diupdate"
);
await pelayananTelunjukSaktiDesa.findMany.load(); // refresh list await pelayananTelunjukSaktiDesa.findMany.load(); // refresh list
return true; return true;
} else { } else {
@@ -522,39 +575,30 @@ const pelayananTelunjukSaktiDesa = proxy({
} }
}, },
}, },
}) });
const pelayananPerizinanBerusaha = proxy({ const pelayananPerizinanBerusaha = proxy({
findById: { findById: {
data: null as pelayananPerizinanBerusahaForm | null, data: null as pelayananPerizinanBerusahaForm | null,
loading: false, loading: false,
initialize() {
pelayananPerizinanBerusaha.findById.data = {
id: "",
name: "",
deskripsi: "",
link: "",
} as pelayananPerizinanBerusahaForm;
},
async load(id: string) { async load(id: string) {
try { try {
pelayananPerizinanBerusaha.findById.loading = true; this.loading = true;
const res = await fetch( const response = await fetch(`/api/desa/layanan/pelayananperizinanberusaha/${id}`);
`/api/desa/layanan/pelayananperizinanberusaha/${id}` if (!response.ok) {
); throw new Error(`HTTP error! status: ${response.status}`);
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;
} }
const result = await response.json();
if (result?.success) {
this.data = result.data; // Make sure this matches your API response structure
}
return result?.data || null;
} catch (error) { } catch (error) {
console.error("Error fetching pelayanan perizinan berusaha:", error); console.error('Error loading data:', error);
pelayananPerizinanBerusaha.findById.data = null; toast.error('Gagal memuat data');
return null;
} finally {
this.loading = false;
} }
}, },
}, },
@@ -596,9 +640,7 @@ const pelayananPerizinanBerusaha = proxy({
} catch (error) { } catch (error) {
console.error("Error fetching pelayanan perizinan berusaha:", error); console.error("Error fetching pelayanan perizinan berusaha:", error);
toast.error( toast.error(
error instanceof Error error instanceof Error ? error.message : "Gagal memuat data"
? error.message
: "Gagal memuat data"
); );
return null; return null;
} }
@@ -713,9 +755,7 @@ const pelayananPendudukNonPermanen = proxy({
} catch (error) { } catch (error) {
console.error("Error fetching pelayanan penduduk non permanen:", error); console.error("Error fetching pelayanan penduduk non permanen:", error);
toast.error( toast.error(
error instanceof Error error instanceof Error ? error.message : "Gagal memuat data"
? error.message
: "Gagal memuat data"
); );
return null; return null;
} }
@@ -760,11 +800,250 @@ const pelayananPendudukNonPermanen = proxy({
}, },
}); });
const ajukanPermohonan = proxy({
create: {
form: { ...defaultAjukanForm },
loading: false,
async create() {
const cek = templateAjukanForm.safeParse(
ajukanPermohonan.create.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
ajukanPermohonan.create.loading = true;
const res = await ApiFetch.api.desa.ajukanpermohonan[
"create"
].post(ajukanPermohonan.create.form);
if (res.status === 200) {
ajukanPermohonan.findMany.load();
return toast.success("Ajukan permohonan berhasil disimpan!");
}
return toast.error("Gagal menyimpan ajukan permohonan");
} catch (error) {
console.log((error as Error).message);
} finally {
ajukanPermohonan.create.loading = false;
}
},
resetForm() {
ajukanPermohonan.create.form = { ...defaultAjukanForm };
},
},
findMany: {
data: null as Prisma.AjukanPermohonanGetPayload<{
include: {
kategori: true;
};
}>[] | null,
page: 1,
totalPages: 1,
total: 0,
loading: false,
search: "",
load: async (page = 1, limit = 10, search = "") => {
// Change to arrow function
ajukanPermohonan.findMany.loading = true; // Use the full path to access the property
ajukanPermohonan.findMany.page = page;
ajukanPermohonan.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.desa.ajukanpermohonan[
"findMany"
].get({
query,
});
if (res.status === 200 && res.data?.success) {
ajukanPermohonan.findMany.data = res.data.data || [];
ajukanPermohonan.findMany.total = res.data.total || 0;
ajukanPermohonan.findMany.totalPages = res.data.totalPages || 1;
} else {
console.error("Failed to load ajukan permohonan:", res.data?.message);
ajukanPermohonan.findMany.data = [];
ajukanPermohonan.findMany.total = 0;
ajukanPermohonan.findMany.totalPages = 1;
}
} catch (error) {
console.error("Error loading ajukan permohonan:", error);
ajukanPermohonan.findMany.data = [];
ajukanPermohonan.findMany.total = 0;
ajukanPermohonan.findMany.totalPages = 1;
} finally {
ajukanPermohonan.findMany.loading = false;
}
},
},
findUnique: {
data: null as Prisma.AjukanPermohonanGetPayload<{
include: {
kategori: true;
}
}> | null,
async load(id: string) {
try {
const res = await fetch(
`/api/desa/ajukanpermohonan/${id}`
);
if (res.ok) {
const data = await res.json();
ajukanPermohonan.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch ajukan permohonan:", res.statusText);
ajukanPermohonan.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching ajukan permohonan:", error);
ajukanPermohonan.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
ajukanPermohonan.delete.loading = true;
const response = await fetch(
`/api/desa/ajukanpermohonan/del/${id}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
);
const result = await response.json();
if (response.ok) {
toast.success(result.message || "Ajukan permohonan berhasil dihapus");
await ajukanPermohonan.findMany.load(); // refresh list
} else {
toast.error(result.message || "Gagal menghapus ajukan permohonan");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus ajukan permohonan");
} finally {
ajukanPermohonan.delete.loading = false;
}
},
},
edit: {
id: "",
form: { ...defaultAjukanForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(
`/api/desa/ajukanpermohonan/${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,
nik: data.nik,
alamat: data.alamat,
nomorKk: data.nomorKk,
kategoriId: data.kategoriId,
};
return data;
} else {
throw new Error(result.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error fetching ajukan permohonan:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async update() {
const cek = templateAjukanForm.safeParse(
ajukanPermohonan.edit.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
ajukanPermohonan.edit.loading = true;
const response = await fetch(
`/api/desa/ajukanpermohonan/${this.id}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
nama: this.form.nama,
nik: this.form.nik,
alamat: this.form.alamat,
nomorKk: this.form.nomorKk,
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(result.message || "Ajukan permohonan berhasil diupdate");
await ajukanPermohonan.findMany.load(); // refresh list
return true;
} else {
throw new Error(
result.message || "Gagal mengupdate ajukan permohonan"
);
}
} catch (error) {
console.error("Error updating ajukan permohonan:", error);
toast.error(
error instanceof Error
? error.message
: "Terjadi kesalahan saat update ajukan permohonan"
);
return false;
} finally {
ajukanPermohonan.edit.loading = false;
}
},
},
});
const stateLayananDesa = proxy({ const stateLayananDesa = proxy({
suratKeterangan, suratKeterangan,
pelayananPerizinanBerusaha, pelayananPerizinanBerusaha,
pelayananTelunjukSaktiDesa, pelayananTelunjukSaktiDesa,
pelayananPendudukNonPermanen, pelayananPendudukNonPermanen,
ajukanPermohonan,
}); });
export default stateLayananDesa; export default stateLayananDesa;

View File

@@ -39,7 +39,7 @@ const penghargaanState = proxy({
); );
if (res.status === 200) { if (res.status === 200) {
penghargaanState.findMany.load(); penghargaanState.findMany.load();
return toast.success("success create"); return toast.success("Sukses menambahkan");
} }
console.log(res); console.log(res);
return toast.error("failed create"); return toast.error("failed create");
@@ -56,16 +56,21 @@ const penghargaanState = proxy({
totalPages: 1, totalPages: 1,
total: 0, total: 0,
loading: false, loading: false,
load: async (page = 1, limit = 10) => { // Change to arrow function search: "",
penghargaanState.findMany.loading = true; // Use the full path to access the property load: async (page = 1, limit = 10, search = "") => {
// Change to arrow function
penghargaanState.findMany.loading = true; // Use the full path to access the property
penghargaanState.findMany.page = page; penghargaanState.findMany.page = page;
penghargaanState.findMany.search = search;
try { try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.desa.penghargaan[ const res = await ApiFetch.api.desa.penghargaan[
"find-many" "find-many"
].get({ ].get({
query: { page, limit }, query,
}); });
if (res.status === 200 && res.data?.success) { if (res.status === 200 && res.data?.success) {
penghargaanState.findMany.data = res.data.data || []; penghargaanState.findMany.data = res.data.data || [];
penghargaanState.findMany.total = res.data.total || 0; penghargaanState.findMany.total = res.data.total || 0;

View File

@@ -55,11 +55,39 @@ const category = proxy({
pengumumans: number; pengumumans: number;
}; };
})[], })[],
page: 1,
totalPages: 1,
total: 0,
loading: false, loading: false,
async load() { search: "",
const res = await ApiFetch.api.desa.kategoripengumuman["findMany"].get(); load: async (page = 1, limit = 10, search = "") => { // Change to arrow function
if (res.status === 200) { category.findMany.loading = true; // Use the full path to access the property
category.findMany.data = res.data?.data ?? []; category.findMany.page = page;
category.findMany.search = search;
try {
const res = await ApiFetch.api.desa.kategoripengumuman[
"findMany"
].get({
query: { page, limit },
});
if (res.status === 200 && res.data?.success) {
category.findMany.data = res.data.data || [];
category.findMany.total = res.data.total || 0;
category.findMany.totalPages = res.data.totalPages || 1;
} else {
console.error("Failed to load potensi desa:", res.data?.message);
category.findMany.data = [];
category.findMany.total = 0;
category.findMany.totalPages = 1;
}
} catch (error) {
console.error("Error loading potensi desa:", error);
category.findMany.data = [];
category.findMany.total = 0;
category.findMany.totalPages = 1;
} finally {
category.findMany.loading = false;
} }
}, },
}, },
@@ -259,7 +287,7 @@ const pengumuman = proxy({
); );
if (res.status === 200) { if (res.status === 200) {
pengumuman.findMany.load(); pengumuman.findMany.load();
return toast.success("success create"); return toast.success("Sukses menambahkan");
} }
console.log(res); console.log(res);
return toast.error("failed create"); return toast.error("failed create");

View File

@@ -56,9 +56,11 @@ const potensiDesa = proxy({
totalPages: 1, totalPages: 1,
total: 0, total: 0,
loading: false, loading: false,
load: async (page = 1, limit = 10) => { // Change to arrow function search: "",
load: async (page = 1, limit = 10, search = "") => { // Change to arrow function
potensiDesa.findMany.loading = true; // Use the full path to access the property potensiDesa.findMany.loading = true; // Use the full path to access the property
potensiDesa.findMany.page = page; potensiDesa.findMany.page = page;
potensiDesa.findMany.search = search;
try { try {
const res = await ApiFetch.api.desa.potensi[ const res = await ApiFetch.api.desa.potensi[
"find-many" "find-many"
@@ -298,11 +300,34 @@ const kategoriPotensi = proxy({
isActive: true; isActive: true;
}; };
}>[], }>[],
page: 1,
totalPages: 1,
loading: false, loading: false,
async load() { search: "",
const res = await ApiFetch.api.desa.kategoripotensi["findMany"].get(); load: async (page = 1, limit = 10, search = "") => {
if (res.status === 200) { kategoriPotensi.findMany.loading = true; // ✅ Akses langsung via nama path
kategoriPotensi.findMany.data = res.data?.data ?? []; kategoriPotensi.findMany.page = page;
kategoriPotensi.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.desa.kategoripotensi["findMany"].get({ query });
if (res.status === 200 && res.data?.success) {
kategoriPotensi.findMany.data = res.data.data ?? [];
kategoriPotensi.findMany.totalPages = res.data.totalPages ?? 1;
} else {
kategoriPotensi.findMany.data = [];
kategoriPotensi.findMany.totalPages = 1;
}
} catch (err) {
console.error("Gagal fetch kategori potensi paginated:", err);
kategoriPotensi.findMany.data = [];
kategoriPotensi.findMany.totalPages = 1;
} finally {
kategoriPotensi.findMany.loading = false;
} }
}, },
}, },

View File

@@ -7,9 +7,13 @@ import { z } from "zod";
const templateApbDesa = z.object({ const templateApbDesa = z.object({
tahun: z.number().min(4, "Tahun minimal 4 karakter"), tahun: z.number().min(4, "Tahun minimal 4 karakter"),
pembiayaanIds: z.array(z.string().uuid()).nonempty("Pilih minimal 1 pembiayaan"), pembiayaanIds: z
.array(z.string().uuid())
.nonempty("Pilih minimal 1 pembiayaan"),
belanjaIds: z.array(z.string().uuid()).nonempty("Pilih minimal 1 belanja"), belanjaIds: z.array(z.string().uuid()).nonempty("Pilih minimal 1 belanja"),
pendapatanIds: z.array(z.string().uuid()).nonempty("Pilih minimal 1 pendapatan"), pendapatanIds: z
.array(z.string().uuid())
.nonempty("Pilih minimal 1 pendapatan"),
}); });
const ApbDesaDefaultForm = { const ApbDesaDefaultForm = {
@@ -54,35 +58,81 @@ const ApbDesa = proxy({
}, },
}, },
findMany: { findMany: {
data: null as data: null as
| Prisma.ApbDesaGetPayload<{ | Prisma.ApbDesaGetPayload<{
include: { include: {
pendapatan: true; pendapatan: true;
belanja: true; belanja: true;
pembiayaan: true; pembiayaan: true;
}; };
}>[] }>[]
| null, | null,
page: 1,
totalPages: 1,
loading: false, loading: false,
async load() { search: "",
load: async (page = 1, limit = 10, search = "") => {
ApbDesa.findMany.loading = true; // ✅ Akses langsung via nama path
ApbDesa.findMany.page = page;
ApbDesa.findMany.search = search;
try { try {
this.loading = true; const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.ekonomi.pendapatanaslidesa.apbdesa[ const res = await ApiFetch.api.ekonomi.pendapatanaslidesa.apbdesa[
"find-many" "find-many"
].get(); ].get({ query });
if (res.status === 200) {
this.data = res.data?.data ?? []; if (res.status === 200 && res.data?.success) {
ApbDesa.findMany.data = res.data.data ?? [];
ApbDesa.findMany.totalPages =
res.data.totalPages ?? 1;
} else { } else {
toast.error(res.data?.message || "Gagal mengambil APB Desa"); ApbDesa.findMany.data = [];
ApbDesa.findMany.totalPages = 1;
}
} catch (err) {
console.error("Gagal fetch APB Desa paginated:", err);
ApbDesa.findMany.data = [];
ApbDesa.findMany.totalPages = 1;
} finally {
ApbDesa.findMany.loading = false;
}
},
},
findFirst: {
data: null as Prisma.ApbDesaGetPayload<{
include: { pendapatan: true; belanja: true; pembiayaan: true };
}> | null,
loading: false,
async load(params?: Record<string, any>) {
try {
this.loading = true;
// ✅ request ke endpoint find-first
const res = await ApiFetch.api.ekonomi.pendapatanaslidesa.apbdesa[
"find-first"
].get({ query: params || {} });
if (res.status === 200 && res.data?.success) {
this.data = res.data.data ?? null;
} else {
this.data = null;
toast.error(res.data?.message || "Gagal memuat data pertama APB Desa");
} }
} catch (error) { } catch (error) {
console.error("Find many error:", error); console.error("Error findFirst APB Desa:", error);
toast.error("Gagal mengambil APB Desa"); toast.error("Gagal memuat data APB Desa pertama");
this.data = null;
} finally { } finally {
this.loading = false; this.loading = false;
} }
}, },
}, reset() {
this.data = null;
},
},
update: { update: {
id: "", id: "",
form: { ...ApbDesaDefaultForm }, form: { ...ApbDesaDefaultForm },
@@ -106,13 +156,13 @@ const ApbDesa = proxy({
throw new Error("Gagal mengambil APB Desa"); throw new Error("Gagal mengambil APB Desa");
} }
const result = await response.json(); const result = await response.json();
if (!result.success) { if (!result.success) {
throw new Error(result.message || "Gagal memuat APB Desa"); throw new Error(result.message || "Gagal memuat APB Desa");
} }
const data = result.data; const data = result.data;
this.id = id; this.id = id;
this.form = { this.form = {
tahun: data.tahun || 0, tahun: data.tahun || 0,
@@ -120,7 +170,7 @@ const ApbDesa = proxy({
belanjaIds: data.belanja?.map((b: any) => b.id) || [], belanjaIds: data.belanja?.map((b: any) => b.id) || [],
pembiayaanIds: data.pembiayaan?.map((p: any) => p.id) || [], pembiayaanIds: data.pembiayaan?.map((p: any) => p.id) || [],
}; };
return data; return data;
} catch (error) { } catch (error) {
console.error("Error loading APB Desa:", error); console.error("Error loading APB Desa:", error);
@@ -189,7 +239,7 @@ const ApbDesa = proxy({
data: null as Prisma.ApbDesaGetPayload<{ data: null as Prisma.ApbDesaGetPayload<{
include: { pendapatan: true; belanja: true; pembiayaan: true }; include: { pendapatan: true; belanja: true; pembiayaan: true };
}> | null, }> | null,
async load(id: string) { async load(id: string) {
try { try {
const response = await fetch( const response = await fetch(
@@ -199,11 +249,11 @@ const ApbDesa = proxy({
throw new Error("Gagal mengambil detail APB Desa"); throw new Error("Gagal mengambil detail APB Desa");
} }
const result = await response.json(); const result = await response.json();
if (!result.success) { if (!result.success) {
throw new Error(result.message || "Gagal mengambil data"); throw new Error(result.message || "Gagal mengambil data");
} }
this.data = result.data; // ✅ fix utama di sini this.data = result.data; // ✅ fix utama di sini
return result.data; return result.data;
} catch (error) { } catch (error) {
@@ -264,34 +314,32 @@ const pendapatan = proxy({
data: null as any[] | null, data: null as any[] | null,
page: 1, page: 1,
totalPages: 1, totalPages: 1,
total: 0,
loading: false, loading: false,
load: async (page = 1, limit = 10) => { search: "",
// Change to arrow function load: async (page = 1, limit = 10, search = "") => {
pendapatan.findMany.loading = true; // Use the full path to access the property pendapatan.findMany.loading = true; // ✅ Akses langsung via nama path
pendapatan.findMany.page = page; pendapatan.findMany.page = page;
pendapatan.findMany.search = search;
try { try {
const res = const query: any = { page, limit };
await ApiFetch.api.ekonomi.pendapatanaslidesa.pendapatanasli[ if (search) query.search = search;
"find-many"
].get({ const res = await ApiFetch.api.ekonomi.pendapatanaslidesa.pendapatanasli[
query: { page, limit }, "find-many"
}); ].get({ query });
if (res.status === 200 && res.data?.success) { if (res.status === 200 && res.data?.success) {
pendapatan.findMany.data = res.data.data || []; pendapatan.findMany.data = res.data.data ?? [];
pendapatan.findMany.total = res.data.total || 0; pendapatan.findMany.totalPages =
pendapatan.findMany.totalPages = res.data.totalPages || 1; res.data.totalPages ?? 1;
} else { } else {
console.error("Failed to load pendapatan:", res.data?.message);
pendapatan.findMany.data = []; pendapatan.findMany.data = [];
pendapatan.findMany.total = 0;
pendapatan.findMany.totalPages = 1; pendapatan.findMany.totalPages = 1;
} }
} catch (error) { } catch (err) {
console.error("Error loading pendapatan:", error); console.error("Gagal fetch pendapatan asli desa paginated:", err);
pendapatan.findMany.data = []; pendapatan.findMany.data = [];
pendapatan.findMany.total = 0;
pendapatan.findMany.totalPages = 1; pendapatan.findMany.totalPages = 1;
} finally { } finally {
pendapatan.findMany.loading = false; pendapatan.findMany.loading = false;
@@ -308,12 +356,15 @@ const pendapatan = proxy({
return null; return null;
} }
try { try {
const response = await fetch(`/api/ekonomi/pendapatanaslidesa/pendapatanasli/${id}`, { const response = await fetch(
method: "GET", `/api/ekonomi/pendapatanaslidesa/pendapatanasli/${id}`,
headers: { {
"Content-Type": "application/json", method: "GET",
}, headers: {
}); "Content-Type": "application/json",
},
}
);
if (!response.ok) { if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`); throw new Error(`HTTP error! status: ${response.status}`);
} }
@@ -349,16 +400,19 @@ const pendapatan = proxy({
try { try {
pendapatan.update.loading = true; pendapatan.update.loading = true;
const response = await fetch(`/api/ekonomi/pendapatanaslidesa/pendapatanasli/${this.id}`, { const response = await fetch(
method: "PUT", `/api/ekonomi/pendapatanaslidesa/pendapatanasli/${this.id}`,
headers: { {
"Content-Type": "application/json", method: "PUT",
}, headers: {
body: JSON.stringify({ "Content-Type": "application/json",
name: this.form.name, },
value: this.form.value, body: JSON.stringify({
}), name: this.form.name,
}); value: this.form.value,
}),
}
);
if (!response.ok) { if (!response.ok) {
const errorData = await response.json().catch(() => ({})); const errorData = await response.json().catch(() => ({}));
throw new Error( throw new Error(
@@ -495,23 +549,37 @@ const belanja = proxy({
name: string; name: string;
value: number; value: number;
}>, }>,
page: 1,
totalPages: 1,
loading: false, loading: false,
async load() { search: "",
load: async (page = 1, limit = 10, search = "") => {
belanja.findMany.loading = true; // ✅ Akses langsung via nama path
belanja.findMany.page = page;
belanja.findMany.search = search;
try { try {
this.loading = true; const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.ekonomi.pendapatanaslidesa.belanja[ const res = await ApiFetch.api.ekonomi.pendapatanaslidesa.belanja[
"find-many" "find-many"
].get(); ].get({ query });
if (res.status === 200) {
this.data = res.data?.data ?? []; if (res.status === 200 && res.data?.success) {
belanja.findMany.data = res.data.data ?? [];
belanja.findMany.totalPages =
res.data.totalPages ?? 1;
} else { } else {
toast.error(res.data?.message || "Gagal mengambil Belanja"); belanja.findMany.data = [];
belanja.findMany.totalPages = 1;
} }
} catch (error) { } catch (err) {
console.error("Find many error:", error); console.error("Gagal fetch Belanja paginated:", err);
toast.error("Gagal mengambil Belanja"); belanja.findMany.data = [];
belanja.findMany.totalPages = 1;
} finally { } finally {
this.loading = false; belanja.findMany.loading = false;
} }
}, },
}, },
@@ -525,12 +593,15 @@ const belanja = proxy({
return null; return null;
} }
try { try {
const response = await fetch(`/api/ekonomi/pendapatanaslidesa/belanja/${id}`, { const response = await fetch(
method: "GET", `/api/ekonomi/pendapatanaslidesa/belanja/${id}`,
headers: { {
"Content-Type": "application/json", method: "GET",
}, headers: {
}); "Content-Type": "application/json",
},
}
);
if (!response.ok) { if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`); throw new Error(`HTTP error! status: ${response.status}`);
} }
@@ -566,16 +637,19 @@ const belanja = proxy({
try { try {
belanja.update.loading = true; belanja.update.loading = true;
const response = await fetch(`/api/ekonomi/pendapatanaslidesa/belanja/${this.id}`, { const response = await fetch(
method: "PUT", `/api/ekonomi/pendapatanaslidesa/belanja/${this.id}`,
headers: { {
"Content-Type": "application/json", method: "PUT",
}, headers: {
body: JSON.stringify({ "Content-Type": "application/json",
name: this.form.name, },
value: this.form.value, body: JSON.stringify({
}), name: this.form.name,
}); value: this.form.value,
}),
}
);
if (!response.ok) { if (!response.ok) {
const errorData = await response.json().catch(() => ({})); const errorData = await response.json().catch(() => ({}));
throw new Error( throw new Error(
@@ -710,23 +784,37 @@ const pembiayaan = proxy({
name: string; name: string;
value: number; value: number;
}>, }>,
page: 1,
totalPages: 1,
loading: false, loading: false,
async load() { search: "",
load: async (page = 1, limit = 10, search = "") => {
pembiayaan.findMany.loading = true; // ✅ Akses langsung via nama path
pembiayaan.findMany.page = page;
pembiayaan.findMany.search = search;
try { try {
this.loading = true; const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.ekonomi.pendapatanaslidesa.pembiayaan[ const res = await ApiFetch.api.ekonomi.pendapatanaslidesa.pembiayaan[
"find-many" "find-many"
].get(); ].get({ query });
if (res.status === 200) {
this.data = res.data?.data ?? []; if (res.status === 200 && res.data?.success) {
pembiayaan.findMany.data = res.data.data ?? [];
pembiayaan.findMany.totalPages =
res.data.totalPages ?? 1;
} else { } else {
toast.error(res.data?.message || "Gagal mengambil Pembiayaan"); pembiayaan.findMany.data = [];
pembiayaan.findMany.totalPages = 1;
} }
} catch (error) { } catch (err) {
console.error("Find many error:", error); console.error("Gagal fetch Pembiayaan paginated:", err);
toast.error("Gagal mengambil Pembiayaan"); pembiayaan.findMany.data = [];
pembiayaan.findMany.totalPages = 1;
} finally { } finally {
this.loading = false; pembiayaan.findMany.loading = false;
} }
}, },
}, },
@@ -740,12 +828,15 @@ const pembiayaan = proxy({
return null; return null;
} }
try { try {
const response = await fetch(`/api/ekonomi/pendapatanaslidesa/pembiayaan/${id}`, { const response = await fetch(
method: "GET", `/api/ekonomi/pendapatanaslidesa/pembiayaan/${id}`,
headers: { {
"Content-Type": "application/json", method: "GET",
}, headers: {
}); "Content-Type": "application/json",
},
}
);
if (!response.ok) { if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`); throw new Error(`HTTP error! status: ${response.status}`);
} }
@@ -781,16 +872,19 @@ const pembiayaan = proxy({
try { try {
pembiayaan.update.loading = true; pembiayaan.update.loading = true;
const response = await fetch(`/api/ekonomi/pendapatanaslidesa/pembiayaan/${this.id}`, { const response = await fetch(
method: "PUT", `/api/ekonomi/pendapatanaslidesa/pembiayaan/${this.id}`,
headers: { {
"Content-Type": "application/json", method: "PUT",
}, headers: {
body: JSON.stringify({ "Content-Type": "application/json",
name: this.form.name, },
value: this.form.value, body: JSON.stringify({
}), name: this.form.name,
}); value: this.form.value,
}),
}
);
if (!response.ok) { if (!response.ok) {
const errorData = await response.json().catch(() => ({})); const errorData = await response.json().catch(() => ({}));
throw new Error( throw new Error(

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch"; import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client"; import { Prisma } from "@prisma/client";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
@@ -48,7 +49,7 @@ const demografiPekerjaan = proxy({
if (res.status === 200) { if (res.status === 200) {
const id = res.data?.data?.id; const id = res.data?.data?.id;
if (id) { if (id) {
toast.success("Success create"); toast.success("Sukses menambahkan");
demografiPekerjaan.create.form = { ...defaultForm }; demografiPekerjaan.create.form = { ...defaultForm };
demografiPekerjaan.findMany.load(); demografiPekerjaan.findMany.load();
return id; return id;
@@ -71,12 +72,37 @@ const demografiPekerjaan = proxy({
omit: { isActive: true }; omit: { isActive: true };
}>[] }>[]
| null, | null,
async load() { page: 1,
const res = await ApiFetch.api.ekonomi.demografipekerjaan[ totalPages: 1,
"find-many" loading: false,
].get(); search: "",
if (res.status === 200) { load: async (page = 1, limit = 10, search = "") => {
demografiPekerjaan.findMany.data = res.data?.data ?? []; demografiPekerjaan.findMany.loading = true; // ✅ Akses langsung via nama path
demografiPekerjaan.findMany.page = page;
demografiPekerjaan.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.ekonomi.demografipekerjaan[
"find-many"
].get({ query });
if (res.status === 200 && res.data?.success) {
demografiPekerjaan.findMany.data = res.data.data ?? [];
demografiPekerjaan.findMany.totalPages =
res.data.totalPages ?? 1;
} else {
demografiPekerjaan.findMany.data = [];
demografiPekerjaan.findMany.totalPages = 1;
}
} catch (err) {
console.error("Gagal fetch demografi pekerjaan paginated:", err);
demografiPekerjaan.findMany.data = [];
demografiPekerjaan.findMany.totalPages = 1;
} finally {
demografiPekerjaan.findMany.loading = false;
} }
}, },
}, },
@@ -194,4 +220,4 @@ const demografiPekerjaan = proxy({
}, },
}, },
}); });
export default demografiPekerjaan export default demografiPekerjaan;

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch"; import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client"; import { Prisma } from "@prisma/client";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
@@ -46,7 +47,7 @@ const jumlahPendudukMiskin = proxy({
if (res.status === 200) { if (res.status === 200) {
const id = res.data?.data?.id; const id = res.data?.data?.id;
if (id) { if (id) {
toast.success("Success create"); toast.success("Sukses menambahkan");
jumlahPendudukMiskin.create.form = { jumlahPendudukMiskin.create.form = {
year: 0, year: 0,
totalPoorPopulation: 0, totalPoorPopulation: 0,
@@ -69,16 +70,37 @@ const jumlahPendudukMiskin = proxy({
select: { id: true; year: true; totalPoorPopulation: true }; select: { id: true; year: true; totalPoorPopulation: true };
}>[] }>[]
| null, | null,
loading: false, page: 1,
async load() { totalPages: 1,
const res = await ApiFetch.api.ekonomi.jumlahpendudukmiskin[ loading: false,
"find-many" search: "",
].get(); load: async (page = 1, limit = 10, search = "") => {
if (res.status === 200) { jumlahPendudukMiskin.findMany.loading = true; // ✅ Akses langsung via nama path
jumlahPendudukMiskin.findMany.data = res.data?.data ?? []; jumlahPendudukMiskin.findMany.page = page;
} jumlahPendudukMiskin.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.ekonomi.jumlahpendudukmiskin["find-many"].get({ query });
if (res.status === 200 && res.data?.success) {
jumlahPendudukMiskin.findMany.data = res.data.data ?? [];
jumlahPendudukMiskin.findMany.totalPages = res.data.totalPages ?? 1;
} else {
jumlahPendudukMiskin.findMany.data = [];
jumlahPendudukMiskin.findMany.totalPages = 1;
}
} catch (err) {
console.error("Gagal fetch jumlah penduduk miskin paginated:", err);
jumlahPendudukMiskin.findMany.data = [];
jumlahPendudukMiskin.findMany.totalPages = 1;
} finally {
jumlahPendudukMiskin.findMany.loading = false;
}
},
}, },
},
findUnique: { findUnique: {
data: null as Prisma.GrafikJumlahPendudukMiskinGetPayload<{ data: null as Prisma.GrafikJumlahPendudukMiskinGetPayload<{
select: { id: true; year: true; totalPoorPopulation: true }; select: { id: true; year: true; totalPoorPopulation: true };

View File

@@ -15,7 +15,8 @@ const templateJumlahPengngguran = z.object({
uneducatedUnemployment: z uneducatedUnemployment: z
.number() .number()
.min(1, "Pengangguran tidak pendidikan harus diisi"), .min(1, "Pengangguran tidak pendidikan harus diisi"),
percentageChange: z.number().min(0, "Persentase perubahan harus diisi"), percentageChange: z.number({ invalid_type_error: "Persentase perubahan harus angka" }),
}); });
type JumlahPengangguran = { type JumlahPengangguran = {
@@ -29,7 +30,7 @@ type JumlahPengangguran = {
const jumlahPengangguranForm: JumlahPengangguran = { const jumlahPengangguranForm: JumlahPengangguran = {
month: "", month: "",
year: 0, year: new Date().getFullYear(), // Default to current year
totalUnemployment: 0, totalUnemployment: 0,
educatedUnemployment: 0, educatedUnemployment: 0,
uneducatedUnemployment: 0, uneducatedUnemployment: 0,
@@ -60,13 +61,21 @@ const jumlahPengangguran = proxy({
form: jumlahPengangguranForm, form: jumlahPengangguranForm,
loading: false, loading: false,
async create() { async create() {
const cek = templateJumlahPengngguran.safeParse( // Ensure all number fields are actual numbers
jumlahPengangguran.create.form const formData = {
); ...jumlahPengangguran.create.form,
year: Number(jumlahPengangguran.create.form.year) || new Date().getFullYear(),
totalUnemployment: Number(jumlahPengangguran.create.form.totalUnemployment) || 0,
educatedUnemployment: Number(jumlahPengangguran.create.form.educatedUnemployment) || 0,
uneducatedUnemployment: Number(jumlahPengangguran.create.form.uneducatedUnemployment) || 0,
percentageChange: Number(jumlahPengangguran.create.form.percentageChange) || 0,
};
const cek = templateJumlahPengngguran.safeParse(formData);
if (!cek.success) { if (!cek.success) {
const err = `[${cek.error.issues const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`) .map((v) => `${v.path.join(".")} (${v.message})`)
.join("\n")}] required`; .join("\n")}]`;
toast.error(err); toast.error(err);
return null; return null;
} }
@@ -78,9 +87,9 @@ const jumlahPengangguran = proxy({
].post(jumlahPengangguran.create.form); ].post(jumlahPengangguran.create.form);
if (res.status === 200) { if (res.status === 200) {
const id = res.data?.data?.id; const id = res.data?.id;
if (id) { if (id) {
toast.success("Success create"); toast.success("Sukses menambahkan");
jumlahPengangguran.create.form = { ...jumlahPengangguranForm }; jumlahPengangguran.create.form = { ...jumlahPengangguranForm };
jumlahPengangguran.findMany.load(); jumlahPengangguran.findMany.load();
return id; return id;
@@ -103,16 +112,40 @@ const jumlahPengangguran = proxy({
omit: { isActive: true }; omit: { isActive: true };
}>[] }>[]
| null, | null,
async load() { page: 1,
const res = totalPages: 1,
await ApiFetch.api.ekonomi.jumlahpengangguran.detaildatapengangguran[ loading: false,
"find-many" search: "",
].get(); load: async (page = 1, limit = 10, search = "") => {
if (res.status === 200) { jumlahPengangguran.findMany.loading = true; // ✅ Akses langsung via nama path
jumlahPengangguran.findMany.data = res.data?.data ?? []; jumlahPengangguran.findMany.page = page;
} jumlahPengangguran.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.ekonomi.jumlahpengangguran.detaildatapengangguran[
"find-many"
].get({ query });
if (res.status === 200 && res.data?.success) {
jumlahPengangguran.findMany.data = res.data.data ?? [];
jumlahPengangguran.findMany.totalPages =
res.data.totalPages ?? 1;
} else {
jumlahPengangguran.findMany.data = [];
jumlahPengangguran.findMany.totalPages = 1;
}
} catch (err) {
console.error("Gagal fetch jumlah pengangguran paginated:", err);
jumlahPengangguran.findMany.data = [];
jumlahPengangguran.findMany.totalPages = 1;
} finally {
jumlahPengangguran.findMany.loading = false;
}
},
}, },
},
findUnique: { findUnique: {
data: null as Prisma.DetailDataPengangguranGetPayload<{ data: null as Prisma.DetailDataPengangguranGetPayload<{

View File

@@ -13,6 +13,7 @@ const templateForm = z.object({
gaji: z.string(), gaji: z.string(),
deskripsi: z.string(), deskripsi: z.string(),
kualifikasi: z.string(), kualifikasi: z.string(),
notelp: z.string(),
}); });
const defaultForm = { const defaultForm = {
@@ -23,6 +24,7 @@ const defaultForm = {
gaji: "", gaji: "",
deskripsi: "", deskripsi: "",
kualifikasi: "", kualifikasi: "",
notelp: "",
}; };
const lowonganKerjaState = proxy({ const lowonganKerjaState = proxy({
@@ -45,7 +47,7 @@ const lowonganKerjaState = proxy({
); );
if (res.status === 200) { if (res.status === 200) {
lowonganKerjaState.create.loading = false; lowonganKerjaState.create.loading = false;
return toast.success("success create"); return toast.success("Sukses menambahkan");
} }
console.log(res); console.log(res);
return toast.error("failed create"); return toast.error("failed create");
@@ -179,6 +181,7 @@ const lowonganKerjaState = proxy({
gaji: data.gaji, gaji: data.gaji,
deskripsi: data.deskripsi, deskripsi: data.deskripsi,
kualifikasi: data.kualifikasi, kualifikasi: data.kualifikasi,
notelp: data.notelp,
}; };
return data; return data;
} else { } else {
@@ -218,6 +221,7 @@ const lowonganKerjaState = proxy({
gaji: this.form.gaji, gaji: this.form.gaji,
deskripsi: this.form.deskripsi, deskripsi: this.form.deskripsi,
kualifikasi: this.form.kualifikasi, kualifikasi: this.form.kualifikasi,
notelp: this.form.notelp,
}), }),
}); });
if (!response.ok) { if (!response.ok) {

View File

@@ -12,6 +12,7 @@ const templatePasarDesaForm = z.object({
imageId: z.string().min(1, "Gambar wajib dipilih"), imageId: z.string().min(1, "Gambar wajib dipilih"),
rating: z.number().min(1, "Rating minimal 1"), rating: z.number().min(1, "Rating minimal 1"),
kategoriId: z.array(z.string()).min(1, "Minimal pilih satu kategori"), kategoriId: z.array(z.string()).min(1, "Minimal pilih satu kategori"),
kontak: z.string().min(1, "Kontak wajib diisi"),
}); });
const defaultPasarDesaForm = { const defaultPasarDesaForm = {
@@ -21,6 +22,7 @@ const defaultPasarDesaForm = {
imageId: "", imageId: "",
rating: 0, rating: 0,
kategoriId: [] as string[], kategoriId: [] as string[],
kontak: "",
}; };
const pasarDesa = proxy({ const pasarDesa = proxy({
@@ -188,6 +190,7 @@ const pasarDesa = proxy({
imageId: data.imageId, imageId: data.imageId,
rating: data.rating, rating: data.rating,
kategoriId: data.kategoriId, kategoriId: data.kategoriId,
kontak: data.kontak,
}; };
return data; return data;
} else { } else {
@@ -225,6 +228,7 @@ const pasarDesa = proxy({
imageId: this.form.imageId, imageId: this.form.imageId,
rating: this.form.rating, rating: this.form.rating,
kategoriId: this.form.kategoriId, kategoriId: this.form.kategoriId,
kontak: this.form.kontak,
}), }),
}); });
if (!response.ok) { if (!response.ok) {
@@ -336,6 +340,40 @@ const kategoriProduk = proxy({
} }
}, },
}, },
// ✅ Versi findManyAll (ambil semua tanpa pagination)
findManyAll: {
data: null as
| Prisma.KategoriProdukGetPayload<{
omit: { isActive: true };
}>[]
| null,
loading: false,
search: "",
load: async (search = "") => {
kategoriProduk.findManyAll.loading = true;
kategoriProduk.findManyAll.search = search;
try {
const query: any = {};
if (search) query.search = search;
const res = await ApiFetch.api.ekonomi.kategoriproduk["find-many-all"].get({
query,
});
if (res.status === 200 && res.data?.success) {
kategoriProduk.findManyAll.data = res.data.data ?? [];
} else {
kategoriProduk.findManyAll.data = [];
}
} catch (err) {
console.error("Gagal fetch kategori produk (all):", err);
kategoriProduk.findManyAll.data = [];
} finally {
kategoriProduk.findManyAll.loading = false;
}
},
},
findUnique: { findUnique: {
data: null as Prisma.KategoriProdukGetPayload<{ data: null as Prisma.KategoriProdukGetPayload<{
omit: { isActive: true }; omit: { isActive: true };

View File

@@ -8,7 +8,7 @@ import { z } from "zod";
const templateForm = z.object({ const templateForm = z.object({
nama: z.string().min(1, "Nama minimal 1 karakter"), nama: z.string().min(1, "Nama minimal 1 karakter"),
deskripsi: z.string().min(1, "Deskripsi minimal 1 karakter"), deskripsi: z.string().min(1, "Deskripsi minimal 1 karakter"),
ikonUrl: z.string().optional(), icon: z.string().min(1, "Icon minimal 1 karakter"),
statistik: z.object({ statistik: z.object({
tahun: z.string().min(1, "Tahun minimal 1 karakter"), tahun: z.string().min(1, "Tahun minimal 1 karakter"),
jumlah: z.string().min(1, "Jumlah minimal 1 karakter"), jumlah: z.string().min(1, "Jumlah minimal 1 karakter"),
@@ -18,7 +18,7 @@ const templateForm = z.object({
const defaultForm = { const defaultForm = {
nama: "", nama: "",
deskripsi: "", deskripsi: "",
ikonUrl: "", icon: "",
statistik: { statistik: {
tahun: "", tahun: "",
jumlah: "", jumlah: "",
@@ -45,7 +45,7 @@ const programKemiskinanState = proxy({
); );
if (res.status === 200) { if (res.status === 200) {
programKemiskinanState.findMany.load(); programKemiskinanState.findMany.load();
return toast.success("success create"); return toast.success("Sukses menambahkan");
} }
console.log(res); console.log(res);
return toast.error("failed create"); return toast.error("failed create");
@@ -148,7 +148,7 @@ const programKemiskinanState = proxy({
this.form = { this.form = {
nama: data.nama, nama: data.nama,
deskripsi: data.deskripsi, deskripsi: data.deskripsi,
ikonUrl: data.ikonUrl || "", icon: data.icon,
statistik: { statistik: {
tahun: data.statistik.tahun, tahun: data.statistik.tahun,
jumlah: data.statistik.jumlah, jumlah: data.statistik.jumlah,
@@ -189,7 +189,7 @@ const programKemiskinanState = proxy({
body: JSON.stringify({ body: JSON.stringify({
nama: this.form.nama, nama: this.form.nama,
deskripsi: this.form.deskripsi, deskripsi: this.form.deskripsi,
ikonUrl: this.form.ikonUrl, icon: this.form.icon,
statistik: { statistik: {
tahun: this.form.statistik.tahun, tahun: this.form.statistik.tahun,
jumlah: this.form.statistik.jumlah, jumlah: this.form.statistik.jumlah,

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch"; import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client"; import { Prisma } from "@prisma/client";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
@@ -45,7 +46,7 @@ const grafikSektorUnggulan = proxy({
if (res.status === 200) { if (res.status === 200) {
const id = res.data?.data?.id; const id = res.data?.data?.id;
if (id) { if (id) {
toast.success("Success create"); toast.success("Sukses menambahkan");
grafikSektorUnggulan.create.form = { grafikSektorUnggulan.create.form = {
name: "", name: "",
description: "", description: "",
@@ -76,13 +77,37 @@ const grafikSektorUnggulan = proxy({
}; };
}>[] }>[]
| null, | null,
page: 1,
totalPages: 1,
loading: false, loading: false,
async load() { search: "",
const res = await ApiFetch.api.ekonomi.sektourunggulandesa[ load: async (page = 1, limit = 10, search = "") => {
"find-many" grafikSektorUnggulan.findMany.loading = true; // ✅ Akses langsung via nama path
].get(); grafikSektorUnggulan.findMany.page = page;
if (res.status === 200) { grafikSektorUnggulan.findMany.search = search;
grafikSektorUnggulan.findMany.data = res.data?.data ?? [];
try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.ekonomi.sektourunggulandesa[
"find-many"
].get({ query });
if (res.status === 200 && res.data?.success) {
grafikSektorUnggulan.findMany.data = res.data.data ?? [];
grafikSektorUnggulan.findMany.totalPages =
res.data.totalPages ?? 1;
} else {
grafikSektorUnggulan.findMany.data = [];
grafikSektorUnggulan.findMany.totalPages = 1;
}
} catch (err) {
console.error("Gagal fetch sektor unggulan desa paginated:", err);
grafikSektorUnggulan.findMany.data = [];
grafikSektorUnggulan.findMany.totalPages = 1;
} finally {
grafikSektorUnggulan.findMany.loading = false;
} }
}, },
}, },

View File

@@ -1,9 +1,173 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* 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 ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client"; 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 StrukturBumDesForm = Prisma.StrukturBumDesGetPayload<{
select: {
id: true;
name: true;
imageId: true;
image?: {
select: {
link: true;
};
};
};
}>;
const stateStruktur = proxy({
struktur: {
data: null as StrukturBumDesForm | 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/ekonomi/struktur-organisasi/${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: StrukturBumDesForm) {
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/ekonomi/struktur-organisasi/${this.id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(this.form),
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(
errorData.message || `HTTP error! status: ${response.status}`
);
}
const result = await response.json();
if (result.success) {
toast.success("Berhasil update struktur");
await stateStruktur.struktur.load(this.id);
return true;
} else {
throw new Error(result.message || "Gagal update struktur");
}
} catch (error) {
const errorMessage = (error as Error).message;
this.error = errorMessage;
console.error("Update struktur error:", errorMessage);
toast.error("Terjadi kesalahan saat update struktur");
return false;
} finally {
this.loading = false;
}
},
reset() {
this.id = "";
this.form = { ...defaultForm };
this.error = null;
this.loading = false;
this.isReadOnly = false;
},
},
async loadForEdit(id: string) {
const strukturData = await this.struktur.load(id);
if (strukturData) {
this.editStruktur.initialize(strukturData);
}
return strukturData;
},
reset() {
this.struktur.reset();
this.editStruktur.reset();
},
});
const templatePosisiOrganisasi = z.object({ const templatePosisiOrganisasi = z.object({
nama: z.string().min(1, "Nama harus diisi"), nama: z.string().min(1, "Nama harus diisi"),
@@ -30,9 +194,7 @@ const posisiOrganisasi = proxy({
try { try {
this.loading = true; this.loading = true;
const res = await ApiFetch.api.ekonomi["struktur-organisasi"][ const res = await ApiFetch.api.ekonomi['struktur-organisasi']['posisi-organisasi']['create'].post(this.form);
"posisi-organisasi"
]["create"].post(this.form);
if (res.status === 200) { if (res.status === 200) {
toast.success("Berhasil menambahkan posisi organisasi"); toast.success("Berhasil menambahkan posisi organisasi");
posisiOrganisasi.findMany.load(); posisiOrganisasi.findMany.load();
@@ -52,6 +214,29 @@ const posisiOrganisasi = proxy({
}, },
}, },
findUnique: {
data: null as Prisma.StrukturOrganisasiBumDesGetPayload<{
omit: { isActive: true };
}> | null,
async load(id: string) {
try {
const res = await fetch(
`/api/ekonomi/struktur-organisasi/posisi-organisasi/${id}`
);
if (res.ok) {
const data = await res.json();
posisiOrganisasi.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch posisiOrganisasi:", res.statusText);
posisiOrganisasi.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching posisiOrganisasi:", error);
posisiOrganisasi.findUnique.data = null;
}
},
},
edit: { edit: {
id: "", id: "",
form: { ...posisiOrganisasiDefaultForm }, form: { ...posisiOrganisasiDefaultForm },
@@ -161,22 +346,73 @@ const posisiOrganisasi = proxy({
deskripsi: string | null; deskripsi: string | null;
hierarki: number; hierarki: number;
}>, }>,
async load() { page: 1,
totalPages: 1,
loading: false,
search: "",
load: async (page = 1, limit?: number, search = "") => {
const appliedLimit = limit ?? 10;
posisiOrganisasi.findMany.page = page;
posisiOrganisasi.findMany.search = search;
try { try {
const res = await ApiFetch.api.ekonomi["struktur-organisasi"][ const query: any = { page, limit: appliedLimit };
"posisi-organisasi" if (search) query.search = search;
]["find-many"].get();
if (res.status === 200) { const res = await ApiFetch.api.ekonomi['struktur-organisasi']['posisi-organisasi']['find-many'].get({ query });
// The API now returns the id field, so we can use it directly
this.data = res.data?.data ?? []; if (res.status === 200 && res.data?.success) {
posisiOrganisasi.findMany.data = res.data.data ?? [];
posisiOrganisasi.findMany.totalPages = res.data.totalPages ?? 1;
} else {
posisiOrganisasi.findMany.data = [];
posisiOrganisasi.findMany.totalPages = 1;
} }
} catch (error) { } catch (err) {
console.error("Find many error:", error); console.error("Gagal fetch posisi organisasi paginated:", err);
this.data = []; posisiOrganisasi.findMany.data = [];
posisiOrganisasi.findMany.totalPages = 1;
} finally {
posisiOrganisasi.findMany.loading = false;
} }
}, },
}, },
findManyAll: {
data: [] as Array<{
id: string;
nama: string;
deskripsi: string | null;
hierarki: number;
}>,
loading: false,
search: "",
load: async (search = "") => {
// Change to arrow function
posisiOrganisasi.findManyAll.loading = true; // Use the full path to access the property
posisiOrganisasi.findManyAll.search = search;
try {
const query: any = { search };
if (search) query.search = search;
const res = await ApiFetch.api.ekonomi['struktur-organisasi']['posisi-organisasi']['find-many-all'].get({
query,
});
if (res.status === 200 && res.data?.success) {
posisiOrganisasi.findManyAll.data = res.data.data || [];
} else {
console.error("Failed to load posisiOrganisasi:", res.data?.message);
posisiOrganisasi.findManyAll.data = [];
}
} catch (error) {
console.error("Error loading posisiOrganisasi:", error);
posisiOrganisasi.findManyAll.data = [];
} finally {
posisiOrganisasi.findManyAll.loading = false;
}
},
},
delete: { delete: {
loading: false, loading: false,
async byId(id: string) { async byId(id: string) {
@@ -215,12 +451,12 @@ const posisiOrganisasi = proxy({
const templatePegawai = z.object({ const templatePegawai = z.object({
namaLengkap: z.string().min(1, "Nama wajib diisi"), namaLengkap: z.string().min(1, "Nama wajib diisi"),
gelarAkademik: z.string().optional(), gelarAkademik: z.string().min(1, "Gelar Akademik wajib diisi"),
imageId: z.string().nullable().optional(), imageId: z.string().min(1, "Gambar wajib dipilih"),
tanggalMasuk: z.string().optional(), // ISO format tanggalMasuk: z.string().min(1, "Tanggal masuk wajib diisi"), // ISO format
email: z.string().email("Email tidak valid").optional(), email: z.string().email("Email tidak valid").optional(),
telepon: z.string().optional(), telepon: z.string().min(1, "Telepom wajib diisi"),
alamat: z.string().optional(), alamat: z.string().min(1, "Alamat wajib diisi"),
posisiId: z.string().min(1, "Posisi wajib diisi"), posisiId: z.string().min(1, "Posisi wajib diisi"),
isActive: z.boolean().default(true), isActive: z.boolean().default(true),
}); });
@@ -251,9 +487,9 @@ const pegawai = proxy({
try { try {
pegawai.create.loading = true; pegawai.create.loading = true;
const res = await ApiFetch.api.ekonomi["struktur-organisasi"][ const res = await ApiFetch.api.ekonomi['struktur-organisasi'].pegawai['create'].post(
"pegawai" pegawai.create.form
]["create"].post(pegawai.create.form); );
if (res.status === 200) { if (res.status === 200) {
toast.success("Pegawai berhasil ditambahkan"); toast.success("Pegawai berhasil ditambahkan");
await pegawai.findMany.load(); await pegawai.findMany.load();
@@ -270,45 +506,56 @@ const pegawai = proxy({
}, },
// In struktur-organisasi.ts // In struktur-organisasi.ts
findMany: { findMany: {
data: null as any[] | null, data: null as
page: 1, | Prisma.PegawaiBumDesGetPayload<{
totalPages: 1, include: {
total: 0, image: true;
loading: false, posisi: true;
load: async (page = 1, limit = 10) => { // Change to arrow function };
pegawai.findMany.loading = true; // Use the full path to access the property }>[]
pegawai.findMany.page = page; | null,
try { page: 1,
const res = await ApiFetch.api.ekonomi["struktur-organisasi"][ totalPages: 1,
"pegawai" total: 0,
]["find-many"].get({ loading: false,
query: { page, limit }, search: "",
}); load: async (page = 1, limit = 10, search = "") => {
// Change to arrow function
pegawai.findMany.loading = true; // Use the full path to access the property
pegawai.findMany.page = page;
pegawai.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
if (res.status === 200 && res.data?.success) { const res = await ApiFetch.api.ekonomi['struktur-organisasi'].pegawai['find-many'].get({
pegawai.findMany.data = res.data.data || []; query,
pegawai.findMany.total = res.data.total || 0; });
pegawai.findMany.totalPages = res.data.totalPages || 1;
} else { if (res.status === 200 && res.data?.success) {
console.error("Failed to load pegawai:", res.data?.message); pegawai.findMany.data = res.data.data || [];
pegawai.findMany.total = res.data.total || 0;
pegawai.findMany.totalPages = res.data.totalPages || 1;
} else {
console.error("Failed to load pegawai:", res.data?.message);
pegawai.findMany.data = [];
pegawai.findMany.total = 0;
pegawai.findMany.totalPages = 1;
}
} catch (error) {
console.error("Error loading pegawai:", error);
pegawai.findMany.data = []; pegawai.findMany.data = [];
pegawai.findMany.total = 0; pegawai.findMany.total = 0;
pegawai.findMany.totalPages = 1; pegawai.findMany.totalPages = 1;
} finally {
pegawai.findMany.loading = false;
} }
} catch (error) { },
console.error("Error loading pegawai:", error);
pegawai.findMany.data = [];
pegawai.findMany.total = 0;
pegawai.findMany.totalPages = 1;
} finally {
pegawai.findMany.loading = false;
}
}, },
},
findUnique: { findUnique: {
data: null as data: null as
| (Prisma.PegawaiGetPayload<{ | (Prisma.PegawaiBumDesGetPayload<{
include: { posisi: true; image: true }; include: { posisi: true; image: true };
}> & { isActive: boolean }) }> & { isActive: boolean })
| null, | null,
@@ -334,12 +581,9 @@ findMany: {
if (!id) return toast.warn("ID tidak valid"); if (!id) return toast.warn("ID tidak valid");
try { try {
pegawai.delete.loading = true; pegawai.delete.loading = true;
const res = await fetch( const res = await fetch(`/api/ekonomi/struktur-organisasi/pegawai/del/${id}`, {
`/api/ekonomi/struktur-organisasi/pegawai/del/${id}`, method: "DELETE",
{ });
method: "DELETE",
}
);
const json = await res.json(); const json = await res.json();
if (res.ok) { if (res.ok) {
toast.success(json.message ?? "Berhasil hapus pegawai"); toast.success(json.message ?? "Berhasil hapus pegawai");
@@ -356,6 +600,31 @@ findMany: {
}, },
}, },
nonActive: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
pegawai.nonActive.loading = true;
const res = await fetch(`/api/ekonomi/struktur-organisasi/pegawai/non-active/${id}`, {
method: "DELETE", // biasanya nonActive pakai PATCH
});
const json = await res.json();
if (res.ok) {
toast.success(json.message ?? "Pegawai berhasil dinonaktifkan");
await pegawai.findMany.load(); // refresh data
} else {
toast.error(json.message ?? "Gagal menonaktifkan pegawai");
}
} catch (error) {
console.error("Gagal nonActive:", error);
toast.error("Terjadi kesalahan saat menonaktifkan pegawai");
} finally {
pegawai.nonActive.loading = false;
}
},
},
edit: { edit: {
id: "", id: "",
form: { ...pegawaiDefaultForm }, form: { ...pegawaiDefaultForm },
@@ -368,15 +637,12 @@ findMany: {
} }
try { try {
const response = await fetch( const response = await fetch(`/api/ekonomi/struktur-organisasi/pegawai/${id}`, {
`/api/ekonomi/struktur-organisasi/pegawai/${id}`, method: "GET",
{ headers: {
method: "GET", "Content-Type": "application/json",
headers: { },
"Content-Type": "application/json", });
},
}
);
if (!response.ok) { if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`); throw new Error(`HTTP error! status: ${response.status}`);
@@ -487,299 +753,10 @@ findMany: {
}, },
}); });
// Schema Zod untuk form validasi const stateStrukturBumDes = proxy({
const templateHubunganOrganisasiForm = z.object({ stateStruktur,
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, posisiOrganisasi,
pegawai, pegawai,
hubunganOrganisasi,
}); });
export default strukturorganisasiState; export default stateStrukturBumDes;

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch"; import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client"; import { Prisma } from "@prisma/client";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
@@ -50,7 +51,7 @@ const grafikBerdasarkanUsiaKerjaNganggur = proxy({
if (res.status === 200) { if (res.status === 200) {
const id = res.data?.data?.id; const id = res.data?.data?.id;
if (id) { if (id) {
toast.success("Success create"); toast.success("Sukses menambahkan");
grafikBerdasarkanUsiaKerjaNganggur.create.form = { grafikBerdasarkanUsiaKerjaNganggur.create.form = {
usia18_25: "", usia18_25: "",
usia26_35: "", usia26_35: "",
@@ -75,16 +76,37 @@ const grafikBerdasarkanUsiaKerjaNganggur = proxy({
omit: { isActive: true }; omit: { isActive: true };
}>[] }>[]
| null, | null,
loading: false, page: 1,
async load() { totalPages: 1,
const res = await ApiFetch.api.ekonomi.grafikusiakerjayangmenganggur[ loading: false,
"find-many" search: "",
].get(); load: async (page = 1, limit = 10, search = "") => {
if (res.status === 200) { grafikBerdasarkanUsiaKerjaNganggur.findMany.loading = true; // ✅ Akses langsung via nama path
grafikBerdasarkanUsiaKerjaNganggur.findMany.data = res.data?.data ?? []; grafikBerdasarkanUsiaKerjaNganggur.findMany.page = page;
} grafikBerdasarkanUsiaKerjaNganggur.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.ekonomi.grafikusiakerjayangmenganggur["find-many"].get({ query });
if (res.status === 200 && res.data?.success) {
grafikBerdasarkanUsiaKerjaNganggur.findMany.data = res.data.data ?? [];
grafikBerdasarkanUsiaKerjaNganggur.findMany.totalPages = res.data.totalPages ?? 1;
} else {
grafikBerdasarkanUsiaKerjaNganggur.findMany.data = [];
grafikBerdasarkanUsiaKerjaNganggur.findMany.totalPages = 1;
}
} catch (err) {
console.error("Gagal fetch grafik berdasarkan usia kerja yang menganggur paginated:", err);
grafikBerdasarkanUsiaKerjaNganggur.findMany.data = [];
grafikBerdasarkanUsiaKerjaNganggur.findMany.totalPages = 1;
} finally {
grafikBerdasarkanUsiaKerjaNganggur.findMany.loading = false;
}
},
}, },
},
findUnique: { findUnique: {
data: null as Prisma.GrafikMenganggurBerdasarkanUsiaGetPayload<{ data: null as Prisma.GrafikMenganggurBerdasarkanUsiaGetPayload<{
omit: { isActive: true }; omit: { isActive: true };
@@ -233,7 +255,7 @@ const grafikBerdasarkanPendidikan = proxy({
if (res.status === 200) { if (res.status === 200) {
const id = res.data?.data?.id; const id = res.data?.data?.id;
if (id) { if (id) {
toast.success("Success create"); toast.success("Sukses menambahkan");
grafikBerdasarkanPendidikan.create.form = { grafikBerdasarkanPendidikan.create.form = {
SD: "", SD: "",
SMP: "", SMP: "",
@@ -259,15 +281,36 @@ const grafikBerdasarkanPendidikan = proxy({
omit: { isActive: true }; omit: { isActive: true };
}>[] }>[]
| null, | null,
loading: false, page: 1,
async load() { totalPages: 1,
const res = await ApiFetch.api.ekonomi.grafikmenganggurberdasarkanpendidikan[ loading: false,
"find-many" search: "",
].get(); load: async (page = 1, limit = 10, search = "") => {
if (res.status === 200) { grafikBerdasarkanPendidikan.findMany.loading = true; // ✅ Akses langsung via nama path
grafikBerdasarkanPendidikan.findMany.data = res.data?.data ?? []; grafikBerdasarkanPendidikan.findMany.page = page;
} grafikBerdasarkanPendidikan.findMany.search = search;
},
try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.ekonomi.grafikmenganggurberdasarkanpendidikan["find-many"].get({ query });
if (res.status === 200 && res.data?.success) {
grafikBerdasarkanPendidikan.findMany.data = res.data.data ?? [];
grafikBerdasarkanPendidikan.findMany.totalPages = res.data.totalPages ?? 1;
} else {
grafikBerdasarkanPendidikan.findMany.data = [];
grafikBerdasarkanPendidikan.findMany.totalPages = 1;
}
} catch (err) {
console.error("Gagal fetch grafik berdasarkan pendidikan paginated:", err);
grafikBerdasarkanPendidikan.findMany.data = [];
grafikBerdasarkanPendidikan.findMany.totalPages = 1;
} finally {
grafikBerdasarkanPendidikan.findMany.loading = false;
}
},
}, },
findUnique: { findUnique: {
data: null as Prisma.GrafikMenganggurBerdasarkanPendidikanGetPayload<{ data: null as Prisma.GrafikMenganggurBerdasarkanPendidikanGetPayload<{

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch"; import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client"; import { Prisma } from "@prisma/client";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
@@ -61,10 +62,37 @@ const ajukanIdeInovatifState = proxy({
}; };
}>[] }>[]
| null, | null,
async load() { page: 1,
const res = await ApiFetch.api.inovasi.ajukanideinovatif["find-many"].get(); totalPages: 1,
if (res.status === 200) { loading: false,
ajukanIdeInovatifState.findMany.data = res.data?.data ?? []; search: "",
load: async (page = 1, limit = 10, search = "") => {
ajukanIdeInovatifState.findMany.loading = true; // ✅ Akses langsung via nama path
ajukanIdeInovatifState.findMany.page = page;
ajukanIdeInovatifState.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res =
await ApiFetch.api.inovasi.ajukanideinovatif[
"find-many"
].get({ query });
if (res.status === 200 && res.data?.success) {
ajukanIdeInovatifState.findMany.data = res.data.data ?? [];
ajukanIdeInovatifState.findMany.totalPages = res.data.totalPages ?? 1;
} else {
ajukanIdeInovatifState.findMany.data = [];
ajukanIdeInovatifState.findMany.totalPages = 1;
}
} catch (err) {
console.error("Gagal fetch ajukan ide inovatif paginated:", err);
ajukanIdeInovatifState.findMany.data = [];
ajukanIdeInovatifState.findMany.totalPages = 1;
} finally {
ajukanIdeInovatifState.findMany.loading = false;
} }
}, },
}, },
@@ -97,16 +125,21 @@ const ajukanIdeInovatifState = proxy({
try { try {
ajukanIdeInovatifState.delete.loading = true; ajukanIdeInovatifState.delete.loading = true;
const response = await fetch(`/api/inovasi/ajukanideinovatif/del/${id}`, { const response = await fetch(
method: "DELETE", `/api/inovasi/ajukanideinovatif/del/${id}`,
headers: { {
"Content-Type": "application/json", method: "DELETE",
}, headers: {
}); "Content-Type": "application/json",
},
}
);
const result = await response.json(); const result = await response.json();
if (response.ok) { if (response.ok) {
toast.success(result.message || "Ajukan Ide Inovatif berhasil dihapus"); toast.success(
result.message || "Ajukan Ide Inovatif berhasil dihapus"
);
await ajukanIdeInovatifState.findMany.load(); await ajukanIdeInovatifState.findMany.load();
} else { } else {
toast.error(result?.message || "Gagal menghapus ajukan ide inovatif"); toast.error(result?.message || "Gagal menghapus ajukan ide inovatif");

View File

@@ -37,7 +37,7 @@ const desaDigitalState = proxy({
); );
if (res.status === 200) { if (res.status === 200) {
desaDigitalState.findMany.load(); desaDigitalState.findMany.load();
return toast.success("success create"); return toast.success("Sukses menambahkan");
} }
console.log(res); console.log(res);
return toast.error("failed create"); return toast.error("failed create");

View File

@@ -37,7 +37,7 @@ const infoTeknoState = proxy({
); );
if (res.status === 200) { if (res.status === 200) {
infoTeknoState.findMany.load(); infoTeknoState.findMany.load();
return toast.success("success create"); return toast.success("Sukses menambahkan");
} }
console.log(res); console.log(res);
return toast.error("failed create"); return toast.error("failed create");

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch"; import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client"; import { Prisma } from "@prisma/client";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
@@ -54,19 +55,20 @@ const administrasiOnline = proxy({
}, },
findMany: { findMany: {
data: null as Array< data: null as Array<
Prisma.AdministrasiOnlineGetPayload<{ Prisma.AdministrasiOnlineGetPayload<{
include: { include: {
jenisLayanan: true; jenisLayanan: true;
}; };
}> }>
> | null, > | null,
page: 1, page: 1,
totalPages: 1, totalPages: 1,
loading: false, loading: false,
search: "",
async load(page = 1, limit = 10) { async load(page = 1, limit = 10, search = "") {
administrasiOnline.findMany.loading = true; administrasiOnline.findMany.loading = true;
administrasiOnline.findMany.page = page; administrasiOnline.findMany.page = page;
administrasiOnline.findMany.search = search;
try { try {
const res = const res =
await ApiFetch.api.inovasi.layananonlinedesa.administrasionline[ await ApiFetch.api.inovasi.layananonlinedesa.administrasionline[
@@ -75,6 +77,7 @@ const administrasiOnline = proxy({
query: { query: {
page, page,
limit, limit,
search,
}, },
}); });
@@ -91,10 +94,10 @@ const administrasiOnline = proxy({
}, },
findUnique: { findUnique: {
data: null as Prisma.AdministrasiOnlineGetPayload<{ data: null as Prisma.AdministrasiOnlineGetPayload<{
include: { include: {
jenisLayanan: true; jenisLayanan: true;
}; };
}> | null, }> | null,
async load(id: string) { async load(id: string) {
try { try {
const res = await fetch( const res = await fetch(
@@ -199,13 +202,37 @@ const jenisLayanan = proxy({
nama: string; nama: string;
deskripsi: string; deskripsi: string;
}> | null, }> | null,
async load() { page: 1,
const res = totalPages: 1,
await ApiFetch.api.inovasi.layananonlinedesa.administrasionline.jenislayanan[ loading: false,
"find-many" search: "",
].get(); load: async (page = 1, limit = 10, search = "") => {
if (res.status === 200) { jenisLayanan.findMany.loading = true; // ✅ Akses langsung via nama path
jenisLayanan.findMany.data = res.data?.data ?? []; jenisLayanan.findMany.page = page;
jenisLayanan.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res =
await ApiFetch.api.inovasi.layananonlinedesa.administrasionline.jenislayanan[
"find-many"
].get({ query });
if (res.status === 200 && res.data?.success) {
jenisLayanan.findMany.data = res.data.data ?? [];
jenisLayanan.findMany.totalPages = res.data.totalPages ?? 1;
} else {
jenisLayanan.findMany.data = [];
jenisLayanan.findMany.totalPages = 1;
}
} catch (err) {
console.error("Gagal fetch jenis layanan paginated:", err);
jenisLayanan.findMany.data = [];
jenisLayanan.findMany.totalPages = 1;
} finally {
jenisLayanan.findMany.loading = false;
} }
}, },
}, },
@@ -403,7 +430,9 @@ const templatePengaduanMasyarakatForm = z.object({
nik: z.string().min(1, "NIK minimal 1 karakter"), nik: z.string().min(1, "NIK minimal 1 karakter"),
judulPengaduan: z.string().min(1, "Judul pengaduan minimal 1 karakter"), judulPengaduan: z.string().min(1, "Judul pengaduan minimal 1 karakter"),
lokasiKejadian: z.string().min(1, "Lokasi kejadian minimal 1 karakter"), lokasiKejadian: z.string().min(1, "Lokasi kejadian minimal 1 karakter"),
deskripsiPengaduan: z.string().min(1, "Deskripsi pengaduan minimal 1 karakter"), deskripsiPengaduan: z
.string()
.min(1, "Deskripsi pengaduan minimal 1 karakter"),
jenisPengaduanId: z.string().min(1, "Jenis pengaduan minimal 1 karakter"), jenisPengaduanId: z.string().min(1, "Jenis pengaduan minimal 1 karakter"),
imageId: z.string().min(1, "Image minimal 1 karakter"), imageId: z.string().min(1, "Image minimal 1 karakter"),
}); });
@@ -455,37 +484,42 @@ const pengaduanMasyarakat = proxy({
}, },
findMany: { findMany: {
data: null as Array< data: null as Array<
Prisma.PengaduanMasyarakatGetPayload<{ Prisma.PengaduanMasyarakatGetPayload<{
include: { include: {
jenisPengaduan: true; jenisPengaduan: true;
image: true; image: true;
}; };
}> }>
> | null, > | null,
page: 1, page: 1,
totalPages: 1, totalPages: 1,
loading: false, loading: false,
search: "",
async load(page = 1, limit = 10) { load: async (page = 1, limit = 10, search = "") => {
pengaduanMasyarakat.findMany.loading = true; pengaduanMasyarakat.findMany.loading = true; // ✅ Akses langsung via nama path
pengaduanMasyarakat.findMany.page = page; pengaduanMasyarakat.findMany.page = page;
pengaduanMasyarakat.findMany.search = search;
try { try {
const query: any = { page, limit };
if (search) query.search = search;
const res = const res =
await ApiFetch.api.inovasi.layananonlinedesa.pengaduanmasyarakat[ await ApiFetch.api.inovasi.layananonlinedesa.pengaduanmasyarakat[
"find-many" "find-many"
].get({ ].get({ query });
query: {
page,
limit,
},
});
if (res.status === 200 && res.data?.success) { if (res.status === 200 && res.data?.success) {
pengaduanMasyarakat.findMany.data = res.data.data ?? []; pengaduanMasyarakat.findMany.data = res.data.data ?? [];
pengaduanMasyarakat.findMany.totalPages = res.data.totalPages ?? 1; pengaduanMasyarakat.findMany.totalPages = res.data.totalPages ?? 1;
} else {
pengaduanMasyarakat.findMany.data = [];
pengaduanMasyarakat.findMany.totalPages = 1;
} }
} catch (err) { } catch (err) {
console.error("Gagal fetch pengaduan masyarakat paginated:", err); console.error("Gagal fetch pengaduan masyarakat paginated:", err);
pengaduanMasyarakat.findMany.data = [];
pengaduanMasyarakat.findMany.totalPages = 1;
} finally { } finally {
pengaduanMasyarakat.findMany.loading = false; pengaduanMasyarakat.findMany.loading = false;
} }
@@ -493,11 +527,11 @@ const pengaduanMasyarakat = proxy({
}, },
findUnique: { findUnique: {
data: null as Prisma.PengaduanMasyarakatGetPayload<{ data: null as Prisma.PengaduanMasyarakatGetPayload<{
include: { include: {
jenisPengaduan: true; jenisPengaduan: true;
image: true; image: true;
}; };
}> | null, }> | null,
async load(id: string) { async load(id: string) {
try { try {
const res = await fetch( const res = await fetch(
@@ -507,7 +541,10 @@ const pengaduanMasyarakat = proxy({
const data = await res.json(); const data = await res.json();
pengaduanMasyarakat.findUnique.data = data.data ?? null; pengaduanMasyarakat.findUnique.data = data.data ?? null;
} else { } else {
console.error("Failed to fetch pengaduan masyarakat:", res.statusText); console.error(
"Failed to fetch pengaduan masyarakat:",
res.statusText
);
pengaduanMasyarakat.findUnique.data = null; pengaduanMasyarakat.findUnique.data = null;
} }
} catch (error) { } catch (error) {
@@ -542,7 +579,9 @@ const pengaduanMasyarakat = proxy({
); );
await pengaduanMasyarakat.findMany.load(); // refresh list await pengaduanMasyarakat.findMany.load(); // refresh list
} else { } else {
toast.error(result?.message || "Gagal menghapus pengaduan masyarakat"); toast.error(
result?.message || "Gagal menghapus pengaduan masyarakat"
);
} }
} catch (error) { } catch (error) {
console.error("Gagal delete:", error); console.error("Gagal delete:", error);
@@ -567,7 +606,9 @@ const jenisPengaduan = proxy({
form: { ...defaultJenisPengaduanForm }, form: { ...defaultJenisPengaduanForm },
loading: false, loading: false,
async create() { async create() {
const cek = templateJenisPengaduanForm.safeParse(jenisPengaduan.create.form); const cek = templateJenisPengaduanForm.safeParse(
jenisPengaduan.create.form
);
if (!cek.success) { if (!cek.success) {
const err = `[${cek.error.issues const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`) .map((v) => `${v.path.join(".")}`)
@@ -598,13 +639,37 @@ const jenisPengaduan = proxy({
id: string; id: string;
nama: string; nama: string;
}> | null, }> | null,
async load() { page: 1,
const res = totalPages: 1,
await ApiFetch.api.inovasi.layananonlinedesa.pengaduanmasyarakat.jenispengaduan[ loading: false,
"find-many" search: "",
].get(); load: async (page = 1, limit = 10, search = "") => {
if (res.status === 200) { jenisPengaduan.findMany.loading = true; // ✅ Akses langsung via nama path
jenisPengaduan.findMany.data = res.data?.data ?? []; jenisPengaduan.findMany.page = page;
jenisPengaduan.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res =
await ApiFetch.api.inovasi.layananonlinedesa.pengaduanmasyarakat.jenispengaduan[
"find-many"
].get({ query });
if (res.status === 200 && res.data?.success) {
jenisPengaduan.findMany.data = res.data.data ?? [];
jenisPengaduan.findMany.totalPages = res.data.totalPages ?? 1;
} else {
jenisPengaduan.findMany.data = [];
jenisPengaduan.findMany.totalPages = 1;
}
} catch (err) {
console.error("Gagal fetch jenis pengaduan paginated:", err);
jenisPengaduan.findMany.data = [];
jenisPengaduan.findMany.totalPages = 1;
} finally {
jenisPengaduan.findMany.loading = false;
} }
}, },
}, },
@@ -693,7 +758,7 @@ const jenisPengaduan = proxy({
const data = result.data; const data = result.data;
this.id = data.id; this.id = data.id;
this.form = { this.form = {
nama: data.nama nama: data.nama,
}; };
return data; return data;
} else { } else {
@@ -709,7 +774,9 @@ const jenisPengaduan = proxy({
}, },
async update() { async update() {
const cek = templateJenisPengaduanForm.safeParse(jenisPengaduan.edit.form); const cek = templateJenisPengaduanForm.safeParse(
jenisPengaduan.edit.form
);
if (!cek.success) { if (!cek.success) {
const err = `[${cek.error.issues const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`) .map((v) => `${v.path.join(".")}`)
@@ -759,7 +826,9 @@ const jenisPengaduan = proxy({
await jenisPengaduan.findMany.load(); // refresh list await jenisPengaduan.findMany.load(); // refresh list
return true; return true;
} else { } else {
throw new Error(result.message || "Gagal mengupdate jenis pengaduan"); throw new Error(
result.message || "Gagal mengupdate jenis pengaduan"
);
} }
} catch (error) { } catch (error) {
// If JSON parsing fails, try to get the response text for better error messages // If JSON parsing fails, try to get the response text for better error messages
@@ -792,7 +861,6 @@ const jenisPengaduan = proxy({
}, },
}); });
const layananonlineDesa = proxy({ const layananonlineDesa = proxy({
administrasiOnline, administrasiOnline,
jenisLayanan, jenisLayanan,

View File

@@ -6,9 +6,9 @@ import { proxy } from "valtio";
import { z } from "zod"; import { z } from "zod";
const templateForm = z.object({ const templateForm = z.object({
name: z.string().min(1, "Nama minimal 1 karakter"), name: z.string().min(5, "Nama minimal 5 karakter"),
deskripsi: z.string().min(1, "Deskripsi minimal 1 karakter"), deskripsi: z.string().min(5, "Deskripsi minimal 5 karakter"),
slug: z.string().min(1, "Deskripsi singkat minimal 1 karakter"), slug: z.string().min(5, "Deskripsi singkat minimal 5 karakter"),
icon: z.string().min(1, "Icon minimal 1 karakter"), icon: z.string().min(1, "Icon minimal 1 karakter"),
}); });
@@ -29,59 +29,64 @@ const programKreatifState = proxy({
const err = `[${cek.error.issues const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`) .map((v) => `${v.path.join(".")}`)
.join("\n")}] required`; .join("\n")}] required`;
return toast.error(err); toast.error(err);
return false; // ⬅️ ini penting
} }
try { try {
programKreatifState.create.loading = true; programKreatifState.create.loading = true;
const res = await ApiFetch.api.inovasi.programkreatif["create"].post( const res = await ApiFetch.api.inovasi.programkreatif["create"].post(
programKreatifState.create.form programKreatifState.create.form
); );
if (res.status === 200) { if (res.status === 200) {
programKreatifState.findMany.load(); programKreatifState.findMany.load();
return toast.success("success create"); toast.success("Sukses menambahkan");
return true;
} }
console.log(res);
return toast.error("failed create"); toast.error("failed create");
return false;
} catch (error) { } catch (error) {
console.log((error as Error).message); console.error((error as Error).message);
toast.error("Terjadi kesalahan saat create");
return false;
} finally { } finally {
programKreatifState.create.loading = false; programKreatifState.create.loading = false;
} }
}, }
}, },
findMany: { findMany: {
data: null as any[] | null, data: null as any[] | null,
page: 1, page: 1,
totalPages: 1, totalPages: 1,
total: 0,
loading: false, loading: false,
load: async (page = 1, limit = 10) => { search: "",
// Change to arrow function load: async (page = 1, limit = 10, search = "") => {
programKreatifState.findMany.loading = true; // Use the full path to access the property programKreatifState.findMany.loading = true; // ✅ Akses langsung via nama path
programKreatifState.findMany.page = page; programKreatifState.findMany.page = page;
programKreatifState.findMany.search = search;
try { try {
const res = await ApiFetch.api.inovasi.programkreatif["find-many"].get({ const query: any = { page, limit };
query: { page, limit }, if (search) query.search = search;
});
const res = await ApiFetch.api.inovasi.programkreatif[
"find-many"
].get({ query });
if (res.status === 200 && res.data?.success) { if (res.status === 200 && res.data?.success) {
programKreatifState.findMany.data = res.data.data || []; programKreatifState.findMany.data = res.data.data ?? [];
programKreatifState.findMany.total = res.data.total || 0; programKreatifState.findMany.totalPages =
programKreatifState.findMany.totalPages = res.data.totalPages || 1; res.data.totalPages ?? 1;
} else { } else {
console.error(
"Failed to load grafik berdasarkan jenis kelamin:",
res.data?.message
);
programKreatifState.findMany.data = []; programKreatifState.findMany.data = [];
programKreatifState.findMany.total = 0;
programKreatifState.findMany.totalPages = 1; programKreatifState.findMany.totalPages = 1;
} }
} catch (error) { } catch (err) {
console.error("Error loading grafik berdasarkan jenis kelamin:", error); console.error("Gagal fetch program kreatif paginated:", err);
programKreatifState.findMany.data = []; programKreatifState.findMany.data = [];
programKreatifState.findMany.total = 0;
programKreatifState.findMany.totalPages = 1; programKreatifState.findMany.totalPages = 1;
} finally { } finally {
programKreatifState.findMany.loading = false; programKreatifState.findMany.loading = false;

View File

@@ -37,7 +37,7 @@ const keamananLingkunganState = proxy({
].post(keamananLingkunganState.create.form); ].post(keamananLingkunganState.create.form);
if (res.status === 200) { if (res.status === 200) {
keamananLingkunganState.findMany.load(); keamananLingkunganState.findMany.load();
return toast.success("success create"); return toast.success("Sukses menambahkan");
} }
console.log(res); console.log(res);
return toast.error("failed create"); return toast.error("failed create");

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch"; import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client"; import { Prisma } from "@prisma/client";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
@@ -6,26 +7,14 @@ import { z } from "zod";
const templateForm = z.object({ const templateForm = z.object({
nama: z.string().min(1, "Nama minimal 1 karakter"), nama: z.string().min(1, "Nama minimal 1 karakter"),
imageId: z.string().nonempty(), icon: z.string().nonempty(),
kontakItems: z.array( kategoriId: z.array(z.string()).min(1, "Minimal pilih satu kategori"),
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 = { const defaultForm = {
nama: "", nama: "",
imageId: "", icon: "",
kontakItems: [ kategoriId: [] as string[],
{
nama: "",
nomorTelepon: "",
imageId: "",
},
],
}; };
const kontakDaruratKeamananState = proxy({ const kontakDaruratKeamananState = proxy({
@@ -49,7 +38,7 @@ const kontakDaruratKeamananState = proxy({
].post(kontakDaruratKeamananState.create.form); ].post(kontakDaruratKeamananState.create.form);
if (res.status === 200) { if (res.status === 200) {
kontakDaruratKeamananState.findMany.load(); kontakDaruratKeamananState.findMany.load();
return toast.success("success create"); return toast.success("Sukses menambahkan");
} }
console.log(res); console.log(res);
return toast.error("failed create"); return toast.error("failed create");
@@ -61,20 +50,49 @@ const kontakDaruratKeamananState = proxy({
}, },
}, },
findMany: { findMany: {
data: null as data: null as Array<
| Prisma.KontakDaruratKeamananGetPayload<{ Prisma.KontakDaruratKeamananGetPayload<{
include: { include: {
kontakItems: true; kategori: true;
image: true; kontakItems: {
include: {
kontakItem: true;
};
}; };
}>[] };
| null, }>
async load() { > | null,
const res = await ApiFetch.api.keamanan.kontakdaruratkeamanan[ page: 1,
"find-many" totalPages: 1,
].get(); loading: false,
if (res.status === 200) { search: "",
kontakDaruratKeamananState.findMany.data = res.data?.data ?? []; load: async (page = 1, limit = 10, search = "") => {
kontakDaruratKeamananState.findMany.loading = true; // ✅ Akses langsung via nama path
kontakDaruratKeamananState.findMany.page = page;
kontakDaruratKeamananState.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.keamanan.kontakdaruratkeamanan[
"findMany"
].get({ query });
if (res.status === 200 && res.data?.success) {
kontakDaruratKeamananState.findMany.data = res.data.data ?? [];
kontakDaruratKeamananState.findMany.totalPages =
res.data.totalPages ?? 1;
} else {
kontakDaruratKeamananState.findMany.data = [];
kontakDaruratKeamananState.findMany.totalPages = 1;
}
} catch (err) {
console.error("Gagal fetch kontak darurat paginated:", err);
kontakDaruratKeamananState.findMany.data = [];
kontakDaruratKeamananState.findMany.totalPages = 1;
} finally {
kontakDaruratKeamananState.findMany.loading = false;
} }
}, },
}, },
@@ -83,10 +101,10 @@ const kontakDaruratKeamananState = proxy({
include: { include: {
kontakItems: { kontakItems: {
include: { include: {
image: true; kontakItem: true;
}; };
}; };
image: true; kategori: true;
}; };
}> | null, }> | null,
loading: false, loading: false,
@@ -168,14 +186,9 @@ const kontakDaruratKeamananState = proxy({
this.id = data.id; this.id = data.id;
this.form = { this.form = {
nama: data.nama, nama: data.nama,
imageId: data.imageId, icon: data.icon || "",
kontakItems: [ kategoriId:
{ data.kontakItems?.map((item: any) => item.kontakItemId) || [],
nama: data.kontakItems.nama,
nomorTelepon: data.kontakItems.nomorTelepon,
imageId: data.kontakItems.imageId,
},
],
}; };
return data; return data;
} else { } else {
@@ -212,14 +225,8 @@ const kontakDaruratKeamananState = proxy({
}, },
body: JSON.stringify({ body: JSON.stringify({
nama: this.form.nama, nama: this.form.nama,
imageId: this.form.imageId, icon: this.form.icon,
kontakItems: [ kategoriId: this.form.kategoriId,
{
nama: this.form.kontakItems[0].nama,
nomorTelepon: this.form.kontakItems[0].nomorTelepon,
imageId: this.form.kontakItems[0].imageId,
},
],
}), }),
} }
); );
@@ -256,4 +263,242 @@ const kontakDaruratKeamananState = proxy({
}, },
}); });
export default kontakDaruratKeamananState; const templateFormItem = z.object({
nama: z.string().min(1, "Nama minimal 1 karakter"),
nomorTelepon: z.string().min(1, "Nomor Telepon minimal 1 karakter"),
icon: z.string().nonempty(),
});
const defaultFormItem = {
nama: "",
nomorTelepon: "",
icon: "",
};
const kontakDaruratItem = proxy({
create: {
form: { ...defaultFormItem },
loading: false,
async create() {
const cek = templateFormItem.safeParse(kontakDaruratItem.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
kontakDaruratItem.create.loading = true;
const res = await ApiFetch.api.keamanan.kontakitem["create"].post(
kontakDaruratItem.create.form
);
if (res.status === 200) {
kontakDaruratItem.findMany.load();
return toast.success("Sukses menambahkan");
}
console.log(res);
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
kontakDaruratItem.create.loading = false;
}
},
},
findMany: {
data: null as Array<
Prisma.KontakItemGetPayload<{
omit: {
isActive: true;
};
}>
> | null,
page: 1,
totalPages: 1,
loading: false,
search: "",
load: async (page = 1, limit = 10, search = "") => {
kontakDaruratItem.findMany.loading = true; // ✅ Akses langsung via nama path
kontakDaruratItem.findMany.page = page;
kontakDaruratItem.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.keamanan.kontakitem["find-many"].get({
query,
});
if (res.status === 200 && res.data?.success) {
kontakDaruratItem.findMany.data = res.data.data ?? [];
kontakDaruratItem.findMany.totalPages = res.data.totalPages ?? 1;
} else {
kontakDaruratItem.findMany.data = [];
kontakDaruratItem.findMany.totalPages = 1;
}
} catch (err) {
console.error("Gagal fetch kontak darurat paginated:", err);
kontakDaruratItem.findMany.data = [];
kontakDaruratItem.findMany.totalPages = 1;
} finally {
kontakDaruratItem.findMany.loading = false;
}
},
},
findUnique: {
data: null as Prisma.KontakItemGetPayload<{
omit: {
isActive: true;
};
}> | null,
loading: false,
async load(id: string) {
try {
const res = await fetch(`/api/keamanan/kontakitem/${id}`);
if (res.ok) {
const data = await res.json();
kontakDaruratItem.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
kontakDaruratItem.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching data:", error);
kontakDaruratItem.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
kontakDaruratItem.delete.loading = true;
const response = await fetch(`/api/keamanan/kontakitem/del/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Kontak item berhasil dihapus");
await kontakDaruratItem.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus kontak item");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus kontak item");
} finally {
kontakDaruratItem.delete.loading = false;
}
},
},
update: {
id: "",
form: { ...defaultFormItem },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(`/api/keamanan/kontakitem/${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,
nomorTelepon: data.nomorTelepon,
icon: data.icon,
};
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 = templateFormItem.safeParse(kontakDaruratItem.update.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
kontakDaruratItem.update.loading = true;
const response = await fetch(`/api/keamanan/kontakitem/${this.id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
nama: this.form.nama,
nomorTelepon: this.form.nomorTelepon,
icon: this.form.icon,
}),
});
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 item");
await kontakDaruratItem.findMany.load(); // refresh list
return true;
} else {
throw new Error(result.message || "Gagal mengupdate kontak item");
}
} catch (error) {
console.error("Error updating kontak item:", error);
toast.error(
error instanceof Error
? error.message
: "Gagal mengupdate kontak item"
);
return false;
} finally {
kontakDaruratItem.update.loading = false;
}
},
reset() {
kontakDaruratItem.update.id = "";
kontakDaruratItem.update.form = { ...defaultFormItem };
},
},
});
const kontakDarurat = proxy({
kontakDaruratKeamananState,
kontakDaruratItem,
});
export default kontakDarurat;

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch"; import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client"; import { Prisma } from "@prisma/client";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
@@ -10,12 +11,24 @@ const templateForm = z.object({
judul: z.string().min(3, "Judul minimal 3 karakter"), judul: z.string().min(3, "Judul minimal 3 karakter"),
lokasi: z.string().min(3, "Lokasi minimal 3 karakter"), lokasi: z.string().min(3, "Lokasi minimal 3 karakter"),
tanggalWaktu: z.string().min(3, "Tanggal Waktu 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(), kronologi: z.string().optional(),
}); });
interface FormData { interface FormData {
judul: string;
lokasi: string;
tanggalWaktu: string;
kronologi: string;
}
const defaultForm: FormData = {
judul: "",
lokasi: "",
tanggalWaktu: new Date().toISOString(),
kronologi: "",
};
interface FormEditData {
judul: string; judul: string;
lokasi: string; lokasi: string;
tanggalWaktu: string; tanggalWaktu: string;
@@ -24,15 +37,16 @@ interface FormData {
kronologi: string; kronologi: string;
} }
const defaultForm: FormData = { const editForm: FormEditData = {
judul: "", judul: "",
lokasi: "", lokasi: "",
tanggalWaktu: new Date().toISOString(), tanggalWaktu: new Date().toISOString(),
kronologi: "",
status: "Proses", status: "Proses",
penanganan: "", penanganan: "",
kronologi: "",
}; };
const laporanPublikState = proxy({ const laporanPublikState = proxy({
create: { create: {
form: { ...defaultForm }, form: { ...defaultForm },
@@ -74,7 +88,7 @@ const laporanPublikState = proxy({
if (res.status === 200) { if (res.status === 200) {
laporanPublikState.findMany.load(); laporanPublikState.findMany.load();
return toast.success("success create"); return toast.success("Sukses menambahkan");
} }
console.log(res); console.log(res);
@@ -97,13 +111,37 @@ const laporanPublikState = proxy({
include: { penanganan: true }; include: { penanganan: true };
}>[] }>[]
| null, | null,
async load() { page: 1,
const res = await ApiFetch.api.keamanan.laporanpublik["find-many"].get(); totalPages: 1,
if (res.status === 200) { loading: false,
laporanPublikState.findMany.data = res.data?.data ?? []; search: "",
} load: async (page = 1, limit = 10, search = "") => {
laporanPublikState.findMany.loading = true; // ✅ Akses langsung via nama path
laporanPublikState.findMany.page = page;
laporanPublikState.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.keamanan.laporanpublik["find-many"].get({ query });
if (res.status === 200 && res.data?.success) {
laporanPublikState.findMany.data = res.data.data ?? [];
laporanPublikState.findMany.totalPages = res.data.totalPages ?? 1;
} else {
laporanPublikState.findMany.data = [];
laporanPublikState.findMany.totalPages = 1;
}
} catch (err) {
console.error("Gagal fetch laporan publik paginated:", err);
laporanPublikState.findMany.data = [];
laporanPublikState.findMany.totalPages = 1;
} finally {
laporanPublikState.findMany.loading = false;
}
},
}, },
},
findUnique: { findUnique: {
data: null as Prisma.LaporanPublikGetPayload<{ data: null as Prisma.LaporanPublikGetPayload<{
include: { penanganan: true }; include: { penanganan: true };
@@ -160,7 +198,7 @@ const laporanPublikState = proxy({
}, },
edit: { edit: {
id: "", id: "",
form: { ...defaultForm }, form: { ...editForm },
loading: false, loading: false,
async load(id: string) { async load(id: string) {
if (!id) { if (!id) {
@@ -266,7 +304,7 @@ const laporanPublikState = proxy({
}, },
reset() { reset() {
laporanPublikState.edit.id = ""; laporanPublikState.edit.id = "";
laporanPublikState.edit.form = { ...defaultForm }; laporanPublikState.edit.form = { ...editForm };
}, },
} }
}); });

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch"; import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client"; import { Prisma } from "@prisma/client";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
@@ -5,45 +6,17 @@ import { proxy } from "valtio";
import { z } from "zod"; import { z } from "zod";
const templateForm = z.object({ const templateForm = z.object({
pencegahanKriminalitas: z.object({ judul: z.string().min(1, "Judul minimal 1 karakter"),
programKeamanan: z.object({ deskripsi: z.string().min(1, "Deskripsi minimal 1 karakter"),
nama: z.string().min(1, "Nama minimal 1 karakter"), deskripsiSingkat: z.string().min(1, "Deskripsi singkat minimal 1 karakter"),
deskripsi: z.string().min(1, "Deskripsi minimal 1 karakter"), linkVideo: z.string().min(1, "Link video 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 = { const defaultForm = {
pencegahanKriminalitas: { judul: "",
programKeamanan: { deskripsi: "",
nama: "", deskripsiSingkat: "",
deskripsi: "", linkVideo: "",
slug: "",
},
tipsKeamanan: {
judul: "",
konten: "",
slug: "",
},
videoKeamanan: {
judul: "",
deskripsi: "",
videoUrl: "",
slug: "",
},
},
}; };
const pencegahanKriminalitasState = proxy({ const pencegahanKriminalitasState = proxy({
@@ -64,10 +37,10 @@ const pencegahanKriminalitasState = proxy({
pencegahanKriminalitasState.create.loading = true; pencegahanKriminalitasState.create.loading = true;
const res = await ApiFetch.api.keamanan.pencegahankriminalitas[ const res = await ApiFetch.api.keamanan.pencegahankriminalitas[
"create" "create"
].post(pencegahanKriminalitasState.create.form.pencegahanKriminalitas); ].post(pencegahanKriminalitasState.create.form);
if (res.status === 200) { if (res.status === 200) {
pencegahanKriminalitasState.findMany.load(); pencegahanKriminalitasState.findMany.load();
return toast.success("success create"); return toast.success("Sukses menambahkan");
} }
console.log(res); console.log(res);
return toast.error("failed create"); return toast.error("failed create");
@@ -81,29 +54,46 @@ const pencegahanKriminalitasState = proxy({
findMany: { findMany: {
data: null as data: null as
| Prisma.PencegahanKriminalitasGetPayload<{ | Prisma.PencegahanKriminalitasGetPayload<{
include: { omit: { isActive: true };
programKeamanan: true;
tipsKeamanan: true;
videoKeamanan: true;
};
}>[] }>[]
| null, | null,
async load() { page: 1,
const res = await ApiFetch.api.keamanan.pencegahankriminalitas[ totalPages: 1,
"find-many" loading: false,
].get(); search: "",
if (res.status === 200) { load: async (page = 1, limit = 10, search = "") => {
pencegahanKriminalitasState.findMany.data = res.data?.data ?? []; pencegahanKriminalitasState.findMany.loading = true; // ✅ Akses langsung via nama path
pencegahanKriminalitasState.findMany.page = page;
pencegahanKriminalitasState.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.keamanan.pencegahankriminalitas[
"find-many"
].get({ query });
if (res.status === 200 && res.data?.success) {
pencegahanKriminalitasState.findMany.data = res.data.data ?? [];
pencegahanKriminalitasState.findMany.totalPages =
res.data.totalPages ?? 1;
} else {
pencegahanKriminalitasState.findMany.data = [];
pencegahanKriminalitasState.findMany.totalPages = 1;
}
} catch (err) {
console.error("Gagal fetch pencegahan kriminalitas paginated:", err);
pencegahanKriminalitasState.findMany.data = [];
pencegahanKriminalitasState.findMany.totalPages = 1;
} finally {
pencegahanKriminalitasState.findMany.loading = false;
} }
}, },
}, },
findUnique: { findUnique: {
data: null as Prisma.PencegahanKriminalitasGetPayload<{ data: null as Prisma.PencegahanKriminalitasGetPayload<{
include: { omit: { isActive: true };
programKeamanan: true;
tipsKeamanan: true;
videoKeamanan: true;
};
}> | null, }> | null,
loading: false, loading: false,
async load(id: string) { async load(id: string) {
@@ -122,6 +112,30 @@ const pencegahanKriminalitasState = proxy({
} }
}, },
}, },
findFirst: {
data: null as Prisma.PencegahanKriminalitasGetPayload<{
omit: { isActive: true };
}> | null,
loading: false,
// findFirst.load()
async load() {
this.loading = true;
try {
const res = await ApiFetch.api.keamanan.pencegahankriminalitas["find-first"].get();
if (res.status === 200 && res.data?.success) {
this.data = res.data.data || null;
} else {
this.data = null;
}
} catch (err) {
console.error("Gagal fetch pencegahan kriminalitas terbaru:", err);
this.data = null;
} finally {
this.loading = false;
}
},
},
delete: { delete: {
loading: false, loading: false,
async byId(id: string) { async byId(id: string) {
@@ -187,24 +201,10 @@ const pencegahanKriminalitasState = proxy({
const data = result.data; const data = result.data;
pencegahanKriminalitasState.update.id = data.id; pencegahanKriminalitasState.update.id = data.id;
pencegahanKriminalitasState.update.form = { pencegahanKriminalitasState.update.form = {
pencegahanKriminalitas: { judul: data.judul,
programKeamanan: { deskripsi: data.deskripsi,
nama: data.programKeamanan.nama, deskripsiSingkat: data.deskripsiSingkat,
deskripsi: data.programKeamanan.deskripsi, linkVideo: data.linkVideo,
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; return data;
} else { } else {
@@ -240,40 +240,11 @@ const pencegahanKriminalitasState = proxy({
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify({ body: JSON.stringify({
pencegahanKriminalitas: { judul: pencegahanKriminalitasState.update.form.judul,
programKeamanan: { deskripsi: pencegahanKriminalitasState.update.form.deskripsi,
nama: pencegahanKriminalitasState.update.form deskripsiSingkat:
.pencegahanKriminalitas.programKeamanan.nama, pencegahanKriminalitasState.update.form.deskripsiSingkat,
deskripsi: linkVideo: pencegahanKriminalitasState.update.form.linkVideo,
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,
},
},
}), }),
} }
); );
@@ -311,4 +282,4 @@ const pencegahanKriminalitasState = proxy({
}, },
}, },
}); });
export default pencegahanKriminalitasState; export default pencegahanKriminalitasState;

View File

@@ -37,7 +37,7 @@ const tipsKeamananState = proxy({
); );
if (res.status === 200) { if (res.status === 200) {
tipsKeamananState.findMany.load(); tipsKeamananState.findMany.load();
return toast.success("success create"); return toast.success("Sukses menambahkan");
} }
console.log(res); console.log(res);
return toast.error("failed create"); return toast.error("failed create");

View File

@@ -31,11 +31,13 @@ const templateForm = z.object({
doctorSign: z.object({ doctorSign: z.object({
content: z.string().min(1, "Content harus diisi"), content: z.string().min(1, "Content harus diisi"),
}), }),
imageId: z.string().min(1, "Image ID harus diisi"),
}); });
const defaultForm = { const defaultForm = {
title: "", title: "",
content: "", content: "",
imageId: "",
introduction: { introduction: {
content: "", content: "",
}, },
@@ -59,6 +61,7 @@ const defaultForm = {
doctorSign: { doctorSign: {
content: "", content: "",
}, },
}; };
const artikelKesehatanState = proxy({ const artikelKesehatanState = proxy({
@@ -112,30 +115,42 @@ const artikelKesehatanState = proxy({
firstaid: true; firstaid: true;
mythvsfact: true; mythvsfact: true;
doctorsign: true; doctorsign: true;
image: true;
}; };
}>[] }>[]
| null, | null,
page: 1,
totalPages: 1,
loading: false, loading: false,
async load() { search: "",
load: async (page = 1, limit = 10, search = "") => {
artikelKesehatanState.findMany.loading = true; // ✅ Akses langsung via nama path
artikelKesehatanState.findMany.page = page;
artikelKesehatanState.findMany.search = search;
try { try {
this.loading = true; const query: any = { page, limit };
const res = await (ApiFetch.api.kesehatan as any)["artikel-kesehatan"][ if (search) query.search = search;
const res = await ApiFetch.api.kesehatan["artikel-kesehatan"][
"find-many" "find-many"
].get(); ].get({ query });
if (res.status === 200) { if (res.status === 200 && res.data?.success) {
this.data = res.data?.data ?? []; artikelKesehatanState.findMany.data =
res.data.data ?? [];
artikelKesehatanState.findMany.totalPages =
res.data.totalPages ?? 1;
} else { } else {
toast.error("Gagal memuat data artikel kesehatan"); artikelKesehatanState.findMany.data = [];
artikelKesehatanState.findMany.totalPages = 1;
} }
return res;
} catch (err) { } catch (err) {
toast.error("Terjadi error saat load data"); console.error("Gagal fetch artikel kesehatan paginated:", err);
console.error("LOAD ERROR:", err); artikelKesehatanState.findMany.data = [];
throw err; artikelKesehatanState.findMany.totalPages = 1;
} finally { } finally {
this.loading = false; artikelKesehatanState.findMany.loading = false;
} }
}, },
}, },
@@ -148,6 +163,7 @@ const artikelKesehatanState = proxy({
firstaid: true; firstaid: true;
mythvsfact: true; mythvsfact: true;
doctorsign: true; doctorsign: true;
image: true;
}; };
}> | null, }> | null,
loading: false, loading: false,
@@ -202,6 +218,7 @@ const artikelKesehatanState = proxy({
doctorSign: { doctorSign: {
content: data.doctorsign.content, content: data.doctorsign.content,
}, },
imageId: data.imageId,
}; };
}, },
async submit() { async submit() {
@@ -242,6 +259,7 @@ const artikelKesehatanState = proxy({
doctorSign: { doctorSign: {
content: artikelKesehatanState.edit.form.doctorSign.content, content: artikelKesehatanState.edit.form.doctorSign.content,
}, },
imageId: artikelKesehatanState.edit.form.imageId,
}; };
const res = await fetch( const res = await fetch(
@@ -280,12 +298,9 @@ const artikelKesehatanState = proxy({
async byId(id: string) { async byId(id: string) {
try { try {
artikelKesehatanState.delete.loading = true; artikelKesehatanState.delete.loading = true;
const res = await fetch( const res = await fetch(`/api/kesehatan/artikel-kesehatan/del/${id}`, {
`/api/kesehatan/artikel-kesehatan/del/${id}`, method: "DELETE",
{ });
method: "DELETE",
}
);
const result = await res.json(); const result = await res.json();
if (res.ok && result.success) { if (res.ok && result.success) {

View File

@@ -9,29 +9,30 @@ import { z } from "zod";
// Validasi form // Validasi form
const templateForm = z.object({ const templateForm = z.object({
name: z.string().min(1, "Nama harus diisi"), name: z.string().min(1, "Nama harus diisi"),
informasiUmum: z.object({ informasiUmum: z.object({
fasilitas: z.string().min(1, "Fasilitas harus diisi"), fasilitas: z.string().min(1),
alamat: z.string().min(1, "Alamat harus diisi"), alamat: z.string().min(1),
jamOperasional: z.string().min(1, "Jam operasional harus diisi"), jamOperasional: z.string().min(1),
}), }),
layananUnggulan: z.object({ layananUnggulan: z.object({
content: z.string().min(1, "Layanan unggulan harus diisi"), content: z.string().min(1),
}),
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"),
}), }),
// NOW ARRAY OF STRING (ID)
dokterdanTenagaMedis: z.array(z.string()).min(1, "Minimal pilih 1 dokter"),
fasilitasPendukung: z.object({ fasilitasPendukung: z.object({
content: z.string().min(1, "Fasilitas pendukung harus diisi"), content: z.string().min(1),
}), }),
prosedurPendaftaran: z.object({ prosedurPendaftaran: z.object({
content: z.string().min(1, "Prosedur pendaftaran harus diisi"), content: z.string().min(1),
}),
tarifDanLayanan: z.object({
layanan: z.string().min(1, "Layanan harus diisi"),
tarif: z.string().min(1, "Tarif harus diisi"),
}), }),
// NOW ARRAY OF STRING (ID)
tarifDanLayanan: z.array(z.string()).min(1, "Minimal pilih 1 tarif"),
}); });
// Default form kosong // Default form kosong
@@ -45,21 +46,34 @@ const defaultForm = {
layananUnggulan: { layananUnggulan: {
content: "", content: "",
}, },
dokterdanTenagaMedis: {
name: "", dokterdanTenagaMedis: [] as string[], // ← array kosong
specialist: "", tarifDanLayanan: [] as string[], // ← array kosong
jadwal: "",
},
fasilitasPendukung: { fasilitasPendukung: {
content: "", content: "",
}, },
prosedurPendaftaran: { prosedurPendaftaran: {
content: "", content: "",
}, },
tarifDanLayanan: { };
layanan: "",
tarif: "", type DokterItem = {
}, id: string;
name: string;
specialist: string;
jadwal: string;
jadwalLibur: string;
jamBukaOperasional: string;
jamTutupOperasional: string;
jamBukaLibur: string;
jamTutupLibur: string;
};
type TarifItem = {
id: string;
layanan: string;
tarif: string;
}; };
const fasilitasKesehatan = proxy({ const fasilitasKesehatan = proxy({
@@ -116,27 +130,38 @@ const fasilitasKesehatan = proxy({
}; };
}>[] }>[]
| null, | null,
page: 1,
totalPages: 1,
loading: false, loading: false,
async load() { search: "",
load: async (page = 1, limit = 10, search = "") => {
fasilitasKesehatanState.fasilitasKesehatan.findMany.loading = true; // ✅ Akses langsung via nama path
fasilitasKesehatanState.fasilitasKesehatan.findMany.page = page;
fasilitasKesehatanState.fasilitasKesehatan.findMany.search = search;
try { try {
this.loading = true; const query: any = { page, limit };
const res = await (ApiFetch.api.kesehatan as any)[ if (search) query.search = search;
"fasilitas-kesehatan"
]["find-many"].get();
if (res.status === 200) { const res = await ApiFetch.api.kesehatan["fasilitas-kesehatan"][
this.data = res.data?.data ?? []; "find-many"
].get({ query });
if (res.status === 200 && res.data?.success) {
fasilitasKesehatanState.fasilitasKesehatan.findMany.data =
res.data.data ?? [];
fasilitasKesehatanState.fasilitasKesehatan.findMany.totalPages =
res.data.totalPages ?? 1;
} else { } else {
toast.error("Gagal memuat data fasilitas kesehatan"); fasilitasKesehatanState.fasilitasKesehatan.findMany.data = [];
fasilitasKesehatanState.fasilitasKesehatan.findMany.totalPages = 1;
} }
return res;
} catch (err) { } catch (err) {
toast.error("Terjadi error saat load data"); console.error("Gagal fetch fasilitas kesehatan paginated:", err);
console.error("LOAD ERROR:", err); fasilitasKesehatanState.fasilitasKesehatan.findMany.data = [];
throw err; fasilitasKesehatanState.fasilitasKesehatan.findMany.totalPages = 1;
} finally { } finally {
this.loading = false; fasilitasKesehatanState.fasilitasKesehatan.findMany.loading = false;
} }
}, },
}, },
@@ -175,33 +200,26 @@ const fasilitasKesehatan = proxy({
const result = await res.json(); const result = await res.json();
const data = result.data; const data = result.data;
this.id = data.id;
fasilitasKesehatan.edit.id = data.id; this.form = {
fasilitasKesehatan.edit.form = {
name: data.name, name: data.name,
informasiUmum: { informasiUmum: {
fasilitas: data.informasiumum.fasilitas, fasilitas: data.informasiumum.fasilitas,
alamat: data.informasiumum.alamat, alamat: data.informasiumum.alamat,
jamOperasional: data.informasiumum.jamOperasional, jamOperasional: data.informasiumum.jamOperasional,
}, },
layananUnggulan: {
content: data.layananunggulan.content,
},
dokterdanTenagaMedis: {
name: data.dokterdantenagamedis.name,
specialist: data.dokterdantenagamedis.specialist,
jadwal: data.dokterdantenagamedis.jadwal,
},
fasilitasPendukung: { fasilitasPendukung: {
content: data.fasilitaspendukung.content, content: data.fasilitaspendukung.content,
}, },
prosedurPendaftaran: { prosedurPendaftaran: {
content: data.prosedurpendaftaran.content, content: data.prosedurpendaftaran.content,
}, },
tarifDanLayanan: { // map relasi -> array of IDs
layanan: data.tarifdanlayanan.layanan, layananUnggulan: {
tarif: data.tarifdanlayanan.tarif, content: data.layananunggulan.content,
}, },
dokterdanTenagaMedis: data.dokterdantenagamedis?.map((v: DokterItem) => v.id) ?? [],
tarifDanLayanan: data.tarifdanlayanan?.map((v: TarifItem) => v.id) ?? [],
}; };
}, },
async submit() { async submit() {
@@ -227,22 +245,15 @@ const fasilitasKesehatan = proxy({
layananUnggulan: { layananUnggulan: {
content: fasilitasKesehatan.edit.form.layananUnggulan.content, content: fasilitasKesehatan.edit.form.layananUnggulan.content,
}, },
dokterdanTenagaMedis: { dokterdanTenagaMedis:
name: fasilitasKesehatan.edit.form.dokterdanTenagaMedis.name, fasilitasKesehatan.edit.form.dokterdanTenagaMedis,
specialist:
fasilitasKesehatan.edit.form.dokterdanTenagaMedis.specialist,
jadwal: fasilitasKesehatan.edit.form.dokterdanTenagaMedis.jadwal,
},
fasilitasPendukung: { fasilitasPendukung: {
content: fasilitasKesehatan.edit.form.fasilitasPendukung.content, content: fasilitasKesehatan.edit.form.fasilitasPendukung.content,
}, },
prosedurPendaftaran: { prosedurPendaftaran: {
content: fasilitasKesehatan.edit.form.prosedurPendaftaran.content, content: fasilitasKesehatan.edit.form.prosedurPendaftaran.content,
}, },
tarifDanLayanan: { tarifDanLayanan: fasilitasKesehatan.edit.form.tarifDanLayanan,
layanan: fasilitasKesehatan.edit.form.tarifDanLayanan.layanan,
tarif: fasilitasKesehatan.edit.form.tarifDanLayanan.tarif,
},
}; };
const res = await fetch( const res = await fetch(
@@ -309,12 +320,26 @@ const templateDokterForm = z.object({
name: z.string().min(1, "Nama tidak boleh kosong"), name: z.string().min(1, "Nama tidak boleh kosong"),
specialist: z.string().min(1, "Spesialis tidak boleh kosong"), specialist: z.string().min(1, "Spesialis tidak boleh kosong"),
jadwal: z.string().min(1, "Jadwal tidak boleh kosong"), jadwal: z.string().min(1, "Jadwal tidak boleh kosong"),
jadwalLibur: z.string().min(1, "Jadwal libur tidak boleh kosong"),
jamBukaOperasional: z
.string()
.min(1, "Jam buka operasional tidak boleh kosong"),
jamTutupOperasional: z
.string()
.min(1, "Jam tutup operasional tidak boleh kosong"),
jamBukaLibur: z.string().min(1, "Jam buka libur tidak boleh kosong"),
jamTutupLibur: z.string().min(1, "Jam tutup libur tidak boleh kosong"),
}); });
const defaultDokterForm = { const defaultDokterForm = {
name: "", name: "",
specialist: "", specialist: "",
jadwal: "", jadwal: "",
jadwalLibur: "",
jamBukaOperasional: "",
jamTutupOperasional: "",
jamBukaLibur: "",
jamTutupLibur: "",
}; };
const dokter = proxy({ const dokter = proxy({
@@ -340,7 +365,7 @@ const dokter = proxy({
if (res.status === 200) { if (res.status === 200) {
const id = res.data?.data; const id = res.data?.data;
if (id) { if (id) {
toast.success("Success create"); toast.success("Sukses menambahkan");
dokter.create.create.form = { ...defaultDokterForm }; dokter.create.create.form = { ...defaultDokterForm };
dokter.findMany.load(); dokter.findMany.load();
return id; return id;
@@ -452,6 +477,11 @@ const dokter = proxy({
name: data.name, name: data.name,
specialist: data.specialist, specialist: data.specialist,
jadwal: data.jadwal, jadwal: data.jadwal,
jadwalLibur: data.jadwalLibur,
jamBukaOperasional: data.jamBukaOperasional,
jamTutupOperasional: data.jamTutupOperasional,
jamBukaLibur: data.jamBukaLibur,
jamTutupLibur: data.jamTutupLibur,
}; };
return data; // Return the loaded data return data; // Return the loaded data
} else { } else {
@@ -476,6 +506,11 @@ const dokter = proxy({
name: this.form.name, name: this.form.name,
specialist: this.form.specialist, specialist: this.form.specialist,
jadwal: this.form.jadwal, jadwal: this.form.jadwal,
jadwalLibur: this.form.jadwalLibur,
jamBukaOperasional: this.form.jamBukaOperasional,
jamTutupOperasional: this.form.jamTutupOperasional,
jamBukaLibur: this.form.jamBukaLibur,
jamTutupLibur: this.form.jamTutupLibur,
}; };
const cek = templateDokterForm.safeParse(formData); const cek = templateDokterForm.safeParse(formData);
@@ -556,9 +591,255 @@ const dokter = proxy({
}, },
}); });
const templateTarifForm = z.object({
tarif: z.string().min(1, "Tarif tidak boleh kosong"),
layanan: z.string().min(1, "Layanan tidak boleh kosong"),
});
const defaultTarifForm = {
tarif: "",
layanan: "",
};
const tarif = proxy({
create: {
form: defaultTarifForm,
loading: false,
async create() {
const cek = templateTarifForm.safeParse(tarif.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return null;
}
try {
tarif.create.loading = true;
const res = await ApiFetch.api.kesehatan.tarifdanlayanan["create"].post(
tarif.create.form
);
if (res.status === 200) {
const id = res.data?.data;
if (id) {
toast.success("Sukses menambahkan");
tarif.create.form = { ...defaultTarifForm };
tarif.findMany.load();
return id;
}
}
toast.error("failed create");
return null;
} catch (error) {
console.log((error as Error).message);
return null;
} finally {
tarif.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.TarifDanLayananGetPayload<{
omit: {
isActive: true;
};
}>[]
| null,
page: 1,
totalPages: 1,
loading: false,
search: "",
load: async (page = 1, limit = 10, search = "") => {
tarif.findMany.loading = true; // ✅ Akses langsung via nama path
tarif.findMany.page = page;
tarif.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.kesehatan.tarifdanlayanan[
"findMany"
].get({ query });
if (res.status === 200 && res.data?.success) {
tarif.findMany.data = res.data.data ?? [];
tarif.findMany.totalPages = res.data.totalPages ?? 1;
} else {
tarif.findMany.data = [];
tarif.findMany.totalPages = 1;
}
} catch (err) {
console.error("Gagal fetch tarif dan layanan paginated:", err);
tarif.findMany.data = [];
tarif.findMany.totalPages = 1;
} finally {
tarif.findMany.loading = false;
}
},
},
findUnique: {
data: null as Prisma.TarifDanLayananGetPayload<{
omit: { isActive: true };
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/kesehatan/tarifdanlayanan/${id}`);
if (res.ok) {
const data = await res.json();
tarif.findUnique.data = data.data ?? null;
} else {
console.error(
"Failed to fetch tarif dan layanan",
res.statusText
);
tarif.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching tarif dan layanan", error);
tarif.findUnique.data = null;
}
},
},
update: {
id: "",
form: { ...defaultTarifForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(`/api/kesehatan/tarifdanlayanan/${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 = {
tarif: data.tarif,
layanan: data.layanan
};
return data; // Return the loaded data
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error loading tarif dan layanan:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async submit() {
const id = this.id;
if (!id) {
toast.warn("ID tidak valid");
return null;
}
const formData = {
tarif: this.form.tarif,
layanan: this.form.layanan
};
const cek = templateTarifForm.safeParse(formData);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v: any) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return null;
}
try {
this.loading = true;
const res = await fetch(`/api/kesehatan/tarifdanlayanan/${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 tarif.findMany.load();
return result.data;
} catch (error) {
console.error("Update error:", error);
toast.error("Gagal update data tarif dan layanan");
throw error;
} finally {
this.loading = false;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) {
return toast.warn("ID tidak valid");
}
try {
tarif.delete.loading = true;
const response = await fetch(
`/api/kesehatan/tarifdanlayanan/del/${id}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
);
const result = await response.json();
if (response.ok && result?.success) {
toast.success(
result.message || "tarif dan layanan berhasil dihapus"
);
await tarif.findMany.load(); // refresh list
} else {
toast.error(
result?.message || "Gagal menghapus tarif dan layanan"
);
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus tarif dan layanan");
} finally {
tarif.delete.loading = false;
}
},
},
});
const fasilitasKesehatanState = proxy({ const fasilitasKesehatanState = proxy({
fasilitasKesehatan, fasilitasKesehatan,
dokter dokter,
tarif
}); });
export default fasilitasKesehatanState; export default fasilitasKesehatanState;

View File

@@ -43,7 +43,7 @@ const grafikkepuasan = proxy({
if (res.status === 200) { if (res.status === 200) {
const id = res.data?.data; const id = res.data?.data;
if (id) { if (id) {
toast.success("Success create"); toast.success("Sukses menambahkan");
grafikkepuasan.create.form = { ...defaultForm }; grafikkepuasan.create.form = { ...defaultForm };
grafikkepuasan.findMany.load(); grafikkepuasan.findMany.load();
return id; return id;

View File

@@ -26,14 +26,6 @@ const templateForm = z.object({
dokumenJadwalKegiatan: z.object({ dokumenJadwalKegiatan: z.object({
content: z.string().min(1, "Content minimal 1 karakter"), 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"),
}),
}); });
const defaultForm = { const defaultForm = {
@@ -55,15 +47,7 @@ const defaultForm = {
}, },
dokumenJadwalKegiatan: { dokumenJadwalKegiatan: {
content: "", content: "",
}, }
pendaftaranJadwalKegiatan: {
name: "",
tanggal: "",
namaOrangtua: "",
nomor: "",
alamat: "",
catatan: "",
},
}; };
const jadwalkegiatanState = proxy({ const jadwalkegiatanState = proxy({
@@ -116,31 +100,39 @@ const jadwalkegiatanState = proxy({
deskripsijadwalkegiatan: true; deskripsijadwalkegiatan: true;
layananjadwalkegiatan: true; layananjadwalkegiatan: true;
dokumenjadwalkegiatan: true; dokumenjadwalkegiatan: true;
pendaftaranjadwalkegiatan: true;
}; };
}>[] }>[]
| null, | null,
page: 1,
totalPages: 1,
loading: false, loading: false,
async load() { search: "",
load: async (page = 1, limit = 10, search = "") => {
jadwalkegiatanState.findMany.loading = true; // ✅ Akses langsung via nama path
jadwalkegiatanState.findMany.page = page;
jadwalkegiatanState.findMany.search = search;
try { try {
this.loading = true; const query: any = { page, limit };
const res = await (ApiFetch.api.kesehatan as any)[ if (search) query.search = search;
"jadwal-kegiatan"
]["find-many"].get();
if (res.status === 200) { const res = await ApiFetch.api.kesehatan["jadwal-kegiatan"][
this.data = res.data?.data ?? []; "find-many"
].get({ query });
if (res.status === 200 && res.data?.success) {
jadwalkegiatanState.findMany.data = res.data.data ?? [];
jadwalkegiatanState.findMany.totalPages = res.data.totalPages ?? 1;
} else { } else {
toast.error("Gagal memuat data jadwal kegiatan"); jadwalkegiatanState.findMany.data = [];
jadwalkegiatanState.findMany.totalPages = 1;
} }
return res;
} catch (err) { } catch (err) {
toast.error("Terjadi error saat load data"); console.error("Gagal fetch jadwal kegiatan paginated:", err);
console.error("LOAD ERROR:", err); jadwalkegiatanState.findMany.data = [];
throw err; jadwalkegiatanState.findMany.totalPages = 1;
} finally { } finally {
this.loading = false; jadwalkegiatanState.findMany.loading = false;
} }
}, },
}, },
@@ -152,7 +144,6 @@ const jadwalkegiatanState = proxy({
layananjadwalkegiatan: true; layananjadwalkegiatan: true;
syaratketentuanjadwalkegiatan: true; syaratketentuanjadwalkegiatan: true;
dokumenjadwalkegiatan: true; dokumenjadwalkegiatan: true;
pendaftaranjadwalkegiatan: true;
}; };
}> | null, }> | null,
loading: false, loading: false,
@@ -200,15 +191,7 @@ const jadwalkegiatanState = proxy({
}, },
dokumenJadwalKegiatan: { dokumenJadwalKegiatan: {
content: data.dokumenjadwalkegiatan.content, 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() { async submit() {
@@ -227,29 +210,28 @@ const jadwalkegiatanState = proxy({
content: jadwalkegiatanState.edit.form.content, content: jadwalkegiatanState.edit.form.content,
informasiJadwalKegiatan: { informasiJadwalKegiatan: {
name: jadwalkegiatanState.edit.form.informasiJadwalKegiatan.name, name: jadwalkegiatanState.edit.form.informasiJadwalKegiatan.name,
tanggal: jadwalkegiatanState.edit.form.informasiJadwalKegiatan.tanggal, tanggal:
jadwalkegiatanState.edit.form.informasiJadwalKegiatan.tanggal,
waktu: jadwalkegiatanState.edit.form.informasiJadwalKegiatan.waktu, waktu: jadwalkegiatanState.edit.form.informasiJadwalKegiatan.waktu,
lokasi: jadwalkegiatanState.edit.form.informasiJadwalKegiatan.lokasi, lokasi:
jadwalkegiatanState.edit.form.informasiJadwalKegiatan.lokasi,
}, },
layananJadwalKegiatan: { layananJadwalKegiatan: {
content: jadwalkegiatanState.edit.form.layananJadwalKegiatan.content, content:
jadwalkegiatanState.edit.form.layananJadwalKegiatan.content,
}, },
deskripsiJadwalKegiatan: { deskripsiJadwalKegiatan: {
deskripsi: jadwalkegiatanState.edit.form.deskripsiJadwalKegiatan.deskripsi, deskripsi:
jadwalkegiatanState.edit.form.deskripsiJadwalKegiatan.deskripsi,
}, },
syaratKetentuanJadwalKegiatan: { syaratKetentuanJadwalKegiatan: {
content: jadwalkegiatanState.edit.form.syaratKetentuanJadwalKegiatan.content, content:
jadwalkegiatanState.edit.form.syaratKetentuanJadwalKegiatan
.content,
}, },
dokumenJadwalKegiatan: { dokumenJadwalKegiatan: {
content: jadwalkegiatanState.edit.form.dokumenJadwalKegiatan.content, 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,
}, },
}; };
@@ -286,7 +268,7 @@ const jadwalkegiatanState = proxy({
}, },
delete: { delete: {
loading: false, loading: false,
async byId(id: string){ async byId(id: string) {
try { try {
jadwalkegiatanState.delete.loading = true; jadwalkegiatanState.delete.loading = true;
const res = await fetch(`/api/kesehatan/jadwal-kegiatan/del/${id}`, { const res = await fetch(`/api/kesehatan/jadwal-kegiatan/del/${id}`, {
@@ -305,7 +287,7 @@ const jadwalkegiatanState = proxy({
} finally { } finally {
jadwalkegiatanState.delete.loading = false; jadwalkegiatanState.delete.loading = false;
} }
} },
}, },
}); });

View File

@@ -0,0 +1,290 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
import { proxy } from "valtio";
import { z } from "zod";
const templateForm = z.object({
name: z.string().min(1, "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"),
});
const defaultForm = {
name: "",
tanggal: "",
namaOrangtua: "",
nomor: "",
alamat: "",
catatan: "",
};
const pendaftaranJadwalKegiatanState = proxy({
create: {
form: { ...defaultForm },
loading: false,
async submit() {
const cek = templateForm.safeParse(this.form);
if (!cek.success) {
const errMsg = cek.error.issues
.map((v) => `${v.path.join(".")}: ${v.message}`)
.join("\n");
toast.error(errMsg);
return null;
}
try {
this.loading = true;
const payload = { ...this.form };
const res = await (ApiFetch.api.kesehatan as any)[
"pendaftaran-jadwal-kegiatan"
].create.post(payload);
if (res.status === 200) {
toast.success("Berhasil menambahkan jadwal kegiatan");
this.resetForm();
await pendaftaranJadwalKegiatanState.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.PendaftaranJadwalKegiatanGetPayload<{
omit: {
isActive: true;
};
}>[]
| null,
page: 1,
totalPages: 1,
loading: false,
search: "",
load: async (page = 1, limit = 10, search = "") => {
pendaftaranJadwalKegiatanState.findMany.loading = true; // ✅ Akses langsung via nama path
pendaftaranJadwalKegiatanState.findMany.page = page;
pendaftaranJadwalKegiatanState.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.kesehatan["pendaftaran-jadwal-kegiatan"][
"find-many"
].get({ query });
if (res.status === 200 && res.data?.success) {
pendaftaranJadwalKegiatanState.findMany.data = res.data.data ?? [];
pendaftaranJadwalKegiatanState.findMany.totalPages =
res.data.totalPages ?? 1;
} else {
pendaftaranJadwalKegiatanState.findMany.data = [];
pendaftaranJadwalKegiatanState.findMany.totalPages = 1;
}
} catch (err) {
console.error(
"Gagal fetch pendaftaran jadwal kegiatan paginated:",
err
);
pendaftaranJadwalKegiatanState.findMany.data = [];
pendaftaranJadwalKegiatanState.findMany.totalPages = 1;
} finally {
pendaftaranJadwalKegiatanState.findMany.loading = false;
}
},
},
findUnique: {
data: null as Prisma.PendaftaranJadwalKegiatanGetPayload<{
omit: {
isActive: true;
};
}> | null,
async load(id: string) {
try {
const res = await fetch(
`/api/kesehatan/pendaftaran-jadwal-kegiatan/${id}`
);
if (res.ok) {
const data = await res.json();
pendaftaranJadwalKegiatanState.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
pendaftaranJadwalKegiatanState.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching data:", error);
pendaftaranJadwalKegiatanState.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
pendaftaranJadwalKegiatanState.delete.loading = true;
const response = await fetch(
`/api/kesehatan/pendaftaran-jadwal-kegiatan/del/${id}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
);
const result = await response.json();
if (response.ok && result?.success) {
toast.success(
result.message || "Pendaftaran jadwal kegiatan berhasil dihapus"
);
await pendaftaranJadwalKegiatanState.findMany.load(); // refresh list
} else {
toast.error(
result?.message || "Gagal menghapus pendaftaran jadwal kegiatan"
);
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error(
"Terjadi kesalahan saat menghapus pendaftaran jadwal kegiatan"
);
} finally {
pendaftaranJadwalKegiatanState.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/pendaftaran-jadwal-kegiatan/${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,
tanggal: data.tanggal,
namaOrangtua: data.namaOrangtua,
nomor: data.nomor,
alamat: data.alamat,
catatan: data.catatan,
};
return data; // Return the loaded data
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error loading pendaftaran jadwal kegiatan:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async update() {
const cek = templateForm.safeParse(pendaftaranJadwalKegiatanState.edit.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return false;
}
try {
pendaftaranJadwalKegiatanState.edit.loading = true;
const response = await fetch(
`/api/kesehatan/pendaftaran-jadwal-kegiatan/${this.id}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: this.form.name,
tanggal: this.form.tanggal,
namaOrangtua: this.form.namaOrangtua,
nomor: this.form.nomor,
alamat: this.form.alamat,
catatan: this.form.catatan,
}),
}
);
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 pendaftaran jadwal kegiatan");
await pendaftaranJadwalKegiatanState.findMany.load(); // refresh list
return true;
} else {
throw new Error(result.message || "Gagal update pendaftaran jadwal kegiatan");
}
} catch (error) {
console.error("Error updating pendaftaran jadwal kegiatan:", error);
toast.error(
error instanceof Error
? error.message
: "Terjadi kesalahan saat update pendaftaran jadwal kegiatan"
);
return false;
} finally {
pendaftaranJadwalKegiatanState.edit.loading = false;
}
},
reset() {
pendaftaranJadwalKegiatanState.edit.id = "";
pendaftaranJadwalKegiatanState.edit.form = { ...defaultForm };
},
},
});
export default pendaftaranJadwalKegiatanState;

View File

@@ -50,7 +50,7 @@ const persentasekelahiran = proxy({
if (res.status === 200) { if (res.status === 200) {
const id = res.data?.data; const id = res.data?.data;
if (id) { if (id) {
toast.success("Success create"); toast.success("Sukses menambahkan");
persentasekelahiran.create.form = { ...defaultForm }; persentasekelahiran.create.form = { ...defaultForm };
persentasekelahiran.findMany.load(); persentasekelahiran.findMany.load();
return id; return id;

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