feat(desa): Kalender Event Budaya — fitur admin/public, seeder, pagination, fix duplicate options
- Tambah fitur Kalender Event Budaya di admin CMS (list, detail, edit, hapus) - Tambah state Valtio (create, findMany, findUnique, edit, delete, findUpcoming) - Tambah endpoint API /find-upcoming untuk event mendatang - Tambah halaman public /darmasaba/desa/event-budaya dengan pagination 5 data/halaman - Switch public page dari findUpcoming ke findMany agar pagination berjalan - Tambah menu "Kalender Event Budaya" di navbar (id: 2.9) - Perluas seeder event budaya: 8 → 34 events mencakup 2025-2026 - Fix: deduplikasi kategoriOptions di kegiatan-desa public page (Mantine Select error) - Hapus STRUKTUR.md yang sudah tidak relevan Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,67 @@
|
|||||||
|
# Summary: Event Budaya — Seeder, Pagination & Fix Duplicate Options
|
||||||
|
|
||||||
|
**Tanggal:** 2026-05-21
|
||||||
|
**Branch:** `tasks/event-budaya/feat-seeder-pagination-fix-duplicate/20260521`
|
||||||
|
**Scope:** Fitur Kalender Event Budaya (admin + public) + fix bug + seeder
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Perubahan yang Dilakukan
|
||||||
|
|
||||||
|
### 1. Fix Bug: Duplicate Options di Halaman Kegiatan Desa (Public)
|
||||||
|
**File:** `src/app/darmasaba/(pages)/desa/kegiatan-desa/_lib/layoutTabs.tsx`
|
||||||
|
|
||||||
|
- **Masalah:** Mantine `<Select>` error `Duplicate options are not supported` untuk value "Sosial" karena data kategori di DB memiliki nama duplikat.
|
||||||
|
- **Fix:** Deduplikasi `kategoriOptions` menggunakan `Map` sebelum data masuk ke `<Select>`.
|
||||||
|
- **Root cause:** Data duplikat di tabel kategori kegiatan desa — perlu dibersihkan dari sisi DB.
|
||||||
|
|
||||||
|
### 2. Fitur Kalender Event Budaya (Admin CMS)
|
||||||
|
**Files:**
|
||||||
|
- `src/app/admin/(dashboard)/desa/event-budaya/page.tsx` — List event dengan table, aksi edit/hapus/lihat
|
||||||
|
- `src/app/admin/(dashboard)/desa/event-budaya/[id]/page.tsx` — Detail & edit event
|
||||||
|
- `src/app/admin/(dashboard)/desa/event-budaya/layout.tsx` — Layout wrapper admin
|
||||||
|
- `src/app/admin/(dashboard)/_state/desa/eventBudaya.ts` — State Valtio: create, findMany, findUnique, edit, delete, findUpcoming
|
||||||
|
- `src/app/api/[[...slugs]]/_lib/desa/event-budaya/index.ts` — Elysia router: tambah endpoint `/find-upcoming`
|
||||||
|
- `src/app/api/[[...slugs]]/_lib/desa/event-budaya/find-upcoming.ts` — Query event mendatang (tanggal >= hari ini, max 20)
|
||||||
|
|
||||||
|
### 3. Fitur Kalender Event Budaya (Public)
|
||||||
|
**File:** `src/app/darmasaba/(pages)/desa/event-budaya/page.tsx`
|
||||||
|
|
||||||
|
- Menampilkan list event budaya dengan pagination **5 data per halaman**
|
||||||
|
- Menggunakan `eventBudayaState.findMany` (bukan `findUpcoming`) agar pagination berjalan
|
||||||
|
- Komponen `Pagination` dari Mantine di bawah list
|
||||||
|
- Skeleton loading saat data belum tersedia
|
||||||
|
|
||||||
|
### 4. Navigasi Navbar
|
||||||
|
**File:** `src/con/navbar-list-menu.ts`
|
||||||
|
|
||||||
|
- Tambah menu **"Kalender Event Budaya"** (id: 2.9) di bawah "Kegiatan Desa" dengan href `/darmasaba/desa/event-budaya`
|
||||||
|
|
||||||
|
### 5. Seeder: Data Event Budaya
|
||||||
|
**Files:**
|
||||||
|
- `prisma/data/desa/event-budaya/event-budaya.json` — Diperluas dari **8 → 34 events**
|
||||||
|
- `prisma/_seeder_list/desa/event-budaya/seed_event_budaya.ts` — Tidak diubah (sudah kompatibel)
|
||||||
|
|
||||||
|
**Cakupan seeder (2025–2026):**
|
||||||
|
- Hari Raya Hindu Bali: Galungan (×5), Kuningan (×5), Nyepi, Melasti, Saraswati, Pagerwesi
|
||||||
|
- Tumpek: Landep, Uduh, Krulut
|
||||||
|
- Upacara desa: Ngusaba Desa, Pujawali Pura Puseh, Melaspas
|
||||||
|
- Event nasional: HUT RI ke-80 & ke-81, Hari Kesaktian Pancasila
|
||||||
|
- Event budaya: Festival Budaya Desa, Parade Ogoh-Ogoh, Pementasan Wayang Kulit
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Arsitektur yang Diikuti
|
||||||
|
|
||||||
|
- **State management:** Valtio proxy di `_state/desa/eventBudaya.ts`
|
||||||
|
- **API:** Elysia.js endpoint di `/api/desa/eventbudaya/*`
|
||||||
|
- **Pagination:** Server-side via `findMany` (skip/take Prisma), state sudah punya `page`, `totalPages`, `total`
|
||||||
|
- **Seeder:** JSON data + `upsert` Prisma (aman dijalankan berulang)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Catatan
|
||||||
|
|
||||||
|
- `STRUKTUR.md` dihapus (file lama tidak relevan)
|
||||||
|
- Seeder bisa dijalankan dengan `bun run prisma/seed.ts`
|
||||||
|
- Bug duplicate kategori di DB belum dibersihkan dari sisi data — hanya di-guard di UI
|
||||||
870
STRUKTUR.md
870
STRUKTUR.md
@@ -1,870 +0,0 @@
|
|||||||
# Dokumentasi Struktur Proyek Desa Darmasaba
|
|
||||||
|
|
||||||
## 1. Ringkasan Proyek
|
|
||||||
|
|
||||||
**Desa Darmasaba** adalah aplikasi web manajemen desa digital untuk Desa Darmasaba, Kabupaten Badung, Bali. Aplikasi ini berfungsi sebagai platform layanan publik digital yang mencakup informasi pemerintahan, layanan kesehatan, keamanan, pendidikan, ekonomi, lingkungan, dan inovasi desa.
|
|
||||||
|
|
||||||
### 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 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. Struktur Direktori
|
|
||||||
|
|
||||||
```
|
|
||||||
desa-darmasaba/
|
|
||||||
├── .github/workflows/ # GitHub Actions CI/CD
|
|
||||||
│ ├── docker-publish.yml # Auto build & push saat tag v*
|
|
||||||
│ ├── publish.yml # Manual build & push ke GHCR
|
|
||||||
│ ├── re-pull.yml # Manual re-pull di Portainer
|
|
||||||
│ └── script/ # Shell scripts untuk deploy
|
|
||||||
├── prisma/
|
|
||||||
│ ├── schema.prisma # Database schema (2413 baris, 100+ model)
|
|
||||||
│ └── seed.ts # Database seeder (400+ baris)
|
|
||||||
│ └── _seeder_list/ # Seed data per modul
|
|
||||||
├── public/ # Static assets
|
|
||||||
│ └── assets/
|
|
||||||
│ └── images/
|
|
||||||
├── src/
|
|
||||||
│ ├── app/ # Next.js App Router
|
|
||||||
│ │ ├── _com/ # Global components (SplashScreen, WebVitals)
|
|
||||||
│ │ ├── admin/ # ADMIN DASHBOARD
|
|
||||||
│ │ │ ├── (dashboard)/ # Route group dashboard
|
|
||||||
│ │ │ │ ├── desa/ # - Berita, Gallery, Layanan, dll
|
|
||||||
│ │ │ │ ├── ppid/ # - Informasi publik, struktur, dasar hukum
|
|
||||||
│ │ │ │ ├── kesehatan/ # - Fasilitas, posyandu, puskesmas, wabah
|
|
||||||
│ │ │ │ ├── ekonomi/ # - APBDes, pasar desa, BUMDes, dll
|
|
||||||
│ │ │ │ ├── kependudukan/ # - Banjar, agama, umur, migrasi
|
|
||||||
│ │ │ │ ├── pendidikan/ # - Sekolah, beasiswa, perpustakaan
|
|
||||||
│ │ │ │ ├── keamanan/ # - Keamanan lingkungan, polsek, dll
|
|
||||||
│ │ │ │ ├── lingkungan/ # - Sampah, penghijauan, gotong royong
|
|
||||||
│ │ │ │ ├── inovasi/ # - Desa digital, kolaborasi, dll
|
|
||||||
│ │ │ │ ├── landing-page/ # - Profil, prestasi, anti-korupsi
|
|
||||||
│ │ │ │ ├── musik/ # - Musik desa
|
|
||||||
│ │ │ │ ├── user&role/ # - Manajemen user & role
|
|
||||||
│ │ │ │ └── _com/ # - Shared admin components
|
|
||||||
│ │ │ ├── auth/ # Login OTP untuk admin
|
|
||||||
│ │ │ ├── csv/ # Demo CSV upload
|
|
||||||
│ │ │ └── layout.tsx # Admin shell (AppShell Mantine)
|
|
||||||
│ │ ├── api/ # ELYSIA.JS API SERVER
|
|
||||||
│ │ │ ├── [[...slugs]]/ # Catch-all route -> Elysia handler
|
|
||||||
│ │ │ │ ├── route.ts # - Main Elysia server export
|
|
||||||
│ │ │ │ └── _lib/ # - Domain route modules
|
|
||||||
│ │ │ │ ├── desa.ts
|
|
||||||
│ │ │ │ ├── ppid.ts
|
|
||||||
│ │ │ │ ├── kesehatan.ts
|
|
||||||
│ │ │ │ ├── ekonomi.ts
|
|
||||||
│ │ │ │ ├── keamanan.ts
|
|
||||||
│ │ │ │ ├── inovasi.ts
|
|
||||||
│ │ │ │ ├── lingkungan.ts
|
|
||||||
│ │ │ │ ├── pendidikan.ts
|
|
||||||
│ │ │ │ ├── kependudukan.ts
|
|
||||||
│ │ │ │ ├── landing_page.ts
|
|
||||||
│ │ │ │ ├── user/ # - User & Role management
|
|
||||||
│ │ │ │ ├── fileStorage/
|
|
||||||
│ │ │ │ ├── search/
|
|
||||||
│ │ │ │ ├── auth/
|
|
||||||
│ │ │ │ ├── upl-img.ts, upl-img-single.ts
|
|
||||||
│ │ │ │ ├── upl-csv.ts, upl-csv-single.ts
|
|
||||||
│ │ │ │ └── img.ts, img-del.ts, imgs.ts
|
|
||||||
│ │ │ ├── auth/ # Auth endpoints (login, logout, me)
|
|
||||||
│ │ │ └── ... # Other API routes
|
|
||||||
│ │ ├── darmasaba/ # PUBLIC-FACING WEBSITE
|
|
||||||
│ │ │ ├── _com/ # Shared components (Navbar, Footer, etc)
|
|
||||||
│ │ │ ├── (pages)/ # Public pages route group
|
|
||||||
│ │ │ │ ├── desa/ # - Profil, berita, gallery, layanan
|
|
||||||
│ │ │ │ ├── ppid/ # - PPID public pages
|
|
||||||
│ │ │ │ ├── kesehatan/ # - Health info pages
|
|
||||||
│ │ │ │ ├── ekonomi/ # - Economy pages
|
|
||||||
│ │ │ │ ├── kependudukan/
|
|
||||||
│ │ │ │ ├── pendidikan/
|
|
||||||
│ │ │ │ ├── keamanan/
|
|
||||||
│ │ │ │ ├── lingkungan/
|
|
||||||
│ │ │ │ ├── inovasi/
|
|
||||||
│ │ │ │ ├── musik/
|
|
||||||
│ │ │ │ └── module/ # - External module links
|
|
||||||
│ │ │ └── (tambahan)/ # Additional pages
|
|
||||||
│ │ ├── login/ # Login page
|
|
||||||
│ │ ├── registrasi/ # Registration page
|
|
||||||
│ │ ├── waiting-room/ # Waiting room (inactive users)
|
|
||||||
│ │ ├── terms-of-service/
|
|
||||||
│ │ ├── layout.tsx # Root layout (MantineProvider, ViewTransitions)
|
|
||||||
│ │ └── page.tsx # Homepage redirect
|
|
||||||
│ ├── components/
|
|
||||||
│ │ └── admin/ # Admin shared components
|
|
||||||
│ │ ├── AdminThemeProvider.tsx
|
|
||||||
│ │ ├── DarkModeToggle.tsx
|
|
||||||
│ │ ├── UnifiedSurface.tsx
|
|
||||||
│ │ └── UnifiedTypography.tsx
|
|
||||||
│ ├── con/ # Constants & configuration
|
|
||||||
│ │ ├── colors.ts # Color palette definitions
|
|
||||||
│ │ ├── images.ts
|
|
||||||
│ │ ├── navbar-list-menu.ts
|
|
||||||
│ │ ├── router.ts # Route mapping
|
|
||||||
│ │ └── sosmed.ts
|
|
||||||
│ ├── context/ # React contexts
|
|
||||||
│ │ └── MusicContext.tsx # Music player context
|
|
||||||
│ ├── hooks/ # Custom React hooks
|
|
||||||
│ ├── lib/ # Utility libraries
|
|
||||||
│ │ ├── router/
|
|
||||||
│ │ ├── api-auth.ts # API authentication helpers
|
|
||||||
│ │ ├── api-fetch.ts # API fetch wrapper
|
|
||||||
│ │ ├── EnvStringParse.ts
|
|
||||||
│ │ ├── prisma.ts # Prisma client singleton
|
|
||||||
│ │ ├── seafile-auth-service.ts
|
|
||||||
│ │ └── session.ts # iron-session helper
|
|
||||||
│ ├── state/ # Global state (Jotai/Valtio)
|
|
||||||
│ │ ├── darkModeStore.ts
|
|
||||||
│ │ ├── state-layanan.ts
|
|
||||||
│ │ ├── state-list-image.ts
|
|
||||||
│ │ └── state-nav.ts
|
|
||||||
│ ├── store/ # Additional stores
|
|
||||||
│ │ └── authStore.ts # Auth state (Jotai)
|
|
||||||
│ ├── types/ # TypeScript type definitions
|
|
||||||
│ └── utils/ # Utility functions
|
|
||||||
│ └── themeTokens.ts # Dark/light theme tokens
|
|
||||||
├── uploads/ # Local upload directory (images/files)
|
|
||||||
├── Dockerfile # Multi-stage Docker build (Bun)
|
|
||||||
├── docker-entrypoint.sh # Entry script (migrate + start)
|
|
||||||
├── next.config.ts # Next.js configuration
|
|
||||||
├── package.json # Dependencies & scripts
|
|
||||||
├── tsconfig.json # TypeScript configuration
|
|
||||||
├── biome.json # Biome linter config
|
|
||||||
├── eslint.config.mjs # ESLint config
|
|
||||||
├── NOTE.md # Deployment notes
|
|
||||||
├── QWEN.md # Project memory & workflow
|
|
||||||
└── AGENTS.md # Agent coding guidelines
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. Arsitektur
|
|
||||||
|
|
||||||
### Pola Arsitektur: Full-Stack Monolith dengan App Router
|
|
||||||
|
|
||||||
```
|
|
||||||
Browser
|
|
||||||
|
|
|
||||||
+-- Next.js 15 (App Router) -- Server Components + Client Components
|
|
||||||
|
|
|
||||||
+-- /darmasaba/* -> Public pages (SSR/CSR)
|
|
||||||
+-- /admin/* -> Admin dashboard (protected)
|
|
||||||
+-- /api/* -> Elysia.js API server
|
|
||||||
|
|
|
||||||
+-- Elysia Server (src/app/api/[[...slugs]]/route.ts)
|
|
||||||
|
|
|
||||||
+-- CORS enabled
|
|
||||||
+-- Swagger docs di /api/docs
|
|
||||||
+-- Static file serving (/api/uploads)
|
|
||||||
+-- Domain modules: Desa, PPID, Kesehatan, Ekonomi, dll
|
|
||||||
+-- Image upload handlers
|
|
||||||
|
|
|
||||||
+-- Prisma ORM --> PostgreSQL
|
|
||||||
+-- Seafile API --> File Storage
|
|
||||||
```
|
|
||||||
|
|
||||||
### Key Architectural Decisions:
|
|
||||||
|
|
||||||
1. **Next.js 15 App Router**: Menggunakan React Server Components sebagai default, dengan `"use client"` untuk interaktivitas
|
|
||||||
2. **Elysia.js di dalam API Routes**: Catch-all route `[[...slugs]]` meneruskan semua request ke Elysia handler
|
|
||||||
3. **Route Groups**: `(dashboard)` dan `(pages)` untuk organisasi tanpa mempengaruhi URL path
|
|
||||||
4. **Multi-tenant Ready**: Role-based access control dengan dynamic navbar berdasarkan roleId
|
|
||||||
5. **File Uploads**: Local uploads + Seafile integration untuk distributed storage
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 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 |
|
|
||||||
| Permohonan Informasi Publik | Form permohonan informasi (NIK, kontak, jenis) |
|
|
||||||
| 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 |
|
|
||||||
|
|
||||||
### 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 |
|
|
||||||
|
|
||||||
### 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 |
|
|
||||||
| Penduduk Usia Kerja Menganggur | Analisis pengangguran by usia & pendidikan |
|
|
||||||
| 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 |
|
|
||||||
| 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 |
|
|
||||||
|
|
||||||
### 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 |
|
|
||||||
|
|
||||||
### 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 |
|
|
||||||
| 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 |
|
|
||||||
|
|
||||||
### 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
|
|
||||||
- **User Session Management**: Multiple sessions per user dengan JWT
|
|
||||||
- **OTP Authentication**: Login dengan nomor telepon + OTP
|
|
||||||
- **Menu Access Control**: Dynamic navbar berdasarkan menu akses user
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. Database Schema (Prisma)
|
|
||||||
|
|
||||||
Schema terdiri dari **2413 baris** dengan **100+ model** dan **berbagai enum**. Berikut model-model utama:
|
|
||||||
|
|
||||||
### 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 |
|
|
||||||
|
|
||||||
### 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 |
|
|
||||||
|
|
||||||
### 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 |
|
|
||||||
|
|
||||||
### 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 |
|
|
||||||
|
|
||||||
### 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 |
|
|
||||||
|
|
||||||
### Kependudukan
|
|
||||||
|
|
||||||
| 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 |
|
|
||||||
|
|
||||||
### 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 |
|
|
||||||
|
|
||||||
### Lingkungan
|
|
||||||
|
|
||||||
| 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 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. API Routes
|
|
||||||
|
|
||||||
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 |
|
|
||||||
|
|
||||||
### 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 |
|
|
||||||
|
|
||||||
### Auth Endpoints
|
|
||||||
|
|
||||||
| 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`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. Halaman Admin
|
|
||||||
|
|
||||||
Admin dashboard menggunakan **Mantine AppShell** dengan sidebar navigasi dinamis berbasis role.
|
|
||||||
|
|
||||||
### 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 |
|
|
||||||
|
|
||||||
### 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
|
|
||||||
- **Session Management**: Multiple sessions per user dengan JWT tokens
|
|
||||||
- **CSV Upload**: Import data via CSV
|
|
||||||
- **Image Upload**: Upload dengan preview dan management
|
|
||||||
- **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/list-posyandu` |
|
|
||||||
| 4 | Admin Pendidikan | `/admin/pendidikan/info-sekolah/jenjang-pendidikan` |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 8. Halaman Publik
|
|
||||||
|
|
||||||
Public website di `/darmasaba/` dengan layout yang mencakup **Navbar**, **Footer**, dan **Fixed Music Player Bar**.
|
|
||||||
|
|
||||||
### 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) |
|
|
||||||
|
|
||||||
### Fitur Publik:
|
|
||||||
|
|
||||||
- **Fixed Music Player Bar**: Player musik yang selalu tampil di bottom
|
|
||||||
- **Global Search**: Pencarian global
|
|
||||||
- **News Reader**: Notifikasi berita modern
|
|
||||||
- **View Transitions**: Smooth page transitions
|
|
||||||
- **Responsive Design**: Mobile-first dengan Mantine breakpoints
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 9. Komponen Utama
|
|
||||||
|
|
||||||
### 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 |
|
|
||||||
|
|
||||||
### 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 |
|
|
||||||
|
|
||||||
### Global Components (`src/app/_com/`)
|
|
||||||
|
|
||||||
| Komponen | Deskripsi |
|
|
||||||
| ----------------- | --------------------- |
|
|
||||||
| `SpashScreen.tsx` | Splash screen on load |
|
|
||||||
| `WebVitals.tsx` | Web Vitals monitoring |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 10. State Management
|
|
||||||
|
|
||||||
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 |
|
|
||||||
|
|
||||||
### State Files:
|
|
||||||
|
|
||||||
```
|
|
||||||
src/state/
|
|
||||||
darkModeStore.ts -- Valtio proxy untuk dark mode
|
|
||||||
state-layanan.ts -- State layanan desa
|
|
||||||
state-list-image.ts -- State list image untuk upload
|
|
||||||
state-nav.ts -- State navigasi
|
|
||||||
|
|
||||||
src/store/
|
|
||||||
authStore.ts -- Jotai atom untuk auth user state
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 11. Autentikasi
|
|
||||||
|
|
||||||
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`
|
|
||||||
4. User memasukkan kode OTP
|
|
||||||
5. Jika valid, session dibuat dengan **iron-session** + **JWT token**
|
|
||||||
6. Session disimpan di `UserSession` model dengan expiry
|
|
||||||
|
|
||||||
### Session Structure:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// src/lib/session.ts
|
|
||||||
type SessionData = {
|
|
||||||
user?: {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
roleId: number;
|
|
||||||
menuIds?: string[] | null;
|
|
||||||
isActive?: boolean;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
### 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/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`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 12. Deployment
|
|
||||||
|
|
||||||
### Docker Setup
|
|
||||||
|
|
||||||
**Dockerfile** menggunakan **multi-stage build** dengan base image `oven/bun:1-debian`:
|
|
||||||
|
|
||||||
```
|
|
||||||
Stage 1: Builder
|
|
||||||
- Install dependencies (bun install --frozen-lockfile)
|
|
||||||
- Generate Prisma client
|
|
||||||
- Build Next.js (bun run build)
|
|
||||||
|
|
||||||
Stage 2: Runner
|
|
||||||
- Copy .next, node_modules, public, prisma, src/lib, tsconfig.json
|
|
||||||
- Non-root user (nextjs:nodejs)
|
|
||||||
- Volume /app/uploads untuk file uploads
|
|
||||||
- Port 3000
|
|
||||||
```
|
|
||||||
|
|
||||||
### Entry Point (`docker-entrypoint.sh`):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
bunx prisma migrate deploy # Run migrations
|
|
||||||
exec bun start # Start Next.js production server
|
|
||||||
```
|
|
||||||
|
|
||||||
### CI/CD dengan GitHub Actions
|
|
||||||
|
|
||||||
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` |
|
|
||||||
|
|
||||||
### Deployment Workflow (Sequential):
|
|
||||||
|
|
||||||
```
|
|
||||||
1. Update version di package.json (semver)
|
|
||||||
2. Commit perubahan
|
|
||||||
3. Push ke branch target (stg/prod)
|
|
||||||
4. Trigger publish.yml:
|
|
||||||
gh workflow run publish.yml --ref main -f stack_env=stg -f tag=<version>
|
|
||||||
5. Tunggu sampai publish selesai (status: completed)
|
|
||||||
6. Trigger re-pull.yml:
|
|
||||||
gh workflow run re-pull.yml --ref main -f stack_name=desa-darmasaba -f stack_env=stg
|
|
||||||
7. Verifikasi di Portainer
|
|
||||||
```
|
|
||||||
|
|
||||||
**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) |
|
|
||||||
|
|
||||||
### Prisma Seed Configuration:
|
|
||||||
|
|
||||||
```json
|
|
||||||
// package.json
|
|
||||||
{
|
|
||||||
"prisma": {
|
|
||||||
"seed": "bun run prisma/seed.ts"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 14. Environment Variables
|
|
||||||
|
|
||||||
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` |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 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`
|
|
||||||
- **CDN**: URL generation untuk public sharing
|
|
||||||
- **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)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 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 |
|
|
||||||
@@ -1,58 +1,240 @@
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
"id": "event-budaya-1",
|
"id": "event-budaya-1",
|
||||||
"nama": "Hari Kesaktian Pancasila",
|
"nama": "Hari Raya Galungan",
|
||||||
"tanggal": "2025-10-01T07:00:00.000Z",
|
"tanggal": "2025-01-15T06:00:00.000Z",
|
||||||
"lokasi": "Balai Desa Darmasaba",
|
"lokasi": "Seluruh wilayah Desa Darmasaba",
|
||||||
"deskripsi": "Peringatan Hari Kesaktian Pancasila diikuti seluruh perangkat desa dan warga Desa Darmasaba dengan upacara bendera dan kegiatan budaya."
|
"deskripsi": "Hari Raya Galungan adalah perayaan kemenangan dharma melawan adharma. Warga Desa Darmasaba merayakannya dengan memasang penjor di depan rumah, sembahyang di pura, dan berkumpul bersama keluarga."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "event-budaya-2",
|
"id": "event-budaya-2",
|
||||||
|
"nama": "Hari Raya Kuningan",
|
||||||
|
"tanggal": "2025-01-25T06:00:00.000Z",
|
||||||
|
"lokasi": "Seluruh wilayah Desa Darmasaba",
|
||||||
|
"deskripsi": "Hari Raya Kuningan menandai akhir perayaan Galungan. Umat Hindu di Desa Darmasaba melaksanakan persembahyangan terakhir sebagai tanda pamitan para leluhur kembali ke alam nirwana."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "event-budaya-3",
|
||||||
|
"nama": "Upacara Melasti",
|
||||||
|
"tanggal": "2025-03-27T05:00:00.000Z",
|
||||||
|
"lokasi": "Pantai dan Sumber Air Suci, Badung",
|
||||||
|
"deskripsi": "Upacara Melasti dilaksanakan menjelang Hari Raya Nyepi sebagai ritual penyucian diri dan alam semesta. Masyarakat Desa Darmasaba bersama-sama melakukan persembahyangan dan membersihkan pratima pura ke sumber air suci."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "event-budaya-4",
|
||||||
|
"nama": "Parade Ogoh-Ogoh",
|
||||||
|
"tanggal": "2025-03-28T15:00:00.000Z",
|
||||||
|
"lokasi": "Jalan Utama Desa Darmasaba",
|
||||||
|
"deskripsi": "Parade ogoh-ogoh antar banjar se-Desa Darmasaba dalam rangka menyambut Hari Raya Nyepi. Ogoh-ogoh melambangkan kekuatan negatif yang kemudian dibakar sebagai simbol penyucian."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "event-budaya-5",
|
||||||
|
"nama": "Hari Raya Nyepi",
|
||||||
|
"tanggal": "2025-03-29T00:00:00.000Z",
|
||||||
|
"lokasi": "Seluruh wilayah Desa Darmasaba",
|
||||||
|
"deskripsi": "Hari Raya Nyepi adalah Tahun Baru Saka bagi umat Hindu. Seluruh warga Desa Darmasaba melaksanakan Catur Brata Penyepian: amati geni, amati karya, amati lelungan, dan amati lelanguan."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "event-budaya-6",
|
||||||
|
"nama": "Hari Raya Saraswati",
|
||||||
|
"tanggal": "2025-04-05T06:00:00.000Z",
|
||||||
|
"lokasi": "Pura dan Sekolah se-Desa Darmasaba",
|
||||||
|
"deskripsi": "Hari Raya Saraswati adalah hari turunnya ilmu pengetahuan. Warga Desa Darmasaba, khususnya pelajar dan akademisi, melaksanakan persembahyangan sebagai rasa syukur atas anugerah ilmu pengetahuan."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "event-budaya-7",
|
||||||
|
"nama": "Hari Raya Pagerwesi",
|
||||||
|
"tanggal": "2025-04-09T06:00:00.000Z",
|
||||||
|
"lokasi": "Pura Puseh Desa Darmasaba",
|
||||||
|
"deskripsi": "Hari Raya Pagerwesi adalah hari untuk memperkuat keimanan dan menghalau pengaruh negatif. Umat Hindu Desa Darmasaba melaksanakan persembahyangan dan meditasi bersama."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "event-budaya-8",
|
||||||
|
"nama": "Hari Raya Galungan",
|
||||||
|
"tanggal": "2025-05-14T06:00:00.000Z",
|
||||||
|
"lokasi": "Seluruh wilayah Desa Darmasaba",
|
||||||
|
"deskripsi": "Hari Raya Galungan periode kedua tahun 2025. Kemenangan dharma melawan adharma dirayakan dengan pemasangan penjor, persembahyangan di pura, dan acara adat bersama keluarga besar."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "event-budaya-9",
|
||||||
|
"nama": "Hari Raya Kuningan",
|
||||||
|
"tanggal": "2025-05-24T06:00:00.000Z",
|
||||||
|
"lokasi": "Seluruh wilayah Desa Darmasaba",
|
||||||
|
"deskripsi": "Hari Raya Kuningan periode kedua tahun 2025. Warga Desa Darmasaba melaksanakan persembahyangan akhir Galungan sebagai tanda pamitan para leluhur."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "event-budaya-10",
|
||||||
|
"nama": "Festival Budaya Desa Darmasaba",
|
||||||
|
"tanggal": "2025-06-15T09:00:00.000Z",
|
||||||
|
"lokasi": "Lapangan Desa Darmasaba",
|
||||||
|
"deskripsi": "Festival tahunan menampilkan kesenian tradisional Bali seperti tari kecak, legong, dan barong oleh sanggar seni dari Desa Darmasaba. Festival ini terbuka untuk umum dan menjadi ajang pelestarian budaya."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "event-budaya-11",
|
||||||
|
"nama": "Tumpek Landep",
|
||||||
|
"tanggal": "2025-06-28T06:00:00.000Z",
|
||||||
|
"lokasi": "Pura Desa Darmasaba",
|
||||||
|
"deskripsi": "Tumpek Landep adalah hari persembahan kepada benda-benda tajam dan peralatan yang menggunakan logam. Warga Desa Darmasaba melakukan pembersihan dan pemujaan terhadap peralatan kerja, kendaraan, dan senjata."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "event-budaya-12",
|
||||||
|
"nama": "Upacara Ngusaba Desa",
|
||||||
|
"tanggal": "2025-08-10T08:00:00.000Z",
|
||||||
|
"lokasi": "Pura Puseh Desa Darmasaba",
|
||||||
|
"deskripsi": "Upacara adat tahunan Ngusaba Desa sebagai bentuk rasa syukur kepada Ida Sang Hyang Widhi Wasa atas keselamatan dan kemakmuran desa sepanjang tahun."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "event-budaya-13",
|
||||||
|
"nama": "Perayaan HUT RI ke-80",
|
||||||
|
"tanggal": "2025-08-17T07:30:00.000Z",
|
||||||
|
"lokasi": "Balai Desa Darmasaba",
|
||||||
|
"deskripsi": "Peringatan Hari Ulang Tahun Kemerdekaan Republik Indonesia ke-80. Warga Desa Darmasaba melaksanakan upacara bendera, lomba-lomba tradisional, dan pertunjukan budaya."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "event-budaya-14",
|
||||||
|
"nama": "Hari Raya Galungan",
|
||||||
|
"tanggal": "2025-09-10T06:00:00.000Z",
|
||||||
|
"lokasi": "Seluruh wilayah Desa Darmasaba",
|
||||||
|
"deskripsi": "Hari Raya Galungan periode ketiga tahun 2025. Seluruh umat Hindu Desa Darmasaba merayakan kemenangan kebaikan dengan berbagai rangkaian upacara adat dan kegiatan budaya."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "event-budaya-15",
|
||||||
|
"nama": "Hari Raya Kuningan",
|
||||||
|
"tanggal": "2025-09-20T06:00:00.000Z",
|
||||||
|
"lokasi": "Seluruh wilayah Desa Darmasaba",
|
||||||
|
"deskripsi": "Hari Raya Kuningan periode ketiga tahun 2025. Rangkaian Galungan-Kuningan ditutup dengan persembahyangan dan acara kekeluargaan bersama warga Desa Darmasaba."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "event-budaya-16",
|
||||||
|
"nama": "Hari Kesaktian Pancasila",
|
||||||
|
"tanggal": "2025-10-01T07:00:00.000Z",
|
||||||
|
"lokasi": "Balai Desa Darmasaba",
|
||||||
|
"deskripsi": "Peringatan Hari Kesaktian Pancasila diikuti seluruh perangkat desa dan warga Desa Darmasaba dengan upacara bendera dan kegiatan budaya sebagai wujud rasa nasionalisme."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "event-budaya-17",
|
||||||
|
"nama": "Pementasan Wayang Kulit",
|
||||||
|
"tanggal": "2025-10-25T19:00:00.000Z",
|
||||||
|
"lokasi": "Wantilan Desa Darmasaba",
|
||||||
|
"deskripsi": "Pementasan wayang kulit semalam suntuk oleh dalang ternama dari Desa Darmasaba sebagai bagian dari pelestarian seni budaya Bali dan perayaan ulang tahun pura banjar."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "event-budaya-18",
|
||||||
"nama": "Upacara Ngusaba Desa",
|
"nama": "Upacara Ngusaba Desa",
|
||||||
"tanggal": "2025-11-15T08:00:00.000Z",
|
"tanggal": "2025-11-15T08:00:00.000Z",
|
||||||
"lokasi": "Pura Puseh Desa Darmasaba",
|
"lokasi": "Pura Puseh Desa Darmasaba",
|
||||||
"deskripsi": "Upacara adat tahunan Ngusaba Desa sebagai bentuk rasa syukur kepada Ida Sang Hyang Widhi Wasa atas keselamatan dan kemakmuran desa."
|
"deskripsi": "Upacara adat tahunan Ngusaba Desa sebagai bentuk rasa syukur kepada Ida Sang Hyang Widhi Wasa atas keselamatan dan kemakmuran desa."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "event-budaya-3",
|
"id": "event-budaya-19",
|
||||||
"nama": "Festival Budaya Desa Darmasaba",
|
"nama": "Tumpek Uduh",
|
||||||
"tanggal": "2026-05-20T09:00:00.000Z",
|
"tanggal": "2025-11-22T06:00:00.000Z",
|
||||||
"lokasi": "Lapangan Desa Darmasaba",
|
"lokasi": "Seluruh wilayah Desa Darmasaba",
|
||||||
"deskripsi": "Festival tahunan menampilkan kesenian tradisional Bali seperti tari kecak, legong, dan barong oleh sanggar seni dari Desa Darmasaba."
|
"deskripsi": "Tumpek Uduh adalah hari pemujaan kepada tumbuh-tumbuhan. Warga Desa Darmasaba melakukan persembahan kepada pepohonan dan tanaman sebagai bentuk syukur atas anugerah alam."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "event-budaya-4",
|
"id": "event-budaya-20",
|
||||||
"nama": "Perayaan HUT Desa Darmasaba",
|
"nama": "Pujawali Pura Puseh",
|
||||||
"tanggal": "2026-08-17T07:30:00.000Z",
|
"tanggal": "2025-12-10T06:00:00.000Z",
|
||||||
"lokasi": "Balai Desa Darmasaba",
|
"lokasi": "Pura Puseh Desa Darmasaba",
|
||||||
"deskripsi": "Peringatan Hari Ulang Tahun Kemerdekaan Republik Indonesia sekaligus hari jadi Desa Darmasaba dengan berbagai lomba dan pertunjukan budaya."
|
"deskripsi": "Upacara pujawali (ulang tahun pura) di Pura Puseh Desa Darmasaba. Seluruh krama desa bersama-sama melaksanakan persembahyangan dan menampilkan berbagai kesenian sakral."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "event-budaya-5",
|
"id": "event-budaya-21",
|
||||||
"nama": "Perayaan Galungan dan Kuningan",
|
"nama": "Perayaan Galungan dan Kuningan",
|
||||||
"tanggal": "2026-03-04T06:00:00.000Z",
|
"tanggal": "2026-03-04T06:00:00.000Z",
|
||||||
"lokasi": "Seluruh wilayah Desa Darmasaba",
|
"lokasi": "Seluruh wilayah Desa Darmasaba",
|
||||||
"deskripsi": "Rangkaian perayaan Hari Raya Galungan dan Kuningan sebagai hari kemenangan dharma melawan adharma, dirayakan seluruh umat Hindu di Desa Darmasaba."
|
"deskripsi": "Rangkaian perayaan Hari Raya Galungan dan Kuningan sebagai hari kemenangan dharma melawan adharma, dirayakan seluruh umat Hindu di Desa Darmasaba."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "event-budaya-6",
|
"id": "event-budaya-22",
|
||||||
|
"nama": "Upacara Melasti",
|
||||||
|
"tanggal": "2026-03-17T05:00:00.000Z",
|
||||||
|
"lokasi": "Pantai dan Sumber Air Suci, Badung",
|
||||||
|
"deskripsi": "Ritual penyucian diri dan benda sakral sebelum Nyepi 2026. Seluruh krama Desa Darmasaba beriringan membawa pratima pura menuju sumber air suci."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "event-budaya-23",
|
||||||
"nama": "Lomba Ogoh-Ogoh Desa",
|
"nama": "Lomba Ogoh-Ogoh Desa",
|
||||||
"tanggal": "2026-03-18T15:00:00.000Z",
|
"tanggal": "2026-03-18T15:00:00.000Z",
|
||||||
"lokasi": "Lapangan Desa Darmasaba",
|
"lokasi": "Lapangan Desa Darmasaba",
|
||||||
"deskripsi": "Lomba pembuatan dan parade ogoh-ogoh antar banjar se-Desa Darmasaba dalam rangka menyambut Hari Raya Nyepi."
|
"deskripsi": "Lomba pembuatan dan parade ogoh-ogoh antar banjar se-Desa Darmasaba dalam rangka menyambut Hari Raya Nyepi 2026."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "event-budaya-7",
|
"id": "event-budaya-24",
|
||||||
|
"nama": "Hari Raya Nyepi",
|
||||||
|
"tanggal": "2026-03-19T00:00:00.000Z",
|
||||||
|
"lokasi": "Seluruh wilayah Desa Darmasaba",
|
||||||
|
"deskripsi": "Tahun Baru Saka 1948. Seluruh warga Desa Darmasaba melaksanakan Catur Brata Penyepian dalam keheningan dan introspeksi diri."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "event-budaya-25",
|
||||||
|
"nama": "Hari Raya Kuningan",
|
||||||
|
"tanggal": "2026-03-14T06:00:00.000Z",
|
||||||
|
"lokasi": "Seluruh wilayah Desa Darmasaba",
|
||||||
|
"deskripsi": "Hari Raya Kuningan 2026. Warga Desa Darmasaba melaksanakan persembahyangan akhir Galungan sebagai tanda pamitan para leluhur kembali ke nirwana."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "event-budaya-26",
|
||||||
|
"nama": "Festival Budaya Desa Darmasaba",
|
||||||
|
"tanggal": "2026-05-20T09:00:00.000Z",
|
||||||
|
"lokasi": "Lapangan Desa Darmasaba",
|
||||||
|
"deskripsi": "Festival tahunan menampilkan kesenian tradisional Bali seperti tari kecak, legong, dan barong oleh sanggar seni dari Desa Darmasaba."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "event-budaya-27",
|
||||||
|
"nama": "Tumpek Krulut",
|
||||||
|
"tanggal": "2026-05-30T06:00:00.000Z",
|
||||||
|
"lokasi": "Pura Desa Darmasaba",
|
||||||
|
"deskripsi": "Tumpek Krulut adalah hari pemujaan kepada alat musik dan kesenian. Seniman dan pengrawit di Desa Darmasaba melaksanakan persembahan kepada gamelan dan alat musik tradisional."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "event-budaya-28",
|
||||||
"nama": "Pementasan Wayang Kulit",
|
"nama": "Pementasan Wayang Kulit",
|
||||||
"tanggal": "2026-06-10T19:00:00.000Z",
|
"tanggal": "2026-06-10T19:00:00.000Z",
|
||||||
"lokasi": "Wantilan Desa Darmasaba",
|
"lokasi": "Wantilan Desa Darmasaba",
|
||||||
"deskripsi": "Pementasan wayang kulit semalam suntuk oleh dalang dari Desa Darmasaba sebagai bagian dari pelestarian seni budaya Bali."
|
"deskripsi": "Pementasan wayang kulit semalam suntuk oleh dalang dari Desa Darmasaba sebagai bagian dari pelestarian seni budaya Bali."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "event-budaya-8",
|
"id": "event-budaya-29",
|
||||||
|
"nama": "Hari Raya Galungan",
|
||||||
|
"tanggal": "2026-08-01T06:00:00.000Z",
|
||||||
|
"lokasi": "Seluruh wilayah Desa Darmasaba",
|
||||||
|
"deskripsi": "Hari Raya Galungan 2026. Umat Hindu Desa Darmasaba merayakan kemenangan kebenaran dengan upacara adat, penjor, dan kegiatan budaya bersama."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "event-budaya-30",
|
||||||
|
"nama": "Perayaan HUT RI ke-81",
|
||||||
|
"tanggal": "2026-08-17T07:30:00.000Z",
|
||||||
|
"lokasi": "Balai Desa Darmasaba",
|
||||||
|
"deskripsi": "Peringatan Hari Ulang Tahun Kemerdekaan Republik Indonesia ke-81 sekaligus hari jadi Desa Darmasaba dengan berbagai lomba dan pertunjukan budaya."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "event-budaya-31",
|
||||||
"nama": "Upacara Melaspas Gedung Balai Banjar",
|
"nama": "Upacara Melaspas Gedung Balai Banjar",
|
||||||
"tanggal": "2026-09-05T08:00:00.000Z",
|
"tanggal": "2026-09-05T08:00:00.000Z",
|
||||||
"lokasi": "Banjar Desa Darmasaba",
|
"lokasi": "Banjar Desa Darmasaba",
|
||||||
"deskripsi": "Upacara Melaspas sebagai ritual penyucian bangunan baru balai banjar agar membawa keselamatan dan kesejahteraan bagi krama banjar."
|
"deskripsi": "Upacara Melaspas sebagai ritual penyucian bangunan baru balai banjar agar membawa keselamatan dan kesejahteraan bagi krama banjar."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "event-budaya-32",
|
||||||
|
"nama": "Hari Raya Kuningan",
|
||||||
|
"tanggal": "2026-08-11T06:00:00.000Z",
|
||||||
|
"lokasi": "Seluruh wilayah Desa Darmasaba",
|
||||||
|
"deskripsi": "Hari Raya Kuningan 2026. Warga Desa Darmasaba bersama keluarga besar melaksanakan persembahyangan penutup rangkaian Galungan-Kuningan."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "event-budaya-33",
|
||||||
|
"nama": "Hari Raya Saraswati",
|
||||||
|
"tanggal": "2026-10-03T06:00:00.000Z",
|
||||||
|
"lokasi": "Pura dan Sekolah se-Desa Darmasaba",
|
||||||
|
"deskripsi": "Hari Raya Saraswati 2026. Warga Desa Darmasaba, terutama pelajar dan pendidik, melaksanakan persembahyangan dan puja saraswati sebagai rasa syukur atas ilmu pengetahuan."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "event-budaya-34",
|
||||||
|
"nama": "Pujawali Pura Puseh",
|
||||||
|
"tanggal": "2026-11-20T06:00:00.000Z",
|
||||||
|
"lokasi": "Pura Puseh Desa Darmasaba",
|
||||||
|
"deskripsi": "Upacara pujawali tahunan di Pura Puseh Desa Darmasaba disertai pertunjukan topeng, gambuh, dan kesenian sakral lainnya."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -182,6 +182,28 @@ const eventBudayaState = proxy({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
findUpcoming: {
|
||||||
|
data: null as Prisma.EventBudayaGetPayload<object>[] | null,
|
||||||
|
loading: false,
|
||||||
|
async load() {
|
||||||
|
eventBudayaState.findUpcoming.loading = true;
|
||||||
|
try {
|
||||||
|
const res = await fetch("/api/desa/eventbudaya/find-upcoming");
|
||||||
|
const result = await res.json();
|
||||||
|
if (result?.success) {
|
||||||
|
eventBudayaState.findUpcoming.data = result.data ?? [];
|
||||||
|
} else {
|
||||||
|
eventBudayaState.findUpcoming.data = [];
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading upcoming events:", error);
|
||||||
|
eventBudayaState.findUpcoming.data = [];
|
||||||
|
} finally {
|
||||||
|
eventBudayaState.findUpcoming.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
delete: {
|
delete: {
|
||||||
loading: false,
|
loading: false,
|
||||||
async byId(id: string) {
|
async byId(id: string) {
|
||||||
|
|||||||
141
src/app/admin/(dashboard)/desa/event-budaya/[id]/page.tsx
Normal file
141
src/app/admin/(dashboard)/desa/event-budaya/[id]/page.tsx
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
'use client';
|
||||||
|
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
||||||
|
import eventBudayaState from '@/app/admin/(dashboard)/_state/desa/eventBudaya';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Paper,
|
||||||
|
Skeleton,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
} from '@mantine/core';
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { IconArrowBack, IconCalendarEvent, IconEdit, IconTrash } from '@tabler/icons-react';
|
||||||
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
export default function DetailEventBudaya() {
|
||||||
|
const state = useProxy(eventBudayaState);
|
||||||
|
const [modalHapus, setModalHapus] = useState(false);
|
||||||
|
const params = useParams();
|
||||||
|
const router = useRouter();
|
||||||
|
const id = params.id as string;
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
state.findUnique.load(id);
|
||||||
|
}, [id]);
|
||||||
|
|
||||||
|
const handleHapus = async () => {
|
||||||
|
await state.delete.byId(id);
|
||||||
|
setModalHapus(false);
|
||||||
|
router.push('/admin/desa/event-budaya');
|
||||||
|
};
|
||||||
|
|
||||||
|
if (state.findUnique.loading || !state.findUnique.data) {
|
||||||
|
return (
|
||||||
|
<Stack py={10}>
|
||||||
|
<Skeleton height={400} radius="md" />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = state.findUnique.data;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box px={{ base: 0, md: 'xs' }} py="xs">
|
||||||
|
<Button
|
||||||
|
variant="subtle"
|
||||||
|
onClick={() => router.push('/admin/desa/event-budaya')}
|
||||||
|
leftSection={<IconArrowBack size={20} color={colors['blue-button']} />}
|
||||||
|
mb={15}
|
||||||
|
>
|
||||||
|
Kembali
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Paper
|
||||||
|
withBorder
|
||||||
|
w={{ base: '100%', md: '70%' }}
|
||||||
|
bg={colors['white-1']}
|
||||||
|
p="lg"
|
||||||
|
radius="md"
|
||||||
|
shadow="sm"
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
|
<Group gap="sm">
|
||||||
|
<IconCalendarEvent size={22} color={colors['blue-button']} />
|
||||||
|
<Text fz="xl" fw="bold" c={colors['blue-button']}>
|
||||||
|
Detail Event Budaya
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
<Paper bg="#ECEEF8" p="md" radius="md" shadow="xs">
|
||||||
|
<Stack gap="sm">
|
||||||
|
<Box>
|
||||||
|
<Text fz="sm" fw={600} c="dimmed">Nama Event</Text>
|
||||||
|
<Text fz="md" fw={500}>{data.nama || '-'}</Text>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fz="sm" fw={600} c="dimmed">Tanggal</Text>
|
||||||
|
<Text fz="md">
|
||||||
|
{new Date(data.tanggal).toLocaleDateString('id-ID', {
|
||||||
|
weekday: 'long',
|
||||||
|
day: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
year: 'numeric',
|
||||||
|
})}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fz="sm" fw={600} c="dimmed">Lokasi</Text>
|
||||||
|
<Text fz="md">{data.lokasi || '-'}</Text>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{data.deskripsi && (
|
||||||
|
<Box>
|
||||||
|
<Text fz="sm" fw={600} c="dimmed">Deskripsi</Text>
|
||||||
|
<Text fz="md" style={{ wordBreak: 'break-word', whiteSpace: 'pre-wrap' }}>
|
||||||
|
{data.deskripsi}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Group gap="sm" mt="xs">
|
||||||
|
<Button
|
||||||
|
color="red"
|
||||||
|
variant="light"
|
||||||
|
radius="md"
|
||||||
|
leftSection={<IconTrash size={16} />}
|
||||||
|
loading={state.delete.loading}
|
||||||
|
onClick={() => setModalHapus(true)}
|
||||||
|
>
|
||||||
|
Hapus
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
color="blue"
|
||||||
|
variant="light"
|
||||||
|
radius="md"
|
||||||
|
leftSection={<IconEdit size={16} />}
|
||||||
|
onClick={() => router.push(`/admin/desa/event-budaya/${id}/edit`)}
|
||||||
|
>
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
<ModalKonfirmasiHapus
|
||||||
|
opened={modalHapus}
|
||||||
|
onClose={() => setModalHapus(false)}
|
||||||
|
onConfirm={handleHapus}
|
||||||
|
text="Apakah anda yakin ingin menghapus event budaya ini?"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
16
src/app/admin/(dashboard)/desa/event-budaya/layout.tsx
Normal file
16
src/app/admin/(dashboard)/desa/event-budaya/layout.tsx
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
'use client';
|
||||||
|
import { Stack, Title } from '@mantine/core';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
function Layout({ children }: { children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<Stack gap="lg">
|
||||||
|
<Title order={3} fw={700} style={{ color: '#1A1B1E' }}>
|
||||||
|
Kalender Event Budaya
|
||||||
|
</Title>
|
||||||
|
{children}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Layout;
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
'use client';
|
'use client';
|
||||||
|
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
||||||
import eventBudayaState from '@/app/admin/(dashboard)/_state/desa/eventBudaya';
|
import eventBudayaState from '@/app/admin/(dashboard)/_state/desa/eventBudaya';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import {
|
import {
|
||||||
@@ -22,9 +23,9 @@ import {
|
|||||||
Title,
|
Title,
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { useDebouncedValue } from '@mantine/hooks';
|
import { useDebouncedValue } from '@mantine/hooks';
|
||||||
import { IconCalendarEvent, IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react';
|
import { IconCalendarEvent, IconEdit, IconEye, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import HeaderSearch from '../../_com/header';
|
import HeaderSearch from '../../_com/header';
|
||||||
|
|
||||||
@@ -37,7 +38,7 @@ function EventBudayaPage() {
|
|||||||
placeholder="Cari nama atau lokasi..."
|
placeholder="Cari nama atau lokasi..."
|
||||||
searchIcon={<IconSearch size={20} />}
|
searchIcon={<IconSearch size={20} />}
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setSearch(e.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
<ListEventBudaya search={search} />
|
<ListEventBudaya search={search} />
|
||||||
</Box>
|
</Box>
|
||||||
@@ -48,6 +49,8 @@ function ListEventBudaya({ search }: { search: string }) {
|
|||||||
const state = useProxy(eventBudayaState);
|
const state = useProxy(eventBudayaState);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [debouncedSearch] = useDebouncedValue(search, 500);
|
const [debouncedSearch] = useDebouncedValue(search, 500);
|
||||||
|
const [modalHapus, setModalHapus] = useState(false);
|
||||||
|
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||||
|
|
||||||
const { data, page, totalPages, loading, load } = state.findMany;
|
const { data, page, totalPages, loading, load } = state.findMany;
|
||||||
|
|
||||||
@@ -55,6 +58,14 @@ function ListEventBudaya({ search }: { search: string }) {
|
|||||||
load(page, 10, debouncedSearch);
|
load(page, 10, debouncedSearch);
|
||||||
}, [page, debouncedSearch]);
|
}, [page, debouncedSearch]);
|
||||||
|
|
||||||
|
const handleHapus = async () => {
|
||||||
|
if (selectedId) {
|
||||||
|
await state.delete.byId(selectedId);
|
||||||
|
setModalHapus(false);
|
||||||
|
setSelectedId(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if (loading || !data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Stack py="md">
|
<Stack py="md">
|
||||||
@@ -82,19 +93,23 @@ function ListEventBudaya({ search }: { search: string }) {
|
|||||||
<Table highlightOnHover layout="fixed" withColumnBorders={false}>
|
<Table highlightOnHover layout="fixed" withColumnBorders={false}>
|
||||||
<TableThead>
|
<TableThead>
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh w="30%">Nama Event</TableTh>
|
<TableTh w="35%">Nama Event</TableTh>
|
||||||
<TableTh w="20%">Tanggal</TableTh>
|
<TableTh w="20%">Tanggal</TableTh>
|
||||||
<TableTh w="25%">Lokasi</TableTh>
|
<TableTh w="25%">Lokasi</TableTh>
|
||||||
<TableTh w="25%">Aksi</TableTh>
|
<TableTh w="20%">Aksi</TableTh>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
<TableTbody>
|
<TableTbody>
|
||||||
{data.length > 0 ? (
|
{data.length > 0 ? (
|
||||||
data.map((item) => (
|
data.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr
|
||||||
|
key={item.id}
|
||||||
|
style={{ cursor: 'pointer' }}
|
||||||
|
onClick={() => router.push(`/admin/desa/event-budaya/${item.id}`)}
|
||||||
|
>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Group gap="xs">
|
<Group gap="xs">
|
||||||
<IconCalendarEvent size={16} color="blue" />
|
<IconCalendarEvent size={16} color={colors['blue-button-5']} />
|
||||||
<Text fz="sm" fw={500} truncate="end" lineClamp={1}>
|
<Text fz="sm" fw={500} truncate="end" lineClamp={1}>
|
||||||
{item.nama}
|
{item.nama}
|
||||||
</Text>
|
</Text>
|
||||||
@@ -114,22 +129,29 @@ function ListEventBudaya({ search }: { search: string }) {
|
|||||||
{item.lokasi}
|
{item.lokasi}
|
||||||
</Text>
|
</Text>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd onClick={(e) => e.stopPropagation()}>
|
||||||
<Group gap="xs">
|
<Group gap="xs">
|
||||||
|
<ActionIcon
|
||||||
|
variant="light"
|
||||||
|
color="teal"
|
||||||
|
onClick={() => router.push(`/admin/desa/event-budaya/${item.id}`)}
|
||||||
|
>
|
||||||
|
<IconEye size={16} />
|
||||||
|
</ActionIcon>
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
variant="light"
|
variant="light"
|
||||||
color="blue"
|
color="blue"
|
||||||
onClick={() =>
|
onClick={() => router.push(`/admin/desa/event-budaya/${item.id}/edit`)}
|
||||||
router.push(`/admin/desa/event-budaya/${item.id}/edit`)
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<IconEdit size={16} />
|
<IconEdit size={16} />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
variant="light"
|
variant="light"
|
||||||
color="red"
|
color="red"
|
||||||
loading={state.delete.loading}
|
onClick={() => {
|
||||||
onClick={() => state.delete.byId(item.id)}
|
setSelectedId(item.id);
|
||||||
|
setModalHapus(true);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<IconTrash size={16} />
|
<IconTrash size={16} />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
@@ -160,6 +182,16 @@ function ListEventBudaya({ search }: { search: string }) {
|
|||||||
</Group>
|
</Group>
|
||||||
)}
|
)}
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
|
<ModalKonfirmasiHapus
|
||||||
|
opened={modalHapus}
|
||||||
|
onClose={() => {
|
||||||
|
setModalHapus(false);
|
||||||
|
setSelectedId(null);
|
||||||
|
}}
|
||||||
|
onConfirm={handleHapus}
|
||||||
|
text="Apakah anda yakin ingin menghapus event budaya ini?"
|
||||||
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
|
||||||
|
async function eventBudayaFindUpcoming() {
|
||||||
|
const today = new Date();
|
||||||
|
today.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await prisma.eventBudaya.findMany({
|
||||||
|
where: {
|
||||||
|
isActive: true,
|
||||||
|
tanggal: { gte: today },
|
||||||
|
},
|
||||||
|
orderBy: { tanggal: "asc" },
|
||||||
|
take: 20,
|
||||||
|
});
|
||||||
|
|
||||||
|
return { success: true, data };
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error di eventBudayaFindUpcoming:", e);
|
||||||
|
return { success: false, message: "Gagal mengambil event mendatang" };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default eventBudayaFindUpcoming;
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import Elysia, { t } from "elysia";
|
import Elysia, { t } from "elysia";
|
||||||
import eventBudayaFindMany from "./find-many";
|
import eventBudayaFindMany from "./find-many";
|
||||||
|
import eventBudayaFindUpcoming from "./find-upcoming";
|
||||||
import eventBudayaFindUnique from "./findUnique";
|
import eventBudayaFindUnique from "./findUnique";
|
||||||
import eventBudayaCreate from "./create";
|
import eventBudayaCreate from "./create";
|
||||||
import eventBudayaDelete from "./del";
|
import eventBudayaDelete from "./del";
|
||||||
@@ -7,6 +8,7 @@ import eventBudayaUpdate from "./updt";
|
|||||||
|
|
||||||
const EventBudaya = new Elysia({ prefix: "/eventbudaya", tags: ["Desa/Event Budaya"] })
|
const EventBudaya = new Elysia({ prefix: "/eventbudaya", tags: ["Desa/Event Budaya"] })
|
||||||
.get("/find-many", eventBudayaFindMany)
|
.get("/find-many", eventBudayaFindMany)
|
||||||
|
.get("/find-upcoming", eventBudayaFindUpcoming)
|
||||||
.get("/:id", eventBudayaFindUnique)
|
.get("/:id", eventBudayaFindUnique)
|
||||||
.post("/create", eventBudayaCreate, {
|
.post("/create", eventBudayaCreate, {
|
||||||
body: t.Object({
|
body: t.Object({
|
||||||
|
|||||||
120
src/app/darmasaba/(pages)/desa/event-budaya/page.tsx
Normal file
120
src/app/darmasaba/(pages)/desa/event-budaya/page.tsx
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
'use client';
|
||||||
|
import eventBudayaState from '@/app/admin/(dashboard)/_state/desa/eventBudaya';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Center,
|
||||||
|
Group,
|
||||||
|
Pagination,
|
||||||
|
Paper,
|
||||||
|
Skeleton,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
Title,
|
||||||
|
} from '@mantine/core';
|
||||||
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { IconCalendarEvent, IconMapPin } from '@tabler/icons-react';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
const LIMIT = 5;
|
||||||
|
|
||||||
|
function KalenderEventBudayaPage() {
|
||||||
|
const state = useProxy(eventBudayaState);
|
||||||
|
const { data, loading, page, totalPages } = state.findMany;
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
state.findMany.load(1, LIMIT);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handlePageChange = (p: number) => {
|
||||||
|
state.findMany.load(p, LIMIT);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box px={{ base: 'md', md: 100 }} pb="xl">
|
||||||
|
<Title order={2} mb="xl" c={colors['blue-button']}>
|
||||||
|
Kalender Event Budaya
|
||||||
|
</Title>
|
||||||
|
|
||||||
|
{loading || !data ? (
|
||||||
|
<Stack gap="md">
|
||||||
|
{Array(LIMIT)
|
||||||
|
.fill(0)
|
||||||
|
.map((_, i) => (
|
||||||
|
<Skeleton key={i} h={80} radius="lg" />
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
) : data.length === 0 ? (
|
||||||
|
<Center py="xl">
|
||||||
|
<Stack align="center" gap="xs">
|
||||||
|
<IconCalendarEvent size={48} color={colors['blue-button-3']} />
|
||||||
|
<Text c="dimmed" fz="sm">
|
||||||
|
Belum ada event budaya.
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
</Center>
|
||||||
|
) : (
|
||||||
|
<Stack gap="md">
|
||||||
|
{data.map((item) => (
|
||||||
|
<Paper
|
||||||
|
key={item.id}
|
||||||
|
withBorder
|
||||||
|
p="lg"
|
||||||
|
radius="lg"
|
||||||
|
shadow="sm"
|
||||||
|
style={{ borderLeft: `4px solid ${colors['blue-button-5']}` }}
|
||||||
|
>
|
||||||
|
<Group justify="space-between" align="flex-start" wrap="nowrap">
|
||||||
|
<Group gap="sm" align="flex-start" wrap="nowrap" style={{ flex: 1, minWidth: 0 }}>
|
||||||
|
<IconCalendarEvent
|
||||||
|
size={22}
|
||||||
|
color={colors['blue-button-5']}
|
||||||
|
style={{ flexShrink: 0, marginTop: 2 }}
|
||||||
|
/>
|
||||||
|
<Stack gap={4} style={{ minWidth: 0 }}>
|
||||||
|
<Text fw={600} fz="md" lineClamp={2}>
|
||||||
|
{item.nama}
|
||||||
|
</Text>
|
||||||
|
<Text fz="sm" c="dimmed">
|
||||||
|
{new Date(item.tanggal).toLocaleDateString('id-ID', {
|
||||||
|
day: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
year: 'numeric',
|
||||||
|
})}
|
||||||
|
</Text>
|
||||||
|
{item.deskripsi && (
|
||||||
|
<Text fz="sm" c="dimmed" lineClamp={2} mt={2}>
|
||||||
|
{item.deskripsi}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
<Group gap={4} style={{ flexShrink: 0 }} align="center">
|
||||||
|
<IconMapPin size={14} color={colors['grey']['2']} />
|
||||||
|
<Text fz="sm" c="dimmed" style={{ whiteSpace: 'nowrap' }}>
|
||||||
|
{item.lokasi}
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
|
</Group>
|
||||||
|
</Paper>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{totalPages > 1 && (
|
||||||
|
<Group justify="center" mt="md">
|
||||||
|
<Pagination
|
||||||
|
total={totalPages}
|
||||||
|
value={page}
|
||||||
|
onChange={handlePageChange}
|
||||||
|
color={colors['blue-button']}
|
||||||
|
radius="md"
|
||||||
|
/>
|
||||||
|
</Group>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default KalenderEventBudayaPage;
|
||||||
@@ -39,10 +39,11 @@ function LayoutTabsKegiatanDesa({ children }: { children: React.ReactNode }) {
|
|||||||
router.push(`/darmasaba/desa/kegiatan-desa/semua?${params.toString()}`);
|
router.push(`/darmasaba/desa/kegiatan-desa/semua?${params.toString()}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const kategoriOptions = (kategoriState.findMany.data || []).map((k: any) => ({
|
const kategoriOptions = Array.from(
|
||||||
value: k.nama,
|
new Map(
|
||||||
label: k.nama,
|
(kategoriState.findMany.data || []).map((k: any) => [k.nama, { value: k.nama, label: k.nama }])
|
||||||
}));
|
).values()
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack pos="relative" bg={colors.Bg} py="xl" gap="22">
|
<Stack pos="relative" bg={colors.Bg} py="xl" gap="22">
|
||||||
|
|||||||
@@ -90,6 +90,11 @@ const navbarListMenu = [
|
|||||||
id: "2.8",
|
id: "2.8",
|
||||||
name: "Kegiatan Desa",
|
name: "Kegiatan Desa",
|
||||||
href: "/darmasaba/desa/kegiatan-desa/semua"
|
href: "/darmasaba/desa/kegiatan-desa/semua"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "2.9",
|
||||||
|
name: "Kalender Event Budaya",
|
||||||
|
href: "/darmasaba/desa/event-budaya"
|
||||||
}
|
}
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|||||||
Reference in New Issue
Block a user