From e71c938b2f9d73dc11f63739de85f8b6209b3e02 Mon Sep 17 00:00:00 2001 From: nico Date: Tue, 5 May 2026 16:12:46 +0800 Subject: [PATCH] refactor(ui): sesuaikan UI balita & ibu-hamil dengan pola penghargaan MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Gunakan HeaderSearch + dua-komponen pattern (outer + inner list) - Ganti Loader → Skeleton h={600}, ActionIcon → Button size="xs" variant="light" - Tambah Paper wrapper, layout="fixed" table, desktop/mobile responsive split - Search debounce 1000ms via useDebouncedValue Co-Authored-By: Claude Sonnet 4.6 --- STRUKTUR.md | 706 +++++++++--------- .../(dashboard)/auth/validasi-admin/page.tsx | 2 +- .../kesehatan/posyandu/balita/page.tsx | 402 ++++++---- .../kesehatan/posyandu/ibu-hamil/page.tsx | 353 ++++++--- src/app/admin/layout.tsx | 4 +- src/app/waiting-room/page.tsx | 26 +- 6 files changed, 873 insertions(+), 620 deletions(-) diff --git a/STRUKTUR.md b/STRUKTUR.md index e9ca3b54..6dd70d60 100644 --- a/STRUKTUR.md +++ b/STRUKTUR.md @@ -6,25 +6,25 @@ ### Tech Stack -| Kategori | Teknologi | -|----------|-----------| -| **Framework** | Next.js 15 (App Router) | -| **Language** | TypeScript (strict mode) | -| **Runtime** | Bun | -| **Backend API** | Elysia.js (high-performance HTTP server) | -| **Database** | PostgreSQL | -| **ORM** | Prisma 6.3.1 | -| **UI Framework** | Mantine UI v7-v8 | -| **State Management** | Jotai + Valtio + SWR | -| **Authentication** | iron-session + JWT (@elysiajs/jwt) | -| **File Storage** | Seafile (self-hosted) | -| **Text Editor** | Tiptap (Rich text editor) | -| **Charts** | Recharts + Chart.js | -| **Maps** | Leaflet + react-leaflet | -| **Testing** | Vitest (unit) + Playwright (E2E) | -| **Styling** | Mantine + PostCSS + Framer Motion | -| **Deployment** | Docker + GHCR + Portainer + GitHub Actions | -| **Version** | 0.1.11 | +| Kategori | Teknologi | +| -------------------- | ------------------------------------------ | +| **Framework** | Next.js 15 (App Router) | +| **Language** | TypeScript (strict mode) | +| **Runtime** | Bun | +| **Backend API** | Elysia.js (high-performance HTTP server) | +| **Database** | PostgreSQL | +| **ORM** | Prisma 6.3.1 | +| **UI Framework** | Mantine UI v7-v8 | +| **State Management** | Jotai + Valtio + SWR | +| **Authentication** | iron-session + JWT (@elysiajs/jwt) | +| **File Storage** | Seafile (self-hosted) | +| **Text Editor** | Tiptap (Rich text editor) | +| **Charts** | Recharts + Chart.js | +| **Maps** | Leaflet + react-leaflet | +| **Testing** | Vitest (unit) + Playwright (E2E) | +| **Styling** | Mantine + PostCSS + Framer Motion | +| **Deployment** | Docker + GHCR + Portainer + GitHub Actions | +| **Version** | 0.1.11 | --- @@ -195,137 +195,148 @@ Browser ## 4. Modul Domain ### A. PPID (Pejabat Pengelola Informasi dan Dokumentasi) + **Lokasi**: `src/app/admin/(dashboard)/ppid/` dan `src/app/darmasaba/(pages)/ppid/` -| Sub-modul | Deskripsi | -|-----------|-----------| -| Profil PPID | Profil pejabat pengelola informasi | -| Struktur PPID | Struktur organisasi PPID dengan hierarki | -| Visi & Misi PPID | Visi dan misi PPID desa | -| Daftar Informasi Publik | Katalog informasi publik yang tersedia | -| Dasar Hukum | Regulasi dan dasar hukum PPID | +| Sub-modul | Deskripsi | +| --------------------------- | ---------------------------------------------- | +| Profil PPID | Profil pejabat pengelola informasi | +| Struktur PPID | Struktur organisasi PPID dengan hierarki | +| Visi & Misi PPID | Visi dan misi PPID desa | +| Daftar Informasi Publik | Katalog informasi publik yang tersedia | +| Dasar Hukum | Regulasi dan dasar hukum PPID | | Permohonan Informasi Publik | Form permohonan informasi (NIK, kontak, jenis) | -| Permohonan Keberatan | Formulir keberatan informasi | -| Indeks Kepuasan Masyarakat | Survey kepuasan dengan grafik demografis | +| Permohonan Keberatan | Formulir keberatan informasi | +| Indeks Kepuasan Masyarakat | Survey kepuasan dengan grafik demografis | ### B. Desa (Landing Page & Umum) + **Lokasi**: `src/app/admin/(dashboard)/desa/` dan `src/app/darmasaba/(pages)/desa/` -| Sub-modul | Deskripsi | -|-----------|-----------| -| Profil Desa | Sejarah, visi-misi, lambang, maskot | -| Profil Perbekel | Biodata, pengalaman, program unggulan perbekel | -| Perbekel dari Masa ke Masa | Historis perbekel per periode | -| Berita | Artikel berita dengan kategori & multi-image | -| Gallery | Foto dan video galeri | -| Pengumuman | Pengumuman desa dengan kategori | -| Potensi Desa | Potensi desa dengan kategori | -| Layanan Desa | Surat keterangan, ajukan permohonan | -| Penghargaan | Prestasi dan penghargaan desa | -| Desa Anti Korupsi | Transparansi anti-korupsi | -| SDGs Desa | Sustainable Development Goals desa | -| APBDes | Anggaran desa dengan hierarki item & realisasi | -| Prestasi Desa | Katalog prestasi | +| Sub-modul | Deskripsi | +| -------------------------- | ---------------------------------------------- | +| Profil Desa | Sejarah, visi-misi, lambang, maskot | +| Profil Perbekel | Biodata, pengalaman, program unggulan perbekel | +| Perbekel dari Masa ke Masa | Historis perbekel per periode | +| Berita | Artikel berita dengan kategori & multi-image | +| Gallery | Foto dan video galeri | +| Pengumuman | Pengumuman desa dengan kategori | +| Potensi Desa | Potensi desa dengan kategori | +| Layanan Desa | Surat keterangan, ajukan permohonan | +| Penghargaan | Prestasi dan penghargaan desa | +| Desa Anti Korupsi | Transparansi anti-korupsi | +| SDGs Desa | Sustainable Development Goals desa | +| APBDes | Anggaran desa dengan hierarki item & realisasi | +| Prestasi Desa | Katalog prestasi | ### C. Kesehatan + **Lokasi**: `src/app/admin/(dashboard)/kesehatan/` dan `src/app/darmasaba/(pages)/kesehatan/` -| Sub-modul | Deskripsi | -|-----------|-----------| -| Fasilitas Kesehatan | Info rumah sakit/klinik (jam, dokter, tarif) | -| Puskesmas | Data puskesmas dengan jam operasional & kontak | -| Posyandu | Jadwal dan informasi posyandu | -| Program Kesehatan | Program-program kesehatan desa | -| Penanganan Darurat | Prosedur penanganan darurat | -| Kontak Darurat | Kontak emergency dengan WhatsApp | -| Info Wabah Penyakit | Informasi wabah penyakit | -| Artikel Kesehatan | Artikel kesehatan lengkap | -| Data Kesehatan Warga | Statistik kesehatan warga | -| Kelahiran & Kematian | Data vital statistik | -| Grafik Kepuasan | Grafik kepuasan layanan kesehatan | +| Sub-modul | Deskripsi | +| -------------------- | ---------------------------------------------- | +| Fasilitas Kesehatan | Info rumah sakit/klinik (jam, dokter, tarif) | +| Puskesmas | Data puskesmas dengan jam operasional & kontak | +| Posyandu | Jadwal dan informasi posyandu | +| Program Kesehatan | Program-program kesehatan desa | +| Penanganan Darurat | Prosedur penanganan darurat | +| Kontak Darurat | Kontak emergency dengan WhatsApp | +| Info Wabah Penyakit | Informasi wabah penyakit | +| Artikel Kesehatan | Artikel kesehatan lengkap | +| Data Kesehatan Warga | Statistik kesehatan warga | +| Kelahiran & Kematian | Data vital statistik | +| Grafik Kepuasan | Grafik kepuasan layanan kesehatan | ### D. Ekonomi + **Lokasi**: `src/app/admin/(dashboard)/ekonomi/` dan `src/app/darmasaba/(pages)/ekonomi/` -| Sub-modul | Deskripsi | -|-----------|-----------| -| Pasar Desa | Katalog pasar desa dengan produk & rating | -| Struktur BUMDes | Organisasi BUMDes dengan pengurus | -| APBDes (PADesa) | Pendapatan Asli Desa | -| Program Kemiskinan | Program dan statistik kemiskinan | -| Sektor Unggulan | Sektor ekonomi unggulan desa | -| Lowongan Kerja Lokal | Info lowongan pekerjaan | -| Demografi Pekerjaan | Distribusi pekerjaan penduduk | -| Jumlah Pengangguran | Statistik pengangguran | +| Sub-modul | Deskripsi | +| ------------------------------ | ------------------------------------------ | +| Pasar Desa | Katalog pasar desa dengan produk & rating | +| Struktur BUMDes | Organisasi BUMDes dengan pengurus | +| APBDes (PADesa) | Pendapatan Asli Desa | +| Program Kemiskinan | Program dan statistik kemiskinan | +| Sektor Unggulan | Sektor ekonomi unggulan desa | +| Lowongan Kerja Lokal | Info lowongan pekerjaan | +| Demografi Pekerjaan | Distribusi pekerjaan penduduk | +| Jumlah Pengangguran | Statistik pengangguran | | Penduduk Usia Kerja Menganggur | Analisis pengangguran by usia & pendidikan | -| Jumlah Penduduk Miskin | Tren kemiskinan tahunan | +| Jumlah Penduduk Miskin | Tren kemiskinan tahunan | ### E. Kependudukan + **Lokasi**: `src/app/admin/(dashboard)/kependudukan/` dan `src/app/darmasaba/(pages)/kependudukan/` -| Sub-modul | Deskripsi | -|-----------|-----------| -| Data Banjar | Data penduduk per banjar | -| Distribusi Agama | Statistik agama penduduk | -| Distribusi Umur | Piramida umur penduduk | -| Migrasi Penduduk | Data migrasi masuk/keluar | +| Sub-modul | Deskripsi | +| ----------------- | -------------------------------------- | +| Data Banjar | Data penduduk per banjar | +| Distribusi Agama | Statistik agama penduduk | +| Distribusi Umur | Piramida umur penduduk | +| Migrasi Penduduk | Data migrasi masuk/keluar | | Dinamika Penduduk | Kelahiran, kematian, migrasi per tahun | ### F. Pendidikan + **Lokasi**: `src/app/admin/(dashboard)/pendidikan/` dan `src/app/darmasaba/(pages)/pendidikan/` -| Sub-modul | Deskripsi | -|-----------|-----------| -| Info Sekolah & PAUD | Data sekolah per jenjang (TK, SD, SMP, SMA) | -| Beasiswa Desa | Program beasiswa & pendaftar | -| Program Pendidikan Anak | Program pendidikan anak | -| Bimbingan Belajar | Informasi bimbingan belajar | -| Pendidikan Non Formal | Tempat & program non-formal | -| Perpustakaan Digital | Katalog buku & peminjaman | -| Data Pendidikan | Statistik pendidikan | +| Sub-modul | Deskripsi | +| ----------------------- | ------------------------------------------- | +| Info Sekolah & PAUD | Data sekolah per jenjang (TK, SD, SMP, SMA) | +| Beasiswa Desa | Program beasiswa & pendaftar | +| Program Pendidikan Anak | Program pendidikan anak | +| Bimbingan Belajar | Informasi bimbingan belajar | +| Pendidikan Non Formal | Tempat & program non-formal | +| Perpustakaan Digital | Katalog buku & peminjaman | +| Data Pendidikan | Statistik pendidikan | ### G. Keamanan + **Lokasi**: `src/app/admin/(dashboard)/keamanan/` dan `src/app/darmasaba/(pages)/keamanan/` -| Sub-modul | Deskripsi | -|-----------|-----------| -| Keamanan Lingkungan (Pecalang/Patwal) | Sistem keamanan tradisional Bali | -| Polsek Terdekat | Data polsek dengan layanan & map | -| Kontak Darurat | Kontak darurat keamanan | -| Pencegahan Kriminalitas | Info pencegahan kriminal | -| Laporan Publik | Laporan masyarakat dengan tracking status | -| Tips Keamanan | Tips dan panduan keamanan | +| Sub-modul | Deskripsi | +| ------------------------------------- | ----------------------------------------- | +| Keamanan Lingkungan (Pecalang/Patwal) | Sistem keamanan tradisional Bali | +| Polsek Terdekat | Data polsek dengan layanan & map | +| Kontak Darurat | Kontak darurat keamanan | +| Pencegahan Kriminalitas | Info pencegahan kriminal | +| Laporan Publik | Laporan masyarakat dengan tracking status | +| Tips Keamanan | Tips dan panduan keamanan | ### H. Lingkungan + **Lokasi**: `src/app/admin/(dashboard)/lingkungan/` dan `src/app/darmasaba/(pages)/lingkungan/` -| Sub-modul | Deskripsi | -|-----------|-----------| -| Pengelolaan Sampah | Bank sampah & pengelolaan | -| Program Penghijauan | Program penghijauan desa | -| Data Lingkungan | Data lingkungan desa | -| Gotong Royong | Kegiatan gotong royong | -| Edukasi Lingkungan | Edukasi lingkungan hidup | +| Sub-modul | Deskripsi | +| -------------------- | --------------------------------- | +| Pengelolaan Sampah | Bank sampah & pengelolaan | +| Program Penghijauan | Program penghijauan desa | +| Data Lingkungan | Data lingkungan desa | +| Gotong Royong | Kegiatan gotong royong | +| Edukasi Lingkungan | Edukasi lingkungan hidup | | Konservasi Adat Bali | Tri Hita Karana & konservasi adat | ### I. Inovasi + **Lokasi**: `src/app/admin/(dashboard)/inovasi/` dan `src/app/darmasaba/(pages)/inovasi/` -| Sub-modul | Deskripsi | -|-----------|-----------| -| Desa Digital (Smart Village) | Transformasi digital desa | -| Program Kreatif Desa | Program kreatif & inovatif | -| Kolaborasi Inovasi | Kolaborasi dengan mitra | -| Info Teknologi Tepat Guna | Info teknologi untuk desa | -| Ajukan Ide Inovatif | Form pengajuan ide dari warga | -| Layanan Online Desa | Layanan administrasi online | +| Sub-modul | Deskripsi | +| ---------------------------- | ----------------------------- | +| Desa Digital (Smart Village) | Transformasi digital desa | +| Program Kreatif Desa | Program kreatif & inovatif | +| Kolaborasi Inovasi | Kolaborasi dengan mitra | +| Info Teknologi Tepat Guna | Info teknologi untuk desa | +| Ajukan Ide Inovatif | Form pengajuan ide dari warga | +| Layanan Online Desa | Layanan administrasi online | ### J. Musik Desa + **Lokasi**: `src/app/admin/(dashboard)/musik/` dan `src/app/darmasaba/(pages)/musik/` Model `MusikDesa` dengan audio file, cover image, genre, dan durasi. Dilengkapi dengan `FixedPlayerBar` di layout publik. ### K. User & Role (Admin) + **Lokasi**: `src/app/admin/(dashboard)/user&role/` - **Role-based Access Control**: Role dengan permission JSON @@ -341,124 +352,124 @@ Schema terdiri dari **2413 baris** dengan **100+ model** dan **berbagai enum**. ### Core Models -| Model | Keterangan | -|-------|-----------| -| `FileStorage` | Central file storage untuk semua uploaded files | -| `AppMenu` / `AppMenuChild` | Menu navigasi aplikasi | -| `User` / `Role` / `UserSession` / `UserMenuAccess` | Sistem autentikasi & otorisasi | -| `KodeOtp` | OTP codes untuk login | +| Model | Keterangan | +| -------------------------------------------------- | ----------------------------------------------- | +| `FileStorage` | Central file storage untuk semua uploaded files | +| `AppMenu` / `AppMenuChild` | Menu navigasi aplikasi | +| `User` / `Role` / `UserSession` / `UserMenuAccess` | Sistem autentikasi & otorisasi | +| `KodeOtp` | OTP codes untuk login | ### Landing Page & Desa -| Model | Keterangan | -|-------|-----------| -| `PejabatDesa` | Pejabat desa dengan foto | -| `ProfilPerbekel` | Profil perbekel (biodata, pengalaman, program) | -| `PerbekelDariMasaKeMasa` | Historis perbekel | -| `Berita` / `KategoriBerita` | Berita desa | -| `PotensiDesa` / `KategoriPotensi` | Potensi desa | -| `Pengumuman` / `CategoryPengumuman` | Pengumuman | -| `GalleryFoto` / `GalleryVideo` | Gallery media | -| `Penghargaan` | Penghargaan desa | -| `APBDes` / `APBDesItem` / `RealisasiItem` | Anggaran dengan realisasi | -| `DesaAntiKorupsi` / `KategoriDesaAntiKorupsi` | Transparansi | -| `SdgsDesa` | SDGs desa | -| `PrestasiDesa` / `KategoriPrestasiDesa` | Prestasi | -| `MusikDesa` | Musik desa | +| Model | Keterangan | +| --------------------------------------------- | ---------------------------------------------- | +| `PejabatDesa` | Pejabat desa dengan foto | +| `ProfilPerbekel` | Profil perbekel (biodata, pengalaman, program) | +| `PerbekelDariMasaKeMasa` | Historis perbekel | +| `Berita` / `KategoriBerita` | Berita desa | +| `PotensiDesa` / `KategoriPotensi` | Potensi desa | +| `Pengumuman` / `CategoryPengumuman` | Pengumuman | +| `GalleryFoto` / `GalleryVideo` | Gallery media | +| `Penghargaan` | Penghargaan desa | +| `APBDes` / `APBDesItem` / `RealisasiItem` | Anggaran dengan realisasi | +| `DesaAntiKorupsi` / `KategoriDesaAntiKorupsi` | Transparansi | +| `SdgsDesa` | SDGs desa | +| `PrestasiDesa` / `KategoriPrestasiDesa` | Prestasi | +| `MusikDesa` | Musik desa | ### PPID -| Model | Keterangan | -|-------|-----------| -| `StrukturPPID` / `PosisiOrganisasiPPID` / `PegawaiPPID` | Struktur organisasi | -| `VisiMisiPPID` | Visi misi | -| `ProfilePPID` | Profil pejabat | -| `DasarHukumPPID` | Regulasi | -| `DaftarInformasiPublik` | Katalog informasi | -| `PermohonanInformasiPublik` | Permohonan + lookup tables | -| `FormulirPermohonanKeberatan` | Keberatan | -| `IndeksKepuasanMasyarakat` + grafik breakdown | Survey kepuasan | +| Model | Keterangan | +| ------------------------------------------------------- | -------------------------- | +| `StrukturPPID` / `PosisiOrganisasiPPID` / `PegawaiPPID` | Struktur organisasi | +| `VisiMisiPPID` | Visi misi | +| `ProfilePPID` | Profil pejabat | +| `DasarHukumPPID` | Regulasi | +| `DaftarInformasiPublik` | Katalog informasi | +| `PermohonanInformasiPublik` | Permohonan + lookup tables | +| `FormulirPermohonanKeberatan` | Keberatan | +| `IndeksKepuasanMasyarakat` + grafik breakdown | Survey kepuasan | ### Kesehatan -| Model | Keterangan | -|-------|-----------| -| `FasilitasKesehatan` | Fasilitas lengkap (dokter, tarif, prosedur) | -| `Puskesmas` / `JamOperasional` / `KontakPuskesmas` | Puskesmas | -| `Posyandu` | Pos pelayanan terpadu | -| `ProgramKesehatan` | Program kesehatan | -| `ArtikelKesehatan` | Artikel lengkap (gejala, pencegahan, P3K, dll) | -| `PenangananDarurat` / `KontakDarurat` | Darurat | -| `InfoWabahPenyakit` | Wabah | -| `DataKematian_Kelahiran` / `Kelahiran` / `Kematian` | Vital statistik | -| `GrafikKepuasan` | Kepuasan | +| Model | Keterangan | +| --------------------------------------------------- | ---------------------------------------------- | +| `FasilitasKesehatan` | Fasilitas lengkap (dokter, tarif, prosedur) | +| `Puskesmas` / `JamOperasional` / `KontakPuskesmas` | Puskesmas | +| `Posyandu` | Pos pelayanan terpadu | +| `ProgramKesehatan` | Program kesehatan | +| `ArtikelKesehatan` | Artikel lengkap (gejala, pencegahan, P3K, dll) | +| `PenangananDarurat` / `KontakDarurat` | Darurat | +| `InfoWabahPenyakit` | Wabah | +| `DataKematian_Kelahiran` / `Kelahiran` / `Kematian` | Vital statistik | +| `GrafikKepuasan` | Kepuasan | ### Ekonomi -| Model | Keterangan | -|-------|-----------| -| `PasarDesa` / `KategoriProduk` / `KategoriToPasar` | Pasar desa | -| `StrukturBumDes` / `PosisiOrganisasiBumDes` / `PegawaiBumDes` | BUMDes | -| `ProgramKemiskinan` / `StatistikKemiskinan` | Kemiskinan | -| `SektorUnggulanDesa` | Sektor unggulan | -| `LowonganPekerjaan` | Lowongan | -| `DataDemografiPekerjaan` | Demografi pekerjaan | -| `DetailDataPengangguran` | Pengangguran | -| `GrafikJumlahPendudukMiskin` | Tren kemiskinan | +| Model | Keterangan | +| ------------------------------------------------------------- | ------------------- | +| `PasarDesa` / `KategoriProduk` / `KategoriToPasar` | Pasar desa | +| `StrukturBumDes` / `PosisiOrganisasiBumDes` / `PegawaiBumDes` | BUMDes | +| `ProgramKemiskinan` / `StatistikKemiskinan` | Kemiskinan | +| `SektorUnggulanDesa` | Sektor unggulan | +| `LowonganPekerjaan` | Lowongan | +| `DataDemografiPekerjaan` | Demografi pekerjaan | +| `DetailDataPengangguran` | Pengangguran | +| `GrafikJumlahPendudukMiskin` | Tren kemiskinan | ### Kependudukan -| Model | Keterangan | -|-------|-----------| -| `DataBanjar` | Data per banjar | -| `DistribusiAgama` | Distribusi agama | -| `DistribusiUmur` | Distribusi umur | -| `MigrasiPenduduk` | Migrasi (MASUK/KELUAR) | -| `DinamikaPenduduk` | Dinamika tahunan | +| Model | Keterangan | +| ------------------ | ---------------------- | +| `DataBanjar` | Data per banjar | +| `DistribusiAgama` | Distribusi agama | +| `DistribusiUmur` | Distribusi umur | +| `MigrasiPenduduk` | Migrasi (MASUK/KELUAR) | +| `DinamikaPenduduk` | Dinamika tahunan | ### Pendidikan -| Model | Keterangan | -|-------|-----------| -| `JenjangPendidikan` / `Lembaga` / `Siswa` / `Pengajar` | Data sekolah | -| `BeasiswaPendaftar` | Beasiswa (dengan enum lengkap) | -| `DataPerpustakaan` / `KategoriBuku` / `PeminjamanBuku` | Perpustakaan | -| `DataPendidikan` | Statistik | +| Model | Keterangan | +| ------------------------------------------------------ | ------------------------------ | +| `JenjangPendidikan` / `Lembaga` / `Siswa` / `Pengajar` | Data sekolah | +| `BeasiswaPendaftar` | Beasiswa (dengan enum lengkap) | +| `DataPerpustakaan` / `KategoriBuku` / `PeminjamanBuku` | Perpustakaan | +| `DataPendidikan` | Statistik | ### Keamanan -| Model | Keterangan | -|-------|-----------| -| `KeamananLingkungan` | Keamanan lingkungan | -| `PolsekTerdekat` / `LayananPolsek` / `LayananToPolsek` | Polsek | -| `KontakDaruratKeamanan` / `KontakItem` | Kontak darurat | -| `PencegahanKriminalitas` | Pencegahan | -| `LaporanPublik` / `PenangananLaporanPublik` (enum StatusLaporan) | Laporan | -| `Pelapor` | Pelapor | -| `MenuTipsKeamanan` | Tips | +| Model | Keterangan | +| ---------------------------------------------------------------- | ------------------- | +| `KeamananLingkungan` | Keamanan lingkungan | +| `PolsekTerdekat` / `LayananPolsek` / `LayananToPolsek` | Polsek | +| `KontakDaruratKeamanan` / `KontakItem` | Kontak darurat | +| `PencegahanKriminalitas` | Pencegahan | +| `LaporanPublik` / `PenangananLaporanPublik` (enum StatusLaporan) | Laporan | +| `Pelapor` | Pelapor | +| `MenuTipsKeamanan` | Tips | ### Lingkungan -| Model | Keterangan | -|-------|-----------| -| `PengelolaanSampah` | Pengelolaan sampah | -| `KeteranganBankSampahTerdekat` | Bank sampah | -| `ProgramPenghijauan` | Penghijauan | -| `DataLingkunganDesa` | Data lingkungan | -| `KegiatanDesa` / `KategoriKegiatan` | Gotong royong | -| `FilosofiTriHita` / `BentukKonservasiBerdasarkanAdat` | Konservasi Bali | +| Model | Keterangan | +| ----------------------------------------------------- | ------------------ | +| `PengelolaanSampah` | Pengelolaan sampah | +| `KeteranganBankSampahTerdekat` | Bank sampah | +| `ProgramPenghijauan` | Penghijauan | +| `DataLingkunganDesa` | Data lingkungan | +| `KegiatanDesa` / `KategoriKegiatan` | Gotong royong | +| `FilosofiTriHita` / `BentukKonservasiBerdasarkanAdat` | Konservasi Bali | ### Inovasi -| Model | Keterangan | -|-------|-----------| -| `DesaDigital` | Smart village | -| `ProgramKreatif` | Program kreatif | -| `KolaborasiInovasi` / `MitraKolaborasi` | Kolaborasi | -| `InfoTekno` | Teknologi tepat guna | -| `AjukanIdeInovatif` | Ide dari warga | -| `AdministrasiOnline` / `JenisLayanan` | Layanan online | -| `PengaduanMasyarakat` / `JenisPengaduan` | Pengaduan | +| Model | Keterangan | +| ---------------------------------------- | -------------------- | +| `DesaDigital` | Smart village | +| `ProgramKreatif` | Program kreatif | +| `KolaborasiInovasi` / `MitraKolaborasi` | Kolaborasi | +| `InfoTekno` | Teknologi tepat guna | +| `AjukanIdeInovatif` | Ide dari warga | +| `AdministrasiOnline` / `JenisLayanan` | Layanan online | +| `PengaduanMasyarakat` / `JenisPengaduan` | Pengaduan | --- @@ -466,43 +477,43 @@ Schema terdiri dari **2413 baris** dengan **100+ model** dan **berbagai enum**. Semua API ditangani oleh **Elysia.js** di `src/app/api/[[...slugs]]/route.ts`: -| Endpoint Group | Prefix | Deskripsi | -|---------------|--------|-----------| -| **File Storage** | `/api/file-storage` | CRUD file storage | -| **Landing Page** | `/api/landing-page` | Profil, prestasi, anti-korupsi, SDGs, APBDes | -| **Desa** | `/api/desa` | Berita, gallery, potensi, pengumuman, layanan | -| **PPID** | `/api/ppid` | Semua endpoint PPID | -| **Kesehatan** | `/api/kesehatan` | Fasilitas, puskesmas, posyandu, artikel, wabah | -| **Ekonomi** | `/api/ekonomi` | Pasar desa, BUMDes, APBDes, pengangguran | -| **Keamanan** | `/api/keamanan` | Keamanan, polsek, laporan, kriminalitas | -| **Lingkungan** | `/api/lingkungan` | Sampah, penghijauan, gotong royong | -| **Pendidikan** | `/api/pendidikan` | Sekolah, beasiswa, perpustakaan | -| **Kependudukan** | `/api/kependudukan` | Banjar, agama, umur, migrasi | -| **Inovasi** | `/api/inovasi` | Desa digital, kolaborasi, pengaduan | -| **User** | `/api/admin/user` | CRUD user | -| **Role** | `/api/admin/role` | CRUD role | -| **Search** | `/api/search` | Global search | -| **Utils** | `/api/utils/version` | Version info | +| Endpoint Group | Prefix | Deskripsi | +| ---------------- | -------------------- | ---------------------------------------------- | +| **File Storage** | `/api/file-storage` | CRUD file storage | +| **Landing Page** | `/api/landing-page` | Profil, prestasi, anti-korupsi, SDGs, APBDes | +| **Desa** | `/api/desa` | Berita, gallery, potensi, pengumuman, layanan | +| **PPID** | `/api/ppid` | Semua endpoint PPID | +| **Kesehatan** | `/api/kesehatan` | Fasilitas, puskesmas, posyandu, artikel, wabah | +| **Ekonomi** | `/api/ekonomi` | Pasar desa, BUMDes, APBDes, pengangguran | +| **Keamanan** | `/api/keamanan` | Keamanan, polsek, laporan, kriminalitas | +| **Lingkungan** | `/api/lingkungan` | Sampah, penghijauan, gotong royong | +| **Pendidikan** | `/api/pendidikan` | Sekolah, beasiswa, perpustakaan | +| **Kependudukan** | `/api/kependudukan` | Banjar, agama, umur, migrasi | +| **Inovasi** | `/api/inovasi` | Desa digital, kolaborasi, pengaduan | +| **User** | `/api/admin/user` | CRUD user | +| **Role** | `/api/admin/role` | CRUD role | +| **Search** | `/api/search` | Global search | +| **Utils** | `/api/utils/version` | Version info | ### Utility Endpoints -| Endpoint | Method | Deskripsi | -|----------|--------|-----------| -| `/api/img/:name` | GET | Serve image dengan resize | -| `/api/img/:name` | DELETE | Delete image | -| `/api/imgs` | GET | List images dengan pagination | -| `/api/upl-img` | POST | Upload multiple images | -| `/api/upl-img-single` | POST | Upload single image | -| `/api/upl-csv` | POST | Upload CSV multiple | -| `/api/upl-csv-single` | POST | Upload single CSV | +| Endpoint | Method | Deskripsi | +| --------------------- | ------ | ----------------------------- | +| `/api/img/:name` | GET | Serve image dengan resize | +| `/api/img/:name` | DELETE | Delete image | +| `/api/imgs` | GET | List images dengan pagination | +| `/api/upl-img` | POST | Upload multiple images | +| `/api/upl-img-single` | POST | Upload single image | +| `/api/upl-csv` | POST | Upload CSV multiple | +| `/api/upl-csv-single` | POST | Upload single CSV | ### Auth Endpoints -| Endpoint | Method | Deskripsi | -|----------|--------|-----------| -| `/api/auth/login` | POST | Login dengan OTP | -| `/api/auth/logout` | POST | Logout | -| `/api/auth/me` | GET | Get current user | +| Endpoint | Method | Deskripsi | +| ------------------ | ------ | ---------------- | +| `/api/auth/login` | POST | Login dengan OTP | +| `/api/auth/logout` | POST | Logout | +| `/api/auth/me` | GET | Get current user | **Swagger Documentation**: Tersedia di `/api/docs` @@ -514,22 +525,23 @@ Admin dashboard menggunakan **Mantine AppShell** dengan sidebar navigasi dinamis ### Route Group: `/admin` -| Section | Path | Deskripsi | -|---------|------|-----------| -| **Landing Page** | `/admin/landing-page/` | Profil desa, prestasi, anti-korupsi, SDGs, media sosial | -| **Desa** | `/admin/desa/` | Berita, gallery, layanan, penghargaan, pengumuman, potensi, profil | -| **PPID** | `/admin/ppid/` | 8 sub-modul PPID lengkap | -| **Kesehatan** | `/admin/kesehatan/` | 8 sub-modul kesehatan | -| **Ekonomi** | `/admin/ekonomi/` | 10 sub-modul ekonomi | -| **Kependudukan** | `/admin/kependudukan/` | 4 sub-modul kependudukan | -| **Pendidikan** | `/admin/pendidikan/` | 7 sub-modul pendidikan | -| **Keamanan** | `/admin/keamanan/` | 6 sub-modul keamanan | -| **Lingkungan** | `/admin/lingkungan/` | 6 sub-modul lingkungan | -| **Inovasi** | `/admin/inovasi/` | 6 sub-modul inovasi | -| **Musik** | `/admin/musik/` | Manajemen musik desa | -| **User & Role** | `/admin/user&role/` | Manajemen user, role, menu access | +| Section | Path | Deskripsi | +| ---------------- | ---------------------- | ------------------------------------------------------------------ | +| **Landing Page** | `/admin/landing-page/` | Profil desa, prestasi, anti-korupsi, SDGs, media sosial | +| **Desa** | `/admin/desa/` | Berita, gallery, layanan, penghargaan, pengumuman, potensi, profil | +| **PPID** | `/admin/ppid/` | 8 sub-modul PPID lengkap | +| **Kesehatan** | `/admin/kesehatan/` | 8 sub-modul kesehatan | +| **Ekonomi** | `/admin/ekonomi/` | 10 sub-modul ekonomi | +| **Kependudukan** | `/admin/kependudukan/` | 4 sub-modul kependudukan | +| **Pendidikan** | `/admin/pendidikan/` | 7 sub-modul pendidikan | +| **Keamanan** | `/admin/keamanan/` | 6 sub-modul keamanan | +| **Lingkungan** | `/admin/lingkungan/` | 6 sub-modul lingkungan | +| **Inovasi** | `/admin/inovasi/` | 6 sub-modul inovasi | +| **Musik** | `/admin/musik/` | Manajemen musik desa | +| **User & Role** | `/admin/user&role/` | Manajemen user, role, menu access | ### Fitur Admin: + - **Role-based Dynamic Navbar**: Navbar berubah berdasarkan roleId user - **Dark Mode Toggle**: Tema gelap/terang - **OTP Login**: Login dengan nomor telepon + kode OTP @@ -539,11 +551,12 @@ Admin dashboard menggunakan **Mantine AppShell** dengan sidebar navigasi dinamis - **Rich Text Editor**: Tiptap untuk konten HTML ### Role-Based Redirect: -| roleId | Role | Default Redirect | -|--------|------|-----------------| -| 0, 1, 2 | Super Admin / Admin Desa | `/admin/landing-page/profil/program-inovasi` | -| 3 | Admin Kesehatan | `/admin/kesehatan/posyandu` | -| 4 | Admin Pendidikan | `/admin/pendidikan/info-sekolah/jenjang-pendidikan` | + +| roleId | Role | Default Redirect | +| ------- | ------------------------ | --------------------------------------------------- | +| 0, 1, 2 | Super Admin / Admin Desa | `/admin/landing-page/profil/program-inovasi` | +| 3 | Admin Kesehatan | `/admin/kesehatan/posyandu/list-posyandu` | +| 4 | Admin Pendidikan | `/admin/pendidikan/info-sekolah/jenjang-pendidikan` | --- @@ -553,22 +566,23 @@ Public website di `/darmasaba/` dengan layout yang mencakup **Navbar**, **Footer ### Route Group: `/darmasaba` -| Section | Path | Deskripsi | -|---------|------|-----------| -| **Home** | `/darmasaba` | Landing page utama | -| **Desa** | `/darmasaba/desa` | Profil, berita, gallery, layanan, pengumuman, potensi | -| **PPID** | `/darmasaba/ppid` | 7 sub-halaman PPID publik | -| **Kesehatan** | `/darmasaba/kesehatan` | Info kesehatan publik | -| **Ekonomi** | `/darmasaba/ekonomi` | Info ekonomi desa | -| **Kependudukan** | `/darmasaba/kependudukan` | Data kependudukan | -| **Pendidikan** | `/darmasaba/pendidikan` | Info pendidikan | -| **Keamanan** | `/darmasaba/keamanan` | Info keamanan | -| **Lingkungan** | `/darmasaba/lingkungan` | Info lingkungan | -| **Inovasi** | `/darmasaba/inovasi` | Info inovasi | -| **Musik** | `/darmasaba/musik` | Musik desa | -| **Module** | `/darmasaba/module/*` | Link ke modul eksternal (DAVES, MANGAN, Bicara-Darma, BARES, dll) | +| Section | Path | Deskripsi | +| ---------------- | ------------------------- | ----------------------------------------------------------------- | +| **Home** | `/darmasaba` | Landing page utama | +| **Desa** | `/darmasaba/desa` | Profil, berita, gallery, layanan, pengumuman, potensi | +| **PPID** | `/darmasaba/ppid` | 7 sub-halaman PPID publik | +| **Kesehatan** | `/darmasaba/kesehatan` | Info kesehatan publik | +| **Ekonomi** | `/darmasaba/ekonomi` | Info ekonomi desa | +| **Kependudukan** | `/darmasaba/kependudukan` | Data kependudukan | +| **Pendidikan** | `/darmasaba/pendidikan` | Info pendidikan | +| **Keamanan** | `/darmasaba/keamanan` | Info keamanan | +| **Lingkungan** | `/darmasaba/lingkungan` | Info lingkungan | +| **Inovasi** | `/darmasaba/inovasi` | Info inovasi | +| **Musik** | `/darmasaba/musik` | Musik desa | +| **Module** | `/darmasaba/module/*` | Link ke modul eksternal (DAVES, MANGAN, Bicara-Darma, BARES, dll) | ### Fitur Publik: + - **Fixed Music Player Bar**: Player musik yang selalu tampil di bottom - **Global Search**: Pencarian global - **News Reader**: Notifikasi berita modern @@ -581,33 +595,33 @@ Public website di `/darmasaba/` dengan layout yang mencakup **Navbar**, **Footer ### Admin Components (`src/components/admin/`) -| Komponen | Deskripsi | -|----------|-----------| -| `AdminThemeProvider.tsx` | Theme provider untuk admin | -| `DarkModeToggle.tsx` | Toggle dark/light mode | -| `UnifiedSurface.tsx` | Consistent surface/card component | -| `UnifiedTypography.tsx` | Consistent typography system | +| Komponen | Deskripsi | +| ------------------------ | --------------------------------- | +| `AdminThemeProvider.tsx` | Theme provider untuk admin | +| `DarkModeToggle.tsx` | Toggle dark/light mode | +| `UnifiedSurface.tsx` | Consistent surface/card component | +| `UnifiedTypography.tsx` | Consistent typography system | ### Public Shared Components (`src/app/darmasaba/_com/`) -| Komponen | Deskripsi | -|----------|-----------| -| `Navbar.tsx` | Main navigation bar | -| `NavbarMainMenu.tsx` | Main menu dengan kategori | -| `NavbarSubMenu.tsx` | Submenu dropdown | -| `Footer.tsx` | Footer dengan info desa | -| `FixedPlayerBar.tsx` | Music player bar fixed di bottom | -| `LoadDataFirstClient.tsx` | Client-side data preloader | -| `globalSearch.tsx` | Global search component | -| `NewsReader.tsx` | News notification reader | -| `ModernNewsNotification.tsx` | News toast notifications | +| Komponen | Deskripsi | +| ---------------------------- | -------------------------------- | +| `Navbar.tsx` | Main navigation bar | +| `NavbarMainMenu.tsx` | Main menu dengan kategori | +| `NavbarSubMenu.tsx` | Submenu dropdown | +| `Footer.tsx` | Footer dengan info desa | +| `FixedPlayerBar.tsx` | Music player bar fixed di bottom | +| `LoadDataFirstClient.tsx` | Client-side data preloader | +| `globalSearch.tsx` | Global search component | +| `NewsReader.tsx` | News notification reader | +| `ModernNewsNotification.tsx` | News toast notifications | ### Global Components (`src/app/_com/`) -| Komponen | Deskripsi | -|----------|-----------| +| Komponen | Deskripsi | +| ----------------- | --------------------- | | `SpashScreen.tsx` | Splash screen on load | -| `WebVitals.tsx` | Web Vitals monitoring | +| `WebVitals.tsx` | Web Vitals monitoring | --- @@ -615,13 +629,13 @@ Public website di `/darmasaba/` dengan layout yang mencakup **Navbar**, **Footer Proyek menggunakan **multi-layer state management**: -| Library | Penggunaan | Lokasi | -|---------|-----------|--------| -| **Jotai** | Auth state (`authStore`) | `src/store/authStore.ts` | -| **Valtio** | Dark mode, layanan, image list, nav state | `src/state/*.ts` | -| **SWR** | Server state fetching & caching | Digunakan di components | -| **React Context** | Music player context | `src/app/context/MusicContext.tsx` | -| **React useState** | Local component state | Di components | +| Library | Penggunaan | Lokasi | +| ------------------ | ----------------------------------------- | ---------------------------------- | +| **Jotai** | Auth state (`authStore`) | `src/store/authStore.ts` | +| **Valtio** | Dark mode, layanan, image list, nav state | `src/state/*.ts` | +| **SWR** | Server state fetching & caching | Digunakan di components | +| **React Context** | Music player context | `src/app/context/MusicContext.tsx` | +| **React useState** | Local component state | Di components | ### State Files: @@ -643,6 +657,7 @@ src/store/ Sistem autentikasi menggunakan **OTP (One-Time Password)** via WhatsApp/Telepon dengan **iron-session** untuk session management. ### Flow Autentikasi: + 1. User memasukkan **nomor telepon** di `/login` 2. Sistem mengirim **kode OTP** via WhatsApp Server 3. OTP disimpan di model `KodeOtp` @@ -651,6 +666,7 @@ Sistem autentikasi menggunakan **OTP (One-Time Password)** via WhatsApp/Telepon 6. Session disimpan di `UserSession` model dengan expiry ### Session Structure: + ```typescript // src/lib/session.ts type SessionData = { @@ -665,13 +681,15 @@ type SessionData = { ``` ### Role-Based Access: -| roleId | Role | Default Redirect | -|--------|------|-----------------| -| 0, 1, 2 | Super Admin / Admin Desa | `/admin/landing-page/profil/program-inovasi` | -| 3 | Admin Kesehatan | `/admin/kesehatan/posyandu` | -| 4 | Admin Pendidikan | `/admin/pendidikan/info-sekolah/jenjang-pendidikan` | + +| roleId | Role | Default Redirect | +| ------- | ------------------------ | --------------------------------------------------- | +| 0, 1, 2 | Super Admin / Admin Desa | `/admin/landing-page/profil/program-inovasi` | +| 3 | Admin Kesehatan | `/admin/kesehatan/posyandu/list-posyandu` | +| 4 | Admin Pendidikan | `/admin/pendidikan/info-sekolah/jenjang-pendidikan` | ### Authorization: + - **UserMenuAccess**: Mapping user ke menu yang boleh diakses - **Dynamic Navbar**: Navbar dirender berdasarkan `menuIds` user - **Inactive Users**: Dialihkan ke `/waiting-room` @@ -698,6 +716,7 @@ Stage 2: Runner ``` ### Entry Point (`docker-entrypoint.sh`): + ```bash bunx prisma migrate deploy # Run migrations exec bun start # Start Next.js production server @@ -707,11 +726,11 @@ exec bun start # Start Next.js production server Terdapat **3 workflow**: -| Workflow | Trigger | Fungsi | -|----------|---------|--------| -| `docker-publish.yml` | Push tag `v*` | Auto build & push ke GHCR | -| `publish.yml` | Manual (workflow_dispatch) | Build & push ke GHCR dengan input `stack_env` + `tag` | -| `re-pull.yml` | Manual (workflow_dispatch) | Re-pull image di Portainer dengan input `stack_name` + `stack_env` | +| Workflow | Trigger | Fungsi | +| -------------------- | -------------------------- | ------------------------------------------------------------------ | +| `docker-publish.yml` | Push tag `v*` | Auto build & push ke GHCR | +| `publish.yml` | Manual (workflow_dispatch) | Build & push ke GHCR dengan input `stack_env` + `tag` | +| `re-pull.yml` | Manual (workflow_dispatch) | Re-pull image di Portainer dengan input `stack_name` + `stack_env` | ### Deployment Workflow (Sequential): @@ -730,32 +749,35 @@ Terdapat **3 workflow**: **PENTING**: `publish.yml` dan `re-pull.yml` TIDAK boleh dijalankan bersamaan (race condition). ### Environments: + - **dev**: Development - **stg**: Staging (`desa-darmasaba-stg.wibudev.com`) - **prod**: Production ### Notification: + - Telegram notification via `notify.sh` script setelah setiap workflow --- ## 13. Scripts -| Script | Command | Deskripsi | -|--------|---------|-----------| -| `dev` | `next dev` | Development server | -| `build` | `next build` | Production build | -| `start` | `next start` | Production server | -| `test:api` | `vitest run` | Run API unit tests | -| `test:e2e` | `playwright test` | Run E2E tests | -| `test` | `bun run test:api && bun run test:e2e` | Run all tests | -| `seed` | `bun run prisma/seed.ts` | Seed database | -| `prisma:generate` | `bunx prisma generate` | Generate Prisma client | -| `prisma:push` | `bunx prisma db push` | Push schema to database | -| `prisma:studio` | `bunx prisma studio` | Open Prisma Studio GUI | -| `gen:api` | *(empty)* | Generate API types (placeholder) | +| Script | Command | Deskripsi | +| ----------------- | -------------------------------------- | -------------------------------- | +| `dev` | `next dev` | Development server | +| `build` | `next build` | Production build | +| `start` | `next start` | Production server | +| `test:api` | `vitest run` | Run API unit tests | +| `test:e2e` | `playwright test` | Run E2E tests | +| `test` | `bun run test:api && bun run test:e2e` | Run all tests | +| `seed` | `bun run prisma/seed.ts` | Seed database | +| `prisma:generate` | `bunx prisma generate` | Generate Prisma client | +| `prisma:push` | `bunx prisma db push` | Push schema to database | +| `prisma:studio` | `bunx prisma studio` | Open Prisma Studio GUI | +| `gen:api` | _(empty)_ | Generate API types (placeholder) | ### Prisma Seed Configuration: + ```json // package.json { @@ -771,35 +793,37 @@ Terdapat **3 workflow**: File: `.env.example` -| Variable | Deskripsi | Contoh | -|----------|-----------|--------| -| `DATABASE_URL` | PostgreSQL connection string | `postgresql://user:pass@localhost:5432/desa-darmasaba` | -| `SEAFILE_TOKEN` | Seafile API token | `your_seafile_token` | -| `SEAFILE_REPO_ID` | Seafile repository ID | `your_repo_id` | -| `SEAFILE_URL` | Seafile instance URL | `https://seafile.example.com` | -| `SEAFILE_PUBLIC_SHARE_TOKEN` | Token untuk public share | `your_share_token` | -| `WIBU_UPLOAD_DIR` | Upload directory path | `uploads` | -| `WA_SERVER_TOKEN` | WhatsApp server token | `your_wa_token` | -| `NEXT_PUBLIC_BASE_URL` | Base URL aplikasi | `/` (relative) | -| `EMAIL_USER` | Email untuk notifikasi | `your_email@gmail.com` | -| `EMAIL_PASS` | Email app password | `your_app_password` | -| `BASE_TOKEN_KEY` | JWT secret key | `your_jwt_secret` | -| `BOT_TOKEN` | Telegram bot token | `your_bot_token` | -| `CHAT_ID` | Telegram chat ID | `your_chat_id` | -| `SESSION_PASSWORD` | iron-session password (min 32 chars) | `secure_32_char_password` | -| `ELEVENLABS_API_KEY` | ElevenLabs API (TTS - optional) | `your_elevenlabs_key` | +| Variable | Deskripsi | Contoh | +| ---------------------------- | ------------------------------------ | ------------------------------------------------------ | +| `DATABASE_URL` | PostgreSQL connection string | `postgresql://user:pass@localhost:5432/desa-darmasaba` | +| `SEAFILE_TOKEN` | Seafile API token | `your_seafile_token` | +| `SEAFILE_REPO_ID` | Seafile repository ID | `your_repo_id` | +| `SEAFILE_URL` | Seafile instance URL | `https://seafile.example.com` | +| `SEAFILE_PUBLIC_SHARE_TOKEN` | Token untuk public share | `your_share_token` | +| `WIBU_UPLOAD_DIR` | Upload directory path | `uploads` | +| `WA_SERVER_TOKEN` | WhatsApp server token | `your_wa_token` | +| `NEXT_PUBLIC_BASE_URL` | Base URL aplikasi | `/` (relative) | +| `EMAIL_USER` | Email untuk notifikasi | `your_email@gmail.com` | +| `EMAIL_PASS` | Email app password | `your_app_password` | +| `BASE_TOKEN_KEY` | JWT secret key | `your_jwt_secret` | +| `BOT_TOKEN` | Telegram bot token | `your_bot_token` | +| `CHAT_ID` | Telegram chat ID | `your_chat_id` | +| `SESSION_PASSWORD` | iron-session password (min 32 chars) | `secure_32_char_password` | +| `ELEVENLABS_API_KEY` | ElevenLabs API (TTS - optional) | `your_elevenlabs_key` | --- ## 15. Layanan Eksternal ### PostgreSQL + - **Provider**: PostgreSQL via Prisma ORM - **Schema**: `public` - **Connection**: Via `DATABASE_URL` environment variable - **Migrations**: `prisma migrate deploy` di docker entrypoint ### Seafile (File Storage) + - **Tipe**: Self-hosted file sync & share - **Penggunaan**: Storage untuk images, documents, audio files - **Integrasi**: `src/lib/seafile-auth-service.ts` @@ -807,19 +831,23 @@ File: `.env.example` - **Config**: Token, repo ID, base URL ### WhatsApp Server + - **Penggunaan**: Kirim OTP codes saat login - **Config**: `WA_SERVER_TOKEN` ### Telegram Bot + - **Penggunaan**: Notifikasi deployment & sistem - **Config**: `BOT_TOKEN` + `CHAT_ID` - **Integration**: `notify.sh` script di GitHub Actions ### ElevenLabs (Optional) + - **Penggunaan**: Text-to-Speech (TTS) features - **Config**: `ELEVENLABS_API_KEY` ### Email (Nodemailer) + - **Penggunaan**: Notifikasi email untuk subscription/pengumuman - **Config**: `EMAIL_USER` + `EMAIL_PASS` - **Provider**: Gmail (app password) @@ -828,15 +856,15 @@ File: `.env.example` ## Ringkasan Cepat -| Aspek | Detail | -|-------|--------| -| **Framework** | Next.js 15 (App Router) + Elysia.js | -| **Database** | PostgreSQL + Prisma (100+ models) | -| **Auth** | OTP + iron-session + JWT | -| **Storage** | Seafile + local uploads | -| **UI** | Mantine UI + Tiptap + Framer Motion | -| **State** | Jotai + Valtio + SWR | -| **Deploy** | Docker + GHCR + Portainer + GitHub Actions | -| **Runtime** | Bun | -| **Testing** | Vitest + Playwright | -| **Version** | 0.1.11 | +| Aspek | Detail | +| ------------- | ------------------------------------------ | +| **Framework** | Next.js 15 (App Router) + Elysia.js | +| **Database** | PostgreSQL + Prisma (100+ models) | +| **Auth** | OTP + iron-session + JWT | +| **Storage** | Seafile + local uploads | +| **UI** | Mantine UI + Tiptap + Framer Motion | +| **State** | Jotai + Valtio + SWR | +| **Deploy** | Docker + GHCR + Portainer + GitHub Actions | +| **Runtime** | Bun | +| **Testing** | Vitest + Playwright | +| **Version** | 0.1.11 | diff --git a/src/app/admin/(dashboard)/auth/validasi-admin/page.tsx b/src/app/admin/(dashboard)/auth/validasi-admin/page.tsx index e6fe0a4b..cb4be561 100644 --- a/src/app/admin/(dashboard)/auth/validasi-admin/page.tsx +++ b/src/app/admin/(dashboard)/auth/validasi-admin/page.tsx @@ -190,7 +190,7 @@ export default function Validasi() { case 2: return '/admin/landing-page/profil/program-inovasi'; case 3: - return '/admin/kesehatan/posyandu'; + return '/admin/kesehatan/posyandu/list-posyandu'; case 4: return '/admin/pendidikan/info-sekolah/jenjang-pendidikan'; default: diff --git a/src/app/admin/(dashboard)/kesehatan/posyandu/balita/page.tsx b/src/app/admin/(dashboard)/kesehatan/posyandu/balita/page.tsx index 029efd2e..7be1ae07 100644 --- a/src/app/admin/(dashboard)/kesehatan/posyandu/balita/page.tsx +++ b/src/app/admin/(dashboard)/kesehatan/posyandu/balita/page.tsx @@ -1,27 +1,34 @@ +/* eslint-disable react-hooks/exhaustive-deps */ 'use client'; +import balitaState from '@/app/admin/(dashboard)/_state/kesehatan/balita/balita'; import colors from '@/con/colors'; import { - ActionIcon, Badge, Box, Button, + Center, Group, - Loader, Pagination, + Paper, Select, + Skeleton, Stack, Table, + TableTbody, + TableTd, + TableTh, + TableThead, + TableTr, Text, - TextInput, Title, } from '@mantine/core'; -import { IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react'; +import { useDebouncedValue } from '@mantine/hooks'; +import { IconDeviceImacCog, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react'; import { useRouter } from 'next/navigation'; import { useEffect, useState } from 'react'; import { useProxy } from 'valtio/utils'; -import balitaState from '../../../_state/kesehatan/balita/balita'; - +import HeaderSearch from '../../../_com/header'; const STUNTING_COLORS: Record = { NORMAL: 'green', @@ -29,161 +36,272 @@ const STUNTING_COLORS: Record = { STUNTING: 'red', }; -export default function BalitaPage() { - const router = useRouter(); - const state = useProxy(balitaState); +function BalitaPage() { const [search, setSearch] = useState(''); + return ( + + } + value={search} + onChange={(e) => setSearch(e.currentTarget.value)} + /> + + + ); +} + +function ListBalita({ search }: { search: string }) { + const state = useProxy(balitaState); + const router = useRouter(); const [statusFilter, setStatusFilter] = useState(''); + const [debouncedSearch] = useDebouncedValue(search, 1000); + + const { data, page, totalPages, loading, load } = state.findMany; useEffect(() => { - state.findMany.load(1, 10, search, statusFilter); - }, []); // eslint-disable-line react-hooks/exhaustive-deps - - const handleSearch = () => { - state.findMany.load(1, 10, search, statusFilter); - }; + load(page, 10, debouncedSearch, statusFilter); + }, [page, debouncedSearch, statusFilter]); const handleDelete = async (id: string, nama: string) => { if (!confirm(`Hapus data balita "${nama}"?`)) return; await state.delete.byId(id); }; - const rows = state.findMany.data?.map((d) => ( - - {d.nama} - {d.jenisKelamin} - - {d.tanggalLahir - ? new Date(d.tanggalLahir).toLocaleDateString('id-ID') - : '-'} - - - - {d.imunisasiLengkap ? 'Lengkap' : 'Belum'} - - - - - {d.giziBaik ? 'Baik' : 'Kurang'} - - - - - {d.pemeriksaanRutin ? 'Rutin' : 'Tidak'} - - - - - {d.statusStunting} - - - - - router.push(`/admin/kesehatan/posyandu/balita/edit/${d.id}`)} - > - - - handleDelete(d.id, d.nama)} - loading={state.delete.loading} - > - - - - - - )); + const filteredData = data || []; + + if (loading || !data) { + return ( + + + + ); + } return ( - - - Balita Terdaftar - - + + + + List Balita + + - - } - value={search} - onChange={(e) => setSearch(e.currentTarget.value)} - onKeyDown={(e) => e.key === 'Enter' && handleSearch()} - radius="md" - style={{ flex: 1, maxWidth: 300 }} - /> - setStatusFilter(v ?? '')} + radius="md" + clearable + /> + - {state.findMany.loading ? ( - - ) : ( - - - - - Nama - JK - Tgl Lahir - Imunisasi - Gizi - Pemeriksaan - Stunting - Aksi - - - - {rows && rows.length > 0 ? rows : ( - - - Tidak ada data - - + {/* Desktop Table */} + +
+ + + Nama + JK + Tgl Lahir + Imunisasi + Gizi + Pemeriksaan + Stunting + Aksi + + + + {filteredData.length > 0 ? ( + filteredData.map((d) => ( + + {d.nama} + {d.jenisKelamin} + + {d.tanggalLahir + ? new Date(d.tanggalLahir).toLocaleDateString('id-ID') + : '-'} + + + + {d.imunisasiLengkap ? 'Lengkap' : 'Belum'} + + + + + {d.giziBaik ? 'Baik' : 'Kurang'} + + + + + {d.pemeriksaanRutin ? 'Rutin' : 'Tidak'} + + + + + {d.statusStunting} + + + + + + + + + + )) + ) : ( + + +
+ + Tidak ada data balita yang cocok + +
+
+
)} - +
+
- {(state.findMany.totalPages ?? 1) > 1 && ( - - state.findMany.load(p, 10, search, statusFilter)} - /> - + {/* Mobile Cards */} + + {filteredData.length > 0 ? ( + filteredData.map((d) => ( + + + + {d.nama} + + + + {d.jenisKelamin} + + + · + + + {d.tanggalLahir + ? new Date(d.tanggalLahir).toLocaleDateString('id-ID') + : '-'} + + + + + {d.imunisasiLengkap ? 'Imunisasi Lengkap' : 'Imunisasi Belum'} + + + Gizi {d.giziBaik ? 'Baik' : 'Kurang'} + + + {d.statusStunting} + + + + + + + + + )) + ) : ( +
+ + Tidak ada data balita yang cocok + +
)}
- )} + + +
+ { + load(newPage, 10, search, statusFilter); + window.scrollTo({ top: 0, behavior: 'smooth' }); + }} + total={totalPages} + mt="lg" + mb="md" + color="blue" + radius="md" + /> +
); } + +export default BalitaPage; diff --git a/src/app/admin/(dashboard)/kesehatan/posyandu/ibu-hamil/page.tsx b/src/app/admin/(dashboard)/kesehatan/posyandu/ibu-hamil/page.tsx index 110e4775..8aafd2d8 100644 --- a/src/app/admin/(dashboard)/kesehatan/posyandu/ibu-hamil/page.tsx +++ b/src/app/admin/(dashboard)/kesehatan/posyandu/ibu-hamil/page.tsx @@ -1,27 +1,34 @@ +/* eslint-disable react-hooks/exhaustive-deps */ 'use client'; +import ibuHamilState from '@/app/admin/(dashboard)/_state/kesehatan/ibu-hamil/ibuHamil'; import colors from '@/con/colors'; import { - ActionIcon, Badge, Box, Button, + Center, Group, - Loader, Pagination, + Paper, Select, + Skeleton, Stack, Table, + TableTbody, + TableTd, + TableTh, + TableThead, + TableTr, Text, - TextInput, Title, } from '@mantine/core'; -import { IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react'; +import { useDebouncedValue } from '@mantine/hooks'; +import { IconDeviceImacCog, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react'; import { useRouter } from 'next/navigation'; import { useEffect, useState } from 'react'; import { useProxy } from 'valtio/utils'; -import ibuHamilState from '../../../_state/kesehatan/ibu-hamil/ibuHamil'; - +import HeaderSearch from '../../../_com/header'; const STATUS_COLORS: Record = { AKTIF: 'green', @@ -30,142 +37,242 @@ const STATUS_COLORS: Record = { NONAKTIF: 'red', }; -export default function IbuHamilPage() { - const router = useRouter(); - const state = useProxy(ibuHamilState); +function IbuHamilPage() { const [search, setSearch] = useState(''); + return ( + + } + value={search} + onChange={(e) => setSearch(e.currentTarget.value)} + /> + + + ); +} + +function ListIbuHamil({ search }: { search: string }) { + const state = useProxy(ibuHamilState); + const router = useRouter(); const [statusFilter, setStatusFilter] = useState(''); + const [debouncedSearch] = useDebouncedValue(search, 1000); + + const { data, page, totalPages, loading, load } = state.findMany; useEffect(() => { - state.findMany.load(1, 10, search, statusFilter); - }, []); // eslint-disable-line react-hooks/exhaustive-deps - - const handleSearch = () => { - state.findMany.load(1, 10, search, statusFilter); - }; + load(page, 10, debouncedSearch, statusFilter); + }, [page, debouncedSearch, statusFilter]); const handleDelete = async (id: string, nama: string) => { if (!confirm(`Hapus data ibu hamil "${nama}"?`)) return; await state.delete.byId(id); }; - const rows = state.findMany.data?.map((d) => ( - - {d.nama} - {d.nik || '-'} - {d.usiaKehamilan} minggu - {d.noHp || '-'} - - - {d.status} - - - - - router.push(`/admin/kesehatan/posyandu/ibu-hamil/edit/${d.id}`)} - > - - - handleDelete(d.id, d.nama)} - loading={state.delete.loading} - > - - - - - - )); + const filteredData = data || []; + + if (loading || !data) { + return ( + + + + ); + } return ( - - - Ibu Hamil - - + + + + List Ibu Hamil + + - - } - value={search} - onChange={(e) => setSearch(e.currentTarget.value)} - onKeyDown={(e) => e.key === 'Enter' && handleSearch()} - radius="md" - style={{ flex: 1, maxWidth: 300 }} - /> - setStatusFilter(v ?? '')} + radius="md" + clearable + /> + - {state.findMany.loading ? ( - - ) : ( - - - - - Nama - NIK - Usia Kehamilan - No. HP - Status - Aksi - - - - {rows && rows.length > 0 ? rows : ( - - - Tidak ada data - - + {/* Desktop Table */} + +
+ + + Nama + NIK + Usia Kehamilan + No. HP + Status + Aksi + + + + {filteredData.length > 0 ? ( + filteredData.map((d) => ( + + {d.nama} + {d.nik || '-'} + {d.usiaKehamilan} minggu + {d.noHp || '-'} + + + {d.status} + + + + + + + + + + )) + ) : ( + + +
+ + Tidak ada data ibu hamil yang cocok + +
+
+
)} - +
+
- {(state.findMany.totalPages ?? 1) > 1 && ( - - state.findMany.load(p, 10, search, statusFilter)} - /> - + {/* Mobile Cards */} + + {filteredData.length > 0 ? ( + filteredData.map((d) => ( + + + + {d.nama} + + + + NIK: {d.nik || '-'} + + + · + + + {d.usiaKehamilan} minggu + + + + + {d.status} + + {d.noHp && ( + + {d.noHp} + + )} + + + + + + + + )) + ) : ( +
+ + Tidak ada data ibu hamil yang cocok + +
)}
- )} + + +
+ { + load(newPage, 10, search, statusFilter); + window.scrollTo({ top: 0, behavior: 'smooth' }); + }} + total={totalPages} + mt="lg" + mb="md" + color="blue" + radius="md" + /> +
); } + +export default IbuHamilPage; diff --git a/src/app/admin/layout.tsx b/src/app/admin/layout.tsx index d5897b59..7808cc52 100644 --- a/src/app/admin/layout.tsx +++ b/src/app/admin/layout.tsx @@ -37,7 +37,7 @@ import { getNavbar } from "./(dashboard)/user&role/_com/dynamicNavbar"; export default function Layout({ children }: { children: React.ReactNode }) { const { isDark } = useDarkMode(); const tokens = themeTokens(isDark); - + const [mounted, setMounted] = useState(false); const [opened, { toggle, close }] = useDisclosure(); const [loading, setLoading] = useState(true); @@ -114,7 +114,7 @@ export default function Layout({ children }: { children: React.ReactNode }) { case 2: return '/admin/landing-page/profil/program-inovasi'; case 3: - return '/admin/kesehatan/posyandu'; + return '/admin/kesehatan/posyandu/list-posyandu'; case 4: return '/admin/pendidikan/info-sekolah/jenjang-pendidikan'; default: diff --git a/src/app/waiting-room/page.tsx b/src/app/waiting-room/page.tsx index 19cd25ad..d5c26a09 100644 --- a/src/app/waiting-room/page.tsx +++ b/src/app/waiting-room/page.tsx @@ -47,7 +47,7 @@ export default function WaitingRoom() { const [error, setError] = useState(null); const [isRedirecting, setIsRedirecting] = useState(false); const [retryCount, setRetryCount] = useState(0); - + // ⏱️ Countdown timer const [timeLeft, setTimeLeft] = useState(CONFIG.TIMEOUT_DURATION / 1000); // dalam detik const [hasTimedOut, setHasTimedOut] = useState(false); @@ -128,7 +128,7 @@ export default function WaitingRoom() { redirectPath = '/admin/landing-page/profil/program-inovasi'; break; case "3": - redirectPath = '/admin/kesehatan/posyandu'; + redirectPath = '/admin/kesehatan/posyandu/list-posyandu'; break; case "4": redirectPath = '/admin/pendidikan/info-sekolah/jenjang-pendidikan'; @@ -200,9 +200,9 @@ export default function WaitingRoom() { Silakan hubungi Superadmin atau coba login ulang nanti. -