Compare commits

...

18 Commits

Author SHA1 Message Date
bb8dab05ba Fix API Jumlah Penganggguran 2025-08-25 11:07:21 +08:00
3081e426bd Fix Seed Jumlah Pengangguran 2025-08-23 11:39:16 +08:00
8a275c2a32 Fix Tampilan Data Kesehatan Warga User 2025-08-22 16:30:35 +08:00
8469ebd2e1 Sinkronisasi UI & API Admin - User Submenu Demografi Pekerjaan, Menu Ekonomi 2025-08-22 01:10:18 +08:00
760ba4b6d2 Sinkronisasi Sinkronisasi UI & API Admin - User Submenu Program Kemiskinan 2025-08-22 00:26:58 +08:00
20d4c90e60 Sinkronisasi UI & API Admin - User Submenu Jumlah Pengangguran - Jumlah Penduduk Miskin 2025-08-21 17:15:06 +08:00
fafbb12a08 Sinkronisasi UI & API Admin - User Submenu Pendapatan Asli Desa 2025-08-21 14:50:23 +08:00
01aa0da5cc Fix admin menu Landing page 2025-08-21 10:16:05 +08:00
b580978f8e Fix eror edit admin lowongan kerja 2025-08-20 17:52:10 +08:00
1c01397c0d Sinkronisasi UI & API Admin - User Submenu lowongan kerja lokal, Menu Ekonomi 2025-08-20 17:17:04 +08:00
90a6605efd Sinkronisasi UI & API Admin - User Submenu Pasar Desa, Menu Ekonomi 2025-08-20 17:01:20 +08:00
c22d865283 Sinkronisasi UI & API Admin - User Submenu Tips Keamanan, Menu Keamanan 2025-08-20 14:35:08 +08:00
49067f0218 Add Detail Semua Polsek, Submenu Polsek Terdekat, Menu Keamanan 2025-08-19 17:40:59 +08:00
d79425d529 Sinkronisasi UI & API Menu Keamanan, Admin - User Submenu Keamanan Lingkungan & Polse Terdekat 2025-08-19 11:12:39 +08:00
4491d23bea Sinkronisasi UI & API Admin - User Submenu Penanganan Darurat, User Submenu Kontak Darurat & User Submenu Info Wabah / Penyakit 2025-08-18 20:56:18 +08:00
1e154ced86 Sinkronisasi UI & API Admin - User Submenu Program Kesehatan 2025-08-18 17:14:33 +08:00
bcc51aec12 Sinkronisasi UI & API Admin - User Submenu Data Kesehatan Warga 2025-08-18 15:01:39 +08:00
8d15563f15 Sinkronisasi UI & API Admin - User Submenu Data Kesehatan Warga
-Dibagian Tanggal Gak Auto Ngambil Tanggal Yang Udah Dipakai
-Dibagian fasilitas kesehatan : data dokter dan tarif rencananya mau pakai select
2025-08-15 14:07:56 +08:00
173 changed files with 9648 additions and 4451 deletions

View File

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

View File

@@ -1,8 +1,14 @@
[
{
"id": "1",
"jenisInformasi": "Peraturan Desa",
"deskripsi": "Dokumen yang berisi kebijakan dan regulasi desa",
"tanggal": "15 Januari 2024"
"id": "cmeppcwzk0000vn5exmudcipd",
"jenisInformasi": "Potensi Desa",
"deskripsi": "<p>“Potensi desa adalah segenap sumber daya alam dan sumber daya manusia yang dimiliki desa sebagai modal dasar yang perlu dikelola dan dikembangkan bagi kelangsungan dan perkembangan desa. Adapun potensi yang dimiliki Desa Darmasaba yaitu:</p><ol><li><p>TPS3R Pudak Mesari</p></li><li><p>Bumdes Pudak Mesari</p></li><li><p>Pertanian</p></li><li><p>Jogging Track Tegeh Aban, Karang Gadon dan Munduk Uma Desa</p></li><li><p>Taman Beji Cengana</p></li><li><p>Dam Tanah Putih</p></li><li><p>Gumuh Sari Water Park</p></li><li><p>UMKM</p></li><li><p>Kawasan Kuliner</p></li><li><p>IKM berbasis Pengolahan Pangan</p></li><li><p>Genteng</p></li><li><p>Peternakan Ikan Lele</p></li><li><p>Pemotongan Daging”</p></li></ol>",
"tanggal": "2021-05-25"
},
{
"id": "cmeppieay0001vn5e8qe658ub",
"jenisInformasi": "Layanan Surat Keterangan Desa",
"deskripsi": "<p>“Desa Darmasaba menyediakan berbagai jenis layanan surat keterangan untuk kebutuhan administratif, antara lain:</p><ul><li><p>Surat Keterangan Domisili Organisasi</p></li><li><p>Surat Keterangan Penghasilan</p></li><li><p>Surat Keterangan Tidak Mampu</p></li><li><p>Surat Keterangan Kelahiran</p></li><li><p>Surat Keterangan Usaha</p></li><li><p>Surat Keterangan Tempat Usaha</p></li><li><p>Surat Keterangan Belum Kawin</p></li><li><p>Surat Keterangan Kelakuan Baik (Pengantar SKCK)</p></li><li><p>Surat Keterangan Kematian</p></li><li><p>Surat Keterangan Perbedaan Biodata Diri</p></li><li><p>Surat Keterangan Yatim/Piatu/Yatim Piatu<br>Untuk surat keterangan lainnya, masyarakat dapat berkonsultasi langsung ke kantor Perbekel Darmasaba.”<br><em>(Sumber: Laman Layanan Desa Darmasaba)</em></p></li></ul>",
"tanggal": "2025-02-21"
}
]

View File

@@ -201,8 +201,8 @@ model PrestasiDesa {
deskripsi String @db.Text
kategori KategoriPrestasiDesa @relation(fields: [kategoriId], references: [id])
kategoriId String
image FileStorage @relation(fields: [imageId], references: [id])
imageId String
image FileStorage? @relation(fields: [imageId], references: [id])
imageId String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
@@ -223,7 +223,7 @@ model KategoriPrestasiDesa {
model Responden {
id String @id @default(cuid())
name String @unique
tanggal DateTime // misal: 2025-05-01
tanggal DateTime @db.Date // misal: 2025-05-01
jenisKelamin JenisKelaminResponden @relation(fields: [jenisKelaminId], references: [id])
jenisKelaminId String
rating PilihanRatingResponden @relation(fields: [ratingId], references: [id])
@@ -292,6 +292,9 @@ model PosisiOrganisasiPPID {
pegawai PegawaiPPID[]
strukturOrganisasi StrukturPPID[] // Relasi balik
parentId String?
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
parent PosisiOrganisasiPPID? @relation("Parent", fields: [parentId], references: [id])
children PosisiOrganisasiPPID[] @relation("Parent")
}
@@ -951,13 +954,16 @@ model Kematian {
// ========================================= GRAFIK KEPUASAN ========================================= //
model GrafikKepuasan {
id String @id @default(cuid())
label String
jumlah String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
id String @id @default(cuid())
nama String
tanggal DateTime
jenisKelamin String
alamat String
penyakit String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
// ========================================= ARTIKEL KESEHATAN ========================================= //
@@ -1544,7 +1550,7 @@ model DataDemografiPekerjaan {
model DetailDataPengangguran {
id String @id @default(uuid()) @db.Uuid
month String @db.VarChar(20)
year Int
year DateTime
totalUnemployment Int
educatedUnemployment Int
uneducatedUnemployment Int

View File

@@ -1,57 +1,62 @@
import prisma from "@/lib/prisma";
import profilePejabatDesa from "./data/landing-page/profile/profile.json";
import penghargaan from "./data/landing-page/penghargaan/penghargaan.json";
import programInovasi from "./data/landing-page/profile/programInovasi.json";
import mediaSosial from "./data/landing-page/profile/mediaSosial.json";
import desaAntiKorupsi from "./data/landing-page/desa-anti-korupsi/desaantiKorpusi.json";
import kategoriDesaAntiKorupsi from "./data/landing-page/desa-anti-korupsi/kategoriDesaAntiKorupsi.json";
import sdgsDesa from "./data/landing-page/sdgs-desa/sdgs-desa.json";
import apbdes from "./data/landing-page/apbdes/apbdes.json";
import pelayananSuratKeterangan from "./data/desa/layanan/pelayananSuratKeterangan.json";
import categoryPengumuman from "./data/category-pengumuman.json";
import kategoriBerita from "./data/kategori-berita.json";
import caraMemperolehInformasi from "./data/list-caraMemperolehInformasi.json";
import caraMemperolehSalinanInformasi from "./data/list-caraMemperolehSalinanInformasi.json";
import jenisInformasiDiminta from "./data/list-jenisInfromasi.json";
import layanan from "./data/list-layanan.json";
import potensi from "./data/list-potensi.json";
import dasarHukumPPID from "./data/ppid/dasar-hukum-ppid/dasarhukumPPID.json";
import kategoriPrestasiDesa from "./data/landing-page/prestasi-desa/kategori-prestasi.json";
import prestasiDesa from "./data/landing-page/prestasi-desa/prestasi-desa.json";
import penghargaan from "./data/landing-page/penghargaan/penghargaan.json";
import profilePPID from "./data/ppid/profile-ppid/profilePPid.json";
import pegawaiPPID from "./data/ppid/struktur-ppid/pegawai-PPID.json";
import posisiOrganisasiPPID from "./data/ppid/struktur-ppid/posisi-organisasi-PPID.json";
import visiMisiPPID from "./data/ppid/visi-misi-ppid/visimisiPPID.json";
import dasarHukumPPID from "./data/ppid/dasar-hukum-ppid/dasarhukumPPID.json";
import jenisKelamin from "./data/ppid/ikm/jenis-kelamin/jenis-kelamin.json";
import daftarInformasiPublik from "./data/ppid/daftar-informasi-publik-desa-darmasaba/daftarInformasi.json"
import pilihanRatingResponden from "./data/ppid/ikm/pilihan-rating-responden/rating-responden.json";
import umurResponden from "./data/ppid/ikm/umur-responden/umur-responden.json";
import categoryPengumuman from "./data/category-pengumuman.json";
import pelayananPerizinanBerusaha from "./data/desa/layanan/pelayananPerizinanBerusaha.json";
import pelayananSuratKeterangan from "./data/desa/layanan/pelayananSuratKeterangan.json";
import pelayananTelunjukSaktiDesa from "./data/desa/layanan/pelayananTelunjukSaktiDesa.json";
import pelayananPendudukNonPermanen from "./data/desa/layanan/pelayanaPendudukNonPermanen.json";
import sejarahDesa from "./data/desa/profile/sejarah_desa.json";
import visiMisiDesa from "./data/desa/profile/visi_misi_desa.json";
import lambangDesa from "./data/desa/profile/lambang_desa.json";
import maskotDesa from "./data/desa/profile/maskot_desa.json";
import profilPerbekel from "./data/desa/profile/profil_perbekel.json";
import sejarahDesa from "./data/desa/profile/sejarah_desa.json";
import visiMisiDesa from "./data/desa/profile/visi_misi_desa.json";
import detailDataPengangguran from "./data/ekonomi/jumlah-pengangguran/detail-data-pengangguran.json";
import kategoriProduk from "./data/ekonomi/pasar-desa/kategori-produk.json";
import hubunganOrganisasi from "./data/ekonomi/struktur-organisasi/hubungan-organisasi.json";
import posisiOrganisasi from "./data/ekonomi/struktur-organisasi/posisi-organisasi.json";
import pegawai from "./data/ekonomi/struktur-organisasi/pegawai.json";
import detailDataPengangguran from "./data/ekonomi/jumlah-pengangguran/detail-data-pengangguran.json";
import tujuanEdukasiLingkungan from "./data/lingkungan/edukasi-lingkungan/tujuan-edukasi-lingkungan.json";
import materiEdukasiLingkungan from "./data/lingkungan/edukasi-lingkungan/materi-edukasi-yang-diberikan.json";
import posisiOrganisasi from "./data/ekonomi/struktur-organisasi/posisi-organisasi.json";
import kategoriBerita from "./data/kategori-berita.json";
import contohEdukasiLingkungan from "./data/lingkungan/edukasi-lingkungan/contoh-kegiatan-di-desa-darmasaba.json";
import nilaiKonservasiAdat from "./data/lingkungan/konservasi-adat-bali/nilai-konservasi-adat.json";
import materiEdukasiLingkungan from "./data/lingkungan/edukasi-lingkungan/materi-edukasi-yang-diberikan.json";
import tujuanEdukasiLingkungan from "./data/lingkungan/edukasi-lingkungan/tujuan-edukasi-lingkungan.json";
import bentukKonservasiBerdasarkanAdat from "./data/lingkungan/konservasi-adat-bali/bentuk-konservasi.json";
import filosofiTriHita from "./data/lingkungan/konservasi-adat-bali/filosofi-tri-hita.json";
import tujuanProgram from "./data/pendidikan/program-pendidikan-anak/tujuan-program.json";
import nilaiKonservasiAdat from "./data/lingkungan/konservasi-adat-bali/nilai-konservasi-adat.json";
import caraMemperolehInformasi from "./data/list-caraMemperolehInformasi.json";
import caraMemperolehSalinanInformasi from "./data/list-caraMemperolehSalinanInformasi.json";
import jenisInformasiDiminta from "./data/list-jenisInfromasi.json";
import potensi from "./data/list-potensi.json";
import fasilitasBimbinganBelajarDesa from "./data/pendidikan/bimbingan-belajar-desa/fasilitas-yang-disediakan.json";
import lokasiJadwalBimbinganBelajarDesa from "./data/pendidikan/bimbingan-belajar-desa/lokasi-dan-jadwal.json";
import tujuanBimbinganBelajarDesa from "./data/pendidikan/bimbingan-belajar-desa/tujuan-bimbingan-belajar-desa.json";
import jenisProgramYangDiselenggarakan from "./data/pendidikan/pendidikan-non-formal/jenis-program-yang-diselenggarakan.json";
import tempatKegiatan from "./data/pendidikan/pendidikan-non-formal/tempat-kegiatan.json";
import tujuanProgram2 from "./data/pendidikan/pendidikan-non-formal/tujuan-program2.json";
import programUnggulan from "./data/pendidikan/program-pendidikan-anak/program-unggulan.json";
import tujuanBimbinganBelajarDesa from "./data/pendidikan/bimbingan-belajar-desa/tujuan-bimbingan-belajar-desa.json";
import lokasiJadwalBimbinganBelajarDesa from "./data/pendidikan/bimbingan-belajar-desa/lokasi-dan-jadwal.json";
import fasilitasBimbinganBelajarDesa from "./data/pendidikan/bimbingan-belajar-desa/fasilitas-yang-disediakan.json";
import tempatKegiatan from "./data/pendidikan/pendidikan-non-formal/tempat-kegiatan.json";
import jenisProgramYangDiselenggarakan from "./data/pendidikan/pendidikan-non-formal/jenis-program-yang-diselenggarakan.json";
import posisiOrganisasiPPID from "./data/ppid/struktur-ppid/posisi-organisasi-PPID.json";
import pegawaiPPID from "./data/ppid/struktur-ppid/pegawai-PPID.json";
import pelayananTelunjukSaktiDesa from "./data/desa/layanan/pelayananTelunjukSaktiDesa.json";
import tujuanProgram from "./data/pendidikan/program-pendidikan-anak/tujuan-program.json";
(async () => {
// =========== LANDING PAGE ===========
// =========== PROFILE ===========
// =========== SUBMENU PROFILE ===========
// =========== PROFILE PEJABAT DESA ===========
for (const p of profilePejabatDesa) {
await prisma.pejabatDesa.upsert({
where: { id: p.id },
@@ -106,6 +111,90 @@ import pelayananTelunjukSaktiDesa from "./data/desa/layanan/pelayananTelunjukSak
}
console.log("media sosial success ...");
// =========== SUBMENU DESA ANTI KORUPSI ===========
// =========== KATEGORI DESA ANTI KORUPSI ===========
for (const k of kategoriDesaAntiKorupsi) {
await prisma.kategoriDesaAntiKorupsi.upsert({
where: { id: k.id },
update: {
name: k.name,
},
create: {
id: k.id,
name: k.name,
},
});
}
console.log("kategori desa anti korupsi success ...");
// =========== DESA ANTI KORUPSI ===========
for (const p of desaAntiKorupsi) {
await prisma.desaAntiKorupsi.upsert({
where: { id: p.id },
update: {
name: p.name,
deskripsi: p.deskripsi,
kategoriId: p.kategoriId,
},
create: {
id: p.id,
name: p.name,
deskripsi: p.deskripsi,
kategoriId: p.kategoriId,
},
});
}
console.log("desa anti korupsi success ...");
// =========== KATEGORI DESA ANTI KORUPSI ===========
for (const p of kategoriDesaAntiKorupsi) {
await prisma.kategoriDesaAntiKorupsi.upsert({
where: { id: p.id },
update: {
name: p.name,
},
create: {
id: p.id,
name: p.name,
},
});
}
console.log("desa anti korupsi success ...");
// =========== KATEGORI PRESTASI DESA===========
for (const c of kategoriPrestasiDesa) {
await prisma.kategoriPrestasiDesa.upsert({
where: { id: c.id },
update: {
name: c.name,
},
create: {
id: c.id,
name: c.name,
},
});
}
console.log("kategori prestasi desa success ...");
// =========== PRESTASI DESA===========
for (const p of prestasiDesa) {
await prisma.prestasiDesa.upsert({
where: { id: p.id },
update: {
name: p.name,
deskripsi: p.deskripsi,
kategoriId: p.kategoriId,
},
create: {
id: p.id,
name: p.name,
deskripsi: p.deskripsi,
kategoriId: p.kategoriId,
},
});
}
console.log("prestasi desa success ...");
// =========== PENGHARGAAN ===========
for (const p of penghargaan) {
await prisma.penghargaan.upsert({
@@ -160,23 +249,6 @@ import pelayananTelunjukSaktiDesa from "./data/desa/layanan/pelayananTelunjukSak
}
console.log("pelayanan surat keterangan success ...");
// =========== LAYANAN ===========
for (const l of layanan) {
await prisma.layanan.upsert({
where: {
name: l.name,
},
update: {
name: l.name,
},
create: {
name: l.name,
},
});
}
console.log("layanan success ...");
// =========== SDGSDesa ===========
for (const l of sdgsDesa) {
await prisma.sDGSDesa.upsert({
@@ -217,6 +289,9 @@ import pelayananTelunjukSaktiDesa from "./data/desa/layanan/pelayananTelunjukSak
console.log("sdgs desa success ...");
// =========== MENU DESA ===========
// =========== SUBMENU PROFILE ===========
// =========== SEJARAH DESA ===========
for (const l of sejarahDesa) {
await prisma.sejarahDesa.upsert({
where: {
@@ -236,6 +311,7 @@ import pelayananTelunjukSaktiDesa from "./data/desa/layanan/pelayananTelunjukSak
console.log("sejarah desa success ...");
// =========== MASKOT DESA ===========
for (const l of maskotDesa) {
await prisma.maskotDesa.upsert({
where: {
@@ -255,6 +331,7 @@ import pelayananTelunjukSaktiDesa from "./data/desa/layanan/pelayananTelunjukSak
console.log("maskot desa success ...");
// =========== LAMBANG DESA ===========
for (const l of lambangDesa) {
await prisma.lambangDesa.upsert({
where: {
@@ -274,6 +351,7 @@ import pelayananTelunjukSaktiDesa from "./data/desa/layanan/pelayananTelunjukSak
console.log("lambang desa success ...");
// =========== PROFIL PERBEKEL ===========
for (const c of profilPerbekel) {
await prisma.profilPerbekel.upsert({
where: { id: c.id },
@@ -298,6 +376,7 @@ import pelayananTelunjukSaktiDesa from "./data/desa/layanan/pelayananTelunjukSak
"✅ profilePerbekel seeded without imageId (editable later via UI)"
);
// =========== VISI MISI DESA ===========
for (const l of visiMisiDesa) {
await prisma.visiMisiDesa.upsert({
where: {
@@ -317,6 +396,35 @@ import pelayananTelunjukSaktiDesa from "./data/desa/layanan/pelayananTelunjukSak
console.log("visi misi desa success ...");
// =========== MENU PPID ===========
// =========== SUBMENU PROFILE PPID ===========
for (const c of profilePPID) {
await prisma.profilePPID.upsert({
where: { id: c.id },
update: {
name: c.name,
biodata: c.biodata,
riwayat: c.riwayat,
pengalaman: c.pengalaman,
unggulan: c.unggulan,
// imageId tidak di-update
},
create: {
id: c.id,
name: c.name,
biodata: c.biodata,
riwayat: c.riwayat,
pengalaman: c.pengalaman,
unggulan: c.unggulan,
// imageId tidak di-create
},
});
}
console.log("✅ profilePPID seeded without imageId (editable later via UI)");
// =========== SUBMENU STRUKTUR PPID ===========
// =========== POSISI ORGANISASI PPID ===========
const flattenedPosisi = posisiOrganisasiPPID.flat();
// ✅ Urutkan berdasarkan hierarki
@@ -341,9 +449,9 @@ import pelayananTelunjukSaktiDesa from "./data/desa/layanan/pelayananTelunjukSak
create: p,
});
}
console.log("✅ Posisi organisasi berhasil");
console.log("posisi organisasi berhasil");
// 2. Seed Pegawai
// =========== PEGAWAI PPID ===========
const flattenedPegawai = pegawaiPPID.flat();
for (const p of flattenedPegawai) {
await prisma.pegawaiPPID.upsert({
@@ -352,7 +460,70 @@ import pelayananTelunjukSaktiDesa from "./data/desa/layanan/pelayananTelunjukSak
create: p,
});
}
console.log("✅ Pegawai berhasil");
console.log("pegawai berhasil");
// =========== SUBMENU VISI MISI PPID ===========
for (const v of visiMisiPPID) {
await prisma.visiMisiPPID.upsert({
where: {
id: v.id,
},
update: {
misi: v.misi,
visi: v.visi,
},
create: {
id: v.id,
misi: v.misi,
visi: v.visi,
},
});
}
console.log("visi misi PPID success ...");
// =========== SUBMENU DASAR HUKUM PPID ===========
for (const v of dasarHukumPPID) {
await prisma.dasarHukumPPID.upsert({
where: {
id: v.id,
},
update: {
judul: v.judul,
content: v.content,
},
create: {
id: v.id,
judul: v.judul,
content: v.content,
},
});
}
console.log("dasar hukum PPID success ...");
// =========== SUBMENU DAFTAR INFORMASI PUBLIK PPID ===========
for (const v of daftarInformasiPublik) {
// Convert string date to Date object
const tanggal = new Date(v.tanggal);
await prisma.daftarInformasiPublik.upsert({
where: {
id: v.id,
},
update: {
jenisInformasi: v.jenisInformasi,
deskripsi: v.deskripsi,
tanggal: tanggal,
},
create: {
id: v.id,
jenisInformasi: v.jenisInformasi,
deskripsi: v.deskripsi,
tanggal: tanggal,
},
});
}
console.log("daftar informasi publik PPID success ...");
for (const l of pelayananPerizinanBerusaha) {
await prisma.pelayananPerizinanBerusaha.upsert({
@@ -486,48 +657,6 @@ import pelayananTelunjukSaktiDesa from "./data/desa/layanan/pelayananTelunjukSak
}
console.log("cara memperoleh salinan informasi success ...");
for (const c of profilePPID) {
await prisma.profilePPID.upsert({
where: { id: c.id },
update: {
name: c.name,
biodata: c.biodata,
riwayat: c.riwayat,
pengalaman: c.pengalaman,
unggulan: c.unggulan,
// imageId tidak di-update
},
create: {
id: c.id,
name: c.name,
biodata: c.biodata,
riwayat: c.riwayat,
pengalaman: c.pengalaman,
unggulan: c.unggulan,
// imageId tidak di-create
},
});
}
console.log("✅ profilePPID seeded without imageId (editable later via UI)");
for (const v of visiMisiPPID) {
await prisma.visiMisiPPID.upsert({
where: {
id: v.id,
},
update: {
misi: v.misi,
visi: v.visi,
},
create: {
id: v.id,
misi: v.misi,
visi: v.visi,
},
});
}
console.log("visi misi PPID success ...");
for (const j of jenisKelamin) {
await prisma.jenisKelaminResponden.upsert({
where: {
@@ -576,24 +705,6 @@ import pelayananTelunjukSaktiDesa from "./data/desa/layanan/pelayananTelunjukSak
}
console.log("umur responden success ...");
for (const v of dasarHukumPPID) {
await prisma.dasarHukumPPID.upsert({
where: {
id: v.id,
},
update: {
judul: v.judul,
content: v.content,
},
create: {
id: v.id,
judul: v.judul,
content: v.content,
},
});
}
console.log("dasar hukum PPID success ...");
for (const k of kategoriProduk) {
await prisma.kategoriProduk.upsert({
where: {
@@ -681,9 +792,12 @@ import pelayananTelunjukSaktiDesa from "./data/desa/layanan/pelayananTelunjukSak
console.log("hubungan organisasi success ...");
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({
where: {
month_year: { month: d.month, year: d.year },
month_year: { month: d.month, year: yearAsDate },
},
update: {
totalUnemployment: d.totalUnemployment,
@@ -693,7 +807,7 @@ import pelayananTelunjukSaktiDesa from "./data/desa/layanan/pelayananTelunjukSak
},
create: {
month: d.month,
year: d.year,
year: yearAsDate,
totalUnemployment: d.totalUnemployment,
educatedUnemployment: d.educatedUnemployment,
uneducatedUnemployment: d.uneducatedUnemployment,

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
@@ -61,13 +62,39 @@ const lowonganKerjaState = proxy({
findMany: {
data: null as
| Prisma.LowonganPekerjaanGetPayload<{
omit: { isActive: true };
omit: {
isActive: true;
};
}>[]
| null,
async load() {
const res = await ApiFetch.api.ekonomi.lowongankerja["find-many"].get();
if (res.status === 200) {
lowonganKerjaState.findMany.data = res.data?.data ?? [];
page: 1,
totalPages: 1,
loading: false,
search: "",
load: async (page = 1, limit = 10, search = "") => {
lowonganKerjaState.findMany.loading = true; // ✅ Akses langsung via nama path
lowonganKerjaState.findMany.page = page;
lowonganKerjaState.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.ekonomi.lowongankerja["find-many"].get({ query });
if (res.status === 200 && res.data?.success) {
lowonganKerjaState.findMany.data = res.data.data ?? [];
lowonganKerjaState.findMany.totalPages = res.data.totalPages ?? 1;
} else {
lowonganKerjaState.findMany.data = [];
lowonganKerjaState.findMany.totalPages = 1;
}
} catch (err) {
console.error("Gagal fetch lowongan kerja paginated:", err);
lowonganKerjaState.findMany.data = [];
lowonganKerjaState.findMany.totalPages = 1;
} finally {
lowonganKerjaState.findMany.loading = false;
}
},
},

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
@@ -53,22 +54,47 @@ const pasarDesa = proxy({
},
},
findMany: {
data: null as Array<
Prisma.PasarDesaGetPayload<{
include: {
image: true;
KategoriToPasar: {
include: {
kategori: true;
data: null as
| Prisma.PasarDesaGetPayload<{
include: {
image: true;
KategoriToPasar: {
include: {
kategori: true;
};
};
};
};
}>
> | null,
async load() {
const res = await ApiFetch.api.ekonomi.pasardesa["find-many"].get();
if (res.status === 200) {
pasarDesa.findMany.data = res.data?.data ?? [];
}>[]
| null,
page: 1,
totalPages: 1,
loading: false,
search: "",
load: async (page = 1, limit = 10, search = "", categoryId?: string) => {
pasarDesa.findMany.loading = true;
pasarDesa.findMany.page = page;
pasarDesa.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
if (categoryId) query.categoryId = categoryId;
const res = await ApiFetch.api.ekonomi.pasardesa["find-many"].get({ query });
if (res.status === 200 && res.data?.success) {
pasarDesa.findMany.data = res.data.data ?? [];
pasarDesa.findMany.totalPages = res.data.totalPages ?? 1;
} else {
pasarDesa.findMany.data = [];
pasarDesa.findMany.totalPages = 1;
}
} catch (err) {
console.error("Gagal fetch keamanan lingkungan paginated:", err);
pasarDesa.findMany.data = [];
pasarDesa.findMany.totalPages = 1;
} finally {
pasarDesa.findMany.loading = false;
}
},
},
@@ -272,14 +298,41 @@ const kategoriProduk = proxy({
},
},
findMany: {
data: null as Array<{
id: string;
nama: string;
}> | null,
async load() {
const res = await ApiFetch.api.ekonomi.kategoriproduk["find-many"].get();
if (res.status === 200) {
kategoriProduk.findMany.data = res.data?.data ?? [];
data: null as
| Prisma.KategoriProdukGetPayload<{
omit: {
isActive: true;
};
}>[]
| null,
page: 1,
totalPages: 1,
loading: false,
search2: "",
load: async (page = 1, limit = 10, search2 = "") => {
kategoriProduk.findMany.loading = true; // ✅ Akses langsung via nama path
kategoriProduk.findMany.page = page;
kategoriProduk.findMany.search2 = search2;
try {
const query: any = { page, limit };
if (search2) query.search2 = search2;
const res = await ApiFetch.api.ekonomi.kategoriproduk["find-many"].get({ query });
if (res.status === 200 && res.data?.success) {
kategoriProduk.findMany.data = res.data.data ?? [];
kategoriProduk.findMany.totalPages = res.data.totalPages ?? 1;
} else {
kategoriProduk.findMany.data = [];
kategoriProduk.findMany.totalPages = 1;
}
} catch (err) {
console.error("Gagal fetch kategori produk paginated:", err);
kategoriProduk.findMany.data = [];
kategoriProduk.findMany.totalPages = 1;
} finally {
kategoriProduk.findMany.loading = false;
}
},
},

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
@@ -11,8 +12,7 @@ const templateForm = z.object({
statistik: z.object({
tahun: z.string().min(1, "Tahun minimal 1 karakter"),
jumlah: z.string().min(1, "Jumlah minimal 1 karakter"),
})
}),
});
const defaultForm = {
@@ -21,8 +21,8 @@ const defaultForm = {
ikonUrl: "",
statistik: {
tahun: "",
jumlah: ""
}
jumlah: "",
},
};
const programKemiskinanState = proxy({
@@ -64,12 +64,35 @@ const programKemiskinanState = proxy({
};
}>[],
loading: false,
async load() {
const res = await ApiFetch.api.ekonomi.programkemiskinan[
"find-many"
].get();
if (res.status === 200) {
programKemiskinanState.findMany.data = res.data?.data ?? [];
page: 1,
totalPages: 1,
search: "",
load: async (page = 1, limit = 10, search = "") => {
programKemiskinanState.findMany.loading = true; // ✅ Akses langsung via nama path
programKemiskinanState.findMany.page = page;
programKemiskinanState.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.ekonomi.programkemiskinan[
"find-many"
].get({ query });
if (res.status === 200 && res.data?.success) {
programKemiskinanState.findMany.data = res.data.data ?? [];
programKemiskinanState.findMany.totalPages = res.data.totalPages ?? 1;
} else {
programKemiskinanState.findMany.data = [];
programKemiskinanState.findMany.totalPages = 1;
}
} catch (err) {
console.error("Gagal fetch program kemiskinan paginated:", err);
programKemiskinanState.findMany.data = [];
programKemiskinanState.findMany.totalPages = 1;
} finally {
programKemiskinanState.findMany.loading = false;
}
},
},

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
@@ -53,15 +54,39 @@ const keamananLingkunganState = proxy({
findMany: {
data: null as
| Prisma.KeamananLingkunganGetPayload<{
include: { image: true };
include: {
image: true;
};
}>[]
| null,
async load() {
const res = await ApiFetch.api.keamanan.keamananlingkungan[
"find-many"
].get();
if (res.status === 200) {
keamananLingkunganState.findMany.data = res.data?.data ?? [];
page: 1,
totalPages: 1,
loading: false,
search: "",
load: async (page = 1, limit = 10, search = "") => {
keamananLingkunganState.findMany.loading = true; // ✅ Akses langsung via nama path
keamananLingkunganState.findMany.page = page;
keamananLingkunganState.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.keamanan.keamananlingkungan["find-many"].get({ query });
if (res.status === 200 && res.data?.success) {
keamananLingkunganState.findMany.data = res.data.data ?? [];
keamananLingkunganState.findMany.totalPages = res.data.totalPages ?? 1;
} else {
keamananLingkunganState.findMany.data = [];
keamananLingkunganState.findMany.totalPages = 1;
}
} catch (err) {
console.error("Gagal fetch keamanan lingkungan paginated:", err);
keamananLingkunganState.findMany.data = [];
keamananLingkunganState.findMany.totalPages = 1;
} finally {
keamananLingkunganState.findMany.loading = false;
}
},
},

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
@@ -63,13 +64,41 @@ const polsekTerdekatState = proxy({
findMany: {
data: null as
| Prisma.PolsekTerdekatGetPayload<{
include: { layananPolsek: true };
include: {
layananPolsek: true;
};
}>[]
| null,
async load() {
const res = await ApiFetch.api.keamanan.polsekterdekat["find-many"].get();
if (res.status === 200) {
polsekTerdekatState.findMany.data = res.data?.data ?? [];
page: 1,
totalPages: 1,
loading: false,
search: "",
load: async (page = 1, limit = 10, search = "") => {
polsekTerdekatState.findMany.loading = true; // ✅ Akses langsung via nama path
polsekTerdekatState.findMany.page = page;
polsekTerdekatState.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.keamanan.polsekterdekat["find-many"].get(
{ query }
);
if (res.status === 200 && res.data?.success) {
polsekTerdekatState.findMany.data = res.data.data ?? [];
polsekTerdekatState.findMany.totalPages = res.data.totalPages ?? 1;
} else {
polsekTerdekatState.findMany.data = [];
polsekTerdekatState.findMany.totalPages = 1;
}
} catch (err) {
console.error("Gagal fetch polsek terdekat paginated:", err);
polsekTerdekatState.findMany.data = [];
polsekTerdekatState.findMany.totalPages = 1;
} finally {
polsekTerdekatState.findMany.loading = false;
}
},
},
@@ -237,6 +266,29 @@ const polsekTerdekatState = proxy({
polsekTerdekatState.edit.form = { ...defaultForm };
},
},
findFirst: {
data: null as Prisma.PolsekTerdekatGetPayload<{
include: {
layananPolsek: true;
};
}> | null,
loading: false,
load: async () => { // Changed to arrow function
polsekTerdekatState.findFirst.loading = true;
try {
const res = await ApiFetch.api.keamanan.polsekterdekat["find-first"].get();
if (res.status === 200 && res.data?.success) {
polsekTerdekatState.findFirst.data = res.data.data || null;
} else {
polsekTerdekatState.findFirst.data = null;
}
} catch (err) {
console.error("Gagal fetch polsek terdekat terbaru:", err);
} finally {
polsekTerdekatState.findFirst.loading = false;
}
}
}
});
export default polsekTerdekatState;

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
@@ -53,15 +54,39 @@ const tipsKeamananState = proxy({
findMany: {
data: null as
| Prisma.MenuTipsKeamananGetPayload<{
include: { image: true };
include: {
image: true;
};
}>[]
| null,
async load() {
const res = await ApiFetch.api.keamanan.menutipskeamanan[
"find-many"
].get();
if (res.status === 200) {
tipsKeamananState.findMany.data = res.data?.data ?? [];
page: 1,
totalPages: 1,
loading: false,
search: "",
load: async (page = 1, limit = 10, search = "") => {
tipsKeamananState.findMany.loading = true; // ✅ Akses langsung via nama path
tipsKeamananState.findMany.page = page;
tipsKeamananState.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.keamanan.menutipskeamanan["find-many"].get({ query });
if (res.status === 200 && res.data?.success) {
tipsKeamananState.findMany.data = res.data.data ?? [];
tipsKeamananState.findMany.totalPages = res.data.totalPages ?? 1;
} else {
tipsKeamananState.findMany.data = [];
tipsKeamananState.findMany.totalPages = 1;
}
} catch (err) {
console.error("Gagal fetch menu tips keamanan paginated:", err);
tipsKeamananState.findMany.data = [];
tipsKeamananState.findMany.totalPages = 1;
} finally {
tipsKeamananState.findMany.loading = false;
}
},
},

View File

@@ -5,6 +5,7 @@ import { toast } from "react-toastify";
import { proxy } from "valtio";
import { z } from "zod";
//fasilitas kesehatan aja
// Validasi form
const templateForm = z.object({
name: z.string().min(1, "Nama harus diisi"),
@@ -61,7 +62,7 @@ const defaultForm = {
},
};
const fasilitasKesehatanState = proxy({
const fasilitasKesehatan = proxy({
create: {
form: { ...defaultForm },
loading: false,
@@ -86,7 +87,7 @@ const fasilitasKesehatanState = proxy({
if (res.status === 200) {
toast.success("Berhasil menambahkan fasilitas kesehatan");
this.resetForm();
await fasilitasKesehatanState.findMany.load();
await fasilitasKesehatan.findMany.load();
return res.data;
}
} catch (err: any) {
@@ -102,7 +103,6 @@ const fasilitasKesehatanState = proxy({
this.form = { ...defaultForm };
},
},
findMany: {
data: null as
| Prisma.FasilitasKesehatanGetPayload<{
@@ -156,7 +156,7 @@ const fasilitasKesehatanState = proxy({
const res = await fetch(`/api/kesehatan/fasilitas-kesehatan/${id}`);
if (res.ok) {
const data = await res.json();
fasilitasKesehatanState.findUnique.data = data.data ?? null;
fasilitasKesehatan.findUnique.data = data.data ?? null;
} else {
toast.error("Gagal load data fasilitas kesehatan");
}
@@ -176,8 +176,8 @@ const fasilitasKesehatanState = proxy({
const result = await res.json();
const data = result.data;
fasilitasKesehatanState.edit.id = data.id;
fasilitasKesehatanState.edit.form = {
fasilitasKesehatan.edit.id = data.id;
fasilitasKesehatan.edit.form = {
name: data.name,
informasiUmum: {
fasilitas: data.informasiumum.fasilitas,
@@ -205,7 +205,7 @@ const fasilitasKesehatanState = proxy({
};
},
async submit() {
const cek = templateForm.safeParse(fasilitasKesehatanState.edit.form);
const cek = templateForm.safeParse(fasilitasKesehatan.edit.form);
if (!cek.success) {
const errMsg = cek.error.issues
.map((v) => `${v.path.join(".")}: ${v.message}`)
@@ -215,42 +215,38 @@ const fasilitasKesehatanState = proxy({
}
try {
fasilitasKesehatanState.edit.loading = true;
fasilitasKesehatan.edit.loading = true;
const payload = {
name: fasilitasKesehatanState.edit.form.name,
name: fasilitasKesehatan.edit.form.name,
informasiUmum: {
fasilitas:
fasilitasKesehatanState.edit.form.informasiUmum.fasilitas,
alamat: fasilitasKesehatanState.edit.form.informasiUmum.alamat,
fasilitas: fasilitasKesehatan.edit.form.informasiUmum.fasilitas,
alamat: fasilitasKesehatan.edit.form.informasiUmum.alamat,
jamOperasional:
fasilitasKesehatanState.edit.form.informasiUmum.jamOperasional,
fasilitasKesehatan.edit.form.informasiUmum.jamOperasional,
},
layananUnggulan: {
content: fasilitasKesehatanState.edit.form.layananUnggulan.content,
content: fasilitasKesehatan.edit.form.layananUnggulan.content,
},
dokterdanTenagaMedis: {
name: fasilitasKesehatanState.edit.form.dokterdanTenagaMedis.name,
name: fasilitasKesehatan.edit.form.dokterdanTenagaMedis.name,
specialist:
fasilitasKesehatanState.edit.form.dokterdanTenagaMedis.specialist,
jadwal:
fasilitasKesehatanState.edit.form.dokterdanTenagaMedis.jadwal,
fasilitasKesehatan.edit.form.dokterdanTenagaMedis.specialist,
jadwal: fasilitasKesehatan.edit.form.dokterdanTenagaMedis.jadwal,
},
fasilitasPendukung: {
content:
fasilitasKesehatanState.edit.form.fasilitasPendukung.content,
content: fasilitasKesehatan.edit.form.fasilitasPendukung.content,
},
prosedurPendaftaran: {
content:
fasilitasKesehatanState.edit.form.prosedurPendaftaran.content,
content: fasilitasKesehatan.edit.form.prosedurPendaftaran.content,
},
tarifDanLayanan: {
layanan: fasilitasKesehatanState.edit.form.tarifDanLayanan.layanan,
tarif: fasilitasKesehatanState.edit.form.tarifDanLayanan.tarif,
layanan: fasilitasKesehatan.edit.form.tarifDanLayanan.layanan,
tarif: fasilitasKesehatan.edit.form.tarifDanLayanan.tarif,
},
};
const res = await fetch(
`/api/kesehatan/fasilitas-kesehatan/${fasilitasKesehatanState.edit.id}`,
`/api/kesehatan/fasilitas-kesehatan/${fasilitasKesehatan.edit.id}`,
{
method: "PUT",
headers: { "Content-Type": "application/json" },
@@ -264,7 +260,7 @@ const fasilitasKesehatanState = proxy({
}
toast.success("Berhasil update fasilitas kesehatan");
await fasilitasKesehatanState.findMany.load();
await fasilitasKesehatan.findMany.load();
return true;
} catch (err) {
toast.error(
@@ -272,37 +268,297 @@ const fasilitasKesehatanState = proxy({
);
return false;
} finally {
fasilitasKesehatanState.edit.loading = false;
fasilitasKesehatan.edit.loading = false;
}
},
resetForm() {
fasilitasKesehatanState.edit.id = "";
fasilitasKesehatanState.edit.form = { ...defaultForm };
fasilitasKesehatan.edit.id = "";
fasilitasKesehatan.edit.form = { ...defaultForm };
},
},
delete: {
loading: false,
async byId(id: string){
async byId(id: string) {
try {
fasilitasKesehatanState.delete.loading = true;
const res = await fetch(`/api/kesehatan/fasilitas-kesehatan/del/${id}`, {
method: "DELETE",
});
fasilitasKesehatan.delete.loading = true;
const res = await fetch(
`/api/kesehatan/fasilitas-kesehatan/del/${id}`,
{
method: "DELETE",
}
);
const result = await res.json();
if (res.ok && result.success) {
toast.success("Fasilitas kesehatan berhasil dihapus");
await fasilitasKesehatanState.findMany.load();
await fasilitasKesehatan.findMany.load();
} else {
toast.error(result.message || "Gagal menghapus");
}
} catch {
toast.error("Terjadi kesalahan saat menghapus");
} finally {
fasilitasKesehatanState.delete.loading = false;
fasilitasKesehatan.delete.loading = false;
}
}
},
},
});
//dokter & tenaga medis
const templateDokterForm = z.object({
name: z.string().min(1, "Nama tidak boleh kosong"),
specialist: z.string().min(1, "Spesialis tidak boleh kosong"),
jadwal: z.string().min(1, "Jadwal tidak boleh kosong"),
});
const defaultDokterForm = {
name: "",
specialist: "",
jadwal: "",
};
const dokter = proxy({
create: {
create: {
form: defaultDokterForm,
loading: false,
async create() {
const cek = templateDokterForm.safeParse(dokter.create.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return null;
}
try {
dokter.create.create.loading = true;
const res = await ApiFetch.api.kesehatan.doktertenagamedis[
"create"
].post(dokter.create.create.form);
if (res.status === 200) {
const id = res.data?.data;
if (id) {
toast.success("Success create");
dokter.create.create.form = { ...defaultDokterForm };
dokter.findMany.load();
return id;
}
}
toast.error("failed create");
return null;
} catch (error) {
console.log((error as Error).message);
return null;
} finally {
dokter.create.create.loading = false;
}
},
},
},
findMany: {
data: null as
| Prisma.DokterdanTenagaMedisGetPayload<{
omit: {
isActive: true;
};
}>[]
| null,
page: 1,
totalPages: 1,
loading: false,
search: "",
load: async (page = 1, limit = 10, search = "") => {
dokter.findMany.loading = true; // ✅ Akses langsung via nama path
dokter.findMany.page = page;
dokter.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.kesehatan.doktertenagamedis[
"findMany"
].get({ query });
if (res.status === 200 && res.data?.success) {
dokter.findMany.data = res.data.data ?? [];
dokter.findMany.totalPages = res.data.totalPages ?? 1;
} else {
dokter.findMany.data = [];
dokter.findMany.totalPages = 1;
}
} catch (err) {
console.error("Gagal fetch dokter tenaga medis paginated:", err);
dokter.findMany.data = [];
dokter.findMany.totalPages = 1;
} finally {
dokter.findMany.loading = false;
}
},
},
findUnique: {
data: null as Prisma.DokterdanTenagaMedisGetPayload<{
omit: { isActive: true };
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/kesehatan/doktertenagamedis/${id}`);
if (res.ok) {
const data = await res.json();
dokter.findUnique.data = data.data ?? null;
} else {
console.error(
"Failed to fetch dokter dan tenaga medis",
res.statusText
);
dokter.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching dokter dan tenaga medis", error);
dokter.findUnique.data = null;
}
},
},
update: {
id: "",
form: { ...defaultDokterForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(`/api/kesehatan/doktertenagamedis/${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,
specialist: data.specialist,
jadwal: data.jadwal,
};
return data; // Return the loaded data
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error loading dokter dan tenaga medis:", 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 = {
name: this.form.name,
specialist: this.form.specialist,
jadwal: this.form.jadwal,
};
const cek = templateDokterForm.safeParse(formData);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return null;
}
try {
this.loading = true;
const res = await fetch(`/api/kesehatan/doktertenagamedis/${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 dokter.findMany.load();
return result.data;
} catch (error) {
console.error("Update error:", error);
toast.error("Gagal update data dokter dan tenaga medis");
throw error;
} finally {
this.loading = false;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) {
return toast.warn("ID tidak valid");
}
try {
dokter.delete.loading = true;
const response = await fetch(
`/api/kesehatan/doktertenagamedis/del/${id}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
);
const result = await response.json();
if (response.ok && result?.success) {
toast.success(
result.message || "Dokter dan tenaga medis berhasil dihapus"
);
await dokter.findMany.load(); // refresh list
} else {
toast.error(
result?.message || "Gagal menghapus dokter dan tenaga medis"
);
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus dokter dan tenaga medis");
} finally {
dokter.delete.loading = false;
}
},
},
});
const fasilitasKesehatanState = proxy({
fasilitasKesehatan,
dokter
});
export default fasilitasKesehatanState;

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
@@ -5,20 +6,19 @@ import { proxy } from "valtio";
import { z } from "zod";
const templateGrafikKepuasan = z.object({
label: z.string().min(2, "Label harus diisi"),
jumlah: z.string().min(1, "Jumlah harus diisi"),
nama: z.string().min(2, "Nama harus diisi"),
tanggal: z.string().min(1, "Tanggal harus diisi"),
jenisKelamin: z.string().min(1, "Jenis kelamin harus diisi"),
alamat: z.string().min(1, "Alamat harus diisi"),
penyakit: z.string().min(1, "Penyakit harus diisi"),
});
type GrafikKepuasan = Prisma.GrafikKepuasanGetPayload<{
select: {
label: true;
jumlah: true;
};
}>;
const defaultForm: GrafikKepuasan = {
label: "",
jumlah: ""
const defaultForm = {
nama: "",
tanggal: "",
jenisKelamin: "",
alamat: "",
penyakit: "",
};
const grafikkepuasan = proxy({
@@ -36,16 +36,15 @@ const grafikkepuasan = proxy({
}
try {
grafikkepuasan.create.loading = true;
const res = await ApiFetch.api.kesehatan.grafikkepuasan["create"].post(grafikkepuasan.create.form);
const res = await ApiFetch.api.kesehatan.grafikkepuasan["create"].post(
grafikkepuasan.create.form
);
if (res.status === 200) {
const id = res.data?.data?.id;
const id = res.data?.data;
if (id) {
toast.success("Success create");
grafikkepuasan.create.form = {
label: "",
jumlah: "",
};
grafikkepuasan.create.form = { ...defaultForm };
grafikkepuasan.findMany.load();
return id;
}
@@ -62,21 +61,49 @@ const grafikkepuasan = proxy({
},
findMany: {
data: null as
| Prisma.GrafikKepuasanGetPayload<{ omit: { isActive: true } }>[]
| Prisma.GrafikKepuasanGetPayload<{
omit: {
isActive: true;
};
}>[]
| null,
async load() {
const res = await ApiFetch.api.kesehatan.grafikkepuasan[
"find-many"
].get();
if (res.status === 200) {
grafikkepuasan.findMany.data = res.data?.data ?? [];
page: 1,
totalPages: 1,
loading: false,
search: "",
load: async (page = 1, limit = 10, search = "") => {
grafikkepuasan.findMany.loading = true; // ✅ Akses langsung via nama path
grafikkepuasan.findMany.page = page;
grafikkepuasan.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.kesehatan.grafikkepuasan[
"find-many"
].get({ query });
if (res.status === 200 && res.data?.success) {
grafikkepuasan.findMany.data = res.data.data ?? [];
grafikkepuasan.findMany.totalPages = res.data.totalPages ?? 1;
} else {
grafikkepuasan.findMany.data = [];
grafikkepuasan.findMany.totalPages = 1;
}
} catch (err) {
console.error("Gagal fetch berita paginated:", err);
grafikkepuasan.findMany.data = [];
grafikkepuasan.findMany.totalPages = 1;
} finally {
grafikkepuasan.findMany.loading = false;
}
},
},
findUnique: {
data: null as Prisma.GrafikKepuasanGetPayload<{
omit: { isActive: true }
}> | null,
omit: { isActive: true };
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/kesehatan/grafikkepuasan/${id}`);
@@ -95,88 +122,137 @@ const grafikkepuasan = proxy({
},
update: {
id: "",
form: {...defaultForm},
form: { ...defaultForm },
loading: false,
async byId() {
},
async submit() {
const id = this.id;
if (!id) {
toast.warn("ID tidak valid");
return null;
}
const cek = templateGrafikKepuasan.safeParse(grafikkepuasan.update.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
this.loading = true;
const response = await fetch(`/api/kesehatan/grafikkepuasan/${id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(this.form),
});
const result = await response.json();
if (!response.ok || !result?.success) {
throw new Error(result?.message || "Gagal update data");
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
toast.success("Berhasil update data!");
try {
const response = await fetch(`/api/kesehatan/grafikkepuasan/${id}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
// ✅ Optional: refresh list kalau kamu langsung ke halaman list
await grafikkepuasan.findMany.load();
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return result.data;
} catch (error) {
console.error("Error update data:", error);
toast.error("Gagal update data grafik kepuasan");
} finally {
this.loading = false;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) {
return toast.warn("ID tidak valid");
}
try {
grafikkepuasan.delete.loading = true;
const result = await response.json();
const response = await fetch(`/api/kesehatan/grafikkepuasan/del/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
const result = await response.json();
if (response.ok && result?.success) {
toast.success(
result.message || "Grafik kepuasan berhasil dihapus"
);
await grafikkepuasan.findMany.load(); // refresh list
} else {
if (result?.success) {
const data = result.data;
this.id = data.id;
this.form = {
nama: data.nama,
tanggal: data.tanggal,
jenisKelamin: data.jenisKelamin,
alamat: data.alamat,
penyakit: data.penyakit,
};
return data; // Return the loaded data
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error loading grafik kepuasan:", error);
toast.error(
result?.message || "Gagal menghapus grafik kepuasan"
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus grafik kepuasan");
} finally {
grafikkepuasan.delete.loading = false;
}
}
}
},
async submit() {
const id = this.id;
if (!id) {
toast.warn("ID tidak valid");
return null;
}
const formData = {
nama: this.form.nama,
tanggal: this.form.tanggal,
jenisKelamin: this.form.jenisKelamin,
alamat: this.form.alamat,
penyakit: this.form.penyakit,
};
const cek = templateGrafikKepuasan.safeParse(formData);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return null;
}
try {
this.loading = true;
const res = await fetch(`/api/kesehatan/grafikkepuasan/${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 grafikkepuasan.findMany.load();
return result.data;
} catch (error) {
console.error("Update error:", error);
toast.error("Gagal update data grafik kepuasan");
throw error;
} finally {
this.loading = false;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) {
return toast.warn("ID tidak valid");
}
try {
grafikkepuasan.delete.loading = true;
const response = await fetch(
`/api/kesehatan/grafikkepuasan/del/${id}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
);
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Grafik kepuasan berhasil dihapus");
await grafikkepuasan.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus grafik kepuasan");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus grafik kepuasan");
} finally {
grafikkepuasan.delete.loading = false;
}
},
},
});
export default grafikkepuasan;

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
@@ -20,17 +21,41 @@ const defaultForm = {
const infoWabahPenyakit = proxy({
findMany: {
data: [] as Prisma.InfoWabahPenyakitGetPayload<{
include: {
image: true;
};
}>[],
async load() {
const res = await ApiFetch.api.kesehatan.infowabahpenyakit[
"find-many"
].get();
if (res.status === 200) {
infoWabahPenyakit.findMany.data = res.data?.data ?? [];
data: null as
| Prisma.InfoWabahPenyakitGetPayload<{
include: {
image: true;
};
}>[]
| null,
page: 1,
totalPages: 1,
loading: false,
search: "",
load: async (page = 1, limit = 10, search = "") => {
infoWabahPenyakit.findMany.loading = true; // ✅ Akses langsung via nama path
infoWabahPenyakit.findMany.page = page;
infoWabahPenyakit.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.kesehatan.infowabahpenyakit["find-many"].get({ query });
if (res.status === 200 && res.data?.success) {
infoWabahPenyakit.findMany.data = res.data.data ?? [];
infoWabahPenyakit.findMany.totalPages = res.data.totalPages ?? 1;
} else {
infoWabahPenyakit.findMany.data = [];
infoWabahPenyakit.findMany.totalPages = 1;
}
} catch (err) {
console.error("Gagal fetch info wabah penyakit paginated:", err);
infoWabahPenyakit.findMany.data = [];
infoWabahPenyakit.findMany.totalPages = 1;
} finally {
infoWabahPenyakit.findMany.loading = false;
}
},
},

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
@@ -5,204 +6,241 @@ import { proxy } from "valtio";
import { z } from "zod";
const templateForm = z.object({
name: z.string().min(3, "Judul minimal 3 karakter"),
deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"),
imageId: z.string().nonempty(),
})
const defaultForm = {
name: "",
deskripsi: "",
imageId: "",
}
const kontakDarurat = proxy({
findMany: {
data: [] as Prisma.KontakDaruratGetPayload<{
include: {
image: true;
};
}>[],
async load() {
const res = await ApiFetch.api.kesehatan.kontakdarurat[
"find-many"
].get();
if (res.status === 200) {
kontakDarurat.findMany.data = res.data?.data ?? [];
}
},
},
create:{
form: {...defaultForm},
loading: false,
async create() {
const cek = templateForm.safeParse(kontakDarurat.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
kontakDarurat.create.loading = true;
const res = await ApiFetch.api.kesehatan.kontakdarurat[
"create"
].post(kontakDarurat.create.form);
if (res.status === 200) {
kontakDarurat.findMany.load();
return toast.success("Kontak Darurat berhasil disimpan!");
}
return toast.error("Gagal menyimpan kontak darurat");
} catch (error) {
console.log((error as Error).message);
} finally {
kontakDarurat.create.loading = false;
}
},
resetForm() {
kontakDarurat.create.form = {...defaultForm};
}
},
findUnique: {
data: null as Prisma.KontakDaruratGetPayload<{
include: {
image: true;
};
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/kesehatan/kontakdarurat/${id}`);
if (res.ok) {
const data = await res.json();
kontakDarurat.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
kontakDarurat.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching data:", error);
kontakDarurat.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
try {
kontakDarurat.delete.loading = true;
const response = await fetch(`/api/kesehatan/kontakdarurat/del/${id}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
},
});
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Kontak darurat berhasil dihapus");
await kontakDarurat.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus kontak darurat");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus kontak darurat");
} finally {
kontakDarurat.delete.loading = false;
}
}
},
edit: {
id: "",
form: { ...defaultForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(`/api/kesehatan/kontakdarurat/${id}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
if (result?.success) {
const data = result.data;
this.id = data.id;
this.form = {
name: data.name,
deskripsi: data.deskripsi,
imageId: data.imageId,
};
return data; // Return the loaded data
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error fetching kontak darurat:", error);
toast.error(error instanceof Error ? error.message : "Gagal memuat data");
return null;
}
},
async update() {
const cek = templateForm.safeParse(kontakDarurat.edit.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
kontakDarurat.edit.loading = true;
const response = await fetch(`/api/kesehatan/kontakdarurat/${this.id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: this.form.name,
deskripsi: this.form.deskripsi,
imageId: this.form.imageId,
}),
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.message || `HTTP error! status: ${response.status}`);
}
const result = await response.json();
if (result.success) {
toast.success(result.message || "Kontak darurat berhasil diupdate");
await kontakDarurat.findMany.load();
return true;
} else {
throw new Error(result.message || "Gagal update kontak darurat");
}
} catch (error) {
console.error("Gagal update:", error);
toast.error(error instanceof Error ? error.message : "Terjadi kesalahan saat mengupdate kontak darurat");
return false;
} finally {
kontakDarurat.edit.loading = false;
}
},
reset() {
kontakDarurat.edit.id = "";
kontakDarurat.edit.form = { ...defaultForm };
},
},
name: z.string().min(3, "Judul minimal 3 karakter"),
deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"),
imageId: z.string().nonempty(),
});
export default kontakDarurat
const defaultForm = {
name: "",
deskripsi: "",
imageId: "",
};
const kontakDarurat = proxy({
findMany: {
data: null as
| Prisma.KontakDaruratGetPayload<{
include: {
image: true;
};
}>[]
| null,
page: 1,
totalPages: 1,
loading: false,
search: "",
load: async (page = 1, limit = 10, search = "") => {
kontakDarurat.findMany.loading = true; // ✅ Akses langsung via nama path
kontakDarurat.findMany.page = page;
kontakDarurat.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.kesehatan.kontakdarurat[
"find-many"
].get({ query });
if (res.status === 200 && res.data?.success) {
kontakDarurat.findMany.data = res.data.data ?? [];
kontakDarurat.findMany.totalPages = res.data.totalPages ?? 1;
} else {
kontakDarurat.findMany.data = [];
kontakDarurat.findMany.totalPages = 1;
}
} catch (err) {
console.error("Gagal fetch kontak darurat paginated:", err);
kontakDarurat.findMany.data = [];
kontakDarurat.findMany.totalPages = 1;
} finally {
kontakDarurat.findMany.loading = false;
}
},
},
create: {
form: { ...defaultForm },
loading: false,
async create() {
const cek = templateForm.safeParse(kontakDarurat.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
kontakDarurat.create.loading = true;
const res = await ApiFetch.api.kesehatan.kontakdarurat["create"].post(
kontakDarurat.create.form
);
if (res.status === 200) {
kontakDarurat.findMany.load();
return toast.success("Kontak Darurat berhasil disimpan!");
}
return toast.error("Gagal menyimpan kontak darurat");
} catch (error) {
console.log((error as Error).message);
} finally {
kontakDarurat.create.loading = false;
}
},
resetForm() {
kontakDarurat.create.form = { ...defaultForm };
},
},
findUnique: {
data: null as Prisma.KontakDaruratGetPayload<{
include: {
image: true;
};
}> | null,
async load(id: string) {
try {
const res = await fetch(`/api/kesehatan/kontakdarurat/${id}`);
if (res.ok) {
const data = await res.json();
kontakDarurat.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch data", res.status, res.statusText);
kontakDarurat.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching data:", error);
kontakDarurat.findUnique.data = null;
}
},
},
delete: {
loading: false,
async byId(id: string) {
try {
kontakDarurat.delete.loading = true;
const response = await fetch(`/api/kesehatan/kontakdarurat/del/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Kontak darurat berhasil dihapus");
await kontakDarurat.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus kontak darurat");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus kontak darurat");
} finally {
kontakDarurat.delete.loading = false;
}
},
},
edit: {
id: "",
form: { ...defaultForm },
loading: false,
async load(id: string) {
if (!id) {
toast.warn("ID tidak valid");
return null;
}
try {
const response = await fetch(`/api/kesehatan/kontakdarurat/${id}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
if (result?.success) {
const data = result.data;
this.id = data.id;
this.form = {
name: data.name,
deskripsi: data.deskripsi,
imageId: data.imageId,
};
return data; // Return the loaded data
} else {
throw new Error(result?.message || "Gagal memuat data");
}
} catch (error) {
console.error("Error fetching kontak darurat:", error);
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async update() {
const cek = templateForm.safeParse(kontakDarurat.edit.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
kontakDarurat.edit.loading = true;
const response = await fetch(
`/api/kesehatan/kontakdarurat/${this.id}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: this.form.name,
deskripsi: this.form.deskripsi,
imageId: this.form.imageId,
}),
}
);
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(
errorData.message || `HTTP error! status: ${response.status}`
);
}
const result = await response.json();
if (result.success) {
toast.success(result.message || "Kontak darurat berhasil diupdate");
await kontakDarurat.findMany.load();
return true;
} else {
throw new Error(result.message || "Gagal update kontak darurat");
}
} catch (error) {
console.error("Gagal update:", error);
toast.error(
error instanceof Error
? error.message
: "Terjadi kesalahan saat mengupdate kontak darurat"
);
return false;
} finally {
kontakDarurat.edit.loading = false;
}
},
reset() {
kontakDarurat.edit.id = "";
kontakDarurat.edit.form = { ...defaultForm };
},
},
});
export default kontakDarurat;

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
@@ -17,21 +18,45 @@ const defaultForm = {
}
const penangananDarurat = proxy({
findMany: {
data: [] as Prisma.PenangananDaruratGetPayload<{
include: {
image: true;
};
}>[],
async load() {
const res = await ApiFetch.api.kesehatan.penanganandarurat[
"find-many"
].get();
if (res.status === 200) {
penangananDarurat.findMany.data = res.data?.data ?? [];
}
},
findMany: {
data: null as
| Prisma.PenangananDaruratGetPayload<{
include: {
image: true;
};
}>[]
| null,
page: 1,
totalPages: 1,
loading: false,
search: "",
load: async (page = 1, limit = 10, search = "") => {
penangananDarurat.findMany.loading = true; // ✅ Akses langsung via nama path
penangananDarurat.findMany.page = page;
penangananDarurat.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.kesehatan.penanganandarurat["find-many"].get({ query });
if (res.status === 200 && res.data?.success) {
penangananDarurat.findMany.data = res.data.data ?? [];
penangananDarurat.findMany.totalPages = res.data.totalPages ?? 1;
} else {
penangananDarurat.findMany.data = [];
penangananDarurat.findMany.totalPages = 1;
}
} catch (err) {
console.error("Gagal fetch berita paginated:", err);
penangananDarurat.findMany.data = [];
penangananDarurat.findMany.totalPages = 1;
} finally {
penangananDarurat.findMany.loading = false;
}
},
},
create:{
form: {...defaultForm},
loading: false,

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
@@ -20,17 +21,43 @@ const defaultForm = {
const programKesehatan = proxy({
findMany: {
data: [] as Prisma.ProgramKesehatanGetPayload<{
include: {
image: true;
};
}>[],
async load() {
const res = await ApiFetch.api.kesehatan.programkesehatan[
"find-many"
].get();
if (res.status === 200) {
programKesehatan.findMany.data = res.data?.data ?? [];
data: null as
| Prisma.ProgramKesehatanGetPayload<{
include: {
image: true;
};
}>[]
| null,
page: 1,
totalPages: 1,
loading: false,
search: "",
load: async (page = 1, limit = 10, search = "") => {
programKesehatan.findMany.loading = true; // ✅ Akses langsung via nama path
programKesehatan.findMany.page = page;
programKesehatan.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.kesehatan.programkesehatan[
"find-many"
].get({ query });
if (res.status === 200 && res.data?.success) {
programKesehatan.findMany.data = res.data.data ?? [];
programKesehatan.findMany.totalPages = res.data.totalPages ?? 1;
} else {
programKesehatan.findMany.data = [];
programKesehatan.findMany.totalPages = 1;
}
} catch (err) {
console.error("Gagal fetch berita paginated:", err);
programKesehatan.findMany.data = [];
programKesehatan.findMany.totalPages = 1;
} finally {
programKesehatan.findMany.loading = false;
}
},
},
@@ -97,12 +124,15 @@ const programKesehatan = proxy({
try {
programKesehatan.delete.loading = true;
const response = await fetch(`/api/kesehatan/programkesehatan/del/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
const response = await fetch(
`/api/kesehatan/programkesehatan/del/${id}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
);
const result = await response.json();
if (response.ok && result?.success) {
@@ -156,57 +186,70 @@ const programKesehatan = proxy({
}
} catch (error) {
console.error("Error fetching program kesehatan:", error);
toast.error(error instanceof Error ? error.message : "Gagal memuat data");
toast.error(
error instanceof Error ? error.message : "Gagal memuat data"
);
return null;
}
},
async update() {
const cek = templateForm.safeParse(programKesehatan.edit.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
const cek = templateForm.safeParse(programKesehatan.edit.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
programKesehatan.edit.loading = true;
const response = await fetch(`/api/kesehatan/programkesehatan/${this.id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: this.form.name,
deskripsiSingkat: this.form.deskripsiSingkat,
deskripsi: this.form.deskripsi,
imageId: this.form.imageId,
}),
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.message || `HTTP error! status: ${response.status}`);
}
const result = await response.json();
if (result.success) {
toast.success(result.message || "Program kesehatan berhasil diupdate");
await programKesehatan.findMany.load();
return true;
} else {
throw new Error(result.message || "Gagal update program kesehatan");
}
} catch (error) {
console.error("Gagal update:", error);
toast.error(error instanceof Error ? error.message : "Terjadi kesalahan saat mengupdate program kesehatan");
return false;
} finally {
programKesehatan.edit.loading = false;
try {
programKesehatan.edit.loading = true;
const response = await fetch(
`/api/kesehatan/programkesehatan/${this.id}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: this.form.name,
deskripsiSingkat: this.form.deskripsiSingkat,
deskripsi: this.form.deskripsi,
imageId: this.form.imageId,
}),
}
);
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(
errorData.message || `HTTP error! status: ${response.status}`
);
}
const result = await response.json();
if (result.success) {
toast.success(
result.message || "Program kesehatan berhasil diupdate"
);
await programKesehatan.findMany.load();
return true;
} else {
throw new Error(result.message || "Gagal update program kesehatan");
}
} catch (error) {
console.error("Gagal update:", error);
toast.error(
error instanceof Error
? error.message
: "Terjadi kesalahan saat mengupdate program kesehatan"
);
return false;
} finally {
programKesehatan.edit.loading = false;
}
},
reset() {
programKesehatan.edit.id = "";
programKesehatan.edit.form = { ...defaultForm };
programKesehatan.edit.id = "";
programKesehatan.edit.form = { ...defaultForm };
},
},
});

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
@@ -163,13 +164,43 @@ const puskesmasState = proxy({
},
findMany: {
data: null as Prisma.PuskesmasGetPayload<{
include: { image: true; jam: true; kontak: true };
}>[] | null,
async load() {
const res = await ApiFetch.api.kesehatan.puskesmas["find-many"].get();
if (res.status === 200) {
puskesmasState.findMany.data = res.data?.data ?? [];
data: null as
| Prisma.PuskesmasGetPayload<{
include: {
image: true;
jam: true;
kontak: true;
};
}>[]
| null,
page: 1,
totalPages: 1,
loading: false,
search: "",
load: async (page = 1, limit = 10, search = "") => {
puskesmasState.findMany.loading = true; // ✅ Akses langsung via nama path
puskesmasState.findMany.page = page;
puskesmasState.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.kesehatan.puskesmas["find-many"].get({ query });
if (res.status === 200 && res.data?.success) {
puskesmasState.findMany.data = res.data.data ?? [];
puskesmasState.findMany.totalPages = res.data.totalPages ?? 1;
} else {
puskesmasState.findMany.data = [];
puskesmasState.findMany.totalPages = 1;
}
} catch (err) {
console.error("Gagal fetch berita paginated:", err);
puskesmasState.findMany.data = [];
puskesmasState.findMany.totalPages = 1;
} finally {
puskesmasState.findMany.loading = false;
}
},
},

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
@@ -50,18 +51,50 @@ const apbdes = proxy({
},
},
findMany: {
data: null as Array<
Prisma.APBDesGetPayload<{
include: {
image: true;
file: true;
};
}>
> | null,
async load() {
const res = await ApiFetch.api.landingpage.apbdes["find-many"].get();
if (res.status === 200) {
apbdes.findMany.data = res.data?.data ?? [];
data: null as
| Prisma.APBDesGetPayload<{
include: {
image: true;
file: true;
};
}>[]
| null,
page: 1,
totalPages: 1,
total: 0,
loading: false,
search: "",
load: async (page = 1, limit = 10, search = "") => { // Change to arrow function
apbdes.findMany.loading = true; // Use the full path to access the property
apbdes.findMany.page = page;
apbdes.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.landingpage.apbdes[
"findMany"
].get({
query
});
if (res.status === 200 && res.data?.success) {
apbdes.findMany.data = res.data.data || [];
apbdes.findMany.total = res.data.total || 0;
apbdes.findMany.totalPages = res.data.totalPages || 1;
} else {
console.error("Failed to load pegawai:", res.data?.message);
apbdes.findMany.data = [];
apbdes.findMany.total = 0;
apbdes.findMany.totalPages = 1;
}
} catch (error) {
console.error("Error loading pegawai:", error);
apbdes.findMany.data = [];
apbdes.findMany.total = 0;
apbdes.findMany.totalPages = 1;
} finally {
apbdes.findMany.loading = false;
}
},
},

View File

@@ -60,16 +60,22 @@ const desaAntikorupsi = proxy({
totalPages: 1,
total: 0,
loading: false,
load: async (page = 1, limit = 10) => { // Change to arrow function
desaAntikorupsi.findMany.loading = true; // Use the full path to access the property
search: "",
load: async (page = 1, limit = 10, search = "") => {
// Change to arrow function
desaAntikorupsi.findMany.loading = true; // Use the full path to access the property
desaAntikorupsi.findMany.page = page;
desaAntikorupsi.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.landingpage.desaantikorupsi[
"findMany"
].get({
query: { page, limit },
query,
});
if (res.status === 200 && res.data?.success) {
desaAntikorupsi.findMany.data = res.data.data || [];
desaAntikorupsi.findMany.total = res.data.total || 0;
@@ -305,20 +311,25 @@ const kategoriDesaAntiKorupsi = proxy({
totalPages: 1,
total: 0,
loading: false,
load: async (page = 1, limit = 10) => { // Change to arrow function
kategoriDesaAntiKorupsi.findMany.loading = true; // Use the full path to access the property
search: "",
load: async (page = 1, limit = 10, search = "") => {
// Change to arrow function
kategoriDesaAntiKorupsi.findMany.loading = true; // Use the full path to access the property
kategoriDesaAntiKorupsi.findMany.page = page;
kategoriDesaAntiKorupsi.findMany.search = search;
try {
const res = await ApiFetch.api.landingpage.kategoridak[
"findMany"
].get({
query: { page, limit },
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.landingpage.kategoridak["findMany"].get({
query,
});
if (res.status === 200 && res.data?.success) {
kategoriDesaAntiKorupsi.findMany.data = res.data.data || [];
kategoriDesaAntiKorupsi.findMany.total = res.data.total || 0;
kategoriDesaAntiKorupsi.findMany.totalPages = res.data.totalPages || 1;
kategoriDesaAntiKorupsi.findMany.totalPages =
res.data.totalPages || 1;
} else {
console.error("Failed to load media sosial:", res.data?.message);
kategoriDesaAntiKorupsi.findMany.data = [];
@@ -363,27 +374,30 @@ const kategoriDesaAntiKorupsi = proxy({
try {
kategoriDesaAntiKorupsi.delete.loading = true;
const response = await fetch(
`/api/landingpage/kategoridak/del/${id}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
);
const response = await fetch(`/api/landingpage/kategoridak/del/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
const result = await response.json();
if (response.ok && result?.success) {
toast.success(result.message || "Kategori desa anti korupsi berhasil dihapus");
toast.success(
result.message || "Kategori desa anti korupsi berhasil dihapus"
);
await kategoriDesaAntiKorupsi.findMany.load(); // refresh list
} else {
toast.error(result?.message || "Gagal menghapus kategori desa anti korupsi");
toast.error(
result?.message || "Gagal menghapus kategori desa anti korupsi"
);
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus kategori desa anti korupsi");
toast.error(
"Terjadi kesalahan saat menghapus kategori desa anti korupsi"
);
} finally {
kategoriDesaAntiKorupsi.delete.loading = false;
}

View File

@@ -181,7 +181,13 @@ const responden = proxy({
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(this.form),
body: JSON.stringify({
name: this.form.name,
tanggal: this.form.tanggal,
jenisKelaminId: this.form.jenisKelaminId,
ratingId: this.form.ratingId,
kelompokUmurId: this.form.kelompokUmurId,
}),
});
const result = await response.json();
if (!response.ok || !result?.success) {

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
@@ -58,16 +59,43 @@ const prestasiDesa = proxy({
Prisma.PrestasiDesaGetPayload<{
include: {
image: true;
kategori: true;
kategori: {
select: {
id: true;
name: true;
};
};
};
}>
> | null,
async load() {
const res = await ApiFetch.api.landingpage.prestasidesa[
"find-many"
].get();
if (res.status === 200) {
prestasiDesa.findMany.data = res.data?.data ?? [];
page: 1,
totalPages: 1,
loading: false,
search: "",
load: async (page = 1, limit = 10, search = "") => {
prestasiDesa.findMany.loading = true; // ✅ Akses langsung via nama path
prestasiDesa.findMany.page = page;
prestasiDesa.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.landingpage.prestasidesa["find-many"].get({ query });
if (res.status === 200 && res.data?.success) {
prestasiDesa.findMany.data = res.data.data ?? [];
prestasiDesa.findMany.totalPages = res.data.totalPages ?? 1;
} else {
prestasiDesa.findMany.data = [];
prestasiDesa.findMany.totalPages = 1;
}
} catch (err) {
console.error("Gagal fetch prestasi desa paginated:", err);
prestasiDesa.findMany.data = [];
prestasiDesa.findMany.totalPages = 1;
} finally {
prestasiDesa.findMany.loading = false;
}
},
},
@@ -283,12 +311,34 @@ const kategoriPrestasi = proxy({
id: string;
name: string;
}> | null,
async load() {
const res = await ApiFetch.api.landingpage.kategoriprestasi[
"find-many"
].get();
if (res.status === 200) {
kategoriPrestasi.findMany.data = res.data?.data ?? [];
page: 1,
totalPages: 1,
loading: false,
search: "",
load: async (page = 1, limit = 10, search = "") => {
kategoriPrestasi.findMany.loading = true; // ✅ Akses langsung via nama path
kategoriPrestasi.findMany.page = page;
kategoriPrestasi.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.landingpage.kategoriprestasi["find-many"].get({ query });
if (res.status === 200 && res.data?.success) {
kategoriPrestasi.findMany.data = res.data.data ?? [];
kategoriPrestasi.findMany.totalPages = res.data.totalPages ?? 1;
} else {
kategoriPrestasi.findMany.data = [];
kategoriPrestasi.findMany.totalPages = 1;
}
} catch (err) {
console.error("Gagal fetch kategori prestasi paginated:", err);
kategoriPrestasi.findMany.data = [];
kategoriPrestasi.findMany.totalPages = 1;
} finally {
kategoriPrestasi.findMany.loading = false;
}
},
},

View File

@@ -65,14 +65,19 @@ const programInovasi = proxy({
totalPages: 1,
total: 0,
loading: false,
load: async (page = 1, limit = 10) => { // Change to arrow function
search: "",
load: async (page = 1, limit = 10, search = "") => { // Change to arrow function
programInovasi.findMany.loading = true; // Use the full path to access the property
programInovasi.findMany.page = page;
programInovasi.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.landingpage.programinovasi[
"findMany"
].get({
query: { page, limit },
query
});
if (res.status === 200 && res.data?.success) {
@@ -482,14 +487,19 @@ const mediaSosial = proxy({
totalPages: 1,
total: 0,
loading: false,
load: async (page = 1, limit = 10) => { // Change to arrow function
search: "",
load: async (page = 1, limit = 10, search = "") => { // Change to arrow function
mediaSosial.findMany.loading = true; // Use the full path to access the property
mediaSosial.findMany.page = page;
try {
mediaSosial.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.landingpage.mediasosial[
"findMany"
].get({
query: { page, limit },
query,
});
if (res.status === 200 && res.data?.success) {

View File

@@ -58,14 +58,19 @@ const sdgsDesa = proxy({
totalPages: 1,
total: 0,
loading: false,
load: async (page = 1, limit = 10) => { // Change to arrow function
search: "",
load: async (page = 1, limit = 10, search = "") => { // Change to arrow function
sdgsDesa.findMany.loading = true; // Use the full path to access the property
sdgsDesa.findMany.page = page;
sdgsDesa.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.landingpage.sdgsdesa[
"findMany"
].get({
query: { page, limit },
query,
});
if (res.status === 200 && res.data?.success) {

View File

@@ -49,35 +49,38 @@ const daftarInformasiPublik = proxy({
},
},
findMany: {
data: null as any[] | null,
data: null as
| Prisma.DaftarInformasiPublikGetPayload<{
omit: {
isActive: true;
};
}>[]
| null,
page: 1,
totalPages: 1,
total: 0,
loading: false,
load: async (page = 1, limit = 10) => { // Change to arrow function
daftarInformasiPublik.findMany.loading = true; // Use the full path to access the property
search: "",
load: async (page = 1, limit = 10, search = "") => {
daftarInformasiPublik.findMany.loading = true; // ✅ Akses langsung via nama path
daftarInformasiPublik.findMany.page = page;
try {
const res = await ApiFetch.api.ppid.daftarinformasipublik[
"find-many"
].get({
query: { page, limit },
});
daftarInformasiPublik.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.ppid.daftarinformasipublik["find-many"].get({ query });
if (res.status === 200 && res.data?.success) {
daftarInformasiPublik.findMany.data = res.data.data || [];
daftarInformasiPublik.findMany.total = res.data.total || 0;
daftarInformasiPublik.findMany.totalPages = res.data.totalPages || 1;
daftarInformasiPublik.findMany.data = res.data.data ?? [];
daftarInformasiPublik.findMany.totalPages = res.data.totalPages ?? 1;
} else {
console.error("Failed to load daftar informasi publik:", res.data?.message);
daftarInformasiPublik.findMany.data = [];
daftarInformasiPublik.findMany.total = 0;
daftarInformasiPublik.findMany.totalPages = 1;
}
} catch (error) {
console.error("Error loading daftar informasi publik:", error);
} catch (err) {
console.error("Gagal fetch daftar informasi publik paginated:", err);
daftarInformasiPublik.findMany.data = [];
daftarInformasiPublik.findMany.total = 0;
daftarInformasiPublik.findMany.totalPages = 1;
} finally {
daftarInformasiPublik.findMany.loading = false;

View File

@@ -348,18 +348,34 @@ const posisiOrganisasi = proxy({
deskripsi: string | null;
hierarki: number;
}>,
async load() {
page: 1,
totalPages: 1,
loading: false,
search: "",
load: async (page = 1, limit = 10, search = "") => {
posisiOrganisasi.findMany.loading = true; // ✅ Akses langsung via nama path
posisiOrganisasi.findMany.page = page;
posisiOrganisasi.findMany.search = search;
try {
const res = await ApiFetch.api.ppid.strukturppid.posisiorganisasi[
"find-many"
].get();
if (res.status === 200) {
// The API now returns the id field, so we can use it directly
this.data = res.data?.data ?? [];
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.ppid.strukturppid.posisiorganisasi["find-many"].get({ query });
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) {
console.error("Find many error:", error);
this.data = [];
} catch (err) {
console.error("Gagal fetch posisi organisasi paginated:", err);
posisiOrganisasi.findMany.data = [];
posisiOrganisasi.findMany.totalPages = 1;
} finally {
posisiOrganisasi.findMany.loading = false;
}
},
},
@@ -438,9 +454,9 @@ const pegawai = proxy({
try {
pegawai.create.loading = true;
const res = await ApiFetch.api.ppid.strukturppid.pegawai[
"create"
].post(pegawai.create.form);
const res = await ApiFetch.api.ppid.strukturppid.pegawai["create"].post(
pegawai.create.form
);
if (res.status === 200) {
toast.success("Pegawai berhasil ditambahkan");
await pegawai.findMany.load();
@@ -457,42 +473,55 @@ const pegawai = proxy({
},
// In struktur-organisasi.ts
findMany: {
data: null as any[] | null,
page: 1,
totalPages: 1,
total: 0,
loading: false,
load: async (page = 1, limit = 10) => { // Change to arrow function
pegawai.findMany.loading = true; // Use the full path to access the property
pegawai.findMany.page = page;
try {
const res = await ApiFetch.api.ppid.strukturppid.pegawai[
"find-many"
].get({
query: { page, limit },
});
findMany: {
data: null as
| Prisma.PegawaiPPIDGetPayload<{
include: {
image: true;
posisi: true;
};
}>[]
| null,
page: 1,
totalPages: 1,
total: 0,
loading: false,
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) {
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);
const res = await ApiFetch.api.ppid.strukturppid.pegawai[
"find-many"
].get({
query,
});
if (res.status === 200 && res.data?.success) {
pegawai.findMany.data = res.data.data || [];
pegawai.findMany.total = res.data.total || 0;
pegawai.findMany.totalPages = res.data.totalPages || 1;
} else {
console.error("Failed to load pegawai:", res.data?.message);
pegawai.findMany.data = [];
pegawai.findMany.total = 0;
pegawai.findMany.totalPages = 1;
}
} catch (error) {
console.error("Error loading pegawai:", error);
pegawai.findMany.data = [];
pegawai.findMany.total = 0;
pegawai.findMany.totalPages = 1;
} finally {
pegawai.findMany.loading = false;
}
} catch (error) {
console.error("Error loading pegawai:", error);
pegawai.findMany.data = [];
pegawai.findMany.total = 0;
pegawai.findMany.totalPages = 1;
} finally {
pegawai.findMany.loading = false;
}
},
},
},
findUnique: {
data: null as
| (Prisma.PegawaiGetPayload<{
@@ -521,12 +550,9 @@ findMany: {
if (!id) return toast.warn("ID tidak valid");
try {
pegawai.delete.loading = true;
const res = await fetch(
`/api/ppid/strukturppid/pegawai/del/${id}`,
{
method: "DELETE",
}
);
const res = await fetch(`/api/ppid/strukturppid/pegawai/del/${id}`, {
method: "DELETE",
});
const json = await res.json();
if (res.ok) {
toast.success(json.message ?? "Berhasil hapus pegawai");
@@ -555,15 +581,12 @@ findMany: {
}
try {
const response = await fetch(
`/api/ppid/strukturppid/pegawai/${id}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
}
);
const response = await fetch(`/api/ppid/strukturppid/pegawai/${id}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
@@ -677,7 +700,7 @@ findMany: {
const stateStrukturPPID = proxy({
stateStruktur,
posisiOrganisasi,
pegawai
pegawai,
});
export default stateStrukturPPID;

View File

@@ -143,8 +143,8 @@ function ListDemografiPekerjaan({ search }: { search: string }) {
dataKey="pekerjaan"
type="stacked"
series={[
{ name: 'lakiLaki', color: 'red.6', label: 'Laki - Laki' },
{ name: 'perempuan', color: 'orange.6', label: 'Perempuan' },
{ name: 'lakiLaki', color: '#5082EE', label: 'Laki - Laki' },
{ name: 'perempuan', color: '#6EDF9C', label: 'Perempuan' },
]}
/>
</Box>

View File

@@ -35,7 +35,7 @@ function EditJumlahPendudukMiskin() {
// Set the ID before submitting
stateJPM.update.id = id;
await stateJPM.update.submit();
router.push('/admin/ekonomi/jumlah-penduduk-miskin-2024-2025')
router.push('/admin/ekonomi/jumlah-penduduk-miskin')
}
return (
<Box>

View File

@@ -32,7 +32,7 @@ function CreateJumlahPendudukMiskin() {
}
}
resetForm();
router.push("/admin/ekonomi/jumlah-penduduk-miskin-2024-2025");
router.push("/admin/ekonomi/jumlah-penduduk-miskin");
}
return (
<Box>

View File

@@ -91,7 +91,7 @@ function ListJumlahPendudukMiskin({ search }: { search: string }) {
<Paper bg={colors['white-1']} p={'md'}>
<JudulList
title='List Jumlah Penduduk Miskin'
href='/admin/ekonomi/jumlah-penduduk-miskin-2024-2025/create'
href='/admin/ekonomi/jumlah-penduduk-miskin/create'
/>
<Table striped withTableBorder withRowBorders>
<TableThead>
@@ -108,7 +108,7 @@ function ListJumlahPendudukMiskin({ search }: { search: string }) {
<TableTd>{item.year}</TableTd>
<TableTd>{item.totalPoorPopulation}</TableTd>
<TableTd>
<Button color='green' onClick={() => router.push(`/admin/ekonomi/jumlah-penduduk-miskin-2024-2025/${item.id}`)}>
<Button color='green' onClick={() => router.push(`/admin/ekonomi/jumlah-penduduk-miskin/${item.id}`)}>
<IconEdit size={20} />
</Button>
</TableTd>

View File

@@ -8,29 +8,36 @@ import { useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { Cell, Pie, PieChart } from 'recharts';
import { useProxy } from 'valtio/utils';
import JudulListTab from '../../../_com/judulListTab';
import HeaderSearch from '../../../_com/header';
import JudulList from '../../../_com/judulList';
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
import grafikNganggur from '../../../_state/ekonomi/usia-kerja-nganggur';
function GrafikBerdasarkanPendidikan() {
const [search, setSearch] = useState("")
return (
<Box>
<Stack gap={"xs"}>
<Title order={3}>Grafik Pengangguran Berdasarkan Pendidikan</Title>
<ListGrafikBerdasarkanPendidikan />
<HeaderSearch
title='Detail Data Pengangguran Berdasarkan Pendidikan'
placeholder='pencarian'
searchIcon={<IconSearch size={20} />}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<ListGrafikBerdasarkanPendidikan search={search}/>
</Stack>
</Box>
);
}
function ListGrafikBerdasarkanPendidikan() {
function ListGrafikBerdasarkanPendidikan({search}: {search: string}) {
const stategrafik = useProxy(grafikNganggur.grafikBerdasarkanPendidikan)
const [donutData, setDonutData] = useState<any[]>([]);
const [mounted, setMounted] = useState(false);
const [modalHapus, setModalHapus] = useState(false)
const [selectedId, setSelectedId] = useState<string | null>(null)
const router = useRouter();
const [search, setSearch] = useState("");
const handleDelete = async () => {
@@ -56,11 +63,11 @@ function ListGrafikBerdasarkanPendidikan() {
const D3 = stategrafik.findMany.data.reduce((acc: number, cur: any) => acc + Number(cur.D3 || 0), 0);
const S1 = stategrafik.findMany.data.reduce((acc: number, cur: any) => acc + Number(cur.S1 || 0), 0);
setDonutData([
{ name: 'SD', value: SD, color: colors['blue-button'], key: 'SD' },
{ name: 'SMP', value: SMP, color: '#10A85AFF', key: 'SMP' },
{ name: 'SMA', value: SMA, color: '#C07B13FF', key: 'SMA' },
{ name: 'D3', value: D3, color: '#1094A8FF', key: 'D3' },
{ name: 'S1', value: S1, color: '#A83610FF', key: 'S1' },
{ name: 'SD', value: SD, color: '#4b6Ef5', key: 'SD' },
{ name: 'SMP', value: SMP, color: '#14b885', key: 'SMP' },
{ name: 'SMA', value: SMA, color: '#E6A03B', key: 'SMA' },
{ name: 'D3', value: D3, color: '#DB524D', key: 'D3' },
{ name: 'S1', value: S1, color: '#1018A8FF', key: 'S1' },
]);
}
}, [stategrafik.findMany.data])
@@ -88,13 +95,9 @@ function ListGrafikBerdasarkanPendidikan() {
<Box>
<Stack gap={"xs"}>
<Paper bg={colors['white-1']} p={"md"}>
<JudulListTab
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
<JudulList
title='List Grafik Pengangguran Berdasarkan Pendidikan'
href='/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_pendidikan/create'
placeholder='pencarian'
searchIcon={<IconSearch size={16} />}
/>
<Table striped withTableBorder withRowBorders>
<TableThead>

View File

@@ -8,29 +8,37 @@ import { useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { Cell, Pie, PieChart } from 'recharts';
import { useProxy } from 'valtio/utils';
import JudulListTab from '../../../_com/judulListTab';
import HeaderSearch from '../../../_com/header';
import JudulList from '../../../_com/judulList';
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
import grafikNganggur from '../../../_state/ekonomi/usia-kerja-nganggur';
function GrafikBerdasarkanUsiaKerjaYangMenganggur() {
const [search, setSearch] = useState("")
return (
<Box>
<Stack gap={"xs"}>
<Title order={3}>Grafik Pengangguran Berdasarkan Usia Kerja</Title>
<ListGrafikBerdasarkanUsiaKerjaYangMenganggur />
<HeaderSearch
title='Detail Data Pengangguran'
placeholder='pencarian'
searchIcon={<IconSearch size={20} />}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<ListGrafikBerdasarkanUsiaKerjaYangMenganggur search={search} />
</Stack>
</Box>
);
}
function ListGrafikBerdasarkanUsiaKerjaYangMenganggur() {
function ListGrafikBerdasarkanUsiaKerjaYangMenganggur({search}: {search: string}) {
const stategrafik = useProxy(grafikNganggur.grafikBerdasarkanUsiaKerjaNganggur)
const [donutData, setDonutData] = useState<any[]>([]);
const [mounted, setMounted] = useState(false);
const [modalHapus, setModalHapus] = useState(false)
const [selectedId, setSelectedId] = useState<string | null>(null)
const router = useRouter();
const [search, setSearch] = useState("");
const handleDelete = async () => {
@@ -85,13 +93,9 @@ function ListGrafikBerdasarkanUsiaKerjaYangMenganggur() {
<Box>
<Stack gap={"xs"}>
<Paper bg={colors['white-1']} p={"md"}>
<JudulListTab
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
<JudulList
title='List Pengangguran Berdasarkan Usia Kerja'
href='/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_usia/create'
placeholder='pencarian'
searchIcon={<IconSearch size={16} />}
/>
<Table striped withTableBorder withRowBorders>
<TableThead>

View File

@@ -66,19 +66,23 @@ function EditDetailDataPengangguran() {
const data = stateDetail.findUnique.data;
if (data) {
// Convert year from Date to number
const yearValue = data.year instanceof Date ? data.year.getFullYear() : data.year;
// Set the ID for update
stateDetail.update.id = id;
// Isi state Valtio untuk update
// Update Valtio state with converted year
stateDetail.update.form = {
...data,
year: yearValue,
percentageChange: data.percentageChange || 0 // Ensure it's always a number
};
// Isi local formData supaya input bisa dikontrol
// Update local formData with converted year
setFormData({
month: data.month,
year: data.year,
year: yearValue,
totalUnemployment: data.totalUnemployment,
educatedUnemployment: data.educatedUnemployment,
uneducatedUnemployment: data.uneducatedUnemployment,

View File

@@ -25,7 +25,7 @@ function DetailJumlahPengangguran() {
stateDetail.delete.byId(selectedId)
setModalHapus(false)
setSelectedId(null)
router.push("/admin/ekonomi/jumlah-pengangguran/detail-data-pengangguran")
router.push("/admin/ekonomi/jumlah-pengangguran")
}
}
@@ -63,7 +63,7 @@ function DetailJumlahPengangguran() {
</Box>
<Box>
<Text fw={"bold"}>Tahun</Text>
<Text>{stateDetail.findUnique.data?.year}</Text>
<Text>{stateDetail.findUnique.data?.year ? new Date(stateDetail.findUnique.data.year).getFullYear() : ''}</Text>
</Box>
<Box>
<Text fw={"bold"}>Bulan</Text>
@@ -86,7 +86,7 @@ function DetailJumlahPengangguran() {
color={"red"}>
<IconX size={20} />
</Button>
<Button onClick={() => router.push(`/admin/ekonomi/jumlah-pengangguran/detail-data-pengangguran/${stateDetail.findUnique.data?.id}/edit`)} color="green">
<Button onClick={() => router.push(`/admin/ekonomi/jumlah-pengangguran/${stateDetail.findUnique.data?.id}/edit`)} color="green">
<IconEdit size={20} />
</Button>
</Flex>
@@ -108,4 +108,3 @@ function DetailJumlahPengangguran() {
}
export default DetailJumlahPengangguran;

View File

@@ -90,7 +90,7 @@ function CreateJumlahPengangguran() {
/>
<TextInput
label="Tahun"
type="number"
type="date"
value={stateDetail.create.form.year}
onChange={(e) =>
(stateDetail.create.form.year = Number(e.currentTarget.value))

View File

@@ -8,21 +8,29 @@ import { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils';
import { BarChart } from '@mantine/charts';
import HeaderSearch from '../../_com/header';
import JudulList from '../../_com/judulList';
import jumlahPengangguranState from '../../_state/ekonomi/jumlah-pengangguran';
import JudulListTab from '../../_com/judulListTab';
function DetailDataPengangguran() {
const [search, setSearch] = useState("")
return (
<Box>
<Stack gap={"xs"}>
<Title order={3}>Detail Data Pengangguran</Title>
<ListDetailDataPengangguran />
<HeaderSearch
title='Detail Data Pengangguran'
placeholder='pencarian'
searchIcon={<IconSearch size={20} />}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<ListDetailDataPengangguran search={search} />
</Stack>
</Box>
);
}
function ListDetailDataPengangguran() {
function ListDetailDataPengangguran({search}: {search: string}) {
type DetailDataPengangguran = {
id: string;
@@ -37,7 +45,6 @@ function ListDetailDataPengangguran() {
const [mounted, setMounted] = useState(false); // untuk memastikan DOM sudah ready
const stateDetail = useProxy(jumlahPengangguranState.jumlahPengangguran)
const router = useRouter();
const [search, setSearch] = useState("")
useShallowEffect(() => {
setMounted(true)
@@ -50,7 +57,7 @@ function ListDetailDataPengangguran() {
setChartData(stateDetail.findMany.data.map((item) => ({
id: item.id,
month: item.month,
year: item.year,
year: item.year instanceof Date ? item.year.getFullYear() : Number(item.year),
educatedUnemployment: Number(item.educatedUnemployment),
uneducatedUnemployment: Number(item.uneducatedUnemployment),
percentageChange: Number(item.percentageChange),
@@ -78,13 +85,9 @@ function ListDetailDataPengangguran() {
<Box>
<Stack gap={"md"}>
<Paper bg={colors['white-1']} p={'md'}>
<JudulListTab
<JudulList
title='List Detail Data Pengangguran'
href='/admin/ekonomi/jumlah-pengangguran/detail-data-pengangguran/create'
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
placeholder='pencarian'
searchIcon={<IconSearch size={16} />}
href='/admin/ekonomi/jumlah-pengangguran/create'
/>
<Table striped withTableBorder withRowBorders>
<TableThead>
@@ -102,7 +105,7 @@ function ListDetailDataPengangguran() {
<TableTd>{item.educatedUnemployment}</TableTd>
<TableTd>{item.uneducatedUnemployment}</TableTd>
<TableTd>
<Button onClick={() => router.push(`/admin/ekonomi/jumlah-pengangguran/detail-data-pengangguran/${item.id}`)}>
<Button onClick={() => router.push(`/admin/ekonomi/jumlah-pengangguran/${item.id}`)}>
<IconDeviceImac size={20} />
</Button>
</TableTd>

View File

@@ -55,17 +55,17 @@ function EditLowonganKerja() {
const handleSubmit = async () => {
try {
lowonganKerjaState.update.form = {
...lowonganKerjaState.update.form,
posisi: formData.posisi,
namaPerusahaan: formData.namaPerusahaan,
lokasi: formData.lokasi,
tipePekerjaan: formData.tipePekerjaan,
gaji: formData.gaji,
deskripsi: formData.deskripsi,
kualifikasi: formData.kualifikasi,
}
await lowonganState.update.update()
// Set the ID for the update
lowonganState.update.id = params?.id as string;
// Update the form state
lowonganState.update.form = {
...lowonganState.update.form,
...formData
};
// Call the update function
await lowonganState.update.update();
toast.success("Lowongan kerja berhasil diperbarui!");
router.push("/admin/ekonomi/lowongan-kerja-lokal");
} catch (error) {
@@ -88,7 +88,7 @@ function EditLowonganKerja() {
<TextInput
value={formData.posisi}
onChange={(val) => {
formData.posisi = val.target.value;
setFormData(prev => ({ ...prev, posisi: val.target.value }));
}}
label={<Text fw={"bold"} fz={"sm"}>Posisi</Text>}
placeholder='Masukkan posisi'
@@ -96,7 +96,7 @@ function EditLowonganKerja() {
<TextInput
value={formData.namaPerusahaan}
onChange={(val) => {
formData.namaPerusahaan = val.target.value;
setFormData(prev => ({ ...prev, namaPerusahaan: val.target.value }));
}}
label={<Text fw={"bold"} fz={"sm"}>Nama Perusahaan</Text>}
placeholder='Masukkan nama perusahaan'
@@ -104,7 +104,7 @@ function EditLowonganKerja() {
<TextInput
value={formData.lokasi}
onChange={(val) => {
formData.lokasi = val.target.value;
setFormData(prev => ({ ...prev, lokasi: val.target.value }));
}}
label={<Text fw={"bold"} fz={"sm"}>Lokasi</Text>}
placeholder='Masukkan lokasi'
@@ -112,7 +112,7 @@ function EditLowonganKerja() {
<TextInput
value={formData.tipePekerjaan}
onChange={(val) => {
formData.tipePekerjaan = val.target.value;
setFormData(prev => ({ ...prev, tipePekerjaan: val.target.value }));
}}
label={<Text fw={"bold"} fz={"sm"}>Tipe Pekerjaan</Text>}
placeholder='Masukkan tipe pekerjaan'
@@ -120,7 +120,7 @@ function EditLowonganKerja() {
<TextInput
value={formData.gaji}
onChange={(val) => {
formData.gaji = val.target.value;
setFormData(prev => ({ ...prev, gaji: val.target.value }));
}}
label={<Text fw={"bold"} fz={"sm"}>Gaji selama 1 bulan</Text>}
placeholder='Masukkan gaji'
@@ -130,7 +130,7 @@ function EditLowonganKerja() {
<EditEditor
value={formData.deskripsi}
onChange={(val) => {
formData.deskripsi = val;
setFormData(prev => ({ ...prev, deskripsi: val }));
}}
/>
</Box>
@@ -139,7 +139,7 @@ function EditLowonganKerja() {
<EditEditor
value={formData.kualifikasi}
onChange={(val) => {
formData.kualifikasi = val;
setFormData(prev => ({ ...prev, kualifikasi: val }));
}}
/>
</Box>

View File

@@ -1,6 +1,6 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
import HeaderSearch from '../../_com/header';
import JudulList from '../../_com/judulList';
@@ -30,20 +30,21 @@ function ListLowonganKerjaLokal({ search }: { search: string }) {
const lowonganState = useProxy(lowonganKerjaState)
const router = useRouter();
const {
data,
page,
totalPages,
loading,
load,
} = lowonganState.findMany
useShallowEffect(() => {
lowonganState.findMany.load();
}, [])
load(page, 10, search)
}, [page, search])
const filteredData = (lowonganState.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.posisi.toLowerCase().includes(keyword) ||
item.namaPerusahaan.toLowerCase().includes(keyword) ||
item.lokasi.toLowerCase().includes(keyword)
);
});
const filteredData = data || []
if (!lowonganState.findMany.data) {
if (loading || !data) {
return (
<Stack py={10}>
<Skeleton h={500} />
@@ -60,18 +61,24 @@ function ListLowonganKerjaLokal({ search }: { search: string }) {
<Table striped withTableBorder withRowBorders>
<TableThead>
<TableTr>
<TableTh>Bekerja Sebagai</TableTh>
<TableTh>Nama Usaha</TableTh>
<TableTh>Alamat Usaha</TableTh>
<TableTh>Pekerjaan</TableTh>
<TableTh>Nama Perusahaan</TableTh>
<TableTh>Lokasi</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>{item.posisi}</TableTd>
<TableTd>{item.namaPerusahaan}</TableTd>
<TableTd>{item.lokasi}</TableTd>
<TableTd>
<Text fz={"md"}>{item.posisi}</Text>
</TableTd>
<TableTd>
<Text fz={"md"}>{item.namaPerusahaan}</Text>
</TableTd>
<TableTd>
<Text fz={"md"}>{item.lokasi}</Text>
</TableTd>
<TableTd>
<Button onClick={() => router.push(`/admin/ekonomi/lowongan-kerja-lokal/${item.id}`)}>
<IconDeviceImac size={20} />
@@ -82,6 +89,14 @@ function ListLowonganKerjaLokal({ search }: { search: string }) {
</TableTbody>
</Table>
</Paper>
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage)}
total={totalPages}
my="md"
/>
</Center>
</Box>
);
}

View File

@@ -1,6 +1,6 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconEdit, IconSearch, IconX } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
@@ -13,31 +13,39 @@ import pasarDesaState from '../../../_state/ekonomi/pasar-desa/pasar-desa';
function PasarDesa() {
const [search, setSearch] = useState("")
const [search2, setSearch2] = useState("")
return (
<Box>
<HeaderSearch
title='Kategori Produk'
placeholder='pencarian'
searchIcon={<IconSearch size={20} />}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
value={search2}
onChange={(e) => setSearch2(e.currentTarget.value)}
/>
<ListPasarDesa search={search} />
<ListPasarDesa search2={search2} />
</Box>
);
}
function ListPasarDesa({ search }: { search: string }) {
function ListPasarDesa({ search2 }: { search2: string }) {
const statePasar = useProxy(pasarDesaState.kategoriProduk)
const [modalHapus, setModalHapus] = useState(false)
const [selectedId, setSelectedId] = useState<string | null>(null)
// const params = useParams()
const router = useRouter()
const {
data,
page,
totalPages,
loading,
load,
} = statePasar.findMany
useShallowEffect(() => {
statePasar.findMany.load()
}, [])
load(page, 10, search2)
}, [page, search2])
const handleHapus = () => {
if (selectedId) {
@@ -47,14 +55,9 @@ function ListPasarDesa({ search }: { search: string }) {
}
}
const filteredData = (statePasar.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.nama.toLowerCase().includes(keyword)
);
});
const filteredData = data || []
if (!statePasar.findMany.data) {
if (loading || !data) {
return (
<Stack py={10}>
<Skeleton h={500} />
@@ -99,6 +102,14 @@ function ListPasarDesa({ search }: { search: string }) {
</TableTbody>
</Table>
</Paper>
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage)}
total={totalPages}
my={"md"}
/>
</Center>
{/* Modal Konfirmasi Hapus */}
<ModalKonfirmasiHapus
opened={modalHapus}

View File

@@ -1,6 +1,6 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
import { useShallowEffect } from '@mantine/hooks';
import { useRouter } from 'next/navigation';
@@ -30,21 +30,21 @@ function ListPasarDesa({ search }: { search: string }) {
const statePasar = useProxy(pasarDesaState.pasarDesa)
const router = useRouter();
const {
data,
page,
totalPages,
loading,
load,
} = statePasar.findMany
useShallowEffect(() => {
statePasar.findMany.load()
}, [])
load(page, 10, search)
}, [page, search])
const filteredData = (statePasar.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.nama.toLowerCase().includes(keyword) ||
item.harga.toString().toLowerCase().includes(keyword) ||
item.rating.toString().toLowerCase().includes(keyword) ||
item.alamatUsaha.toLowerCase().includes(keyword)
);
});
const filteredData = data || []
if (!statePasar.findMany.data) {
if (loading || !data) {
return (
<Stack py={10}>
<Skeleton h={500} />
@@ -86,6 +86,14 @@ function ListPasarDesa({ search }: { search: string }) {
</TableTbody>
</Table>
</Paper>
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage)}
total={totalPages}
my={"md"}
/>
</Center>
</Box>
);
}

View File

@@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
'use client'
import colors from '@/con/colors';
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
import HeaderSearch from '../../_com/header';
import JudulList from '../../_com/judulList';
@@ -23,7 +23,7 @@ function ProgramKemiskinan() {
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<ListProgramKemiskinan search={search}/>
<ListProgramKemiskinan search={search} />
</Box>
);
}
@@ -34,14 +34,22 @@ function ListProgramKemiskinan({ search }: { search: string }) {
const [lineChart, setLineChart] = useState<any[]>([]);
const [mounted, setMounted] = useState(false);
const {
data,
page,
totalPages,
loading,
load,
} = programState.findMany;
useShallowEffect(() => {
setMounted(true)
programState.findMany.load()
load(page, 10, search)
}, [])
useEffect(() => {
if (programState.findMany.data) {
const chartData = programState.findMany.data
if (data) {
const chartData = data
.filter(item => item.statistik)
.map(item => ({
tahun: item.statistik?.tahun,
@@ -52,18 +60,11 @@ function ListProgramKemiskinan({ search }: { search: string }) {
setLineChart(chartData);
}
}, [programState.findMany.data])
}, [data])
const filteredData = (programState.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.nama.toLowerCase().includes(keyword) ||
item.deskripsi.toLowerCase().includes(keyword) ||
item.statistik?.tahun.toString().includes(keyword)
);
});
const filteredData = data || []
if (!programState.findMany.data) {
if (loading || !data) {
return (
<Stack py={10}>
<Skeleton h={500} />
@@ -112,7 +113,7 @@ function ListProgramKemiskinan({ search }: { search: string }) {
<Box >
<Title pb={10} order={3}>Grafik Berdasarkan Responden</Title>
{mounted && lineChart.length > 0 ? (<Box style={{ width: '100%', height: 'auto', }}>
<Box w={"100%"} style={{overflowX: 'auto'}}>
<Box w={"100%"} style={{ overflowX: 'auto' }}>
<LineChart
width={820}
height={300}
@@ -143,6 +144,14 @@ function ListProgramKemiskinan({ search }: { search: string }) {
</Box>
</Stack>
</Paper>
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage)}
total={totalPages}
my={"md"}
/>
</Center>
</Box>
</Box>
);

View File

@@ -2,15 +2,16 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
'use client'
import colors from '@/con/colors';
import { Box, Button, Group, Paper, Stack, TextInput, Title } from '@mantine/core';
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
import { IconArrowBack } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import { useProxy } from 'valtio/utils';
import grafikSektorUnggulan from '../../../_state/ekonomi/sektor-unggulan-desa';
import CreateEditor from '../../../_com/createEditor';
function CreateSektorUnggulanDesa() {
const stateGrafik= useProxy(grafikSektorUnggulan);
const stateGrafik = useProxy(grafikSektorUnggulan);
const [chartData, setChartData] = useState<any[]>([]);
const router = useRouter()
@@ -54,15 +55,15 @@ function CreateSektorUnggulanDesa() {
stateGrafik.create.form.name = val.currentTarget.value;
}}
/>
<TextInput
label="Deskripsi Sektor Unggulan"
type="text"
value={stateGrafik.create.form.description}
placeholder="Masukkan deskripsi sektor unggulan"
onChange={(val) => {
stateGrafik.create.form.description = val.currentTarget.value;
}}
/>
<Box>
<Text fw={"bold"} fz={"sm"}>Deskripsi Sektor Ungggulan</Text>
<CreateEditor
value={stateGrafik.create.form.description}
onChange={(val) => {
stateGrafik.create.form.description = val;
}}
/>
</Box>
<TextInput
label="Jumlah"
type="number"

View File

@@ -1,6 +1,6 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Paper, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
import { Box, Button, Paper, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
import HeaderSearch from '../../_com/header';
import JudulList from '../../_com/judulList';
@@ -30,7 +30,7 @@ function SektorUnggulanDesa() {
function ListSektorUnggulanDesa({ search }: { search: string }) {
const router = useRouter();
const state = useProxy(grafikSektorUnggulan);
const [chartData, setChartData] = useState<{id: string; name: string; description: string | null; value: number | null}[]>([]);
const [chartData, setChartData] = useState<{ id: string; name: string; description: string | null; value: number | null }[]>([]);
const [mounted, setMounted] = useState(false); // untuk memastikan DOM sudah ready
const isTablet = useMediaQuery('(max-width: 1024px)')
const isMobile = useMediaQuery('(max-width: 768px)')
@@ -61,38 +61,41 @@ function ListSektorUnggulanDesa({ search }: { search: string }) {
}, [state.findMany.data]);
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<JudulList
title='List Sektor Unggulan Desa'
href='/admin/ekonomi/sektor-unggulan-desa/create'
/>
<Table striped withTableBorder withRowBorders>
<TableThead>
<TableTr>
<TableTh>Nama Sektor Unggulan</TableTh>
<TableTh>Deskripsi Sektor Unggulan</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>{item.name}</TableTd>
<TableTd>{item.description}</TableTd>
<TableTd>
<Button onClick={() => router.push(`/admin/ekonomi/sektor-unggulan-desa/${item.id}`)}>
<IconDeviceImac size={20} />
</Button>
</TableTd>
<Box>
<Stack gap={"xs"}>
<Paper bg={colors['white-1']} p={'md'}>
<JudulList
title='List Sektor Unggulan Desa'
href='/admin/ekonomi/sektor-unggulan-desa/create'
/>
<Table striped withTableBorder withRowBorders>
<TableThead>
<TableTr>
<TableTh>Nama Sektor Unggulan</TableTh>
<TableTh>Deskripsi Sektor Unggulan</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
))}
</TableTbody>
</Table>
</Paper>
</TableThead>
<TableTbody>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>{item.name}</TableTd>
<TableTd>
<Text truncate={"end"} fz={'sm'} lineClamp={1} dangerouslySetInnerHTML={{ __html: item.description || '' }}></Text>
</TableTd>
<TableTd>
<Button onClick={() => router.push(`/admin/ekonomi/sektor-unggulan-desa/${item.id}`)}>
<IconDeviceImac size={20} />
</Button>
</TableTd>
</TableTr>
))}
</TableTbody>
</Table>
</Paper>
{/* Chart */}
{!mounted && !chartData ? (
{/* Chart */}
{!mounted && !chartData ? (
<Box style={{ width: '100%', minWidth: 300, height: 400, minHeight: 300 }}>
<Paper bg={colors['white-1']} p={'md'}>
<Title pb={10} order={3}>Grafik Hasil Kepuasan Masyarakat</Title>
@@ -115,6 +118,7 @@ function ListSektorUnggulanDesa({ search }: { search: string }) {
</Paper>
</Box>
)}
</Stack>
</Box>
);
}

View File

@@ -54,6 +54,10 @@ function DetailKeamananLingkungan() {
{keamananState.findUnique.data ? (
<Paper key={keamananState.findUnique.data.id} bg={colors['BG-trans']} p={'md'}>
<Stack gap={"xs"}>
<Box>
<Text fw={"bold"} fz={"lg"}>Gambar</Text>
<Image w={{ base: 150, md: 490}} src={keamananState.findUnique.data?.image?.link} alt="gambar" />
</Box>
<Box>
<Text fw={"bold"} fz={"lg"}>Judul Keamanan Lingkungan</Text>
<Text fz={"lg"}>{keamananState.findUnique.data?.name}</Text>
@@ -62,10 +66,6 @@ function DetailKeamananLingkungan() {
<Text fw={"bold"} fz={"lg"}>Deskripsi</Text>
<Text fz={"lg"} dangerouslySetInnerHTML={{ __html: keamananState.findUnique.data?.deskripsi }} />
</Box>
<Box>
<Text fw={"bold"} fz={"lg"}>Gambar</Text>
<Image w={{ base: 150, md: 150, lg: 150 }} src={keamananState.findUnique.data?.image?.link} alt="gambar" />
</Box>
<Flex gap={"xs"} mt={10}>
<Button
onClick={() => {

View File

@@ -1,6 +1,6 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
import HeaderSearch from '../../_com/header';
import JudulList from '../../_com/judulList';
@@ -30,19 +30,21 @@ function ListKeamananLingkungan({ search }: { search: string }) {
const keamananState = useProxy(keamananLingkunganState)
const router = useRouter();
const {
data,
page,
totalPages,
loading,
load,
} = keamananState.findMany;
useShallowEffect(() => {
keamananState.findMany.load()
}, [])
load(page, 10, search)
}, [page, search])
const filteredData = (keamananState.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.name.toLowerCase().includes(keyword) ||
item.deskripsi.toLowerCase().includes(keyword)
);
});
const filteredData = data || []
if (!keamananState.findMany.data) {
if (loading || !data) {
return (
<Stack py={10}>
<Skeleton h={500} />
@@ -67,9 +69,15 @@ function ListKeamananLingkungan({ search }: { search: string }) {
<TableTbody>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>{item.name}</TableTd>
<TableTd>
<Text fz={"md"} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
<Box w={180}>
<Text fz={"md"} truncate={"end"} lineClamp={1}>{item.name}</Text>
</Box>
</TableTd>
<TableTd>
<Box w={250}>
<Text fz={"md"} truncate={"end"} lineClamp={1} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
</Box>
</TableTd>
<TableTd>
<Button onClick={() => router.push(`/admin/keamanan/keamanan-lingkungan-pecalang-patwal/${item.id}`)}>
@@ -81,6 +89,14 @@ function ListKeamananLingkungan({ search }: { search: string }) {
</TableTbody>
</Table>
</Paper>
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage)}
total={totalPages}
my={"md"}
/>
</Center>
</Box>
);
}

View File

@@ -1,6 +1,6 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
@@ -21,7 +21,7 @@ function PolsekTerdekat() {
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<ListPolsekTerdekat search={search}/>
<ListPolsekTerdekat search={search} />
</Box>
);
}
@@ -30,20 +30,21 @@ function ListPolsekTerdekat({ search }: { search: string }) {
const polsekState = useProxy(polsekTerdekat)
const router = useRouter();
const {
data,
page,
totalPages,
loading,
load,
} = polsekState.findMany;
useShallowEffect(() => {
polsekState.findMany.load()
}, [])
load(page, 10, search)
}, [page, search])
const filteredData = (polsekState.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.nama.toLowerCase().includes(keyword) ||
item.jarakKeDesa.toLowerCase().includes(keyword) ||
item.alamat.toLowerCase().includes(keyword)
);
});
const filteredData = data || []
if (!polsekState.findMany.data) {
if (loading || !data) {
return (
<Stack py={10}>
<Skeleton h={500} />
@@ -64,24 +65,44 @@ function ListPolsekTerdekat({ search }: { search: string }) {
<TableTh>Jarak Polsek</TableTh>
<TableTh>Alamat</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableTr>
</TableThead>
<TableTbody>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>{item.nama}</TableTd>
<TableTd>{item.jarakKeDesa}</TableTd>
<TableTd>{item.alamat}</TableTd>
<TableTd>
<Box w={180}>
<Text fz='md' truncate={"end"} lineClamp={1}>{item.nama}</Text>
</Box>
</TableTd>
<TableTd>
<Box w={180}>
<Text fz='md' truncate={"end"} lineClamp={1}>{item.jarakKeDesa}</Text>
</Box>
</TableTd>
<TableTd>
<Box w={250}>
<Text fz='md' truncate={"end"} lineClamp={1}>{item.alamat}</Text>
</Box>
</TableTd>
<TableTd>
<Button onClick={() => router.push(`/admin/keamanan/polsek-terdekat/${item.id}`)}>
<IconDeviceImac size={20} />
</Button>
</TableTd>
</TableTr>
))}
<IconDeviceImac size={20} />
</Button>
</TableTd>
</TableTr>
))}
</TableTbody>
</Table>
</Table>
</Paper>
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage)}
total={totalPages}
my="md"
/>
</Center>
</Box>
);
}

View File

@@ -1,6 +1,6 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
import HeaderSearch from '../../_com/header';
import JudulList from '../../_com/judulList';
@@ -30,19 +30,21 @@ function ListTipsKeamanan({ search }: { search: string }) {
const stateKeamanan = useProxy(tipsKeamananState)
const router = useRouter();
const {
data,
page,
totalPages,
loading,
load,
} = stateKeamanan.findMany
useShallowEffect(() => {
stateKeamanan.findMany.load()
}, [])
load(page, 10, search)
}, [page, search])
const filteredData = (stateKeamanan.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.judul.toLowerCase().includes(keyword) ||
item.deskripsi.toLowerCase().includes(keyword)
);
});
const filteredData = data || []
if (!stateKeamanan.findMany.data) {
if (loading || !data) {
return (
<Stack py={10}>
<Skeleton h={500} />
@@ -67,9 +69,15 @@ function ListTipsKeamanan({ search }: { search: string }) {
<TableTbody>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>{item.judul}</TableTd>
<TableTd>
<Text fz={"xs"} dangerouslySetInnerHTML={{__html: item.deskripsi}} />
<Box w={150}>
<Text fz={"md"} truncate={"end"} lineClamp={1}>{item.judul}</Text>
</Box>
</TableTd>
<TableTd>
<Box w={250}>
<Text fz={"md"} truncate={"end"} lineClamp={1} dangerouslySetInnerHTML={{__html: item.deskripsi}} />
</Box>
</TableTd>
<TableTd>
<Button onClick={() => router.push(`/admin/keamanan/tips-keamanan/${item.id}`)}>
@@ -81,6 +89,14 @@ function ListTipsKeamanan({ search }: { search: string }) {
</TableTbody>
</Table>
</Paper>
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage)}
total={totalPages}
my="md"
/>
</Center>
</Box>
);
}

View File

@@ -1,6 +1,6 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
@@ -22,7 +22,7 @@ function ArtikelKesehatan() {
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<ListArtikelKesehatan search={search}/>
<ListArtikelKesehatan search={search} />
</Box>
);
}
@@ -46,45 +46,53 @@ function ListArtikelKesehatan({ search }: { search: string }) {
if (!stateArtikelKesehatan.findMany.data) {
return (
<Box py={10}>
<Skeleton h={500}/>
<Skeleton h={500} />
</Box>
)
}
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<Stack>
<JudulList
title='List Artikel Kesehatan'
href='/admin/kesehatan/data-kesehatan-warga/artikel_kesehatan/create'
/>
<Box style={{ overflowX: "auto" }}>
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
<TableThead>
<TableTr>
<TableTh>Judul</TableTh>
<TableTh>Content</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>{item.title}</TableTd>
<TableTd>{item.content}</TableTd>
<TableTd>
<Button onClick={() => router.push(`/admin/kesehatan/data-kesehatan-warga/artikel_kesehatan/${item.id}`)}>
<IconDeviceImacCog size={25} />
</Button>
</TableTd>
<Paper bg={colors['white-1']} p={'md'}>
<Stack>
<JudulList
title='List Artikel Kesehatan'
href='/admin/kesehatan/data-kesehatan-warga/artikel_kesehatan/create'
/>
<Box style={{ overflowX: "auto" }}>
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
<TableThead>
<TableTr>
<TableTh>Judul</TableTh>
<TableTh>Content</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
))}
</TableTbody>
</Table>
</Box>
</Stack>
</Paper>
</Box>
</TableThead>
<TableTbody>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>
<Box w={100}>
<Text truncate={'end'} lineClamp={1} fz={'h5'}>{item.title}</Text>
</Box>
</TableTd>
<TableTd>
<Box w={200}>
<Text truncate={'end'} lineClamp={1} fz={'h5'}>{item.content}</Text>
</Box>
</TableTd>
<TableTd>
<Button onClick={() => router.push(`/admin/kesehatan/data-kesehatan-warga/artikel_kesehatan/${item.id}`)}>
<IconDeviceImacCog size={25} />
</Button>
</TableTd>
</TableTr>
))}
</TableTbody>
</Table>
</Box>
</Stack>
</Paper>
</Box>
)
}

View File

@@ -39,7 +39,7 @@ interface FasilitasKesehatanFormBase {
}
function EditFasilitasKesehatan() {
const stateFasilitasKesehatan = useProxy(fasilitasKesehatanState);
const stateFasilitasKesehatan = useProxy(fasilitasKesehatanState.fasilitasKesehatan);
const router = useRouter();
const params = useParams();

View File

@@ -2,7 +2,7 @@
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
import fasilitasKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
import colors from '@/con/colors';
import { Box, Button, Flex, Paper, Skeleton, Stack, Text } from '@mantine/core';
import { Box, Button, Flex, Grid, GridCol, Paper, Skeleton, Stack, Text } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
import { useParams, useRouter } from 'next/navigation';
@@ -12,7 +12,7 @@ import { useProxy } from 'valtio/utils';
function DetailFasilitasKesehatan() {
const params = useParams()
const router = useRouter();
const stateFasilitasKesehatan = useProxy(fasilitasKesehatanState)
const stateFasilitasKesehatan = useProxy(fasilitasKesehatanState.fasilitasKesehatan)
const [modalHapus, setModalHapus] = useState(false);
const [selectedId, setSelectedId] = useState<string | null>(null)
@@ -45,9 +45,23 @@ function DetailFasilitasKesehatan() {
<IconArrowBack color={colors['blue-button']} size={25} />
</Button>
</Box>
<Paper w={{ base: "100%", md: "50%" }} bg={colors['white-1']} p={'md'}>
<Paper bg={colors['white-1']} p={'md'}>
<Stack>
<Text fz={"xl"} fw={"bold"}>Detail Fasilitas Kesehatan</Text>
<Grid>
<GridCol span={12}>
<Text fz={"xl"} fw={"bold"}>Detail Fasilitas Kesehatan</Text>
</GridCol>
{/* <GridCol span={12}>
<Flex gap={"xs"}>
<Button color={colors['blue-button']} onClick={() => router.push(`/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/${params?.id}/dokter-tenaga-medis`)}>
Tambah Dokter
</Button>
<Button color={colors['blue-button']} onClick={() => router.push(`/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/${params?.id}/layanan-unggulan/create`)}>
Tambah Layanan
</Button>
</Flex>
</GridCol> */}
</Grid>
{stateFasilitasKesehatan.findUnique.data ? (
<Paper bg={colors['BG-trans']} p={'md'}>
<Stack gap={"xs"}>
@@ -68,6 +82,14 @@ function DetailFasilitasKesehatan() {
<Text fz={"md"} fw={"bold"}>Layanan Unggulan</Text>
<Text fz={"md"} dangerouslySetInnerHTML={{ __html: stateFasilitasKesehatan.findUnique.data.layananunggulan.content }} />
</Box>
<Box>
<Text fz={"lg"} fw={"bold"}>Fasilitas Pendukung</Text>
<Text fz={"md"} dangerouslySetInnerHTML={{ __html: stateFasilitasKesehatan.findUnique.data.fasilitaspendukung.content }} />
</Box>
<Box>
<Text fz={"lg"} fw={"bold"}>Prosedur Pendaftaran</Text>
<Text fz={"md"} dangerouslySetInnerHTML={{ __html: stateFasilitasKesehatan.findUnique.data.prosedurpendaftaran.content }} />
</Box>
<Box>
<Text fz={"md"} fw={"bold"}>Dokter dan Tenaga Medis</Text>
<Text fz={"md"} fw={"bold"}>Nama Dokter</Text>
@@ -77,14 +99,6 @@ function DetailFasilitasKesehatan() {
<Text fz={"md"} fw={"bold"}>Jadwal</Text>
<Text fz={"md"}>{stateFasilitasKesehatan.findUnique.data.dokterdantenagamedis.jadwal}</Text>
</Box>
<Box>
<Text fz={"lg"} fw={"bold"}>Fasilitas Pendukung</Text>
<Text fz={"md"} dangerouslySetInnerHTML={{ __html: stateFasilitasKesehatan.findUnique.data.fasilitaspendukung.content }} />
</Box>
<Box>
<Text fz={"lg"} fw={"bold"}>Prosedur Pendaftaran</Text>
<Text fz={"md"} dangerouslySetInnerHTML={{ __html: stateFasilitasKesehatan.findUnique.data.prosedurpendaftaran.content }} />
</Box>
<Box>
<Text fz={"lg"} fw={"bold"}>Tarif dan Layanan</Text>
<Text fz={"md"} fw={"bold"}>Layanan</Text>
@@ -111,6 +125,7 @@ function DetailFasilitasKesehatan() {
</Paper>
) : null}
</Stack>
</Paper>
{/* Modal Hapus */}

View File

@@ -10,7 +10,7 @@ import { useProxy } from 'valtio/utils';
function CreateFasilitasKesehatan() {
const stateFasilitasKesehatan = useProxy(fasilitasKesehatanState)
const stateFasilitasKesehatan = useProxy(fasilitasKesehatanState.fasilitasKesehatan)
const router = useRouter();

View File

@@ -0,0 +1,11 @@
import React from 'react';
function Page() {
return (
<div>
Page
</div>
);
}
export default Page;

View File

@@ -0,0 +1,11 @@
import React from 'react';
function Page() {
return (
<div>
Page
</div>
);
}
export default Page;

View File

@@ -0,0 +1,75 @@
'use client'
import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
import fasilitasKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
import colors from '@/con/colors';
import { Box, Button, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
import { IconArrowBack } from '@tabler/icons-react';
import { useParams, useRouter } from 'next/navigation';
import { useProxy } from 'valtio/utils';
function CreateDokter() {
const params = useParams()
const createState = useProxy(fasilitasKesehatanState.dokter)
const router = useRouter();
const resetForm = () => {
createState.create.create.form = {
name: "",
specialist: "",
jadwal: "",
};
};
const handleSubmit = async () => {
await createState.create.create.create();
resetForm();
router.push(`/admin/kesehatan/fasilitas-kesehatan/${params?.id}/dokter-tenaga-medis`)
};
return (
<Box component="form" onSubmit={handleSubmit}>
<Box mb={10}>
<Button variant="subtle" onClick={() => router.back()}>
<IconArrowBack color={colors['blue-button']} size={25} />
</Button>
</Box>
<Paper bg={colors['white-1']} p="md" w={{ base: '100%', md: '50%' }}>
<Stack gap="xs">
<Title order={3}>Create Dokter</Title>
<TextInput
label={<Text fz="sm" fw="bold">Nama Dokter</Text>}
placeholder="masukkan nama dokter"
value={createState.create.create.form.name}
onChange={(e) => {
createState.create.create.form.name = e.target.value;
}}
/>
<Text fz="md" fw="bold">Specialist</Text>
<TextInput
label={<Text fz="sm" fw="bold">Specialist</Text>}
placeholder="masukkan specialist"
value={createState.create.create.form.specialist}
onChange={(e) => {
createState.create.create.form.specialist = e.target.value;
}}
/>
<Box>
<Text fz="md" fw="bold">Jadwal</Text>
<CreateEditor
value={createState.create.create.form.jadwal}
onChange={(htmlContent) => {
createState.create.create.form.jadwal = htmlContent;
}}
/>
</Box>
<Button onClick={handleSubmit} bg={colors['blue-button']}>
Simpan
</Button>
</Stack>
</Paper>
</Box>
);
}
export default CreateDokter;

View File

@@ -0,0 +1,112 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconArrowBack, IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useProxy } from 'valtio/utils';
import HeaderSearch from '@/app/admin/(dashboard)/_com/header';
import JudulList from '@/app/admin/(dashboard)/_com/judulList';
import fasilitasKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
import { useState } from 'react';
function DokterTenagaMedis() {
const [search, setSearch] = useState("");
const router = useRouter();
return (
<Box>
<Box mb={10}>
<Button variant="subtle" onClick={() => router.back()}>
<IconArrowBack color={colors['blue-button']} size={25} />
</Button>
</Box>
<HeaderSearch
title='Dokter dan Tenaga Medis'
placeholder='pencarian'
searchIcon={<IconSearch size={20} />}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<ListDokterTenagaMedis search={search} />
</Box>
);
}
function ListDokterTenagaMedis({ search }: { search: string }) {
const stateFasilitasKesehatan = useProxy(fasilitasKesehatanState.dokter)
const router = useRouter();
const {
data,
loading,
load,
page,
totalPages
} = stateFasilitasKesehatan.findMany
useShallowEffect(() => {
load(page, 10, search)
}, [page, search])
const filteredData = data || []
if (loading || !data) {
return (
<Box py={10}>
<Skeleton h={500} />
</Box>
)
}
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<Stack>
<JudulList
title='List Fasilitas Kesehatan'
href={`/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/dokter-tenaga-medis/create`}
/>
<Box style={{ overflowX: "auto" }}>
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
<TableThead>
<TableTr>
<TableTh>Fasilitas Kesehatan</TableTh>
<TableTh>Alamat</TableTh>
<TableTh>Jam Operasional</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>{item.name}</TableTd>
<TableTd>{item.specialist}</TableTd>
<TableTd>
<Text dangerouslySetInnerHTML={{ __html: item.jadwal }} />
</TableTd>
<TableTd>
<Button onClick={() => router.push(`/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/${item.id}`)}>
<IconDeviceImacCog size={25} />
</Button>
</TableTd>
</TableTr>
))}
</TableTbody>
</Table>
</Box>
</Stack>
</Paper>
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage)} // ini penting!
total={totalPages}
mt="md"
mb="md"
/>
</Center>
</Box>
)
}
export default DokterTenagaMedis;

View File

@@ -4,17 +4,39 @@ import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableT
import { useShallowEffect } from '@mantine/hooks';
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import { useProxy } from 'valtio/utils';
import fasilitasKesehatanState from '../../../_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
import HeaderSearch from '../../../_com/header';
import JudulList from '../../../_com/judulList';
import { useState } from 'react';
import fasilitasKesehatanState from '../../../_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
function FasilitasKesehatan() {
const [search, setSearch] = useState("");
// const router = useRouter();
return (
<Box>
{/* <Grid>
<GridCol span={12}>
<HeaderSearch
title='Fasilitas Kesehatan'
placeholder='pencarian'
searchIcon={<IconSearch size={20} />}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
</GridCol>
<GridCol span={12}>
<Flex gap={"xs"}>
<Button color={colors['blue-button']} onClick={() => router.push(`/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/dokter-tenaga-medis`)}>
<IconList size={20} /> List Dokter
</Button>
<Button color={colors['blue-button']} onClick={() => router.push(`/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/tarif-layanan`)}>
<IconList size={20} /> List Layanan
</Button>
</Flex>
</GridCol>
</Grid> */}
<HeaderSearch
title='Fasilitas Kesehatan'
placeholder='pencarian'
@@ -22,13 +44,13 @@ function FasilitasKesehatan() {
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<ListFasilitasKesehatan search={search}/>
<ListFasilitasKesehatan search={search} />
</Box>
);
}
function ListFasilitasKesehatan({ search }: { search: string }) {
const stateFasilitasKesehatan = useProxy(fasilitasKesehatanState)
const stateFasilitasKesehatan = useProxy(fasilitasKesehatanState.fasilitasKesehatan)
const router = useRouter();
useShallowEffect(() => {
@@ -47,47 +69,47 @@ function ListFasilitasKesehatan({ search }: { search: string }) {
if (!stateFasilitasKesehatan.findMany.data) {
return (
<Box py={10}>
<Skeleton h={500}/>
<Skeleton h={500} />
</Box>
)
}
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<Stack>
<JudulList
title='List Fasilitas Kesehatan'
href='/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/create'
/>
<Box style={{ overflowX: "auto" }}>
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
<TableThead>
<TableTr>
<TableTh>Fasilitas Kesehatan</TableTh>
<TableTh>Alamat</TableTh>
<TableTh>Jam Operasional</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>{item.name}</TableTd>
<TableTd>{item.informasiumum.alamat}</TableTd>
<TableTd>{item.informasiumum.jamOperasional}</TableTd>
<TableTd>
<Button onClick={() => router.push(`/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/${item.id}`)}>
<IconDeviceImacCog size={25} />
</Button>
</TableTd>
<Paper bg={colors['white-1']} p={'md'}>
<Stack>
<JudulList
title='List Fasilitas Kesehatan'
href='/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/create'
/>
<Box style={{ overflowX: "auto" }}>
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
<TableThead>
<TableTr>
<TableTh>Fasilitas Kesehatan</TableTh>
<TableTh>Dokter</TableTh>
<TableTh>Layanan</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
))}
</TableTbody>
</Table>
</Box>
</Stack>
</Paper>
</Box>
</TableThead>
<TableTbody>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>{item.name}</TableTd>
<TableTd>{item.dokterdantenagamedis.name}</TableTd>
<TableTd>{item.tarifdanlayanan.layanan}</TableTd>
<TableTd>
<Button onClick={() => router.push(`/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/${item.id}`)}>
<IconDeviceImacCog size={25} />
</Button>
</TableTd>
</TableTr>
))}
</TableTbody>
</Table>
</Box>
</Stack>
</Paper>
</Box>
)
}

View File

@@ -0,0 +1,112 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconArrowBack, IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useProxy } from 'valtio/utils';
import HeaderSearch from '@/app/admin/(dashboard)/_com/header';
import JudulList from '@/app/admin/(dashboard)/_com/judulList';
import fasilitasKesehatanState from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
import { useState } from 'react';
function TarifLayanan() {
const [search, setSearch] = useState("");
const router = useRouter();
return (
<Box>
<Box mb={10}>
<Button variant="subtle" onClick={() => router.back()}>
<IconArrowBack color={colors['blue-button']} size={25} />
</Button>
</Box>
<HeaderSearch
title='Dokter dan Tenaga Medis'
placeholder='pencarian'
searchIcon={<IconSearch size={20} />}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<ListTarifLayanan search={search} />
</Box>
);
}
function ListTarifLayanan({ search }: { search: string }) {
const stateFasilitasKesehatan = useProxy(fasilitasKesehatanState.dokter)
const router = useRouter();
const {
data,
loading,
load,
page,
totalPages
} = stateFasilitasKesehatan.findMany
useShallowEffect(() => {
load(page, 10, search)
}, [page, search])
const filteredData = data || []
if (loading || !data) {
return (
<Box py={10}>
<Skeleton h={500} />
</Box>
)
}
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<Stack>
<JudulList
title='List Fasilitas Kesehatan'
href={`/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/dokter-tenaga-medis/create`}
/>
<Box style={{ overflowX: "auto" }}>
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
<TableThead>
<TableTr>
<TableTh>Fasilitas Kesehatan</TableTh>
<TableTh>Alamat</TableTh>
<TableTh>Jam Operasional</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>{item.name}</TableTd>
<TableTd>{item.specialist}</TableTd>
<TableTd>
<Text dangerouslySetInnerHTML={{ __html: item.jadwal }} />
</TableTd>
<TableTd>
<Button onClick={() => router.push(`/admin/kesehatan/data-kesehatan-warga/fasilitas_kesehatan/${item.id}`)}>
<IconDeviceImacCog size={25} />
</Button>
</TableTd>
</TableTr>
))}
</TableTbody>
</Table>
</Box>
</Stack>
</Paper>
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage)} // ini penting!
total={totalPages}
mt="md"
mb="md"
/>
</Center>
</Box>
)
}
export default TarifLayanan;

View File

@@ -0,0 +1,116 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import grafikkepuasan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/grafikKepuasan';
import colors from '@/con/colors';
import { Box, Button, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
import { IconArrowBack } from '@tabler/icons-react';
import { useParams, useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { toast } from 'react-toastify';
import { useProxy } from 'valtio/utils';
function EditGrafikHasilKepuasan() {
const editState = useProxy(grafikkepuasan)
const router = useRouter();
const params = useParams();
const [formData, setFormData] = useState({
nama: editState.update.form.nama || '',
tanggal: editState.update.form.tanggal || '',
jenisKelamin: editState.update.form.jenisKelamin || '',
alamat: editState.update.form.alamat || '',
penyakit: editState.update.form.penyakit || '',
});
useEffect(() => {
const loadKelahiran = async () => {
const id = params?.id as string;
if (!id) return;
try {
const data = await editState.update.load(id); // akses langsung, bukan dari proxy
if (data) {
setFormData({
nama: data.nama || '',
tanggal: data.tanggal || '',
jenisKelamin: data.jenisKelamin || '',
alamat: data.alamat || '',
penyakit: data.penyakit || '',
});
}
} catch (error) {
console.error("Error loading grafik hasil kepuasan:", error);
toast.error("Gagal memuat data grafik hasil kepuasan");
}
};
loadKelahiran();
}, [params?.id]);
const handleSubmit = async () => {
try {
editState.update.form = {
...editState.update.form,
nama: formData.nama,
tanggal: formData.tanggal,
jenisKelamin: formData.jenisKelamin,
alamat: formData.alamat,
penyakit: formData.penyakit,
};
await editState.update.submit();
toast.success('grafik hasil kepuasan berhasil diperbarui!');
router.push('/admin/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan');
} catch (error) {
console.error('Error updating grafik hasil kepuasan:', error);
toast.error('Terjadi kesalahan saat memperbarui grafik hasil kepuasan');
}
};
return (
<Box>
<Box mb={10}>
<Button variant="subtle" onClick={() => router.back()}>
<IconArrowBack color={colors["blue-button"]} size={30} />
</Button>
</Box>
<Paper bg={"white"} p={"md"} w={{ base: "100%", md: "50%" }}>
<Stack gap={"xs"}>
<Title order={3}>Edit grafik hasil kepuasan</Title>
<TextInput
value={formData.nama}
onChange={(e) => setFormData({ ...formData, nama: e.target.value })}
label={<Text fz={"sm"} fw={"bold"}>Nama</Text>}
placeholder="masukkan nama"
/>
<TextInput
type='date'
value={formData.tanggal}
onChange={(e) => setFormData({ ...formData, tanggal: e.target.value })}
label={<Text fz={"sm"} fw={"bold"}>Tanggal</Text>}
placeholder="masukkan tanggal"
/>
<TextInput
value={formData.jenisKelamin}
onChange={(e) => setFormData({ ...formData, jenisKelamin: e.target.value })}
label={<Text fz={"sm"} fw={"bold"}>Jenis Kelamin</Text>}
placeholder="masukkan jenis kelamin"
/>
<TextInput
value={formData.alamat}
onChange={(e) => setFormData({ ...formData, alamat: e.target.value })}
label={<Text fz={"sm"} fw={"bold"}>Alamat</Text>}
placeholder="masukkan alamat"
/>
<TextInput
value={formData.penyakit}
onChange={(e) => setFormData({ ...formData, penyakit: e.target.value })}
label={<Text fz={"sm"} fw={"bold"}>Penyakit</Text>}
placeholder="masukkan penyakit"
/>
<Button onClick={handleSubmit}>Simpan</Button>
</Stack>
</Paper>
</Box>
);
}
export default EditGrafikHasilKepuasan;

View File

@@ -1,80 +1,125 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import grafikkepuasan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/grafikKepuasan';
import colors from '@/con/colors';
import { Box, Button, Paper, Stack, TextInput, Title } from '@mantine/core';
import { IconArrowBack } from '@tabler/icons-react';
import { useParams, useRouter } from 'next/navigation';
import { useEffect } from 'react';
import { useProxy } from 'valtio/utils';
function EditGrafikHasilKepuasan() {
import { Box, Button, Flex, Paper, Skeleton, Stack, Text } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
import { useParams, useRouter } from 'next/navigation';
import { useState } from 'react';
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
import grafikkepuasan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/grafikKepuasan';
import colors from '@/con/colors';
function DetailGrafikHasilKepuasan() {
const state = useProxy(grafikkepuasan)
const [modalHapus, setModalHapus] = useState(false)
const [selectedId, setSelectedId] = useState<string | null>(null)
const params = useParams()
const router = useRouter()
const params = useParams() as { id: string }
const stateGrafikKepuasan = useProxy(grafikkepuasan)
const id = params.id
useShallowEffect(() => {
state.findUnique.load(params?.id as string)
}, [])
// Load data saat komponen mount
useEffect(() => {
if (id) {
stateGrafikKepuasan.findUnique.load(id).then(() => {
const data = stateGrafikKepuasan.findUnique.data
if (data) {
stateGrafikKepuasan.update.form = {
label: data.label || '',
jumlah: data.jumlah || '',
}
}
})
const handleHapus = () => {
if (selectedId) {
state.delete.byId(selectedId)
setModalHapus(false)
setSelectedId(null)
router.push("/admin/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan")
}
}, [id])
const handleSubmit = async () => {
// Set the ID before submitting
stateGrafikKepuasan.update.id = id;
await stateGrafikKepuasan.update.submit();
router.push('/admin/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan')
}
return (
if (!state.findUnique.data) {
return (
<Stack py={10}>
<Skeleton h={40} />
</Stack>
)
}
return (
<Box>
<Box mb={10}>
<Button variant="subtle" onClick={() => router.back()}>
<IconArrowBack size={20} />
<IconArrowBack color={colors['blue-button']} size={25} />
</Button>
</Box>
<Paper bg={colors['white-1']} w={{ base: '100%', md: '50%' }} p={'md'}>
<Paper bg={colors['white-1']} w={{ base: "100%", md: "100%", lg: "50%" }} p={'md'}>
<Stack>
<Title order={3}>Edit Grafik Hasil Kepuasan</Title>
<TextInput
label="Label"
placeholder="masukkan label"
value={stateGrafikKepuasan.update.form.label}
onChange={(val) => {
stateGrafikKepuasan.update.form.label = val.currentTarget.value;
}}
/>
<TextInput
label="Jumlah"
type="number"
placeholder="masukkan jumlah"
value={stateGrafikKepuasan.update.form.jumlah}
onChange={(val) => {
stateGrafikKepuasan.update.form.jumlah = val.currentTarget.value;
}}
/>
<Button
mt={10}
bg={colors['blue-button']}
onClick={handleSubmit}
>
Simpan
</Button>
<Text fz={"xl"} fw={"bold"}>Detail Data Grafik Hasil Kepuasan</Text>
{state.findUnique.data ? (
<Paper key={state.findUnique.data.id} bg={colors['BG-trans']} p={'md'}>
<Stack gap={"xs"}>
<Box>
<Text fw={"bold"} fz={"lg"}>Nama</Text>
<Text fz={"lg"}>{state.findUnique.data?.nama}</Text>
</Box>
<Box>
<Text fw={"bold"} fz={"lg"}>Tanggal</Text>
<Text fz={"lg"}>
{new Date(state.findUnique.data?.tanggal).toLocaleDateString('id-ID', {
day: '2-digit',
month: 'long',
year: 'numeric'
})}
</Text>
</Box>
<Box>
<Text fw={"bold"} fz={"lg"}>Jenis Kelamin</Text>
<Text fz={"lg"} >{state.findUnique.data?.jenisKelamin}</Text>
</Box>
<Box>
<Text fw={"bold"} fz={"lg"}>Alamat</Text>
<Text fz={"lg"} >{state.findUnique.data?.alamat}</Text>
</Box>
<Box>
<Text fw={"bold"} fz={"lg"}>Penyakit</Text>
<Text fz={"lg"} >{state.findUnique.data?.penyakit}</Text>
</Box>
<Flex gap={"xs"} mt={10}>
<Button
onClick={() => {
if (state.findUnique.data) {
setSelectedId(state.findUnique.data.id);
setModalHapus(true);
}
}}
disabled={state.delete.loading || !state.findUnique.data}
color={"red"}
>
<IconX size={20} />
</Button>
<Button
onClick={() => {
if (state.findUnique.data) {
router.push(`/admin/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan/${state.findUnique.data.id}/edit`);
}
}}
disabled={!state.findUnique.data}
color={"green"}
>
<IconEdit size={20} />
</Button>
</Flex>
</Stack>
</Paper>
) : null}
</Stack>
</Paper>
{/* Modal Konfirmasi Hapus */}
<ModalKonfirmasiHapus
opened={modalHapus}
onClose={() => setModalHapus(false)}
onConfirm={handleHapus}
text='Apakah anda yakin ingin menghapus data ini?'
/>
</Box>
)
);
}
export default EditGrafikHasilKepuasan;
export default DetailGrafikHasilKepuasan;

View File

@@ -16,20 +16,16 @@ function CreateGrafikHasilKepuasanMasyarakat() {
const resetForm = () => {
stateGrafikKepuasan.create.form = {
label: "",
jumlah: "",
nama: "",
tanggal: "",
jenisKelamin: "",
alamat: "",
penyakit: "",
}
}
const handleSubmit = async () => {
const id = await stateGrafikKepuasan.create.create();
if (id) {
const idStr = String(id);
await stateGrafikKepuasan.findUnique.load(idStr);
if (stateGrafikKepuasan.findUnique.data) {
setChartData([stateGrafikKepuasan.findUnique.data]);
}
}
await stateGrafikKepuasan.create.create();
resetForm();
router.push("/admin/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan");
}
@@ -45,21 +41,48 @@ function CreateGrafikHasilKepuasanMasyarakat() {
<Title order={4}>Tambah Grafik Hasil Kepuasan Masyarakat</Title>
<Stack gap={"xs"}>
<TextInput
label="Label"
label="Nama"
type="text"
value={stateGrafikKepuasan.create.form.label}
placeholder="Masukkan label"
value={stateGrafikKepuasan.create.form.nama}
placeholder="Masukkan nama"
onChange={(val) => {
stateGrafikKepuasan.create.form.label = val.currentTarget.value;
stateGrafikKepuasan.create.form.nama = val.currentTarget.value;
}}
/>
<TextInput
label="Jumlah"
type="number"
value={stateGrafikKepuasan.create.form.jumlah}
placeholder="Masukkan jumlah"
label="Tanggal"
type="date"
value={stateGrafikKepuasan.create.form.tanggal}
placeholder="Masukkan tanggal"
onChange={(val) => {
stateGrafikKepuasan.create.form.jumlah = val.currentTarget.value;
stateGrafikKepuasan.create.form.tanggal = val.currentTarget.value;
}}
/>
<TextInput
label="Jenis Kelamin"
type="text"
value={stateGrafikKepuasan.create.form.jenisKelamin}
placeholder="Masukkan jenis kelamin"
onChange={(val) => {
stateGrafikKepuasan.create.form.jenisKelamin = val.currentTarget.value;
}}
/>
<TextInput
label="Alamat"
type="text"
value={stateGrafikKepuasan.create.form.alamat}
placeholder="Masukkan alamat"
onChange={(val) => {
stateGrafikKepuasan.create.form.alamat = val.currentTarget.value;
}}
/>
<TextInput
label="Penyakit"
type="text"
value={stateGrafikKepuasan.create.form.penyakit}
placeholder="Masukkan penyakit"
onChange={(val) => {
stateGrafikKepuasan.create.form.penyakit = val.currentTarget.value;
}}
/>
<Group>

View File

@@ -1,16 +1,16 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import colors from '@/con/colors';
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
import { useMediaQuery, useShallowEffect } from '@mantine/hooks';
import { IconEdit, IconSearch, IconTrash } from '@tabler/icons-react';
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { Bar, BarChart, Legend, Tooltip, XAxis, YAxis } from 'recharts';
import { useProxy } from 'valtio/utils';
import JudulListTab from '../../../_com/judulListTab';
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
import grafikkepuasan from '../../../_state/kesehatan/data_kesehatan_warga/grafikKepuasan';
import HeaderSearch from '../../../_com/header';
import JudulList from '../../../_com/judulList';
import grafikkepuasan from '../../../_state/kesehatan/data_kesehatan_warga/grafikKepuasan';
function GrafikHasilKepuasanMasyarakat() {
const [search, setSearch] = useState("");
@@ -30,53 +30,79 @@ function GrafikHasilKepuasanMasyarakat() {
function ListGrafikHasilKepuasanMasyarakat({ search }: { search: string }) {
type PDKMGrafik = {
id: string;
label: string;
jumlah: number;
nama: string;
tanggal: string | Date; // Allow both string and Date types
jenisKelamin: string;
alamat: string;
penyakit: string;
createdAt?: Date; // Add optional fields that might come from the API
updatedAt?: Date;
deletedAt?: Date | null;
}
const stateGrafikKepuasan = useProxy(grafikkepuasan);
const [chartData, setChartData] = useState<PDKMGrafik[]>([]);
const [mounted, setMounted] = useState(false); // untuk memastikan DOM sudah ready
const isTablet = useMediaQuery('(max-width: 1024px)')
const isMobile = useMediaQuery('(max-width: 768px)')
const [modalHapus, setModalHapus] = useState(false)
const [selectedId, setSelectedId] = useState<string | null>(null)
const router = useRouter();
const handleDelete = () => {
if (selectedId) {
stateGrafikKepuasan.delete.byId(selectedId)
setModalHapus(false)
setSelectedId(null)
stateGrafikKepuasan.findMany.load()
}
}
const {
data,
page,
totalPages,
loading,
load
} = stateGrafikKepuasan.findMany;
useShallowEffect(() => {
setMounted(true)
stateGrafikKepuasan.findMany.load()
}, [])
load(page, 10, search)
}, [page, search])
useEffect(() => {
setMounted(true);
if (stateGrafikKepuasan.findMany.data) {
setChartData(stateGrafikKepuasan.findMany.data.map((item) => ({
if (data) {
setChartData(data.map((item) => ({
id: item.id,
label: item.label,
jumlah: Number(item.jumlah),
nama: item.nama,
tanggal: item.tanggal instanceof Date ? item.tanggal.toISOString() : item.tanggal,
jenisKelamin: item.jenisKelamin,
alamat: item.alamat,
penyakit: item.penyakit,
})));
}
}, [stateGrafikKepuasan.findMany.data]);
}, [data]);
const filteredData = (stateGrafikKepuasan.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.label.toLowerCase().includes(keyword) ||
item.jumlah.toString().toLowerCase().includes(keyword)
);
});
// Add this function to process the data
const processDiseaseData = (data: PDKMGrafik[]) => {
const diseaseCount: Record<string, number> = {};
if (!stateGrafikKepuasan.findMany.data) {
data.forEach(item => {
const penyakit = item.penyakit.trim();
if (penyakit) {
diseaseCount[penyakit] = (diseaseCount[penyakit] || 0) + 1;
}
});
return Object.entries(diseaseCount).map(([name, count]) => ({
name,
count
}));
};
// Add this state to store the processed chart data
const [diseaseChartData, setDiseaseChartData] = useState<{ name: string, count: number }[]>([]);
// Update the chart data when data changes
useEffect(() => {
if (data && data.length > 0) {
setDiseaseChartData(processDiseaseData(data));
}
}, [data]);
const filteredData = data || [];
if (loading || !data) {
return (
<Stack>
<Skeleton h={500} />
@@ -89,40 +115,36 @@ function ListGrafikHasilKepuasanMasyarakat({ search }: { search: string }) {
<Stack gap={"xs"}>
{/* Form Input */}
<Paper bg={colors['white-1']} p={'md'}>
<JudulListTab
<JudulList
title='List Grafik Hasil Kepuasan Masyarakat'
href='/admin/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan/create'
placeholder='pencarian'
searchIcon={<IconSearch size={16} />}
/>
<Table striped withTableBorder withRowBorders>
<TableThead>
<TableTr>
<TableTh>Label</TableTh>
<TableTh>Jumlah</TableTh>
<TableTh>Edit</TableTh>
<TableTh>Delete</TableTh>
<TableTh>Nama</TableTh>
<TableTh>Tanggal</TableTh>
<TableTh>Jenis Kelamin</TableTh>
<TableTh>Penyakit</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>{item.label}</TableTd>
<TableTd>{item.jumlah}</TableTd>
<TableTd>{item.nama}</TableTd>
<TableTd>
{new Date(item.tanggal).toLocaleDateString('id-ID', {
day: '2-digit',
month: 'long',
year: 'numeric'
})}
</TableTd>
<TableTd>{item.jenisKelamin}</TableTd>
<TableTd>{item.penyakit}</TableTd>
<TableTd>
<Button color='green' onClick={() => router.push(`/admin/kesehatan/data-kesehatan-warga/grafik_hasil_kepuasan/${item.id}`)}>
<IconEdit size={20} />
</Button>
</TableTd>
<TableTd>
<Button
color='red'
disabled={stateGrafikKepuasan.delete.loading}
onClick={() => {
setSelectedId(item.id)
setModalHapus(true)
}}>
<IconTrash size={20} />
<IconDeviceImacCog size={20} />
</Button>
</TableTd>
</TableTr>
@@ -130,6 +152,15 @@ function ListGrafikHasilKepuasanMasyarakat({ search }: { search: string }) {
</TableTbody>
</Table>
</Paper>
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage)} // ini penting!
total={totalPages}
mt="md"
mb="md"
/>
</Center>
{/* Chart */}
@@ -141,30 +172,29 @@ function ListGrafikHasilKepuasanMasyarakat({ search }: { search: string }) {
</Paper>
</Box>
) : (
<Box style={{ width: '100%', minWidth: 300, height: 400, minHeight: 300 }}>
<Box style={{ width: '100%', minWidth: 300, height: 420, minHeight: 300 }}>
<Paper bg={colors['white-1']} p={'md'}>
<Title pb={10} order={4}>Grafik Hasil Kepuasan Masyarakat</Title>
{mounted && chartData.length > 0 && (
<BarChart width={isMobile ? 450 : isTablet ? 500 : 550} height={350} data={chartData} >
<XAxis dataKey="label" />
{mounted && diseaseChartData.length > 0 && (
<BarChart width={isMobile ? 450 : isTablet ? 500 : 550} height={350} data={diseaseChartData} >
<XAxis
dataKey="name"
tick={{ fontSize: 12 }}
interval={0}
angle={-45}
textAnchor="end"
height={70}
/>
<YAxis />
<Tooltip />
<Legend />
<Bar dataKey="jumlah" fill={colors['blue-button']} name="Jumlah" />
<Bar dataKey="count" fill={colors['blue-button']} name="Jumlah Kasus" />
</BarChart>
)}
</Paper>
</Box>
)}
</Stack>
{/* Modal Konfirmasi Hapus */}
<ModalKonfirmasiHapus
opened={modalHapus}
onClose={() => setModalHapus(false)}
onConfirm={handleDelete}
text='Apakah anda yakin ingin menghapus grafik hasil kepuasan masyarakat ini?'
/>
</Box>
);
}

View File

@@ -5,7 +5,7 @@ import persentasekelahiran from '@/app/admin/(dashboard)/_state/kesehatan/data_k
import colors from '@/con/colors';
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconArrowBack, IconEdit, IconSearch } from '@tabler/icons-react';
import { IconArrowBack, IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import { useProxy } from 'valtio/utils';
@@ -47,7 +47,7 @@ function ListKelahiran({ search }: { search: string }) {
useShallowEffect(() => {
load(page, 10, search)
}, [search])
}, [page, search])
const filteredData = data || []
@@ -93,7 +93,7 @@ function ListKelahiran({ search }: { search: string }) {
<TableTd>{item.alamat}</TableTd>
<TableTd>
<Button color='green' onClick={() => router.push(`/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/kelahiran/${item.id}`)}>
<IconEdit size={20} />
<IconDeviceImacCog size={20} />
</Button>
</TableTd>
</TableTr>

View File

@@ -2,7 +2,7 @@
'use client'
import persentasekelahiran from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/persentaseKelahiran';
import colors from '@/con/colors';
import { ActionIcon, Box, Button, Center, Flex, Paper, Skeleton, Stack, Table, Text, Title } from '@mantine/core';
import { ActionIcon, Box, Center, Flex, Paper, Select, Skeleton, Stack, Table, Text, Title } from '@mantine/core';
import { useMediaQuery, useShallowEffect } from '@mantine/hooks';
import { IconBabyCarriage, IconGrave2 } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
@@ -157,20 +157,18 @@ function GrafikPersentaseKelahiranKematian() {
) : (
<>
{/* Year Selector */}
<Box mb="md">
<Text mb="xs" fw={500}>Pilih Tahun:</Text>
<div style={{ display: 'flex', gap: '8px', flexWrap: 'wrap' }}>
{chartData.map(({ tahun }) => (
<Button
key={tahun}
variant={selectedYear === tahun ? 'filled' : 'outline'}
onClick={() => setSelectedYear(tahun)}
size="xs"
>
{tahun}
</Button>
))}
</div>
<Box mb="md" style={{ maxWidth: '200px' }}>
<Select
label="Pilih Tahun"
placeholder="Pilih Tahun"
data={chartData.map((item) => ({
value: item.tahun,
label: item.tahun
}))}
value={selectedYear}
onChange={(value) => setSelectedYear(value || '')}
size="xs"
/>
</Box>
{/* Main Chart */}

View File

@@ -1,6 +1,6 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Image, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { Box, Button, Center, Image, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
import JudulList from '../../_com/judulList';
import HeaderSearch from '../../_com/header';
@@ -21,7 +21,7 @@ function InfoWabahPenyakit() {
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<ListInfoWabahPenyakit search={search}/>
<ListInfoWabahPenyakit search={search} />
</Box>
);
}
@@ -30,19 +30,21 @@ function ListInfoWabahPenyakit({ search }: { search: string }) {
const infoWabahPenyakitState = useProxy(infoWabahPenyakit)
const router = useRouter()
const {
data,
page,
totalPages,
loading,
load,
} = infoWabahPenyakitState.findMany;
useShallowEffect(() => {
infoWabahPenyakitState.findMany.load()
}, [])
load(page, 10, search)
}, [page, search])
const filteredData = (infoWabahPenyakitState.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.name.toLowerCase().includes(keyword) ||
item.deskripsiSingkat.toLowerCase().includes(keyword)
);
});
const filteredData = data || []
if (!infoWabahPenyakitState.findMany.data) {
if (loading || !data) {
return (
<Box py={10}>
<Skeleton h={500} />
@@ -51,49 +53,60 @@ function ListInfoWabahPenyakit({ search }: { search: string }) {
}
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<Stack>
<JudulList
title='List Info Wabah Penyakit'
href='/admin/kesehatan/info-wabah-penyakit/create'
/>
<Box style={{ overflowX: "auto" }}>
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
<TableThead>
<TableTr>
<TableTh>Judul</TableTh>
<TableTh>Deskripsi Singkat</TableTh>
<TableTh>Image</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>
<Box w={100}>
<Text truncate="end" fz={"sm"}>{item.name}</Text>
</Box>
</TableTd>
<TableTd>
<Text truncate="end" fz={"sm"}>{item.deskripsiSingkat}</Text>
</TableTd>
<TableTd>
<Image w={100} src={item.image?.link} alt="image" />
</TableTd>
<TableTd>
<Button onClick={() => router.push(`/admin/kesehatan/info-wabah-penyakit/${item.id}`)}>
<IconDeviceImacCog size={25} />
</Button>
</TableTd>
<Paper bg={colors['white-1']} p={'md'}>
<Stack>
<JudulList
title='List Info Wabah Penyakit'
href='/admin/kesehatan/info-wabah-penyakit/create'
/>
<Box style={{ overflowX: "auto" }}>
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
<TableThead>
<TableTr>
<TableTh>Judul</TableTh>
<TableTh>Deskripsi Singkat</TableTh>
<TableTh>Image</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
))}
</TableTbody>
</Table>
</Box>
</Stack>
</Paper>
</Box>
</TableThead>
<TableTbody>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>
<Box w={100}>
<Text truncate="end" fz={"sm"}>{item.name}</Text>
</Box>
</TableTd>
<TableTd>
<Box w={100}>
<Text truncate="end" fz={"sm"}>{item.deskripsiSingkat}</Text>
</Box>
</TableTd>
<TableTd>
<Image w={100} src={item.image?.link} alt="image" />
</TableTd>
<TableTd>
<Button onClick={() => router.push(`/admin/kesehatan/info-wabah-penyakit/${item.id}`)}>
<IconDeviceImacCog size={25} />
</Button>
</TableTd>
</TableTr>
))}
</TableTbody>
</Table>
</Box>
</Stack>
</Paper>
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage)} // ini penting!
total={totalPages}
mt="md"
mb="md"
/>
</Center>
</Box>
)
}

View File

@@ -1,6 +1,6 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Image, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { Box, Button, Center, Image, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
import JudulList from '../../_com/judulList';
import HeaderSearch from '../../_com/header';
@@ -21,7 +21,7 @@ function KontakDarurat() {
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<ListKontakDarurat search={search}/>
<ListKontakDarurat search={search} />
</Box>
);
}
@@ -30,19 +30,21 @@ function ListKontakDarurat({ search }: { search: string }) {
const kontakDaruratState = useProxy(kontakDarurat)
const router = useRouter();
const {
data,
page,
totalPages,
loading,
load,
} = kontakDaruratState.findMany;
useShallowEffect(() => {
kontakDaruratState.findMany.load()
}, [])
load(page, 10, search)
}, [page, search])
const filteredData = (kontakDaruratState.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.name.toLowerCase().includes(keyword) ||
item.deskripsi.toLowerCase().includes(keyword)
);
});
const filteredData = data || []
if (!kontakDaruratState.findMany.data) {
if (loading || !data) {
return (
<Box py={10}>
<Skeleton h={500} />
@@ -77,7 +79,9 @@ function ListKontakDarurat({ search }: { search: string }) {
</Box>
</TableTd>
<TableTd>
<Text truncate="end" fz={"sm"} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
<Box w={100}>
<Text truncate="end" lineClamp={1} fz={"sm"} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
</Box>
</TableTd>
<TableTd>
<Image w={100} src={item.image?.link} alt="image" />
@@ -94,6 +98,15 @@ function ListKontakDarurat({ search }: { search: string }) {
</Box>
</Stack>
</Paper>
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage)} // ini penting!
total={totalPages}
mt="md"
mb="md"
/>
</Center>
</Box>
)
}

View File

@@ -1,6 +1,6 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Image, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { Box, Button, Center, Image, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
import JudulList from '../../_com/judulList';
import HeaderSearch from '../../_com/header';
@@ -21,7 +21,7 @@ function PenangananDarurat() {
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<ListPenangananDarurat search={search}/>
<ListPenangananDarurat search={search} />
</Box>
);
}
@@ -30,19 +30,21 @@ function ListPenangananDarurat({ search }: { search: string }) {
const penangananDaruratState = useProxy(penangananDarurat)
const router = useRouter();
const {
data,
page,
totalPages,
loading,
load,
} = penangananDaruratState.findMany;
useShallowEffect(() => {
penangananDaruratState.findMany.load()
}, [])
load(page, 10, search)
}, [page, search])
const filteredData = (penangananDaruratState.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.name.toLowerCase().includes(keyword) ||
item.deskripsi.toLowerCase().includes(keyword)
);
});
const filteredData = data || []
if (!penangananDaruratState.findMany.data) {
if (loading || !data) {
return (
<Box py={10}>
<Skeleton h={500} />
@@ -52,48 +54,59 @@ function ListPenangananDarurat({ search }: { search: string }) {
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<Stack>
<JudulList
title='List Penanganan Darurat'
href='/admin/kesehatan/penanganan-darurat/create'
<Paper bg={colors['white-1']} p={'md'}>
<Stack>
<JudulList
title='List Penanganan Darurat'
href='/admin/kesehatan/penanganan-darurat/create'
/>
<Box style={{ overflowX: "auto" }}>
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
<TableThead>
<TableTr>
<TableTh>Judul</TableTh>
<TableTh>Deskripsi</TableTh>
<TableTh>Image</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>
<Box w={100}>
<Text truncate="end" fz={"sm"}>{item.name}</Text>
</Box></TableTd>
<TableTd>
<Box w={100}>
<Text lineClamp={1} truncate="end" fz={"sm"} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
</Box>
</TableTd>
<TableTd>
<Image w={100} src={item.image?.link} alt="image" />
</TableTd>
<TableTd>
<Button onClick={() => router.push(`/admin/kesehatan/penanganan-darurat/${item.id}`)}>
<IconDeviceImacCog size={25} />
</Button>
</TableTd>
</TableTr>
))}
</TableTbody>
</Table>
</Box>
</Stack>
</Paper>
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage)} // ini penting!
total={totalPages}
mt="md"
mb="md"
/>
<Box style={{ overflowX: "auto" }}>
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
<TableThead>
<TableTr>
<TableTh>Judul</TableTh>
<TableTh>Deskripsi</TableTh>
<TableTh>Image</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>
<Box w={100}>
<Text truncate="end" fz={"sm"}>{item.name}</Text>
</Box></TableTd>
<TableTd>
<Text truncate="end" fz={"sm"} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
</TableTd>
<TableTd>
<Image w={100} src={item.image?.link} alt="image" />
</TableTd>
<TableTd>
<Button onClick={() => router.push(`/admin/kesehatan/penanganan-darurat/${item.id}`)}>
<IconDeviceImacCog size={25} />
</Button>
</TableTd>
</TableTr>
))}
</TableTbody>
</Table>
</Box>
</Stack>
</Paper>
</Box>
</Center>
</Box>
)
}

View File

@@ -1,6 +1,6 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Image, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { Box, Button, Center, Image, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
import JudulList from '../../_com/judulList';
import HeaderSearch from '../../_com/header';
@@ -21,7 +21,7 @@ function ProgramKesehatan() {
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<ListProgramKesehatan search={search}/>
<ListProgramKesehatan search={search} />
</Box>
);
}
@@ -30,19 +30,21 @@ function ListProgramKesehatan({ search }: { search: string }) {
const programKesehatanState = useProxy(programKesehatan)
const router = useRouter()
const {
data,
page,
totalPages,
loading,
load,
} = programKesehatanState.findMany;
useShallowEffect(() => {
programKesehatanState.findMany.load()
}, [])
load(page, 10, search)
}, [page, search])
const filteredData = (programKesehatanState.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.name.toLowerCase().includes(keyword) ||
item.deskripsiSingkat.toLowerCase().includes(keyword)
);
});
const filteredData = data || []
if (!programKesehatanState.findMany.data) {
if (loading || !data) {
return (
<Box py={10}>
<Skeleton h={500} />
@@ -77,7 +79,9 @@ function ListProgramKesehatan({ search }: { search: string }) {
</Box>
</TableTd>
<TableTd>
<Text truncate="end" fz={"sm"} dangerouslySetInnerHTML={{ __html: item.deskripsiSingkat }} />
<Box w={100}>
<Text truncate="end" fz={"sm"} dangerouslySetInnerHTML={{ __html: item.deskripsiSingkat }} />
</Box>
</TableTd>
<TableTd>
<Image w={100} src={item.image?.link} alt="image" />
@@ -94,6 +98,15 @@ function ListProgramKesehatan({ search }: { search: string }) {
</Box>
</Stack>
</Paper>
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage)} // ini penting!
total={totalPages}
mt="md"
mb="md"
/>
</Center>
</Box>
)
}

View File

@@ -1,6 +1,6 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Image, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
import { Box, Button, Center, Image, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
@@ -21,7 +21,7 @@ function Puskesmas() {
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<ListPuskesmas search={search}/>
<ListPuskesmas search={search} />
</Box>
);
}
@@ -30,64 +30,75 @@ function ListPuskesmas({ search }: { search: string }) {
const statePuskesmas = useProxy(puskesmasState)
const router = useRouter();
const {
data,
page,
totalPages,
loading,
load,
} = statePuskesmas.findMany;
useShallowEffect(() => {
statePuskesmas.findMany.load()
}, [])
load(page, 10, search)
}, [page, search])
const filteredData = (statePuskesmas.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.name.toLowerCase().includes(keyword) ||
item.alamat.toLowerCase().includes(keyword)
);
});
const filteredData = data || []
if (!statePuskesmas.findMany.data) {
if (loading || !data) {
return (
<Box py={10}>
<Skeleton h={500}/>
<Skeleton h={500} />
</Box>
)
}
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<Stack>
<JudulList
title='List Puskesmas'
href='/admin/kesehatan/puskesmas/create'
/>
<Box style={{ overflowX: "auto" }}>
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
<TableThead>
<TableTr>
<TableTh>Nama Puskesmas</TableTh>
<TableTh>Alamat</TableTh>
<TableTh>Image</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>{item.name}</TableTd>
<TableTd>{item.alamat}</TableTd>
<TableTd>
<Image w={100} src={item.image.link} alt="image" />
</TableTd>
<TableTd>
<Button onClick={() => router.push(`/admin/kesehatan/puskesmas/${item.id}`)}>
<IconDeviceImacCog size={25} />
</Button>
</TableTd>
<Paper bg={colors['white-1']} p={'md'}>
<Stack>
<JudulList
title='List Puskesmas'
href='/admin/kesehatan/puskesmas/create'
/>
<Box style={{ overflowX: "auto" }}>
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
<TableThead>
<TableTr>
<TableTh>Nama Puskesmas</TableTh>
<TableTh>Alamat</TableTh>
<TableTh>Image</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
))}
</TableTbody>
</Table>
</Box>
</Stack>
</Paper>
</Box>
</TableThead>
<TableTbody>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>{item.name}</TableTd>
<TableTd>{item.alamat}</TableTd>
<TableTd>
<Image w={100} src={item.image.link} alt="image" />
</TableTd>
<TableTd>
<Button onClick={() => router.push(`/admin/kesehatan/puskesmas/${item.id}`)}>
<IconDeviceImacCog size={25} />
</Button>
</TableTd>
</TableTr>
))}
</TableTbody>
</Table>
</Box>
</Stack>
</Paper>
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage)} // ini penting!
total={totalPages}
mt="md"
mb="md"
/>
</Center>
</Box>
)
}

View File

@@ -1,10 +1,10 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import colors from '@/con/colors';
import { ActionIcon, Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { ActionIcon, Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconDeviceImacCog, IconFile, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { useState } from 'react';
import { useProxy } from 'valtio/utils';
import HeaderSearch from '../../_com/header';
import JudulList from '../../_com/judulList';
@@ -30,19 +30,22 @@ function APBDes() {
function ListAPBDes({ search }: { search: string }) {
const listState = useProxy(apbdes)
const router = useRouter();
useEffect(() => {
listState.findMany.load()
}, [])
const filteredData = (listState.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.name.toLowerCase().includes(keyword) ||
item.jumlah.toLowerCase().includes(keyword)
)
});
const {
data,
page,
totalPages,
loading,
load,
} = listState.findMany
if (!listState.findMany.data) {
useShallowEffect(() => {
load(page, 10, search)
}, [page, search])
const filteredData = data || []
if (loading || !data) {
return (
<Stack py={10}>
<Skeleton h={500} />
@@ -88,7 +91,7 @@ function ListAPBDes({ search }: { search: string }) {
rel="noopener noreferrer"
variant='transparent'
>
<IconFile size={25} color={colors['blue-button']}/>
<IconFile size={25} color={colors['blue-button']} />
</ActionIcon>
) : (
<Text>Tidak ada dokumen tersedia</Text>
@@ -106,6 +109,14 @@ function ListAPBDes({ search }: { search: string }) {
</Box>
</Stack>
</Paper>
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage)}
total={totalPages}
my={"md"}
/>
</Center>
</Box>
)
}

View File

@@ -1,10 +1,10 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import colors from '@/con/colors';
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconEdit, IconSearch, IconX } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useEffect, useMemo, useState } from 'react';
import { useState } from 'react';
import { useProxy } from 'valtio/utils';
import HeaderSearch from '../../../_com/header';
import JudulList from '../../../_com/judulList';
@@ -50,19 +50,11 @@ function ListKategoriKegiatan({ search }: { search: string }) {
}
}
useEffect(() => {
load(page, 10)
}, [page])
useShallowEffect(() => {
load(page, 10, search)
}, [page, search])
const filteredData = useMemo(() => {
if (!data) return [];
return data.filter(item => {
const keyword = search.toLowerCase();
return (
item.name?.toLowerCase().includes(keyword)
);
})
}, [data, search]);
const filteredData = data || []
// Handle loading state
if (loading || !data) {

View File

@@ -1,10 +1,10 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import colors from '@/con/colors';
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useEffect, useMemo, useState } from 'react';
import { useState } from 'react';
import { useProxy } from 'valtio/utils';
import HeaderSearch from '../../../_com/header';
import JudulList from '../../../_com/judulList';
@@ -38,22 +38,11 @@ function ListDesaAntiKorupsi({ search }: { search: string }) {
load,
} = listState.findMany;
useEffect(() => {
load(page, 10);
}, [page]);
useShallowEffect(() => {
load(page, 10, search);
}, [page, search]);
const filteredData = useMemo(() => {
if (!data) return [];
return data.filter(item => {
const keyword = search.toLowerCase();
return (
item.name?.toLowerCase().includes(keyword) ||
item.deskripsi?.toLowerCase().includes(keyword) ||
item.kategori?.name?.toLowerCase().includes(keyword)
);
})
.sort((a, b) => b.createdAt - a.createdAt);
}, [data, search]);
const filteredData = data || []
// Handle loading state
if (loading || !data) {

View File

@@ -0,0 +1,63 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import colors from '@/con/colors';
import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
import { usePathname, useRouter } from 'next/navigation';
import React, { useEffect, useState } from 'react';
function LayoutTabsKepuasan({ children }: { children: React.ReactNode }) {
const router = useRouter()
const pathname = usePathname()
const tabs = [
{
label: "Grafik Kepuasan Masyarakat",
value: "grafikkepuasannamasyarakat",
href: "/admin/landing-page/indeks-kepuasan-masyarakat/grafik-kepuasan-masyarakat"
},
{
label: "Responden",
value: "responden",
href: "/admin/landing-page/indeks-kepuasan-masyarakat/responden"
},
];
const curentTab = tabs.find(tab => tab.href === pathname)
const [activeTab, setActiveTab] = useState<string | null>(curentTab?.value || tabs[0].value);
const handleTabChange = (value: string | null) => {
const tab = tabs.find(t => t.value === value)
if (tab) {
router.push(tab.href)
}
setActiveTab(value)
}
useEffect(() => {
const match = tabs.find(tab => tab.href === pathname)
if (match) {
setActiveTab(match.value)
}
}, [pathname])
return (
<Stack>
<Title order={3}>Indeks Kepuasan Masyarakat</Title>
<Tabs color={colors['blue-button']} variant='pills' value={activeTab} onChange={handleTabChange}>
<TabsList p={"xs"} bg={"#BBC8E7FF"}>
{tabs.map((e, i) => (
<TabsTab key={i} value={e.value}>{e.label}</TabsTab>
))}
</TabsList>
{tabs.map((e, i) => (
<TabsPanel key={i} value={e.value}>
{/* Konten dummy, bisa diganti tergantung routing */}
<></>
</TabsPanel>
))}
</Tabs>
{children}
</Stack>
);
}
export default LayoutTabsKepuasan;

View File

@@ -0,0 +1,256 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
'use client';
import colors from '@/con/colors';
import { PieChart, BarChart } from '@mantine/charts'; // ✅ Ganti recharts dengan Mantine
import {
Box,
Center,
Flex,
Paper,
SimpleGrid,
Skeleton,
Stack,
Text,
Title,
} from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { useState } from 'react';
import { useProxy } from 'valtio/utils';
import indeksKepuasanState from '../../../_state/landing-page/indeks-kepuasan';
interface ChartDataItem {
name: string;
value: number;
color: string;
label?: string;
}
function Page() {
const state = useProxy(indeksKepuasanState.responden);
const { data, loading } = state.findMany;
const [donutDataJenisKelamin, setDonutDataJenisKelamin] = useState<ChartDataItem[]>([]);
const [donutDataRating, setDonutDataRating] = useState<ChartDataItem[]>([]);
const [donutDataKelompokUmur, setDonutDataKelompokUmur] = useState<ChartDataItem[]>([]);
const [barChartData, setBarChartData] = useState<Array<{ month: string; count: number }>>([]);
useShallowEffect(() => {
if (!data && !loading) {
state.findMany.load();
return;
}
if (data) {
// Hitung total berdasarkan jenis kelamin
const totalLaki = data.filter((item: any) => item.jenisKelamin?.name?.toLowerCase() === 'laki-laki').length;
const totalPerempuan = data.filter((item: any) => item.jenisKelamin?.name?.toLowerCase() === 'perempuan').length;
// Hitung total berdasarkan rating
const totalSangatBaik = data.filter((item: any) => item.rating?.name?.toLowerCase() === 'sangat baik').length;
const totalBaik = data.filter((item: any) => item.rating?.name?.toLowerCase() === 'baik').length;
const totalKurangBaik = data.filter((item: any) => item.rating?.name?.toLowerCase() === 'kurang baik').length;
const totalSangatKurangBaik = data.filter((item: any) => item.rating?.name?.toLowerCase() === 'sangat kurang baik').length;
// Hitung total berdasarkan kelompok umur
const totalMuda = data.filter((item: any) => item.kelompokUmur?.name?.toLowerCase() === 'muda').length;
const totalDewasa = data.filter((item: any) => item.kelompokUmur?.name?.toLowerCase() === 'dewasa').length;
const totalLansia = data.filter((item: any) => item.kelompokUmur?.name?.toLowerCase() === 'lansia').length;
// Update gender chart data
setDonutDataJenisKelamin([
{ name: 'Laki-laki', value: totalLaki, color: colors['blue-button'] },
{ name: 'Perempuan', value: totalPerempuan, color: '#10A85AFF' },
]);
// Update rating chart data
setDonutDataRating([
{ name: 'Sangat Baik', value: totalSangatBaik, color: colors['blue-button'] },
{ name: 'Baik', value: totalBaik, color: '#10A85AFF' },
{ name: 'Kurang Baik', value: totalKurangBaik, color: '#FFA500' },
{ name: 'Sangat Kurang Baik', value: totalSangatKurangBaik, color: '#FF4500' },
]);
// Update age group chart data
setDonutDataKelompokUmur([
{ name: 'Muda', value: totalMuda, color: colors['blue-button'] },
{ name: 'Dewasa', value: totalDewasa, color: '#10A85AFF' },
{ name: 'Lansia', value: totalLansia, color: '#FFA500' },
]);
// Process data for bar chart (group by month)
const monthYearMap = new Map<string, number>();
data.forEach((item: any) => {
// Try both createdAt and tanggal fields
const dateValue = item.tanggal || item.createdAt;
if (!dateValue) return;
const parsedDate = new Date(dateValue);
if (isNaN(parsedDate.getTime())) return;
const month = parsedDate.getMonth() + 1;
const year = parsedDate.getFullYear();
const monthYearKey = `${year}-${String(month).padStart(2, '0')}`;
monthYearMap.set(monthYearKey, (monthYearMap.get(monthYearKey) || 0) + 1);
});
// Convert map to array and sort by date
const barData = Array.from(monthYearMap.entries())
.map(([key, count]) => {
const [year, month] = key.split('-');
const monthName = new Date(Number(year), Number(month) - 1, 1)
.toLocaleString('id-ID', { month: 'long' });
return {
month: `${monthName} ${year}`,
count,
sortKey: parseInt(`${year}${String(month).padStart(2, '0')}`, 10)
};
})
.sort((a, b) => a.sortKey - b.sortKey)
.map(({ month, count }) => ({ month, count }));
setBarChartData(barData);
}
}, [data]);
if (loading || !data) {
return (
<Stack py={10}>
<Skeleton height={730} />
</Stack>
);
}
if (data.length === 0) {
return (
<Stack py={10}>
<Text c="dimmed" ta="center" my="md">
Belum ada data untuk ditampilkan
</Text>
</Stack>
);
}
return (
<Stack gap="xs">
{/* Bar Chart - Data per Tanggal */}
<Paper bg={colors['white-1']} p="md" radius="md" mb="md">
<Title order={4} mb="md" ta="center">Jumlah Responden per Bulan</Title>
<Box h={300}>
<BarChart
h={300}
data={barChartData}
dataKey="month"
series={[{ name: 'count', color: colors['blue-button'] }]}
tickLine="y"
xAxisLabel="Bulan"
yAxisLabel="Jumlah Responden"
withTooltip
tooltipAnimationDuration={200}
/>
</Box>
</Paper>
<SimpleGrid cols={{ base: 1, md: 3 }}>
{/* Chart Jenis Kelamin */}
<Paper bg={colors['white-1']} p="md" radius="md">
<Stack>
<Title order={4}>Jenis Kelamin</Title>
{donutDataJenisKelamin.every(item => item.value === 0) ? (
<Text c="dimmed" ta="center" my="md">
Belum ada data untuk ditampilkan dalam grafik
</Text>
) : (
<Box>
<Center>
<PieChart
withLabels
withTooltip
labelsType="percent"
size={250}
data={donutDataJenisKelamin}
/>
</Center>
<Stack gap="sm" mt="md">
{donutDataJenisKelamin.map((entry) => (
<Flex key={entry.name} gap="md" align="center">
<Box bg={entry.color} w={20} h={20} style={{ flexShrink: 0 }} />
<Text size="sm">{entry.name}: {entry.value}</Text>
</Flex>
))}
</Stack>
</Box>
)}
</Stack>
</Paper>
{/* Chart Rating */}
<Paper bg={colors['white-1']} p="md" radius="md">
<Stack>
<Title order={4}>Pilihan</Title>
{donutDataRating.every(item => item.value === 0) ? (
<Text c="dimmed" ta="center" my="md">
Belum ada data untuk ditampilkan dalam grafik
</Text>
) : (
<Box>
<Center>
<PieChart
withLabels
withTooltip
labelsType="percent"
size={250}
data={donutDataRating}
/>
</Center>
<Stack gap="sm" mt="md">
{donutDataRating.map((entry) => (
<Flex key={entry.name} gap="md" align="center">
<Box bg={entry.color} w={20} h={20} style={{ flexShrink: 0 }} />
<Text size="sm">{entry.name}: {entry.value}</Text>
</Flex>
))}
</Stack>
</Box>
)}
</Stack>
</Paper>
{/* Chart Kelompok Umur */}
<Paper bg={colors['white-1']} p="md" radius="md">
<Stack>
<Title order={4}>Umur</Title>
{donutDataKelompokUmur.every(item => item.value === 0) ? (
<Text c="dimmed" ta="center" my="md">
Belum ada data untuk ditampilkan dalam grafik
</Text>
) : (
<Box>
<Center>
<PieChart
withLabels
withTooltip
labelsType="percent"
size={250}
data={donutDataKelompokUmur}
/>
</Center>
<Stack gap="sm" mt="md">
{donutDataKelompokUmur.map((entry) => (
<Flex key={entry.name} gap="md" align="center">
<Box bg={entry.color} w={20} h={20} style={{ flexShrink: 0 }} />
<Text size="sm">{entry.name}: {entry.value}</Text>
</Flex>
))}
</Stack>
</Box>
)}
</Stack>
</Paper>
</SimpleGrid>
</Stack>
);
}
export default Page;

View File

@@ -0,0 +1,12 @@
import React from 'react';
import LayoutTabsKepuasan from './_lib/layoutTab';
function Layout({ children }: { children: React.ReactNode }) {
return (
<LayoutTabsKepuasan>
{children}
</LayoutTabsKepuasan>
);
}
export default Layout;

View File

@@ -0,0 +1,190 @@
'use client'
/* eslint-disable react-hooks/exhaustive-deps */
import React, { useEffect, useState } from 'react';
import { useRouter, useParams } from 'next/navigation';
import { useProxy } from 'valtio/utils';
import colors from '@/con/colors';
import { Box, Button, Paper, Stack, Title, TextInput, Text, Select } from '@mantine/core';
import { IconArrowBack } from '@tabler/icons-react';
import indeksKepuasanState from '@/app/admin/(dashboard)/_state/landing-page/indeks-kepuasan';
import { toast } from 'react-toastify';
interface FormResponden {
name: string;
tanggal: string;
jenisKelaminId: string;
ratingId: string;
kelompokUmurId: string;
}
function EditResponden() {
const router = useRouter()
const params = useParams() as { id: string }
const state = useProxy(indeksKepuasanState.responden)
const id = params.id
const [formData, setFormData] = useState<FormResponden>({
name: '',
tanggal: '',
jenisKelaminId: '',
ratingId: '',
kelompokUmurId: '',
})
useEffect(() => {
indeksKepuasanState.jenisKelaminResponden.findMany.load();
indeksKepuasanState.pilihanRatingResponden.findMany.load();
indeksKepuasanState.kelompokUmurResponden.findMany.load();
const loadResponden = async () => {
const id = params?.id as string;
if (!id) return;
try {
const data = await state.update.load(id);
if (data) {
state.update.id = id;
state.update.form = {
name: data.name,
tanggal: data.tanggal,
jenisKelaminId: data.jenisKelaminId,
ratingId: data.ratingId,
kelompokUmurId: data.kelompokUmurId,
};
setFormData({
name: data.name,
tanggal: data.tanggal,
jenisKelaminId: data.jenisKelaminId,
ratingId: data.ratingId,
kelompokUmurId: data.kelompokUmurId,
});
}
} catch (error) {
console.error("Error loading program penghijauan:", error);
toast.error("Gagal memuat data program penghijauan");
}
}
loadResponden();
}, [params?.id]);
const handleSubmit = async () => {
state.update.id = id;
state.update.form = { ...formData }; // <-- sinkronisasi manual
await state.update.submit();
router.push('/admin/ppid/ikm-desa-darmasaba/responden')
}
return (
<Box>
<Box mb={10}>
<Button variant="subtle" onClick={() => router.back()}>
<IconArrowBack size={20} />
</Button>
</Box>
<Paper bg={colors['white-1']} w={{ base: '100%', md: '50%' }} p={'md'}>
<Stack>
<Title order={3}>Edit Responden</Title>
<TextInput
label="Nama"
type='text'
placeholder="masukkan nama"
value={formData.name}
onChange={(val) => {
setFormData({
...formData,
name: val.currentTarget.value
})
}}
/>
<TextInput
label="Tanggal"
type="date"
placeholder='Pilih tanggal'
value={formData.tanggal ? new Date(formData.tanggal).toISOString().split('T')[0] : ''}
onChange={(e) => {
const selectedDate = e.currentTarget.value;
setFormData({
...formData,
tanggal: selectedDate,
});
}}
/>
<Select
key={"jenisKelamin"}
label={<Text fw="bold" fz="sm">Jenis Kelamin</Text>}
placeholder="Pilih jenis kelamin"
value={formData.jenisKelaminId}
onChange={(val) => setFormData({ ...formData, jenisKelaminId: val || "" })}
data={
(indeksKepuasanState.jenisKelaminResponden.findMany.data || [])
.filter(Boolean) // Hapus null/undefined
.map((v) => ({
value: v.id || '',
label: typeof v.name === 'string' ? v.name : 'Tanpa Nama'
}))
}
disabled={indeksKepuasanState.jenisKelaminResponden.findMany.loading} // ✅ disable saat loading
clearable
searchable
required
error={!formData.jenisKelaminId ? "Pilih jenis kelamin" : undefined}
/>
<Select
key={"rating"}
value={formData.ratingId}
onChange={(val) => setFormData({ ...formData, ratingId: val || "" })}
label={<Text fw={"bold"} fz={"sm"}>Rating</Text>}
placeholder='Pilih rating'
data={
(indeksKepuasanState.pilihanRatingResponden.findMany.data || [])
.filter(Boolean)
.map((v) => ({
value: v.id || '',
label: typeof v.name === 'string' ? v.name : 'Tanpa Nama'
}))
}
disabled={indeksKepuasanState.pilihanRatingResponden.findMany.loading}
clearable
searchable
required
error={!formData.ratingId ? "Pilih rating" : undefined}
/>
<Select
key={"kelompokUmur"}
value={formData.kelompokUmurId}
onChange={(val) => setFormData({ ...formData, kelompokUmurId: val || "" })}
label={<Text fw={"bold"} fz={"sm"}>Kelompok Umur</Text>}
placeholder='Pilih kelompok umur'
data={
(indeksKepuasanState.kelompokUmurResponden.findMany.data || [])
.filter(Boolean)
.map((v) => ({
value: v.id || '',
label: typeof v.name === 'string' ? v.name : 'Tanpa Nama'
}))
}
disabled={indeksKepuasanState.kelompokUmurResponden.findMany.loading}
clearable
searchable
required
error={!formData.kelompokUmurId ? "Pilih kelompok umur" : undefined}
/>
<Button
mt={10}
bg={colors['blue-button']}
onClick={handleSubmit}
>
Submit
</Button>
</Stack>
</Paper>
</Box>
);
}
export default EditResponden;

View File

@@ -0,0 +1,116 @@
'use client'
import { ModalKonfirmasiHapus } from "@/app/admin/(dashboard)/_com/modalKonfirmasiHapus"
import indeksKepuasanState from "@/app/admin/(dashboard)/_state/landing-page/indeks-kepuasan"
import colors from "@/con/colors"
import { Box, Button, Flex, Paper, Skeleton, Stack, Text } from "@mantine/core"
import { useShallowEffect } from "@mantine/hooks"
import { IconArrowBack, IconEdit, IconX } from "@tabler/icons-react"
import { useRouter, useParams } from "next/navigation"
import { useState } from "react"
import { useProxy } from "valtio/utils"
export default function DetailResponden() {
const [modalHapus, setModalHapus] = useState(false)
const stateDetail = useProxy(indeksKepuasanState.responden)
const router = useRouter()
const params = useParams()
const [selectedId, setSelectedId] = useState<string | null>(null)
useShallowEffect(() => {
stateDetail.findUnique.load(params?.id as string)
}, [params?.id])
const handleHapus = () => {
if (selectedId) {
stateDetail.delete.byId(selectedId)
setModalHapus(false)
setSelectedId(null)
router.push("/admin/ppid/ikm-desa-darmasaba/responden")
}
}
if (!stateDetail.findUnique.data) {
return (
<Stack>
<Skeleton h={500} />
</Stack>
)
}
return (
<Box>
<Box mb={10}>
<Button variant="subtle" onClick={() => router.back()}>
<IconArrowBack color={colors['blue-button']} size={25} />
</Button>
</Box>
<Paper w={{ base: "100%", md: "50%" }} bg={colors['white-1']} p={'md'}>
<Stack>
<Text fz={"xl"} fw={"bold"}>Detail Responden</Text>
<Paper bg={colors['BG-trans']} p={'md'}>
<Stack gap={"xs"}>
<Box>
<Text fz={"lg"} fw={"bold"}>Nama Responden</Text>
<Text fz={"lg"}>{stateDetail.findUnique.data?.name}</Text>
</Box>
<Box>
<Text fz={"lg"} fw={"bold"}>Tanggal</Text>
<Text fz={"lg"}>{
stateDetail.findUnique.data?.tanggal
? new Date(stateDetail.findUnique.data.tanggal).toLocaleDateString('id-ID')
: '-'
}</Text>
</Box>
<Box>
<Text fz={"lg"} fw={"bold"}>Jenis Kelamin</Text>
<Text fz={"lg"}>{stateDetail.findUnique.data?.jenisKelamin?.name}</Text>
</Box>
<Box>
<Text fz={"lg"} fw={"bold"}>Rating</Text>
<Text fz={"lg"}>{stateDetail.findUnique.data?.rating?.name}</Text>
</Box>
<Box>
<Text fz={"lg"} fw={"bold"}>Kelompok Umur</Text>
<Text fz={"lg"}>{stateDetail.findUnique.data?.kelompokUmur?.name}</Text>
</Box>
<Flex gap={"xs"} mt={10}>
<Button
onClick={() => {
if (stateDetail.findUnique.data) {
setSelectedId(stateDetail.findUnique.data.id);
setModalHapus(true);
}
}}
disabled={stateDetail.delete.loading || !stateDetail.findUnique.data}
color={"red"}
>
<IconX size={20} />
</Button>
<Button
onClick={() => {
if (stateDetail.findUnique.data) {
router.push(`/admin/ppid/ikm-desa-darmasaba/responden/${stateDetail.findUnique.data.id}/edit`);
}
}}
disabled={!stateDetail.findUnique.data}
color={"green"}
>
<IconEdit size={20} />
</Button>
</Flex>
</Stack>
</Paper>
</Stack>
</Paper>
{/* Modal Hapus */}
<ModalKonfirmasiHapus
opened={modalHapus}
onClose={() => setModalHapus(false)}
onConfirm={handleHapus}
text="Apakah anda yakin ingin menghapus responden ini?"
/>
</Box>
)
}

View File

@@ -0,0 +1,148 @@
'use client'
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable @typescript-eslint/no-explicit-any */
import React from 'react';
import { useRouter } from 'next/navigation';
import grafikBerdasarkanJenisKelamin from '@/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanJenisKelamin';
import { useProxy } from 'valtio/utils';
import { useState } from 'react';
import colors from '@/con/colors';
import { Box, Button, Paper, Stack, Title, TextInput, Select, Text } from '@mantine/core';
import { IconArrowBack } from '@tabler/icons-react';
import indeksKepuasanState from '@/app/admin/(dashboard)/_state/landing-page/indeks-kepuasan';
import { useShallowEffect } from '@mantine/hooks';
function RespondenCreate() {
const router = useRouter();
const stategrafikBerdasarkanResponden = useProxy(indeksKepuasanState.responden)
const [donutData, setDonutData] = useState<any[]>([]);
const resetForm = () => {
stategrafikBerdasarkanResponden.create.form = {
...stategrafikBerdasarkanResponden.create.form,
name: "",
tanggal: "",
jenisKelaminId: "",
ratingId: "",
kelompokUmurId: "",
}
}
useShallowEffect(() => {
indeksKepuasanState.jenisKelaminResponden.findMany.load()
indeksKepuasanState.pilihanRatingResponden.findMany.load()
indeksKepuasanState.kelompokUmurResponden.findMany.load()
})
const handleSubmit = async () => {
try {
const id = await stategrafikBerdasarkanResponden.create.create();
if (typeof id !== 'undefined') {
const idStr = String(id);
await stategrafikBerdasarkanResponden.findUnique.load(idStr);
if (stategrafikBerdasarkanResponden.findUnique.data) {
setDonutData([stategrafikBerdasarkanResponden.findUnique.data]);
}
}
resetForm();
router.push("/admin/ppid/ikm-desa-darmasaba/responden");
} catch (error) {
console.error('Error submitting form:', error);
}
}
return (
<Box>
<Box mb={10}>
<Button variant="subtle" onClick={() => router.back()}>
<IconArrowBack size={20} />
</Button>
</Box>
<Paper bg={colors['white-1']} w={{ base: '100%', md: '50%' }} p={'md'}>
<Stack>
<Title order={3}>Grafik Hasil Kepuasan Masyarakat Terhadap Pelayanan Publik</Title>
<TextInput
label="Nama"
type='text'
placeholder="masukkan nama"
value={stategrafikBerdasarkanResponden.create.form.name}
onChange={(val) => {
stategrafikBerdasarkanResponden.create.form.name = val.currentTarget.value;
}}
/>
<TextInput
label="Tanggal"
type="date"
placeholder="masukkan tanggal"
value={stategrafikBerdasarkanResponden.create.form.tanggal}
onChange={(val) => {
stategrafikBerdasarkanResponden.create.form.tanggal = val.currentTarget.value;
}}
/>
<Select
key={"jenisKelamin"}
label={"Jenis Kelamin"}
placeholder={indeksKepuasanState.jenisKelaminResponden.findMany.loading ? 'Memuat...' : 'Pilih jenis kelamin'}
value={stategrafikBerdasarkanResponden.create.form.jenisKelaminId || ""}
onChange={(val) => {
stategrafikBerdasarkanResponden.create.form.jenisKelaminId = val ?? "";
}}
data={
(indeksKepuasanState.jenisKelaminResponden.findMany.data || [])
.filter(Boolean) // Hapus null, undefined, dll
.map((item) => ({
value: item.id,
label: item.name || 'Tanpa Nama',
}))
}
disabled={indeksKepuasanState.jenisKelaminResponden.findMany.loading}
/>
<Select
key={"rating_responden"}
label={"Rating"}
placeholder={indeksKepuasanState.pilihanRatingResponden.findMany.loading ? 'Memuat...' : 'Pilih rating'}
value={stategrafikBerdasarkanResponden.create.form.ratingId || ""}
onChange={(val) => {
stategrafikBerdasarkanResponden.create.form.ratingId = val ?? "";
}}
data={
(indeksKepuasanState.pilihanRatingResponden.findMany.data || [])
.filter(Boolean) // Hapus null, undefined, dll
.map((item) => ({
value: item.id,
label: item.name || 'Tanpa Nama',
}))
}
disabled={indeksKepuasanState.pilihanRatingResponden.findMany.loading}
/>
<Select
key={"kelompokUmur"}
label={"Kelompok Umur"}
placeholder={indeksKepuasanState.kelompokUmurResponden.findMany.loading ? 'Memuat...' : 'Pilih kelompok umur'}
value={stategrafikBerdasarkanResponden.create.form.kelompokUmurId || ""}
onChange={(val) => {
stategrafikBerdasarkanResponden.create.form.kelompokUmurId = val ?? "";
}}
data={
(indeksKepuasanState.kelompokUmurResponden.findMany.data || [])
.filter(Boolean) // Hapus null, undefined, dll
.map((item) => ({
value: item.id,
label: item.name || 'Tanpa Nama',
}))
}
disabled={indeksKepuasanState.kelompokUmurResponden.findMany.loading}
/>
<Button
mt={10}
bg={colors['blue-button']}
onClick={handleSubmit}
>
Submit
</Button>
</Stack>
</Paper>
</Box>
);
}
export default RespondenCreate;

View File

@@ -0,0 +1,144 @@
'use client';
import colors from '@/con/colors';
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import { useProxy } from 'valtio/utils';
import HeaderSearch from '../../../_com/header';
import indeksKepuasanState from '../../../_state/landing-page/indeks-kepuasan';
function Responden() {
const [search, setSearch] = useState("");
return (
<Box>
<HeaderSearch
title='Responden'
placeholder='pencarian'
searchIcon={<IconSearch size={20} />}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<ListResponden search={search} />
</Box>
);
}
interface ListRespondenProps {
search: string;
}
function ListResponden({ search }: ListRespondenProps) {
const state = useProxy(indeksKepuasanState.responden);
const router = useRouter();
const { data, page, totalPages, loading, load } = state.findMany;
useShallowEffect(() => {
load(page, 10)
}, [page]);
const filteredData = (data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.name.toLowerCase().includes(keyword)
);
});
if (loading || !data) {
return (
<Stack py={10}>
<Skeleton height={730} />
</Stack>
);
}
if (data.length === 0) {
return (
<Box py={10}>
<Paper p="md">
<Stack>
<Title>Responden</Title>
<Table striped withTableBorder withRowBorders>
<TableThead>
<TableTr>
<TableTh>No</TableTh>
<TableTh>Nama</TableTh>
<TableTh>Tanggal</TableTh>
<TableTh>Jenis Kelamin</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
</Table>
<Text ta="center">Tidak ada data berdasarkan jenis kelamin responden yang tersedia</Text>
</Stack>
</Paper>
</Box >
);
}
return (
<Box>
<Stack gap="xs">
<Paper bg={colors['white-1']} p="md" h={{ base: 730, md: 650 }}>
<Title mb={10} order={3}>List Responden</Title>
<Table striped withTableBorder withRowBorders>
<TableThead>
<TableTr>
<TableTh style={{ width: '5%', textAlign: 'center' }}>No</TableTh>
<TableTh style={{ width: '20%', textAlign: 'center' }}>Nama</TableTh>
<TableTh style={{ width: '20%', textAlign: 'center' }}>Tanggal</TableTh>
<TableTh style={{ width: '20%', textAlign: 'center' }}>Jenis Kelamin</TableTh>
<TableTh style={{ width: '15%', textAlign: 'center' }}>Detail</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{filteredData.length === 0 ? (
<TableTr>
<TableTd colSpan={6}>
<Text ta='center' c='dimmed'>Belum ada data responden</Text>
</TableTd>
</TableTr>
) : (
filteredData.map((item, index) => (
<TableTr key={item.id}>
<TableTd style={{ width: '5%', textAlign: 'center' }}>{index + 1}</TableTd>
<TableTd style={{ width: '20%', textAlign: 'center' }}>{item.name}</TableTd>
<TableTd style={{ width: '20%', textAlign: 'center' }}>{item.tanggal
? new Date(item.tanggal).toLocaleDateString('id-ID')
: '-'}</TableTd>
<TableTd style={{ width: '20%', textAlign: 'center' }}>{item.jenisKelamin.name}</TableTd>
<TableTd style={{ width: '15%', textAlign: 'center' }}>
<Button color='green' onClick={() => router.push(`/admin/ppid/ikm-desa-darmasaba/responden/${item.id}`)}>
<IconDeviceImac size={20} />
</Button>
</TableTd>
</TableTr>
))
)}
</TableTbody>
</Table>
</Paper>
<Center>
<Pagination
value={page}
onChange={(newPage) => {
load(newPage, 10);
window.scrollTo(0, 0);
}}
total={totalPages}
mt="md"
mb="md"
/>
</Center>
</Stack>
</Box>
);
}
export default Responden;

View File

@@ -1,6 +1,6 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconEdit, IconSearch, IconX } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
@@ -42,18 +42,21 @@ function ListKategoriPrestasi({ search }: { search: string }) {
}
}
const {
data,
page,
totalPages,
loading,
load,
} = stateKategori.findMany
useShallowEffect(() => {
stateKategori.findMany.load()
}, [])
load(page, 10, search)
}, [page, search])
const filteredData = (stateKategori.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.name.toLowerCase().includes(keyword)
);
});
const filteredData = data || []
if (!stateKategori.findMany.data) {
if (loading || !data) {
return (
<Stack py={10}>
<Skeleton h={500} />
@@ -100,6 +103,14 @@ function ListKategoriPrestasi({ search }: { search: string }) {
</Table>
</Box>
</Paper>
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage)}
total={totalPages}
my={"md"}
/>
</Center>
{/* Modal Konfirmasi Hapus */}
<ModalKonfirmasiHapus
opened={modalHapus}

View File

@@ -1,7 +1,7 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import colors from '@/con/colors';
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
@@ -30,20 +30,36 @@ function ListPrestasiDesa() {
function ListPrestasi({ search }: { search: string }) {
const listState = useProxy(prestasiState.prestasiDesa)
const router = useRouter();
useEffect(() => {
listState.findMany.load()
}, [])
const filteredData = (listState.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.name.toLowerCase().includes(keyword) ||
item.deskripsi.toLowerCase().includes(keyword) ||
item.kategori?.name?.toLowerCase().includes(keyword)
);
const{
data,
page,
totalPages,
loading,
load,
} = listState.findMany
// Debug log
console.log('ListPrestasi state:', {
loading,
data: data?.length,
page,
totalPages,
search
});
if (!listState.findMany.data) {
useEffect(() => {
console.log('Loading data...', { page, search });
load(page, 10, search).then(() => {
console.log('Data loaded:', listState.findMany.data);
}).catch(error => {
console.error('Error loading data:', error);
});
}, [page, search])
const filteredData = data || []
if (loading || !data) {
return (
<Stack py={10}>
<Skeleton h={500} />
@@ -82,7 +98,7 @@ function ListPrestasi({ search }: { search: string }) {
<Text truncate="end" fz={"sm"} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
</Box>
</TableTd>
<TableTd>{item.kategori?.name}</TableTd>
<TableTd>{item.kategori?.name || 'No Category'}</TableTd>
<TableTd>
<Button onClick={() => router.push(`/admin/landing-page/prestasi-desa/list-prestasi-desa/${item.id}`)}>
<IconDeviceImacCog size={25} />
@@ -95,6 +111,14 @@ function ListPrestasi({ search }: { search: string }) {
</Box>
</Stack>
</Paper>
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage)}
total={totalPages}
my={"md"}
/>
</Center>
</Box>
)
}

View File

@@ -1,10 +1,10 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import colors from '@/con/colors';
import { Box, Button, Center, Image, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useEffect, useMemo, useState } from 'react';
import { useState } from 'react';
import { useProxy } from 'valtio/utils';
import HeaderSearch from '../../../_com/header';
import JudulList from '../../../_com/judulList';
@@ -38,20 +38,11 @@ function ListMediaSosial({ search }: { search: string }) {
load,
} = stateMediaSosial.findMany;
useEffect(() => {
load(page, 10)
}, [page])
useShallowEffect(() => {
load(page, 10, search)
}, [page, search])
const filteredData = useMemo(() => {
if (!data) return [];
return data.filter(item => {
const keyword = search.toLowerCase();
return (
item.name?.toLowerCase().includes(keyword) ||
item.iconUrl?.toLowerCase().includes(keyword)
);
})
}, [data, search]);
const filteredData = data || []
// Handle loading state
if (loading || !data) {

View File

@@ -55,7 +55,10 @@ function DetailProgramInovasi() {
</Box>
<Box>
<Text fz={"lg"} fw={"bold"}>Deskripsi</Text>
<Text fz={"lg"}>{stateProgramInovasi.findUnique.data?.description}</Text>
<Text
fz={"lg"}
>{stateProgramInovasi.findUnique.data?.description}</Text>
</Box>
<Box>
<Text fz={"lg"} fw={"bold"}>Link</Text>
@@ -63,6 +66,12 @@ function DetailProgramInovasi() {
href={stateProgramInovasi.findUnique.data?.link || "#"}
target="_blank"
rel="noopener noreferrer"
style={{
wordWrap: 'break-word',
whiteSpace: 'pre-wrap',
overflowWrap: 'break-word',
width: '100%'
}}
>
{stateProgramInovasi.findUnique.data?.link || "Tidak ada link"}
</a>

View File

@@ -1,10 +1,10 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import colors from '@/con/colors';
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useEffect, useMemo, useState } from 'react';
import { useState } from 'react';
import { useProxy } from 'valtio/utils';
import HeaderSearch from '../../../_com/header';
import JudulList from '../../../_com/judulList';
@@ -38,21 +38,11 @@ function ListProgramInovasi({ search }: { search: string }) {
load,
} = stateProgramInovasi.findMany;
useEffect(() => {
load(page, 10);
}, [page]);
useShallowEffect(() => {
load(page, 10, search);
}, [page, search]);
const filteredData = useMemo(() => {
if (!data) return [];
return data.filter(item => {
const keyword = search.toLowerCase();
return (
item.name?.toLowerCase().includes(keyword) ||
item.description?.toLowerCase().includes(keyword) ||
item.link?.toLowerCase().includes(keyword)
);
})
}, [data, search]);
const filteredData = data || []
if (loading || !data) {
return (

View File

@@ -1,14 +1,14 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import colors from '@/con/colors';
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useEffect, useMemo, useState } from 'react';
import { useState } from 'react';
import { useProxy } from 'valtio/utils';
import HeaderSearch from '../../_com/header';
import sdgsDesa from '../../_state/landing-page/sdgs-desa';
import JudulList from '../../_com/judulList';
import sdgsDesa from '../../_state/landing-page/sdgs-desa';
function SdgsDesa() {
@@ -39,20 +39,11 @@ function ListSdgsDesa({ search }: { search: string }) {
load,
} = listState.findMany;
useEffect(() => {
load(page, 10)
}, [])
useShallowEffect(() => {
load(page, 10, search)
}, [page, search])
const filteredData = useMemo(() => {
if (!data) return [];
return data.filter(item => {
const keyword = search.toLowerCase();
return (
item.name?.toLowerCase().includes(keyword) ||
item.jumlah?.toLowerCase().includes(keyword)
);
})
}, [data, search]);
const filteredData = data || []
// Handle loading state
if (loading || !data) {

View File

@@ -105,9 +105,19 @@ function ListDaftarInformasi({ search }: { search: string }) {
<TableTbody>
{filteredData.map((item, index) => (
<TableTr key={item.id}>
<TableTd style={{ textAlign: 'center' }}>{index + 1}</TableTd>
<TableTd style={{ wordWrap: 'break-word' }}>{item.jenisInformasi}</TableTd>
<TableTd style={{ wordWrap: 'break-word' }} dangerouslySetInnerHTML={{ __html: item.deskripsi }}></TableTd>
<TableTd style={{ textAlign: 'center' }}>
<Text mt={10} fz={"md"}>{index + 1}</Text>
</TableTd>
<TableTd style={{ wordWrap: 'break-word' }}>
<Box w={200}>
<Text mt={10} fz={"md"} truncate={"end"} lineClamp={1}>{item.jenisInformasi}</Text>
</Box>
</TableTd>
<TableTd style={{ wordWrap: 'break-word' }}>
<Box w={200}>
<Text fz={"md"} truncate={"end"} lineClamp={1} dangerouslySetInnerHTML={{ __html: item.deskripsi }}></Text>
</Box>
</TableTd>
<TableTd style={{ textAlign: 'center' }}>
<Button bg={"green"} onClick={() => router.push(`/admin/ppid/daftar-informasi-publik-desa-darmasaba/${item.id}`)}>
<IconDeviceImacCog size={25} />

View File

@@ -41,15 +41,11 @@ function EditResponden() {
try {
const data = await state.update.load(id);
if (data) {
const formattedDate = data.tanggal && !isNaN(new Date(data.tanggal).getTime())
? new Date(data.tanggal).toISOString().split('T')[0]
: '';
// ⬇️ FIX PENTING: tambahkan ini
state.update.id = id;
state.update.form = {
name: data.name,
tanggal: formattedDate,
tanggal: data.tanggal,
jenisKelaminId: data.jenisKelaminId,
ratingId: data.ratingId,
kelompokUmurId: data.kelompokUmurId,
@@ -76,6 +72,7 @@ function EditResponden() {
const handleSubmit = async () => {
state.update.id = id;
state.update.form = { ...formData }; // <-- sinkronisasi manual
await state.update.submit();
router.push('/admin/ppid/ikm-desa-darmasaba/responden')
}
@@ -103,12 +100,15 @@ function EditResponden() {
}}
/>
<TextInput
label="Tanggal"
type="date"
value={formData.tanggal}
placeholder='Pilih tanggal'
value={formData.tanggal ? new Date(formData.tanggal).toISOString().split('T')[0] : ''}
onChange={(e) => {
const selectedDate = e.currentTarget.value;
setFormData({
...formData,
tanggal: e.currentTarget.value, // ✅ sudah format YYYY-MM-DD
tanggal: selectedDate,
});
}}
/>

View File

@@ -1,13 +1,12 @@
'use client';
import colors from '@/con/colors';
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import { useProxy } from 'valtio/utils';
import HeaderSearch from '../../../_com/header';
import JudulList from '../../../_com/judulList';
import indeksKepuasanState from '../../../_state/landing-page/indeks-kepuasan';
function Responden() {
@@ -60,10 +59,7 @@ function ListResponden({ search }: ListRespondenProps) {
<Box py={10}>
<Paper p="md">
<Stack>
<JudulList
title='List Data Berdasarkan Jenis Kelamin Responden'
href='/admin/ppid/ikm-desa-darmasaba/responden/create'
/>
<Title>Responden</Title>
<Table striped withTableBorder withRowBorders>
<TableThead>
<TableTr>
@@ -86,10 +82,7 @@ function ListResponden({ search }: ListRespondenProps) {
<Box>
<Stack gap="xs">
<Paper bg={colors['white-1']} p="md" h={{ base: 730, md: 650 }}>
<JudulList
title='List Data Responden'
href='/admin/ppid/ikm-desa-darmasaba/responden/create'
/>
<Title mb={10} order={3}>List Responden</Title>
<Table striped withTableBorder withRowBorders>
<TableThead>
<TableTr>

View File

@@ -4,7 +4,7 @@ import colors from '@/con/colors';
import { Badge, Box, Button, Center, Group, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, ThemeIcon } from '@mantine/core';
import { IconCheck, IconDeviceImacCog, IconSearch, IconX } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useEffect, useMemo, useState } from 'react';
import { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils';
import HeaderSearch from '../../../_com/header';
import JudulList from '../../../_com/judulList';
@@ -39,22 +39,10 @@ function ListPegawaiPPID({ search }: { search: string }) {
} = stateOrganisasi.findMany;
useEffect(() => {
load(page, 10);
}, [page]);
load(page, 10, search);
}, [page, search]);
const filteredData = useMemo(() => {
if (!data) return [];
return data.filter(item => {
const keyword = search.toLowerCase();
return (
item.namaLengkap?.toLowerCase().includes(keyword) ||
item.gelarAkademik?.toLowerCase().includes(keyword) ||
item.telepon?.toLowerCase().includes(keyword) ||
item.posisi?.nama?.toLowerCase().includes(keyword)
);
})
.sort((a, b) => a.posisi?.hierarki - b.posisi?.hierarki);
}, [data, search]);
const filteredData = data || []
// Handle loading state
if (loading || !data) {

View File

@@ -1,7 +1,7 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import colors from '@/con/colors';
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { IconEdit, IconSearch, IconTrash } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
@@ -33,9 +33,17 @@ function ListPosisiOrganisasiPPID({ search }: { search: string }) {
const [modalHapus, setModalHapus] = useState(false)
const [selectedId, setSelectedId] = useState<string | null>(null)
const {
data,
page,
totalPages,
loading,
load,
} = stateOrganisasi.findMany;
useEffect(() => {
stateOrganisasi.findMany.load()
}, [])
load(page, 10, search);
}, [page, search]);
const handleHapus = async () => {
if (selectedId) {
@@ -45,17 +53,9 @@ function ListPosisiOrganisasiPPID({ search }: { search: string }) {
}
}
const filteredData = (stateOrganisasi.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.nama?.toLowerCase().includes(keyword) ||
item.deskripsi?.toLowerCase().includes(keyword) ||
item.hierarki?.toString().toLowerCase().includes(keyword)
);
})
.sort((a, b) => a.hierarki - b.hierarki);
const filteredData = data || []
if (!stateOrganisasi.findMany.data) {
if (loading || !data) {
return (
<Stack py={10}>
<Skeleton h={500} />
@@ -120,6 +120,14 @@ function ListPosisiOrganisasiPPID({ search }: { search: string }) {
</Table>
</Box>
</Paper>
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage)}
total={totalPages}
my={"md"}
/>
</Center>
{/* Modal Hapus */}
<ModalKonfirmasiHapus
opened={modalHapus}

View File

@@ -17,7 +17,7 @@ export const navBar = [
{
id: "Landing_Page_3",
name: "Indeks Kepuasan Masyarakat",
path: "/admin/landing-page/indeks-kepuasan-masyarakat/list-survey"
path: "/admin/landing-page/indeks-kepuasan-masyarakat/grafik-kepuasan-masyarakat"
},
{
id: "Landing_Page_4",

View File

@@ -96,28 +96,36 @@ export default async function detailDataPengangguranCreate(context: Context) {
// Cari bulan sebelumnya
const currentMonthIndex = monthOrder.indexOf(body.month);
const prevMonth = currentMonthIndex > 0 ? monthOrder[currentMonthIndex - 1] : "Des";
const prevYear = currentMonthIndex > 0 ? body.year : body.year - 1;
// Handle year as number for calculations
const currentYear = typeof body.year === 'number' ? body.year : new Date(body.year).getFullYear();
const prevYear = currentMonthIndex > 0 ? currentYear : currentYear - 1;
// Create date objects for Prisma
const currentYearDate = new Date(currentYear, 0, 1);
const prevYearDate = new Date(prevYear, 0, 1);
const prevData = await prisma.detailDataPengangguran.findFirst({
where: {
month: prevMonth,
year: prevYear,
year: prevYearDate,
},
});
let percentageChange: number | null = null;
if (prevData) {
const change = ((body.totalUnemployment - prevData.totalUnemployment) / prevData.totalUnemployment) * 100;
const change = ((Number(body.totalUnemployment) - Number(prevData.totalUnemployment)) / Number(prevData.totalUnemployment)) * 100;
percentageChange = parseFloat(change.toFixed(1));
}
// Create the new record with properly typed values
const created = await prisma.detailDataPengangguran.create({
data: {
month: body.month,
year: body.year,
totalUnemployment: body.totalUnemployment,
educatedUnemployment: body.educatedUnemployment,
uneducatedUnemployment: body.uneducatedUnemployment,
year: currentYearDate,
totalUnemployment: Number(body.totalUnemployment),
educatedUnemployment: Number(body.educatedUnemployment),
uneducatedUnemployment: Number(body.uneducatedUnemployment),
percentageChange,
},
select: {

View File

@@ -15,7 +15,7 @@ export default async function findByMonthYear(context: Context) {
const data = await prisma.detailDataPengangguran.findFirst({
where: {
month,
year: Number(year),
year: new Date(Number(year), 0, 1), // Convert year number to Date object for Prisma
},
});

View File

@@ -81,13 +81,18 @@ export default async function detailDataPengangguranUpdate(context: Context) {
return { success: false, message: "ID tidak ditemukan" };
}
const { month, year, totalUnemployment, educatedUnemployment, uneducatedUnemployment } = context.body as {
const { month, year: yearInput, totalUnemployment, educatedUnemployment, uneducatedUnemployment } = context.body as {
month: string;
year: number;
totalUnemployment: number;
educatedUnemployment: number;
uneducatedUnemployment: number;
year: number | string | Date;
totalUnemployment: number | string;
educatedUnemployment: number | string;
uneducatedUnemployment: number | string;
};
// Normalize year to Date object
const year = typeof yearInput === 'number' ? new Date(yearInput, 0, 1) :
yearInput instanceof Date ? yearInput :
new Date(parseInt(yearInput as string), 0, 1);
const duplicate = await prisma.detailDataPengangguran.findFirst({
where: {
@@ -109,18 +114,19 @@ export default async function detailDataPengangguranUpdate(context: Context) {
const currentMonthIndex = monthOrder.indexOf(month);
const prevMonth = currentMonthIndex > 0 ? monthOrder[currentMonthIndex - 1] : "Des";
const prevYear = currentMonthIndex > 0 ? year : year - 1;
const currentYear = year.getFullYear();
const prevYear = currentMonthIndex > 0 ? currentYear : currentYear - 1;
const prevData = await prisma.detailDataPengangguran.findFirst({
where: {
month: prevMonth,
year: prevYear,
year: new Date(prevYear, 0, 1),
},
});
let percentageChange: number | null = null;
if (prevData) {
const change = ((totalUnemployment - prevData.totalUnemployment) / prevData.totalUnemployment) * 100;
const change = ((Number(totalUnemployment) - Number(prevData.totalUnemployment)) / Number(prevData.totalUnemployment)) * 100;
percentageChange = parseFloat(change.toFixed(1));
}
@@ -128,10 +134,10 @@ export default async function detailDataPengangguranUpdate(context: Context) {
where: { id },
data: {
month,
year,
totalUnemployment,
educatedUnemployment,
uneducatedUnemployment,
year: new Date(year), // Ensure it's a new Date instance
totalUnemployment: Number(totalUnemployment),
educatedUnemployment: Number(educatedUnemployment),
uneducatedUnemployment: Number(uneducatedUnemployment),
percentageChange,
},
});

View File

@@ -1,21 +1,55 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
// /api/berita/findManyPaginated.ts
import prisma from "@/lib/prisma";
import { Context } from "elysia";
export default async function lowonganKerjaFindMany() {
try {
const data = await prisma.lowonganPekerjaan.findMany({
where: { isActive: true },
});
async function lowonganKerjaFindMany(context: Context) {
// Ambil parameter dari query
const page = Number(context.query.page) || 1;
const limit = Number(context.query.limit) || 10;
const search = (context.query.search as string) || '';
const skip = (page - 1) * limit;
return {
success: true,
message: "Success fetch lowongan kerja",
data,
};
} catch (e) {
console.error("Find many error:", e);
return {
success: false,
message: "Failed fetch lowongan kerja",
};
}
}
// Buat where clause
const where: any = { isActive: true };
// Tambahkan pencarian (jika ada)
if (search) {
where.OR = [
{ posisi: { contains: search, mode: 'insensitive' } },
{ namaPerusahaan: { contains: search, mode: 'insensitive' } },
{ lokasi: { contains: search, mode: 'insensitive' } },
];
}
try {
// Ambil data dan total count secara paralel
const [data, total] = await Promise.all([
prisma.lowonganPekerjaan.findMany({
where,
skip,
take: limit,
orderBy: { createdAt: 'desc' },
}),
prisma.lowonganPekerjaan.count({ where }),
]);
return {
success: true,
message: "Berhasil ambil lowongan kerja dengan pagination",
data,
page,
limit,
total,
totalPages: Math.ceil(total / limit),
};
} catch (e) {
console.error("Error di findMany paginated:", e);
return {
success: false,
message: "Gagal mengambil data lowongan kerja",
};
}
}
export default lowonganKerjaFindMany;

View File

@@ -1,26 +1,84 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
// /api/berita/findManyPaginated.ts
import prisma from "@/lib/prisma";
import { Context } from "elysia";
export default async function pasarDesaFindMany() {
const data = await prisma.pasarDesa.findMany({
where: {
isActive: true, // Opsional filter
},
orderBy: {
createdAt: "desc",
},
include: {
image: true,
KategoriToPasar: {
async function pasarDesaFindMany(context: Context) {
// Ambil parameter dari query
const page = Number(context.query.page) || 1;
const limit = Number(context.query.limit) || 10;
const search = (context.query.search as string) || '';
const categoryId = context.query.categoryId as string | undefined;
const skip = (page - 1) * limit;
// Buat where clause
const where: any = { isActive: true };
// Tambahkan filter kategori (jika ada)
if (categoryId) {
where.KategoriToPasar = {
some: {
kategoriId: categoryId
}
};
}
// Tambahkan pencarian (jika ada)
if (search) {
where.AND = where.AND || [];
where.AND.push({
OR: [
{ nama: { contains: search, mode: 'insensitive' } },
{ alamatUsaha: { contains: search, mode: 'insensitive' } },
{
KategoriToPasar: {
some: {
kategori: {
nama: { contains: search, mode: 'insensitive' }
}
}
}
}
]
});
}
try {
// Ambil data dan total count secara paralel
const [data, total] = await Promise.all([
prisma.pasarDesa.findMany({
where,
include: {
kategori: true,
image: true,
KategoriToPasar: {
include: {
kategori: true
}
}
},
},
},
});
skip,
take: limit,
orderBy: { createdAt: 'desc' },
}),
prisma.pasarDesa.count({ where }),
]);
return {
success: true,
message: "Berhasil mengambil semua data pasar desa",
data,
};
return {
success: true,
message: "Berhasil ambil pasar desa dengan pagination",
data,
page,
limit,
total,
totalPages: Math.ceil(total / limit),
};
} catch (e) {
console.error("Error di findMany paginated:", e);
return {
success: false,
message: "Gagal mengambil data pasar desa",
};
}
}
export default pasarDesaFindMany;

View File

@@ -1,15 +1,60 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
// /api/berita/findManyPaginated.ts
import prisma from "@/lib/prisma";
import { Context } from "elysia";
export default async function kategoriProdukFindMany() {
const data = await prisma.kategoriProduk.findMany();
return {
success: true,
data: data.map((item: any) => {
return {
id: item.id,
nama: item.nama,
}
}),
async function kategoriProdukFindMany(context: Context) {
// Ambil parameter dari query
const page = Number(context.query.page) || 1;
const limit = Number(context.query.limit) || 10;
const search2 = (context.query.search as string) || '';
const skip = (page - 1) * limit;
// Buat where clause
const where: any = { isActive: true };
// Tambahkan pencarian (jika ada)
if (search2) {
where.OR = [
{ nama: { contains: search2, mode: 'insensitive' } },
{KategoriToPasar : {
some: {
kategori: {
nama: { contains: search2, mode: 'insensitive' }
}
}
}}
];
}
try {
// Ambil data dan total count secara paralel
const [data, total] = await Promise.all([
prisma.kategoriProduk.findMany({
where,
skip,
take: limit,
orderBy: { createdAt: 'desc' },
}),
prisma.kategoriProduk.count({ where }),
]);
return {
success: true,
message: "Berhasil ambil kategori produk dengan pagination",
data,
page,
limit,
total,
totalPages: Math.ceil(total / limit),
};
}
} catch (e) {
console.error("Error di findMany paginated:", e);
return {
success: false,
message: "Gagal mengambil data kategori produk",
};
}
}
export default kategoriProdukFindMany;

View File

@@ -1,18 +1,53 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import prisma from "@/lib/prisma";
import { Context } from "elysia";
export default async function programKemiskinanFindMany() {
const data = await prisma.programKemiskinan.findMany({
include: {
statistik: true, // ikut sertakan relasinya
},
orderBy: {
createdAt: "desc",
},
});
return {
success: true,
message: "Success get all program layanan",
data,
};
export default async function programKemiskinanFindMany(context: Context) {
const page = Number(context.query.page) || 1;
const limit = Number(context.query.limit) || 10;
const search = (context.query.search as string) || '';
const skip = (page - 1) * limit;
// Buat where clause
const where: any = { isActive: true };
// Tambahkan pencarian (jika ada)
if (search) {
where.OR = [
{ nama: { contains: search, mode: 'insensitive' } },
{ deskripsi: { contains: search, mode: 'insensitive' } },
];
}
try {
// Ambil data dan total count secara paralel
const [data, total] = await Promise.all([
prisma.programKemiskinan.findMany({
where,
include: {
statistik: true,
},
skip,
take: limit,
orderBy: { createdAt: 'desc' },
}),
prisma.programKemiskinan.count({ where }),
]);
return {
success: true,
message: "Berhasil ambil program kemiskinan dengan pagination",
data,
page,
limit,
total,
totalPages: Math.ceil(total / limit),
};
} catch (e) {
console.error("Error di findMany paginated:", e);
return {
success: false,
message: "Gagal mengambil data program kemiskinan",
};
}
}

View File

@@ -1,24 +1,57 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
// /api/berita/findManyPaginated.ts
import prisma from "@/lib/prisma";
import { Context } from "elysia";
export default async function keamananLingkunganFindMany() {
try {
const data = await prisma.keamananLingkungan.findMany({
where: { isActive: true },
include: {
image: true,
},
});
async function keamananLingkunganFindMany(context: Context) {
// Ambil parameter dari query
const page = Number(context.query.page) || 1;
const limit = Number(context.query.limit) || 10;
const search = (context.query.search as string) || '';
const skip = (page - 1) * limit;
return {
success: true,
message: "Success fetch keamanan lingkungan",
data,
};
} catch (e) {
console.error("Find many error:", e);
return {
success: false,
message: "Failed fetch keamanan lingkungan",
};
}
}
// Buat where clause
const where: any = { isActive: true };
// Tambahkan pencarian (jika ada)
if (search) {
where.OR = [
{ name: { contains: search, mode: 'insensitive' } },
{ deskripsi: { contains: search, mode: 'insensitive' } },
];
}
try {
// Ambil data dan total count secara paralel
const [data, total] = await Promise.all([
prisma.keamananLingkungan.findMany({
where,
include: {
image: true,
},
skip,
take: limit,
orderBy: { createdAt: 'desc' },
}),
prisma.keamananLingkungan.count({ where }),
]);
return {
success: true,
message: "Berhasil ambil keamanan lingkungan dengan pagination",
data,
page,
limit,
total,
totalPages: Math.ceil(total / limit),
};
} catch (e) {
console.error("Error di findMany paginated:", e);
return {
success: false,
message: "Gagal mengambil data keamanan lingkungan",
};
}
}
export default keamananLingkunganFindMany;

View File

@@ -0,0 +1,31 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
// find-first.ts
import prisma from "@/lib/prisma";
async function polsekTerdekatFindFirst() {
const where: any = { isActive: true };
try {
const data = await prisma.polsekTerdekat.findFirst({
where,
include: {
layananPolsek: true,
},
orderBy: { createdAt: 'desc' },
});
return {
success: true,
message: "Berhasil ambil polsek terdekat terbaru",
data,
};
} catch (e) {
console.error("Error di findFirst:", e);
return {
success: false,
message: "Gagal ambil polsek terdekat terbaru",
};
}
}
export default polsekTerdekatFindFirst;

View File

@@ -1,24 +1,57 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
// /api/berita/findManyPaginated.ts
import prisma from "@/lib/prisma";
import { Context } from "elysia";
export default async function polsekTerdekatFindMany() {
try {
const data = await prisma.polsekTerdekat.findMany({
where: { isActive: true },
include: {
layananPolsek: true,
},
});
async function polsekTerdekatFindMany(context: Context) {
// Ambil parameter dari query
const page = Number(context.query.page) || 1;
const limit = Number(context.query.limit) || 10;
const search = (context.query.search as string) || '';
const skip = (page - 1) * limit;
return {
success: true,
message: "Success fetch polsek terdekat",
data,
};
} catch (e) {
console.error("Find many error:", e);
return {
success: false,
message: "Failed fetch polsek terdekat",
};
}
}
// Buat where clause
const where: any = { isActive: true };
// Tambahkan pencarian (jika ada)
if (search) {
where.OR = [
{ nama: { contains: search, mode: 'insensitive' } },
{ alamat: { contains: search, mode: 'insensitive' } },
];
}
try {
// Ambil data dan total count secara paralel
const [data, total] = await Promise.all([
prisma.polsekTerdekat.findMany({
where,
include: {
layananPolsek: true,
},
skip,
take: limit,
orderBy: { createdAt: 'desc' },
}),
prisma.polsekTerdekat.count({ where }),
]);
return {
success: true,
message: "Berhasil ambil polsek terdekat dengan pagination",
data,
page,
limit,
total,
totalPages: Math.ceil(total / limit),
};
} catch (e) {
console.error("Error di findMany paginated:", e);
return {
success: false,
message: "Gagal mengambil data polsek terdekat",
};
}
}
export default polsekTerdekatFindMany;

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