feat: update components with Mantine UI and improve dark mode support
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
285
Dashboard-MD/BUMDES.md
Normal file
285
Dashboard-MD/BUMDES.md
Normal file
@@ -0,0 +1,285 @@
|
||||
# BUMDES & UMKM Desa — UI & Functional Specification
|
||||
|
||||
Dokumen ini menjelaskan spesifikasi **UI, tata letak, gaya visual, responsivitas, serta ekspektasi data & interaksi** untuk halaman **BUMDes & UMKM Desa (Jenna Analytic Module)**. Dokumen ini menjadi kontrak desain antara **UI/UX, Frontend, dan Backend**.
|
||||
|
||||
---
|
||||
|
||||
## 1. Tujuan Halaman
|
||||
|
||||
Halaman **BUMDes & UMKM Desa** berfungsi sebagai dashboard monitoring:
|
||||
|
||||
* Aktivitas UMKM desa
|
||||
* Performa penjualan produk unggulan
|
||||
* Omzet BUMDes
|
||||
* Stok & tren produk
|
||||
|
||||
Target pengguna:
|
||||
|
||||
* Kepala Desa
|
||||
* Admin Desa
|
||||
* Operator BUMDes
|
||||
|
||||
---
|
||||
|
||||
## 2. Tata Letak Keseluruhan (Layout)
|
||||
|
||||
### 2.1 Struktur Utama
|
||||
|
||||
Layout menggunakan pola **Dashboard Sidebar Layout**:
|
||||
|
||||
* **Sidebar kiri (fixed)**
|
||||
* **Topbar/header (fixed)**
|
||||
* **Main content (scrollable)**
|
||||
|
||||
Grid utama:
|
||||
|
||||
* Desktop: `12-column grid`
|
||||
* Tablet: `8-column grid`
|
||||
* Mobile: `4-column grid`
|
||||
|
||||
---
|
||||
|
||||
### 2.2 Sidebar
|
||||
|
||||
Komponen:
|
||||
|
||||
* Logo DESA + tagline
|
||||
* Search bar ("cari apa saja")
|
||||
* Menu navigasi vertikal
|
||||
|
||||
Menu aktif:
|
||||
|
||||
* **Bumdes & UMKM Desa** (highlight biru muda)
|
||||
|
||||
Perilaku:
|
||||
|
||||
* Collapsible pada layar kecil
|
||||
* Ikon + label (label hidden di mobile)
|
||||
|
||||
---
|
||||
|
||||
### 2.3 Topbar
|
||||
|
||||
Komponen:
|
||||
|
||||
* Nama desa
|
||||
* Nama & jabatan user
|
||||
* Avatar user
|
||||
* Ikon notifikasi (badge)
|
||||
* Ikon pengaturan
|
||||
|
||||
Perilaku:
|
||||
|
||||
* Sticky
|
||||
* Dropdown user menu
|
||||
|
||||
---
|
||||
|
||||
## 3. Komponen UI Utama
|
||||
|
||||
### 3.1 KPI Cards (Ringkasan Atas)
|
||||
|
||||
Menampilkan ringkasan metrik utama:
|
||||
|
||||
| KPI | Deskripsi |
|
||||
| -------------- | ---------------------- |
|
||||
| UMKM Aktif | Jumlah UMKM beroperasi |
|
||||
| UMKM Terdaftar | Total UMKM terdaftar |
|
||||
| Omzet | Omzet BUMDes per bulan |
|
||||
| Kategori UMKM | Jumlah kategori aktif |
|
||||
|
||||
Karakteristik:
|
||||
|
||||
* Card rounded
|
||||
* Icon di kanan
|
||||
* Angka besar (headline)
|
||||
|
||||
---
|
||||
|
||||
### 3.2 Update Penjualan Produk
|
||||
|
||||
Komponen:
|
||||
|
||||
* Section header berwarna biru gelap
|
||||
* Button filter waktu:
|
||||
|
||||
* "Minggu ini"
|
||||
* "Bulan ini"
|
||||
|
||||
Perilaku:
|
||||
|
||||
* Toggle data chart & tabel
|
||||
* Default: Bulan ini
|
||||
|
||||
---
|
||||
|
||||
### 3.3 Produk Unggulan (Left Column)
|
||||
|
||||
Sub-komponen:
|
||||
|
||||
1. **Total Penjualan**
|
||||
|
||||
* Nominal
|
||||
* Persentase perubahan
|
||||
|
||||
2. **Produk Aktif**
|
||||
|
||||
* Jumlah produk
|
||||
* Jumlah kategori
|
||||
|
||||
3. **Total Transaksi**
|
||||
|
||||
* Jumlah transaksi bulan berjalan
|
||||
|
||||
4. **Top 3 Produk Terlaris**
|
||||
|
||||
* Ranking
|
||||
* Nama produk
|
||||
* Pelaku UMKM
|
||||
* Growth indicator
|
||||
|
||||
---
|
||||
|
||||
### 3.4 Detail Penjualan Produk (Right Column)
|
||||
|
||||
Bentuk:
|
||||
|
||||
* Tabel data
|
||||
|
||||
Kolom:
|
||||
|
||||
* Produk
|
||||
* Penjualan bulan ini
|
||||
* Bulan lalu
|
||||
* Trend
|
||||
* Volume
|
||||
* Stok
|
||||
* Aksi
|
||||
|
||||
Elemen khusus:
|
||||
|
||||
* Trend: arrow hijau/merah
|
||||
* Stok: badge warna (aman / kritis)
|
||||
* Aksi: tombol "Detail"
|
||||
|
||||
Filter:
|
||||
|
||||
* Dropdown filter kategori / stok
|
||||
|
||||
---
|
||||
|
||||
## 4. Gaya & Tema Visual
|
||||
|
||||
### 4.1 Warna
|
||||
|
||||
| Elemen | Warna |
|
||||
| ---------- | ------------------------- |
|
||||
| Primary | Biru tua (#183A63 approx) |
|
||||
| Secondary | Biru muda (#E6F0FF) |
|
||||
| Success | Hijau (#22C55E) |
|
||||
| Danger | Merah (#EF4444) |
|
||||
| Background | Abu muda / putih |
|
||||
|
||||
---
|
||||
|
||||
### 4.2 Tipografi
|
||||
|
||||
* Font family: **Inter / Poppins / Sans-serif modern**
|
||||
* Heading: Semi-bold
|
||||
* Body text: Regular
|
||||
* Angka KPI: Bold
|
||||
|
||||
Ukuran:
|
||||
|
||||
* H1: 24–28px
|
||||
* H2: 18–20px
|
||||
* Body: 14–16px
|
||||
|
||||
---
|
||||
|
||||
### 4.3 Spasi & Card
|
||||
|
||||
* Padding card: 16–20px
|
||||
* Gap antar card: 16px
|
||||
* Border radius: 12–16px
|
||||
* Shadow ringan
|
||||
|
||||
---
|
||||
|
||||
## 5. Responsivitas
|
||||
|
||||
### Desktop
|
||||
|
||||
* Sidebar terbuka penuh
|
||||
* Tabel full
|
||||
|
||||
### Tablet
|
||||
|
||||
* Sidebar collapse
|
||||
* KPI card wrap
|
||||
|
||||
### Mobile
|
||||
|
||||
* Sidebar drawer
|
||||
* KPI card stack
|
||||
* Tabel menjadi card list
|
||||
|
||||
---
|
||||
|
||||
## 6. Interaksi & UX Behavior
|
||||
|
||||
* Hover effect pada card & row tabel
|
||||
* Tooltip pada ikon & trend
|
||||
* Loading skeleton saat fetch data
|
||||
* Empty state ("Belum ada data")
|
||||
|
||||
---
|
||||
|
||||
## 7. Ekspektasi Data (Backend Contract)
|
||||
|
||||
### KPI API
|
||||
|
||||
```json
|
||||
{
|
||||
"umkmAktif": 45,
|
||||
"umkmTerdaftar": 68,
|
||||
"omzet": 48000000,
|
||||
"kategori": 34
|
||||
}
|
||||
```
|
||||
|
||||
### Produk API
|
||||
|
||||
```json
|
||||
{
|
||||
"produk": "Beras Premium Organik",
|
||||
"penjualanBulanIni": 8500000,
|
||||
"bulanLalu": 8500000,
|
||||
"trend": 10,
|
||||
"volume": "650 Kg",
|
||||
"stok": "850 Kg"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Catatan Implementasi Teknis
|
||||
|
||||
* Frontend: React + Vite
|
||||
* State management: query-based (TanStack Query)
|
||||
* Chart (jika ditambah): Recharts / Chart.js
|
||||
* Format uang: Intl.NumberFormat("id-ID")
|
||||
|
||||
---
|
||||
|
||||
## 9. Acceptance Checklist
|
||||
|
||||
* [ ] Semua KPI muncul & valid
|
||||
* [ ] Filter waktu berfungsi
|
||||
* [ ] Trend naik/turun akurat
|
||||
* [ ] Responsif mobile
|
||||
* [ ] Empty & loading state tersedia
|
||||
|
||||
---
|
||||
|
||||
Dokumen ini bersifat **final reference** untuk implementasi halaman **BUMDes & UMKM Desa**.
|
||||
224
Dashboard-MD/KEAMANAN.md
Normal file
224
Dashboard-MD/KEAMANAN.md
Normal file
@@ -0,0 +1,224 @@
|
||||
# KEAMANAN
|
||||
|
||||
Dokumen ini mendeskripsikan spesifikasi UI/UX dan ekspektasi teknis untuk modul **Keamanan Lingkungan Desa** pada sistem dashboard DESA. Dokumen ini menjadi acuan bersama bagi tim desain, frontend, dan backend.
|
||||
|
||||
---
|
||||
|
||||
## 1. Tujuan Halaman
|
||||
|
||||
Memberikan visibilitas real-time dan historis terhadap kondisi keamanan desa, meliputi:
|
||||
|
||||
* Status CCTV aktif
|
||||
* Laporan kejadian keamanan
|
||||
* Peta lokasi CCTV
|
||||
* Monitoring tren dan respons kejadian
|
||||
|
||||
Target pengguna utama:
|
||||
|
||||
* Kepala Desa
|
||||
* Perangkat Desa / Petugas Keamanan
|
||||
|
||||
---
|
||||
|
||||
## 2. Struktur Tata Letak (Layout)
|
||||
|
||||
### 2.1 Pola Umum
|
||||
|
||||
* **Layout dashboard dua kolom**
|
||||
|
||||
* Kolom kiri: monitoring visual (peta)
|
||||
* Kolom kanan: daftar laporan keamanan
|
||||
* Header KPI ringkas di bagian atas
|
||||
* Semua konten dibungkus dalam **card container** dengan radius konsisten
|
||||
|
||||
Grid:
|
||||
|
||||
* Desktop: 12-column grid
|
||||
* Tablet: 8-column grid
|
||||
* Mobile: 1-column (stack vertikal)
|
||||
|
||||
---
|
||||
|
||||
## 3. Komponen UI
|
||||
|
||||
### 3.1 KPI Cards (Header)
|
||||
|
||||
1. **CCTV Aktif**
|
||||
|
||||
* Value: `20`
|
||||
* Subtext: `Kamera Online`
|
||||
* Ikon: Kamera CCTV
|
||||
|
||||
2. **Laporan Keamanan**
|
||||
|
||||
* Value: `15`
|
||||
* Subtext: `Minggu ini`
|
||||
* Ikon: Chat / laporan
|
||||
|
||||
Perilaku:
|
||||
|
||||
* Tooltip saat hover (penjelasan metrik)
|
||||
* Klik opsional → navigasi ke halaman detail
|
||||
|
||||
---
|
||||
|
||||
### 3.2 Peta Keamanan CCTV
|
||||
|
||||
**Judul Card:** Peta Keamanan CCTV
|
||||
**Subjudul:** Titik Lokasi CCTV
|
||||
|
||||
Isi:
|
||||
|
||||
* Embedded map (Google Maps / Mapbox)
|
||||
* Marker lokasi CCTV
|
||||
|
||||
* Hijau: aktif
|
||||
* Abu-abu: offline (future)
|
||||
|
||||
Kontrol:
|
||||
|
||||
* Toggle tampilan (grid / fullscreen)
|
||||
* Zoom in / out
|
||||
|
||||
Interaksi:
|
||||
|
||||
* Klik marker → popup info CCTV
|
||||
|
||||
* ID CCTV
|
||||
* Lokasi
|
||||
* Status
|
||||
* Waktu terakhir aktif
|
||||
|
||||
---
|
||||
|
||||
### 3.3 Daftar Laporan Keamanan
|
||||
|
||||
**Judul:** Laporan Keamanan Lingkungan
|
||||
|
||||
Item laporan (card list):
|
||||
|
||||
* Judul kejadian (contoh: *Pencurian Motor*)
|
||||
* Timestamp relatif ("2 jam yang lalu")
|
||||
* Tanggal & jam
|
||||
* Lokasi kejadian
|
||||
* Badge status: `Baru`
|
||||
|
||||
Perilaku:
|
||||
|
||||
* Scrollable container
|
||||
* Klik item → detail laporan (modal / halaman baru)
|
||||
|
||||
---
|
||||
|
||||
## 4. Gaya & Tema Visual
|
||||
|
||||
### 4.1 Warna
|
||||
|
||||
* Primary: Navy Blue (`#1F3A5F` – perkiraan)
|
||||
* Background: Light Blue / Off-white (`#F5F9FC`)
|
||||
* Card: Putih
|
||||
* Success: Hijau (status aktif)
|
||||
* Alert: Merah muda / merah untuk laporan baru
|
||||
|
||||
### 4.2 Tipografi
|
||||
|
||||
* Font utama: Sans-serif modern (Inter / Poppins)
|
||||
* Heading: Semi-bold
|
||||
* Body text: Regular
|
||||
* Angka KPI: Bold
|
||||
|
||||
Ukuran relatif:
|
||||
|
||||
* Heading: 16–18px
|
||||
* Body: 13–14px
|
||||
* KPI Value: 22–28px
|
||||
|
||||
### 4.3 Spasi & Elevasi
|
||||
|
||||
* Padding card: 16–20px
|
||||
* Gap antar card: 16px
|
||||
* Border radius: 12–16px
|
||||
* Shadow ringan untuk card
|
||||
|
||||
---
|
||||
|
||||
## 5. Responsivitas
|
||||
|
||||
### Desktop
|
||||
|
||||
* Peta dan laporan tampil berdampingan
|
||||
|
||||
### Tablet
|
||||
|
||||
* KPI tetap horizontal
|
||||
* Peta dan laporan bisa stack 2 baris
|
||||
|
||||
### Mobile
|
||||
|
||||
* Semua komponen stack vertikal
|
||||
* Peta dalam mode scroll / fullscreen
|
||||
* List laporan menjadi full-width
|
||||
|
||||
---
|
||||
|
||||
## 6. Ekspektasi Interaksi & UX
|
||||
|
||||
* Hover state pada card & list item
|
||||
* Tooltip pada ikon KPI
|
||||
* Loading skeleton saat data fetch
|
||||
* Empty state bila tidak ada laporan
|
||||
* Badge status real-time (baru / diproses / selesai – future)
|
||||
|
||||
---
|
||||
|
||||
## 7. Ekspektasi Data (Backend Contract)
|
||||
|
||||
### 7.1 KPI Summary
|
||||
|
||||
```json
|
||||
{
|
||||
"cctvActive": 20,
|
||||
"securityReportsWeekly": 15
|
||||
}
|
||||
```
|
||||
|
||||
### 7.2 CCTV Locations
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": "CCTV-01",
|
||||
"lat": -8.5,
|
||||
"lng": 115.2,
|
||||
"status": "active",
|
||||
"lastSeen": "2025-10-06T14:30:00Z"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### 7.3 Security Reports
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": "REP-001",
|
||||
"title": "Pencurian Motor",
|
||||
"reportedAt": "2025-10-06T15:30:00Z",
|
||||
"location": "Jl. Kecubung 20",
|
||||
"status": "new"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Catatan Pengembangan Lanjutan
|
||||
|
||||
* Filter laporan berdasarkan jenis & waktu
|
||||
* Integrasi snapshot CCTV
|
||||
* Heatmap area rawan
|
||||
* Role-based access (admin / petugas)
|
||||
|
||||
---
|
||||
|
||||
Dokumen ini wajib diperbarui bila terdapat perubahan UI atau kebutuhan data pada modul **Keamanan**.
|
||||
239
Dashboard-MD/KINERJA-DIVISI-2.md
Normal file
239
Dashboard-MD/KINERJA-DIVISI-2.md
Normal file
@@ -0,0 +1,239 @@
|
||||
# KINERJA-DIVISI-2
|
||||
|
||||
## 1. Tujuan Halaman
|
||||
|
||||
Halaman **Kinerja Divisi** berfungsi sebagai dashboard operasional untuk memantau progres pekerjaan lintas divisi perangkat desa secara real-time, mencakup status tugas, arsip digital, kegiatan, diskusi internal, dan statistik pendukung.
|
||||
|
||||
Halaman ini dipakai oleh:
|
||||
|
||||
* Kepala Desa
|
||||
* Sekretaris Desa
|
||||
* Kepala Divisi
|
||||
* Admin Operasional
|
||||
|
||||
---
|
||||
|
||||
## 2. Struktur Tata Letak (Overall Layout)
|
||||
|
||||
### Pola Layout
|
||||
|
||||
* **Dashboard berbasis Card + Grid**
|
||||
* Scroll vertikal satu halaman
|
||||
* Tidak ada sidebar khusus (diasumsikan sudah ada global sidebar)
|
||||
|
||||
### Urutan Section (Top → Bottom)
|
||||
|
||||
1. Grafik Progres Tugas per Divisi
|
||||
2. Ringkasan Tugas per Divisi (Sekretariat, Keuangan, Sosial, Humas)
|
||||
3. Arsip Digital Perangkat Desa
|
||||
4. Kartu Progres Kegiatan (Event / Program)
|
||||
5. Statistik Dokumen & Progres Kegiatan
|
||||
6. Diskusi Internal
|
||||
7. Agenda / Acara Hari Ini
|
||||
|
||||
---
|
||||
|
||||
## 3. Komponen UI
|
||||
|
||||
### 3.1 Grafik Progres Tugas per Divisi
|
||||
|
||||
**Tipe:** Grouped Bar Chart
|
||||
|
||||
**Data:**
|
||||
|
||||
* Divisi: Sekretariat, Keuangan, Sosial, Humas
|
||||
* Status:
|
||||
|
||||
* Selesai
|
||||
* Berjalan
|
||||
* Tertunda
|
||||
|
||||
**Interaksi:**
|
||||
|
||||
* Hover tooltip menampilkan jumlah tugas
|
||||
* Legend clickable (toggle series)
|
||||
|
||||
---
|
||||
|
||||
### 3.2 Card Ringkasan Divisi
|
||||
|
||||
Setiap divisi memiliki satu card berisi daftar tugas.
|
||||
|
||||
**Struktur Card:**
|
||||
|
||||
* Judul divisi
|
||||
* List tugas:
|
||||
|
||||
* Nama tugas
|
||||
* Badge status: `selesai | proses | tertunda`
|
||||
|
||||
**Status Badge:**
|
||||
|
||||
* Hijau: Selesai
|
||||
* Kuning: Proses
|
||||
* Merah: Tertunda
|
||||
|
||||
---
|
||||
|
||||
### 3.3 Arsip Digital Perangkat Desa
|
||||
|
||||
**Tipe:** Action Card / Button Card
|
||||
|
||||
**Item:**
|
||||
|
||||
* Surat Keputusan
|
||||
* Laporan Keuangan
|
||||
* Dokumentasi
|
||||
* Notulensi Rapat
|
||||
|
||||
**Interaksi:**
|
||||
|
||||
* Klik membuka modul arsip
|
||||
* Role-based access (view / upload)
|
||||
|
||||
---
|
||||
|
||||
### 3.4 Progres Kegiatan / Program
|
||||
|
||||
**Tipe:** Progress Card
|
||||
|
||||
**Isi:**
|
||||
|
||||
* Nama kegiatan
|
||||
* Progress bar horizontal
|
||||
* Tanggal
|
||||
* Badge status (selesai)
|
||||
|
||||
---
|
||||
|
||||
### 3.5 Statistik Dokumen
|
||||
|
||||
#### a. Jumlah Dokumen
|
||||
|
||||
**Tipe:** Bar Chart
|
||||
|
||||
* Kategori: Gambar, Dokumen
|
||||
|
||||
#### b. Progres Kegiatan
|
||||
|
||||
**Tipe:** Pie Chart
|
||||
|
||||
**Status:**
|
||||
|
||||
* Selesai
|
||||
* Dikerjakan
|
||||
* Segera Dikerjakan
|
||||
* Dibatalkan
|
||||
|
||||
---
|
||||
|
||||
### 3.6 Diskusi Internal
|
||||
|
||||
**Tipe:** List Card
|
||||
|
||||
**Isi:**
|
||||
|
||||
* Judul diskusi
|
||||
* Pengirim
|
||||
* Timestamp
|
||||
|
||||
**Interaksi:**
|
||||
|
||||
* Klik membuka thread diskusi
|
||||
|
||||
---
|
||||
|
||||
### 3.7 Agenda / Acara Hari Ini
|
||||
|
||||
**Tipe:** Informational Card
|
||||
|
||||
**State:**
|
||||
|
||||
* Empty state: "Tidak ada acara hari ini"
|
||||
|
||||
---
|
||||
|
||||
## 4. Gaya & Tema Visual
|
||||
|
||||
### 4.1 Warna
|
||||
|
||||
* Background utama: `#0F172A` / `#111827`
|
||||
* Card: `#1E293B`
|
||||
* Primary accent: Biru tua
|
||||
* Success: Hijau
|
||||
* Warning: Kuning
|
||||
* Danger: Merah
|
||||
|
||||
### 4.2 Tipografi
|
||||
|
||||
* Font utama: **Inter / Poppins**
|
||||
* Heading: Semi-bold
|
||||
* Body: Regular
|
||||
* Angka & statistik: Medium
|
||||
|
||||
### 4.3 Spasi & Radius
|
||||
|
||||
* Padding card: 16–24px
|
||||
* Gap grid: 16–20px
|
||||
* Border radius: 12–16px
|
||||
|
||||
---
|
||||
|
||||
## 5. Responsivitas
|
||||
|
||||
### Desktop
|
||||
|
||||
* Multi-column grid (2–4 kolom)
|
||||
|
||||
### Tablet
|
||||
|
||||
* Grid 2 kolom
|
||||
* Grafik full-width
|
||||
|
||||
### Mobile
|
||||
|
||||
* Semua card 1 kolom
|
||||
* Chart di-scroll horizontal bila perlu
|
||||
|
||||
---
|
||||
|
||||
## 6. Ekspektasi Interaksi & Data
|
||||
|
||||
### 6.1 State Management
|
||||
|
||||
* Filter status tugas
|
||||
* Sinkronisasi real-time (polling / websocket)
|
||||
|
||||
### 6.2 Contoh Struktur Data
|
||||
|
||||
```json
|
||||
{
|
||||
"division": "Keuangan",
|
||||
"tasks": [
|
||||
{ "title": "Laporan APBDes", "status": "selesai" },
|
||||
{ "title": "Verifikasi Dana", "status": "tertunda" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Acceptance Criteria
|
||||
|
||||
* Semua status warna konsisten
|
||||
* Chart sesuai data backend
|
||||
* Empty state ditampilkan bila data kosong
|
||||
* Mobile friendly tanpa overflow
|
||||
|
||||
---
|
||||
|
||||
## 8. Catatan Implementasi
|
||||
|
||||
* Chart library: Chart.js / Recharts / ECharts
|
||||
* Dark mode default
|
||||
* Akses berbasis role (viewer, editor, admin)
|
||||
|
||||
---
|
||||
|
||||
**Status Dokumen:** Final Draft
|
||||
**Digunakan untuk:** Implementasi Frontend & Kontrak API
|
||||
225
Dashboard-MD/PENGAUDAN.md
Normal file
225
Dashboard-MD/PENGAUDAN.md
Normal file
@@ -0,0 +1,225 @@
|
||||
# PENGADUAN-2.md
|
||||
|
||||
## 1. Tujuan Halaman
|
||||
|
||||
Dashboard **Pengaduan Masyarakat** berfungsi untuk memantau, menganalisis, dan menindaklanjuti pengaduan warga secara real-time. Halaman ini mendukung **Light Mode** dan **Dark Mode** dengan struktur UI dan interaksi yang konsisten.
|
||||
|
||||
---
|
||||
|
||||
## 2. Struktur Komponen UI
|
||||
|
||||
### 2.1 Summary Cards (Statistik Utama)
|
||||
|
||||
Menampilkan ringkasan status pengaduan bulan berjalan.
|
||||
|
||||
**Komponen:**
|
||||
|
||||
* Total Pengaduan
|
||||
* Baru (Belum diproses)
|
||||
* Diproses (Sedang ditangani)
|
||||
* Selesai (Terselesaikan)
|
||||
|
||||
**Elemen UI:**
|
||||
|
||||
* Angka utama (bold, besar)
|
||||
* Label deskriptif
|
||||
* Ikon status (chat, alert, clock, check)
|
||||
|
||||
**Interaksi:**
|
||||
|
||||
* Hover: efek elevasi + highlight border
|
||||
* Click (opsional): filter dashboard berdasarkan status
|
||||
|
||||
---
|
||||
|
||||
### 2.2 Grafik Tren Pengaduan (Line Chart)
|
||||
|
||||
Menampilkan jumlah pengaduan per bulan.
|
||||
|
||||
**Komponen:**
|
||||
|
||||
* Sumbu X: Bulan
|
||||
* Sumbu Y: Jumlah pengaduan
|
||||
* Garis tren + titik data
|
||||
|
||||
**Interaksi:**
|
||||
|
||||
* Tooltip saat hover (bulan & jumlah)
|
||||
* Toggle range waktu (bulanan/tahunan – opsional)
|
||||
|
||||
---
|
||||
|
||||
### 2.3 Surat Terbanyak (Horizontal Bar Chart)
|
||||
|
||||
Menunjukkan jenis surat/pengaduan yang paling sering diajukan.
|
||||
|
||||
**Contoh Data:**
|
||||
|
||||
* KTP
|
||||
* KK
|
||||
* Domisili
|
||||
* Usaha
|
||||
* Lainnya
|
||||
|
||||
**Interaksi:**
|
||||
|
||||
* Hover tooltip
|
||||
* Click bar → filter daftar pengajuan
|
||||
|
||||
---
|
||||
|
||||
### 2.4 Pengajuan Terbaru (List)
|
||||
|
||||
Daftar pengaduan terbaru dari warga.
|
||||
|
||||
**Isi Item:**
|
||||
|
||||
* Nama pengaju
|
||||
* Jenis pengaduan
|
||||
* Waktu pengajuan
|
||||
* Badge status: Baru / Diproses / Selesai
|
||||
|
||||
**Interaksi:**
|
||||
|
||||
* Click item → halaman detail
|
||||
* Scrollable list
|
||||
|
||||
---
|
||||
|
||||
### 2.5 Ajuan Ide Inovatif (List)
|
||||
|
||||
Menampilkan ide/inisiatif warga untuk desa.
|
||||
|
||||
**Isi Item:**
|
||||
|
||||
* Nama pengaju
|
||||
* Judul ide
|
||||
* Kategori
|
||||
* Tombol "Detail"
|
||||
|
||||
---
|
||||
|
||||
## 3. Tata Letak Keseluruhan (Layout)
|
||||
|
||||
**Desktop:**
|
||||
|
||||
* Grid 12 kolom
|
||||
* Baris 1: 4 Summary Cards
|
||||
* Baris 2: Line Chart (full width)
|
||||
* Baris 3: 3 kolom (Bar Chart, Pengajuan Terbaru, Ide Inovatif)
|
||||
|
||||
**Tablet:**
|
||||
|
||||
* Summary card 2x2
|
||||
* Chart full width
|
||||
* List stack vertikal
|
||||
|
||||
**Mobile:**
|
||||
|
||||
* Semua komponen stacked
|
||||
* Chart scroll horizontal
|
||||
|
||||
---
|
||||
|
||||
## 4. Gaya & Tema Visual
|
||||
|
||||
### 4.1 Light Mode
|
||||
|
||||
**Warna:**
|
||||
|
||||
* Background utama: #F6F9FC
|
||||
* Card: #FFFFFF
|
||||
* Primary: #2563EB (Blue)
|
||||
* Text utama: #0F172A
|
||||
* Text sekunder: #64748B
|
||||
* Border: #E2E8F0
|
||||
|
||||
**Chart:**
|
||||
|
||||
* Line: Biru
|
||||
* Bar: Biru tua
|
||||
* Grid: Abu muda
|
||||
|
||||
---
|
||||
|
||||
### 4.2 Dark Mode
|
||||
|
||||
**Warna:**
|
||||
|
||||
* Background utama: #0B1220
|
||||
* Card: #111827 / #0F172A
|
||||
* Primary: #3B82F6
|
||||
* Text utama: #E5E7EB
|
||||
* Text sekunder: #9CA3AF
|
||||
* Border: #1F2937
|
||||
|
||||
**Chart:**
|
||||
|
||||
* Line: Biru terang
|
||||
* Bar: Biru medium
|
||||
* Grid: Abu gelap
|
||||
|
||||
---
|
||||
|
||||
## 5. Tipografi
|
||||
|
||||
* Font utama: Inter / Poppins
|
||||
* Heading: 600–700
|
||||
* Body: 400–500
|
||||
* Angka statistik: 700
|
||||
|
||||
---
|
||||
|
||||
## 6. Spasi & Radius
|
||||
|
||||
* Padding card: 16–24px
|
||||
* Gap grid: 16–24px
|
||||
* Border radius card: 12–16px
|
||||
* Icon container: circle / rounded-full
|
||||
|
||||
---
|
||||
|
||||
## 7. Responsivitas
|
||||
|
||||
* Mobile-first
|
||||
* Breakpoint umum: 640px, 768px, 1024px
|
||||
* Chart auto-resize
|
||||
* List menjadi full-width
|
||||
|
||||
---
|
||||
|
||||
## 8. Ekspektasi Data & Interaksi Backend
|
||||
|
||||
### 8.1 Contoh Data Summary
|
||||
|
||||
```json
|
||||
{
|
||||
"total": 42,
|
||||
"baru": 14,
|
||||
"diproses": 14,
|
||||
"selesai": 14
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 Contoh Data Tren
|
||||
|
||||
```json
|
||||
[
|
||||
{ "bulan": "Apr", "jumlah": 30 },
|
||||
{ "bulan": "Mei", "jumlah": 50 },
|
||||
{ "bulan": "Jun", "jumlah": 42 }
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. Catatan Teknis
|
||||
|
||||
* Chart: Recharts / Chart.js / ECharts
|
||||
* State: loading, empty, error
|
||||
* Theme switch: CSS variable / Tailwind dark mode
|
||||
* Konsistensi warna status di seluruh modul
|
||||
|
||||
---
|
||||
|
||||
Dokumen ini menjadi **UI Spec + UX Contract** untuk modul Pengaduan dan siap diturunkan ke desain Figma maupun implementasi Frontend.
|
||||
37
Dashboard-MD/SIDEBAR.md
Normal file
37
Dashboard-MD/SIDEBAR.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# SIDEBAR.md
|
||||
## Spesifikasi Sidebar Navigasi – DESA+
|
||||
|
||||
### Tujuan
|
||||
Membuat sidebar navigasi dengan **indikator menu aktif (isActive)** seperti pada desain:
|
||||
- Menu aktif memiliki **background biru muda**
|
||||
- Ada **indikator garis vertikal di sisi kiri**
|
||||
- Status aktif mengikuti **route/path yang sedang dibuka**
|
||||
|
||||
---
|
||||
## Perilaku isActive
|
||||
|
||||
### Definisi isActive
|
||||
Sebuah menu dianggap **aktif** jika:
|
||||
- `currentPath === menu.path`
|
||||
- atau (opsional) `currentPath.startsWith(menu.path)` untuk nested route
|
||||
|
||||
---
|
||||
|
||||
## Tampilan Menu Aktif
|
||||
|
||||
Jika menu **AKTIF**, maka:
|
||||
- Background: `#E6F0FF` (biru muda)
|
||||
- Text: bold / semibold
|
||||
- Border kiri:
|
||||
- Lebar: `4px`
|
||||
- Warna: `#1E40AF` (biru tua)
|
||||
- Border radius: `8px`
|
||||
- Transition halus (`150–200ms`)
|
||||
|
||||
Jika menu **TIDAK AKTIF**:
|
||||
- Background transparan
|
||||
- Text normal
|
||||
- Hover state:
|
||||
- Background `#F1F5F9`
|
||||
|
||||
---
|
||||
230
Dashboard-MD/SOSIAL.md
Normal file
230
Dashboard-MD/SOSIAL.md
Normal file
@@ -0,0 +1,230 @@
|
||||
# SOSIAL.md
|
||||
|
||||
Dokumen ini mendeskripsikan spesifikasi UI/UX, tata letak, gaya visual, serta ekspektasi data & interaksi untuk **Halaman Sosial Desa**. Digunakan sebagai acuan bersama antara UI/UX Designer, Frontend Developer, dan Backend/API.
|
||||
|
||||
---
|
||||
|
||||
## 1. Tujuan Halaman
|
||||
|
||||
Halaman **Sosial** berfungsi sebagai dashboard monitoring:
|
||||
|
||||
* Kesehatan masyarakat (ibu hamil, balita, stunting)
|
||||
* Aktivitas Posyandu
|
||||
* Pendidikan desa
|
||||
* Beasiswa
|
||||
* Event sosial & budaya
|
||||
|
||||
Fokus utama: **ringkas, informatif, real-time friendly**, dan mudah dipahami perangkat desa.
|
||||
|
||||
---
|
||||
|
||||
## 2. Tata Letak Keseluruhan
|
||||
|
||||
### Struktur Global
|
||||
|
||||
* **Sidebar kiri (persisten)**
|
||||
|
||||
* Navigasi utama aplikasi desa
|
||||
* Menu aktif: `Sosial`
|
||||
|
||||
* **Topbar**
|
||||
|
||||
* Nama desa
|
||||
* Profil user (Kepala Desa)
|
||||
* Notifikasi
|
||||
* Toggle tema (opsional)
|
||||
|
||||
* **Main Content Area (scrollable)**
|
||||
|
||||
* Grid berbasis card
|
||||
* Layout desktop: 2–3 kolom
|
||||
* Mobile: 1 kolom (stack)
|
||||
|
||||
---
|
||||
|
||||
## 3. Komponen UI
|
||||
|
||||
### 3.1 Statistik Kesehatan (Summary Cards)
|
||||
|
||||
**Tipe:** KPI Cards
|
||||
|
||||
| Komponen | Deskripsi |
|
||||
| ---------------- | -------------------------------- |
|
||||
| Ibu Hamil Aktif | Total ibu hamil aktif saat ini |
|
||||
| Balita Terdaftar | Total balita tercatat |
|
||||
| Alert Stunting | Jumlah kasus/peringatan stunting |
|
||||
| Posyandu Aktif | Jumlah posyandu yang aktif |
|
||||
|
||||
**Elemen UI:**
|
||||
|
||||
* Angka besar (headline)
|
||||
* Label kecil
|
||||
* Ikon kontekstual
|
||||
|
||||
---
|
||||
|
||||
### 3.2 Statistik Kesehatan (Progress Bar)
|
||||
|
||||
Menampilkan capaian dalam bentuk **horizontal progress bar**:
|
||||
|
||||
* Imunisasi Lengkap
|
||||
* Pemeriksaan Rutin
|
||||
* Gizi Baik
|
||||
* Target Stunting
|
||||
|
||||
**Behavior:**
|
||||
|
||||
* Persentase dinamis
|
||||
* Warna bar menyesuaikan status (normal / warning)
|
||||
|
||||
---
|
||||
|
||||
### 3.3 Jadwal Posyandu
|
||||
|
||||
**Tipe:** List Card
|
||||
|
||||
Setiap item menampilkan:
|
||||
|
||||
* Nama Posyandu
|
||||
* Tanggal
|
||||
* Jam kegiatan
|
||||
|
||||
**Interaksi (opsional):**
|
||||
|
||||
* Klik item → detail jadwal
|
||||
* Filter berdasarkan wilayah / tanggal
|
||||
|
||||
---
|
||||
|
||||
### 3.4 Pendidikan
|
||||
|
||||
**Tipe:** Informational List
|
||||
|
||||
#### Data Siswa
|
||||
|
||||
* TK / PAUD
|
||||
* SD
|
||||
* SMP
|
||||
* SMA
|
||||
|
||||
#### Info Sekolah
|
||||
|
||||
* Jumlah lembaga pendidikan
|
||||
* Jumlah tenaga pengajar
|
||||
|
||||
Layout sederhana, fokus ke angka.
|
||||
|
||||
---
|
||||
|
||||
### 3.5 Beasiswa Desa
|
||||
|
||||
**Tipe:** Highlight Card
|
||||
|
||||
* Jumlah penerima beasiswa
|
||||
* Total dana tersalurkan
|
||||
* Tahun ajaran
|
||||
|
||||
Digunakan sebagai **headline informasi bantuan pendidikan**.
|
||||
|
||||
---
|
||||
|
||||
### 3.6 Kalender Event Budaya
|
||||
|
||||
**Tipe:** Event List
|
||||
|
||||
Setiap event:
|
||||
|
||||
* Nama kegiatan
|
||||
* Tanggal
|
||||
* Lokasi
|
||||
|
||||
**Interaksi:**
|
||||
|
||||
* Klik → detail event
|
||||
* Potensi integrasi kalender
|
||||
|
||||
---
|
||||
|
||||
## 4. Gaya & Tema Visual
|
||||
|
||||
### Warna
|
||||
|
||||
* **Primary:** Biru navy (#1F3A5F – estimasi)
|
||||
* **Secondary:** Biru muda / abu terang
|
||||
* **Success:** Hijau
|
||||
* **Warning:** Oranye / Merah (stunting alert)
|
||||
|
||||
### Font
|
||||
|
||||
* Sans-serif modern (Inter / Poppins)
|
||||
* Hierarki jelas:
|
||||
|
||||
* Judul card
|
||||
* Angka utama
|
||||
* Label kecil
|
||||
|
||||
### Spasi & Bentuk
|
||||
|
||||
* Padding card: 16–24px
|
||||
* Border radius: 12–16px
|
||||
* Shadow ringan (soft elevation)
|
||||
|
||||
---
|
||||
|
||||
## 5. Responsivitas
|
||||
|
||||
### Desktop
|
||||
|
||||
* Grid 2–3 kolom
|
||||
* Semua card terlihat tanpa overflow horizontal
|
||||
|
||||
### Tablet
|
||||
|
||||
* Grid 2 kolom
|
||||
|
||||
### Mobile
|
||||
|
||||
* 1 kolom (stack)
|
||||
* KPI cards jadi swipe / vertical list
|
||||
|
||||
---
|
||||
|
||||
## 6. Ekspektasi Interaksi & Data
|
||||
|
||||
### Interaksi Umum
|
||||
|
||||
* Hover state pada card
|
||||
* Click-through ke halaman detail
|
||||
* Data auto-refresh (optional)
|
||||
|
||||
### Ekspektasi API (contoh)
|
||||
|
||||
```json
|
||||
{
|
||||
"ibu_hamil": 87,
|
||||
"balita": 342,
|
||||
"alert_stunting": 12,
|
||||
"posyandu_aktif": 8,
|
||||
"kesehatan": {
|
||||
"imunisasi": 92,
|
||||
"pemeriksaan": 88,
|
||||
"gizi": 86,
|
||||
"stunting": 14
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Acceptance Checklist
|
||||
|
||||
* [ ] Semua data tampil konsisten
|
||||
* [ ] Tidak ada overflow di mobile
|
||||
* [ ] Progress bar sesuai persentase
|
||||
* [ ] Event & jadwal dapat diklik
|
||||
* [ ] Alert stunting terlihat jelas
|
||||
|
||||
---
|
||||
|
||||
**Catatan:**
|
||||
Dokumen ini bersifat living document dan dapat diperluas ke modul detail (kesehatan, pendidikan, bantuan sosial).
|
||||
303
src/components/bumdes-page.tsx
Normal file
303
src/components/bumdes-page.tsx
Normal file
@@ -0,0 +1,303 @@
|
||||
import { useState } from "react";
|
||||
import {
|
||||
Card,
|
||||
Grid,
|
||||
GridCol,
|
||||
Group,
|
||||
Text,
|
||||
Title,
|
||||
Button,
|
||||
Badge,
|
||||
Table,
|
||||
Stack,
|
||||
Select,
|
||||
useMantineColorScheme
|
||||
} from "@mantine/core";
|
||||
import { IconBuildingStore, IconCategory, IconCurrency, IconUsers } from "@tabler/icons-react";
|
||||
|
||||
const BumdesPage = () => {
|
||||
const { colorScheme } = useMantineColorScheme();
|
||||
const dark = colorScheme === 'dark';
|
||||
|
||||
const [timeFilter, setTimeFilter] = useState<string>("bulan");
|
||||
|
||||
// Sample data for KPI cards
|
||||
const kpiData = [
|
||||
{
|
||||
title: "UMKM Aktif",
|
||||
value: 45,
|
||||
icon: <IconUsers size={24} />,
|
||||
color: "darmasaba-blue",
|
||||
},
|
||||
{
|
||||
title: "UMKM Terdaftar",
|
||||
value: 68,
|
||||
icon: <IconBuildingStore size={24} />,
|
||||
color: "darmasaba-success",
|
||||
},
|
||||
{
|
||||
title: "Omzet",
|
||||
value: "Rp 48.000.000",
|
||||
icon: <IconCurrency size={24} />,
|
||||
color: "darmasaba-warning",
|
||||
},
|
||||
{
|
||||
title: "Kategori UMKM",
|
||||
value: 34,
|
||||
icon: <IconCategory size={24} />,
|
||||
color: "darmasaba-danger",
|
||||
},
|
||||
];
|
||||
|
||||
// Sample data for top products
|
||||
const topProducts = [
|
||||
{
|
||||
rank: 1,
|
||||
name: "Beras Premium Organik",
|
||||
umkmOwner: "Warung Pak Joko",
|
||||
growth: "+12%",
|
||||
},
|
||||
{
|
||||
rank: 2,
|
||||
name: "Keripik Singkong",
|
||||
umkmOwner: "Ibu Sari Snack",
|
||||
growth: "+8%",
|
||||
},
|
||||
{
|
||||
rank: 3,
|
||||
name: "Madu Alami",
|
||||
umkmOwner: "Peternakan Lebah",
|
||||
growth: "+5%",
|
||||
},
|
||||
];
|
||||
|
||||
// Sample data for product sales
|
||||
const productSales = [
|
||||
{
|
||||
produk: "Beras Premium Organik",
|
||||
penjualanBulanIni: "Rp 8.500.000",
|
||||
bulanLalu: "Rp 8.500.000",
|
||||
trend: 10,
|
||||
volume: "650 Kg",
|
||||
stok: "850 Kg",
|
||||
},
|
||||
{
|
||||
produk: "Keripik Singkong",
|
||||
penjualanBulanIni: "Rp 4.200.000",
|
||||
bulanLalu: "Rp 3.800.000",
|
||||
trend: 10,
|
||||
volume: "320 Kg",
|
||||
stok: "120 Kg",
|
||||
},
|
||||
{
|
||||
produk: "Madu Alami",
|
||||
penjualanBulanIni: "Rp 3.750.000",
|
||||
bulanLalu: "Rp 4.100.000",
|
||||
trend: -8,
|
||||
volume: "150 Liter",
|
||||
stok: "45 Liter",
|
||||
},
|
||||
{
|
||||
produk: "Kecap Tradisional",
|
||||
penjualanBulanIni: "Rp 2.800.000",
|
||||
bulanLalu: "Rp 2.500.000",
|
||||
trend: 12,
|
||||
volume: "280 Botol",
|
||||
stok: "95 Botol",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Stack gap="lg">
|
||||
{/* Page Header */}
|
||||
<Group justify="space-between" align="center">
|
||||
<Title order={2} c={dark ? "dark.0" : "black"}>
|
||||
BUMDes & UMKM Desa
|
||||
</Title>
|
||||
</Group>
|
||||
|
||||
{/* KPI Cards */}
|
||||
<Grid gutter="md">
|
||||
{kpiData.map((kpi, index) => (
|
||||
<GridCol key={index} span={{ base: 12, sm: 6, md: 3 }}>
|
||||
<Card withBorder radius="md" padding="lg">
|
||||
<Group justify="space-between" align="center">
|
||||
<Stack gap={0}>
|
||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
|
||||
{kpi.title}
|
||||
</Text>
|
||||
<Text size="xl" fw={700} c={dark ? "dark.0" : "black"}>
|
||||
{typeof kpi.value === 'number' ? kpi.value.toLocaleString() : kpi.value}
|
||||
</Text>
|
||||
</Stack>
|
||||
<Badge
|
||||
variant="light"
|
||||
color={kpi.color}
|
||||
p={8}
|
||||
radius="md"
|
||||
>
|
||||
{kpi.icon}
|
||||
</Badge>
|
||||
</Group>
|
||||
</Card>
|
||||
</GridCol>
|
||||
))}
|
||||
</Grid>
|
||||
|
||||
{/* Update Penjualan Produk Header */}
|
||||
<Card withBorder radius="md" bg={dark ? "dark.8" : "darmasaba-blue.0"}>
|
||||
<Group justify="space-between" align="center" px="md" py="xs">
|
||||
<Title order={3} c={dark ? "dark.0" : "black"}>
|
||||
Update Penjualan Produk
|
||||
</Title>
|
||||
<Group>
|
||||
<Button
|
||||
variant={timeFilter === "minggu" ? "filled" : "light"}
|
||||
onClick={() => setTimeFilter("minggu")}
|
||||
color="darmasaba-blue"
|
||||
>
|
||||
Minggu ini
|
||||
</Button>
|
||||
<Button
|
||||
variant={timeFilter === "bulan" ? "filled" : "light"}
|
||||
onClick={() => setTimeFilter("bulan")}
|
||||
color="darmasaba-blue"
|
||||
>
|
||||
Bulan ini
|
||||
</Button>
|
||||
</Group>
|
||||
</Group>
|
||||
</Card>
|
||||
|
||||
<Grid gutter="md">
|
||||
{/* Produk Unggulan (Left Column) */}
|
||||
<GridCol span={{ base: 12, lg: 4 }}>
|
||||
<Stack gap="md">
|
||||
{/* Total Penjualan, Produk Aktif, Total Transaksi */}
|
||||
<Card withBorder radius="md" p="lg">
|
||||
<Stack gap="md">
|
||||
<Group justify="space-between">
|
||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>Total Penjualan</Text>
|
||||
<Text size="lg" fw={700} c={dark ? "dark.0" : "black"}>Rp 28.500.000</Text>
|
||||
</Group>
|
||||
<Group justify="space-between">
|
||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>Produk Aktif</Text>
|
||||
<Text size="lg" fw={700} c={dark ? "dark.0" : "black"}>124 Produk</Text>
|
||||
</Group>
|
||||
<Group justify="space-between">
|
||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>Total Transaksi</Text>
|
||||
<Text size="lg" fw={700} c={dark ? "dark.0" : "black"}>1.240 Transaksi</Text>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Card>
|
||||
|
||||
{/* Top 3 Produk Terlaris */}
|
||||
<Card withBorder radius="md" p="lg">
|
||||
<Title order={4} mb="md" c={dark ? "dark.0" : "black"}>Top 3 Produk Terlaris</Title>
|
||||
<Stack gap="sm">
|
||||
{topProducts.map((product) => (
|
||||
<Group key={product.rank} justify="space-between" align="center">
|
||||
<Group gap="sm">
|
||||
<Badge
|
||||
variant="filled"
|
||||
color={product.rank === 1 ? "gold" : product.rank === 2 ? "gray" : "bronze"}
|
||||
radius="xl"
|
||||
size="lg"
|
||||
>
|
||||
{product.rank}
|
||||
</Badge>
|
||||
<Stack gap={0}>
|
||||
<Text fw={500} c={dark ? "dark.0" : "black"}>{product.name}</Text>
|
||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>{product.umkmOwner}</Text>
|
||||
</Stack>
|
||||
</Group>
|
||||
<Badge
|
||||
variant="light"
|
||||
color={product.growth.startsWith('+') ? "green" : "red"}
|
||||
>
|
||||
{product.growth}
|
||||
</Badge>
|
||||
</Group>
|
||||
))}
|
||||
</Stack>
|
||||
</Card>
|
||||
</Stack>
|
||||
</GridCol>
|
||||
|
||||
{/* Detail Penjualan Produk (Right Column) */}
|
||||
<GridCol span={{ base: 12, lg: 8 }}>
|
||||
<Card withBorder radius="md" p="lg">
|
||||
<Group justify="space-between" mb="md">
|
||||
<Title order={4} c={dark ? "dark.0" : "black"}>Detail Penjualan Produk</Title>
|
||||
<Select
|
||||
placeholder="Filter kategori"
|
||||
data={[
|
||||
{ value: 'semua', label: 'Semua Kategori' },
|
||||
{ value: 'makanan', label: 'Makanan' },
|
||||
{ value: 'minuman', label: 'Minuman' },
|
||||
{ value: 'kerajinan', label: 'Kerajinan' },
|
||||
]}
|
||||
defaultValue="semua"
|
||||
w={200}
|
||||
/>
|
||||
</Group>
|
||||
|
||||
<Table striped highlightOnHover withColumnBorders>
|
||||
<Table.Thead>
|
||||
<Table.Tr>
|
||||
<Table.Th><Text c={dark ? "dark.3" : "dimmed"}>Produk</Text></Table.Th>
|
||||
<Table.Th><Text c={dark ? "dark.3" : "dimmed"}>Penjualan Bulan Ini</Text></Table.Th>
|
||||
<Table.Th><Text c={dark ? "dark.3" : "dimmed"}>Bulan Lalu</Text></Table.Th>
|
||||
<Table.Th><Text c={dark ? "dark.3" : "dimmed"}>Trend</Text></Table.Th>
|
||||
<Table.Th><Text c={dark ? "dark.3" : "dimmed"}>Volume</Text></Table.Th>
|
||||
<Table.Th><Text c={dark ? "dark.3" : "dimmed"}>Stok</Text></Table.Th>
|
||||
<Table.Th><Text c={dark ? "dark.3" : "dimmed"}>Aksi</Text></Table.Th>
|
||||
</Table.Tr>
|
||||
</Table.Thead>
|
||||
<Table.Tbody>
|
||||
{productSales.map((product, index) => (
|
||||
<Table.Tr key={index}>
|
||||
<Table.Td>
|
||||
<Text fw={500} c={dark ? "dark.0" : "black"}>{product.produk}</Text>
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<Text fz={"sm"} c={dark ? "dark.0" : "black"}>{product.penjualanBulanIni}</Text>
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<Text fz={"sm"} c={dark ? "dark.3" : "dimmed"}>{product.bulanLalu}</Text>
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<Group gap="xs">
|
||||
<Text c={product.trend >= 0 ? "green" : "red"}>
|
||||
{product.trend >= 0 ? '↑' : '↓'} {Math.abs(product.trend)}%
|
||||
</Text>
|
||||
</Group>
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<Text fz={"sm"} c={dark ? "dark.0" : "black"}>{product.volume}</Text>
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<Badge
|
||||
variant="light"
|
||||
color={parseInt(product.stok) > 200 ? "green" : "yellow"}
|
||||
>
|
||||
{product.stok}
|
||||
</Badge>
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<Button variant="subtle" size="compact-sm" color="darmasaba-blue">
|
||||
Detail
|
||||
</Button>
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
))}
|
||||
</Table.Tbody>
|
||||
</Table>
|
||||
</Card>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default BumdesPage;
|
||||
@@ -74,7 +74,7 @@ export function DashboardContent() {
|
||||
{/* Stats Cards */}
|
||||
<Grid gutter="md">
|
||||
<Grid.Col span={{ base: 12, md: 6, lg: 3 }}>
|
||||
<Card p="md" radius="md" h="100%" withBorder>
|
||||
<Card p="md" style={{borderColor: dark ? "#141D34" : "white"}} radius="md" h="100%" withBorder bg={dark ? "#141D34" : "white"}>
|
||||
<Group justify="space-between" align="flex-start" w="100%">
|
||||
<Box style={{ flex: 1 }}>
|
||||
<Text size="sm" c="dimmed" mb="xs">
|
||||
@@ -99,7 +99,7 @@ export function DashboardContent() {
|
||||
</Card>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={{ base: 12, md: 6, lg: 3 }}>
|
||||
<Card p="md" radius="md" h="100%" withBorder>
|
||||
<Card p="md" style={{borderColor: dark ? "#141D34" : "white"}} radius="md" h="100%" withBorder bg={dark ? "#141D34" : "white"}>
|
||||
<Group justify="space-between" align="flex-start" w="100%">
|
||||
<Box style={{ flex: 1 }}>
|
||||
<Text size="sm" c="dimmed" mb="xs">
|
||||
@@ -121,7 +121,7 @@ export function DashboardContent() {
|
||||
</Card>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={{ base: 12, md: 6, lg: 3 }}>
|
||||
<Card p="md" radius="md" h="100%" withBorder>
|
||||
<Card p="md" style={{borderColor: dark ? "#141D34" : "white"}} radius="md" h="100%" withBorder bg={dark ? "#141D34" : "white"}>
|
||||
<Group justify="space-between" align="flex-start" w="100%">
|
||||
<Box style={{ flex: 1 }}>
|
||||
<Text size="sm" c="dimmed" mb="xs">
|
||||
@@ -146,7 +146,7 @@ export function DashboardContent() {
|
||||
</Card>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={{ base: 12, md: 6, lg: 3 }}>
|
||||
<Card p="md" radius="md" h="100%" withBorder>
|
||||
<Card p="md" style={{borderColor: dark ? "#141D34" : "white"}} radius="md" h="100%" withBorder bg={dark ? "#141D34" : "white"}>
|
||||
<Group justify="space-between" align="flex-start" w="100%">
|
||||
<Box style={{ flex: 1 }}>
|
||||
<Text size="sm" c="dimmed" mb="xs">
|
||||
@@ -171,7 +171,7 @@ export function DashboardContent() {
|
||||
<Grid gutter="lg">
|
||||
{/* Bar Chart */}
|
||||
<Grid.Col span={{ base: 12, lg: 6 }}>
|
||||
<Card p="md" radius="md" withBorder>
|
||||
<Card p="md" style={{borderColor: dark ? "#141D34" : "white"}} radius="md" withBorder bg={dark ? "#141D34" : "white"}>
|
||||
<Group justify="space-between" mb="md">
|
||||
<Box>
|
||||
<Title order={4} mb={5}>
|
||||
@@ -232,7 +232,7 @@ export function DashboardContent() {
|
||||
|
||||
{/* Pie Chart */}
|
||||
<Grid.Col span={{ base: 12, lg: 6 }}>
|
||||
<Card p="md" radius="md" withBorder>
|
||||
<Card p="md" style={{borderColor: dark ? "#141D34" : "white"}} radius="md" withBorder bg={dark ? "#141D34" : "white"}>
|
||||
<Title order={4} mb={5}>
|
||||
Tingkat Kepuasan
|
||||
</Title>
|
||||
@@ -283,7 +283,7 @@ export function DashboardContent() {
|
||||
<Grid gutter="lg">
|
||||
{/* Divisi Teraktif */}
|
||||
<Grid.Col span={{ base: 12, lg: 6 }}>
|
||||
<Card p="md" radius="md" withBorder>
|
||||
<Card p="md" style={{borderColor: dark ? "#141D34" : "white"}} radius="md" withBorder bg={dark ? "#141D34" : "white"}>
|
||||
<Group gap="xs" mb="lg">
|
||||
<Box>
|
||||
{/* Original SVG icon */}
|
||||
@@ -355,7 +355,7 @@ export function DashboardContent() {
|
||||
|
||||
{/* Kalender */}
|
||||
<Grid.Col span={{ base: 12, lg: 6 }}>
|
||||
<Card p="md" radius="md" withBorder>
|
||||
<Card p="md" style={{borderColor: dark ? "#141D34" : "white"}} radius="md" withBorder bg={dark ? "#141D34" : "white"}>
|
||||
<Group gap="xs" mb="lg">
|
||||
<Calendar style={{ width: 20, height: 20 }} />
|
||||
<Title order={4}>Kalender & Kegiatan Mendatang</Title>
|
||||
@@ -378,7 +378,7 @@ export function DashboardContent() {
|
||||
</Grid>
|
||||
|
||||
{/* APBDes Chart */}
|
||||
<Card p="md" radius="md" withBorder>
|
||||
<Card p="md" style={{borderColor: dark ? "#141D34" : "white"}} radius="md" withBorder bg={dark ? "#141D34" : "white"}>
|
||||
<Title order={4} mb="lg">
|
||||
Grafik APBDes
|
||||
</Title>
|
||||
|
||||
@@ -140,7 +140,7 @@ const DemografiPekerjaan = () => {
|
||||
<Box className="space-y-6">
|
||||
<Stack gap="xl">
|
||||
<Group justify="space-between" align="center">
|
||||
<Title order={1} fw={700}>
|
||||
<Title order={2} fw={700}>
|
||||
Demografi & Kependudukan
|
||||
</Title>
|
||||
<Button variant="filled">Export Data</Button>
|
||||
|
||||
@@ -22,7 +22,7 @@ export function Header() {
|
||||
const getPageTitle = () => {
|
||||
switch (location.pathname) {
|
||||
case "/":
|
||||
return "Dashboard";
|
||||
return "Desa Darmasaba";
|
||||
case "/kinerja-divisi":
|
||||
return "Kinerja Divisi";
|
||||
case "/pengaduan":
|
||||
@@ -43,14 +43,14 @@ export function Header() {
|
||||
case "/pengaturan":
|
||||
return "Pengaturan";
|
||||
default:
|
||||
return "Dashboard";
|
||||
return "Desa Darmasaba";
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Group justify="space-between" w="100%">
|
||||
{/* Title */}
|
||||
<Title order={2}>{getPageTitle()}</Title>
|
||||
<Title order={3} c={"white"}>{getPageTitle()}</Title>
|
||||
|
||||
{/* Right Section */}
|
||||
<Group gap="md">
|
||||
@@ -59,15 +59,15 @@ export function Header() {
|
||||
{/* User Info */}
|
||||
<Group gap="sm">
|
||||
<Box ta="right">
|
||||
<Text size="sm" fw={500}>
|
||||
<Text c={"white"} size="sm" fw={500}>
|
||||
I. B. Surya Prabhawa M...
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed">
|
||||
<Text c={"white"} size="xs">
|
||||
Kepala Desa
|
||||
</Text>
|
||||
</Box>
|
||||
<Avatar color="blue" radius="xl">
|
||||
<UserIcon style={{ width: "70%", height: "70%" }} />
|
||||
<UserIcon color="white" style={{ width: "70%", height: "70%" }} />
|
||||
</Avatar>
|
||||
</Group>
|
||||
|
||||
@@ -84,13 +84,13 @@ export function Header() {
|
||||
aria-label="Toggle color scheme"
|
||||
>
|
||||
{dark ? (
|
||||
<Sun style={{ width: "70%", height: "70%" }} />
|
||||
<Sun color="white" style={{ width: "70%", height: "70%" }} />
|
||||
) : (
|
||||
<Moon style={{ width: "70%", height: "70%" }} />
|
||||
<Moon color="white" style={{ width: "70%", height: "70%" }} />
|
||||
)}
|
||||
</ActionIcon>
|
||||
<ActionIcon variant="subtle" size="lg" radius="xl" pos="relative">
|
||||
<Bell style={{ width: "70%", height: "70%" }} />
|
||||
<Bell color="white" style={{ width: "70%", height: "70%" }} />
|
||||
<Badge
|
||||
size="xs"
|
||||
color="red"
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
Stack,
|
||||
Grid,
|
||||
Box,
|
||||
useMantineColorScheme,
|
||||
} from "@mantine/core";
|
||||
import { BarChart } from "@mantine/charts";
|
||||
|
||||
@@ -133,21 +134,17 @@ const busyHours = [
|
||||
];
|
||||
|
||||
const JennaAnalytic = () => {
|
||||
const { colorScheme } = useMantineColorScheme();
|
||||
const dark = colorScheme === "dark";
|
||||
|
||||
return (
|
||||
<Box className="space-y-6">
|
||||
<Stack gap="xl">
|
||||
<Group justify="space-between" align="center">
|
||||
<Title order={1} fw={700}>
|
||||
Jenna Analytic
|
||||
</Title>
|
||||
<Button variant="filled">Export Data</Button>
|
||||
</Group>
|
||||
|
||||
{/* KPI Cards */}
|
||||
<Grid gutter="lg">
|
||||
{kpiData.map((kpi) => (
|
||||
<Grid.Col key={kpi.id} span={{ base: 12, md: 6, lg: 3 }}>
|
||||
<Card shadow="sm" padding="lg" radius="md" withBorder>
|
||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}>
|
||||
<Group justify="space-between" align="flex-start" mb="xs">
|
||||
<Text size="sm" fw={500} c="dimmed">
|
||||
{kpi.title}
|
||||
@@ -185,21 +182,42 @@ const JennaAnalytic = () => {
|
||||
))}
|
||||
</Grid>
|
||||
|
||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}>
|
||||
<Title order={3} fw={500} mb="md">
|
||||
Interaksi Chatbot
|
||||
</Title>
|
||||
<BarChart
|
||||
h={300}
|
||||
data={chartData}
|
||||
dataKey="day"
|
||||
series={[{ name: 'total', color: 'blue' }]}
|
||||
withLegend
|
||||
/>
|
||||
</Card>
|
||||
|
||||
{/* Charts and Lists Section */}
|
||||
<Grid gutter="lg">
|
||||
{/* Grafik Interaksi Chatbot (now Bar Chart) */}
|
||||
<Grid.Col span={{ base: 12, lg: 6 }}>
|
||||
<Card shadow="sm" padding="lg" radius="md" withBorder>
|
||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }} h="100%">
|
||||
<Title order={3} fw={500} mb="md">
|
||||
Interaksi Chatbot
|
||||
Jam Tersibuk
|
||||
</Title>
|
||||
<BarChart
|
||||
h={300}
|
||||
data={chartData}
|
||||
dataKey="day"
|
||||
series={[{ name: 'total', color: 'blue' }]}
|
||||
withLegend
|
||||
/>
|
||||
<Stack gap="sm">
|
||||
{busyHours.map((item, index) => (
|
||||
<Box key={index}>
|
||||
<Text size="sm">
|
||||
{item.period}
|
||||
</Text>
|
||||
<Group align="center">
|
||||
<Progress value={item.percentage} flex={1} />
|
||||
<Text size="sm" fw={500}>
|
||||
{item.percentage}%
|
||||
</Text>
|
||||
</Group>
|
||||
</Box>
|
||||
))}
|
||||
</Stack>
|
||||
</Card>
|
||||
</Grid.Col>
|
||||
|
||||
@@ -207,7 +225,7 @@ const JennaAnalytic = () => {
|
||||
<Grid.Col span={{ base: 12, lg: 6 }}>
|
||||
<Stack gap="lg">
|
||||
{/* Topik Pertanyaan Terbanyak */}
|
||||
<Card shadow="sm" padding="lg" radius="md" withBorder>
|
||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }} h="100%">
|
||||
<Title order={3} fw={500} mb="md">
|
||||
Topik Pertanyaan Terbanyak
|
||||
</Title>
|
||||
@@ -231,29 +249,12 @@ const JennaAnalytic = () => {
|
||||
</Card>
|
||||
|
||||
{/* Jam Tersibuk */}
|
||||
<Card shadow="sm" padding="lg" radius="md" withBorder>
|
||||
<Title order={3} fw={500} mb="md">
|
||||
Jam Tersibuk
|
||||
</Title>
|
||||
<Stack gap="sm">
|
||||
{busyHours.map((item, index) => (
|
||||
<Group key={index} align="center">
|
||||
<Text w={80} size="sm">
|
||||
{item.period}
|
||||
</Text>
|
||||
<Progress value={item.percentage} flex={1} />
|
||||
<Text size="sm" fw={500}>
|
||||
{item.percentage}%
|
||||
</Text>
|
||||
</Group>
|
||||
))}
|
||||
</Stack>
|
||||
</Card>
|
||||
</Stack>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Grid >
|
||||
|
||||
</Stack >
|
||||
</Box >
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
225
src/components/keamanan-page.tsx
Normal file
225
src/components/keamanan-page.tsx
Normal file
@@ -0,0 +1,225 @@
|
||||
import { useState } from "react";
|
||||
import {
|
||||
Card,
|
||||
Grid,
|
||||
GridCol,
|
||||
Group,
|
||||
Text,
|
||||
Title,
|
||||
Stack,
|
||||
useMantineColorScheme,
|
||||
Badge,
|
||||
List,
|
||||
ThemeIcon,
|
||||
Box
|
||||
} from "@mantine/core";
|
||||
import {
|
||||
IconCamera,
|
||||
IconAlertTriangle,
|
||||
IconMapPin,
|
||||
IconClock,
|
||||
IconEye,
|
||||
IconShieldLock
|
||||
} from "@tabler/icons-react";
|
||||
|
||||
const KeamananPage = () => {
|
||||
const { colorScheme } = useMantineColorScheme();
|
||||
const dark = colorScheme === 'dark';
|
||||
|
||||
// Sample data for KPI cards
|
||||
const kpiData = [
|
||||
{
|
||||
title: "CCTV Aktif",
|
||||
value: 20,
|
||||
subtitle: "Kamera Online",
|
||||
icon: <IconCamera size={24} />,
|
||||
color: "darmasaba-success",
|
||||
},
|
||||
{
|
||||
title: "Laporan Keamanan",
|
||||
value: 15,
|
||||
subtitle: "Minggu ini",
|
||||
icon: <IconAlertTriangle size={24} />,
|
||||
color: "darmasaba-danger",
|
||||
},
|
||||
];
|
||||
|
||||
// Sample data for CCTV locations
|
||||
const cctvLocations = [
|
||||
{ id: "CCTV-01", lat: -8.5, lng: 115.2, status: "active", lastSeen: "2 jam yang lalu", location: "Balai Desa" },
|
||||
{ id: "CCTV-02", lat: -8.6, lng: 115.3, status: "active", lastSeen: "1 jam yang lalu", location: "Pintu Masuk Desa" },
|
||||
{ id: "CCTV-03", lat: -8.4, lng: 115.1, status: "offline", lastSeen: "1 hari yang lalu", location: "Taman Desa" },
|
||||
{ id: "CCTV-04", lat: -8.7, lng: 115.4, status: "active", lastSeen: "30 menit yang lalu", location: "Pasar Desa" },
|
||||
];
|
||||
|
||||
// Sample data for security reports
|
||||
const securityReports = [
|
||||
{
|
||||
id: "REP-001",
|
||||
title: "Pencurian Motor",
|
||||
reportedAt: "2 jam yang lalu",
|
||||
date: "12 Feb 2026, 14:30",
|
||||
location: "Jl. Kecubung 20",
|
||||
status: "baru",
|
||||
},
|
||||
{
|
||||
id: "REP-002",
|
||||
title: "Kerusuhan Antar Warga",
|
||||
reportedAt: "4 jam yang lalu",
|
||||
date: "12 Feb 2026, 12:15",
|
||||
location: "RT 05 RW 02",
|
||||
status: "baru",
|
||||
},
|
||||
{
|
||||
id: "REP-003",
|
||||
title: "Kebakaran Rumah",
|
||||
reportedAt: "1 hari yang lalu",
|
||||
date: "11 Feb 2026, 08:45",
|
||||
location: "Jl. Flamboyan 15",
|
||||
status: "diproses",
|
||||
},
|
||||
{
|
||||
id: "REP-004",
|
||||
title: "Kehilangan Barang",
|
||||
reportedAt: "2 hari yang lalu",
|
||||
date: "10 Feb 2026, 16:20",
|
||||
location: "Taman Desa",
|
||||
status: "selesai",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Stack gap="lg">
|
||||
{/* Page Header */}
|
||||
<Group justify="space-between" align="center">
|
||||
<Title order={2} c={dark ? "dark.0" : "black"}>
|
||||
Keamanan Lingkungan Desa
|
||||
</Title>
|
||||
</Group>
|
||||
|
||||
{/* KPI Cards */}
|
||||
<Grid gutter="md">
|
||||
{kpiData.map((kpi, index) => (
|
||||
<GridCol key={index} span={{ base: 12, sm: 6, md: 6 }}>
|
||||
<Card withBorder radius="md" padding="lg">
|
||||
<Group justify="space-between" align="center">
|
||||
<Stack gap={0}>
|
||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
|
||||
{kpi.subtitle}
|
||||
</Text>
|
||||
<Group gap="xs" align="center">
|
||||
<Text size="xl" fw={700} c={dark ? "dark.0" : "black"}>
|
||||
{kpi.value}
|
||||
</Text>
|
||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
|
||||
{kpi.title}
|
||||
</Text>
|
||||
</Group>
|
||||
</Stack>
|
||||
<ThemeIcon variant="light" color={kpi.color} size="xl" radius="xl">
|
||||
{kpi.icon}
|
||||
</ThemeIcon>
|
||||
</Group>
|
||||
</Card>
|
||||
</GridCol>
|
||||
))}
|
||||
</Grid>
|
||||
|
||||
<Grid gutter="md">
|
||||
{/* Peta Keamanan CCTV */}
|
||||
<GridCol span={{ base: 12, lg: 6 }}>
|
||||
<Card withBorder radius="md" p="lg">
|
||||
<Title order={3} mb="md" c={dark ? "dark.0" : "black"}>Peta Keamanan CCTV</Title>
|
||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"} mb="md">Titik Lokasi CCTV</Text>
|
||||
|
||||
{/* Placeholder for map */}
|
||||
<Box
|
||||
style={{
|
||||
backgroundColor: dark ? '#2d3748' : '#e2e8f0',
|
||||
borderRadius: '8px',
|
||||
height: '400px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
}}
|
||||
>
|
||||
<Stack align="center">
|
||||
<IconMapPin size={48} stroke={1.5} color={dark ? '#94a3b8' : '#64748b'} />
|
||||
<Text c={dark ? "dark.3" : "dimmed"}>Peta Lokasi CCTV</Text>
|
||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"} ta="center">Integrasi dengan Google Maps atau Mapbox akan ditampilkan di sini</Text>
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
{/* CCTV Locations List */}
|
||||
<Stack mt="md" gap="sm">
|
||||
<Title order={4} c={dark ? "dark.0" : "black"}>Daftar CCTV</Title>
|
||||
{cctvLocations.map((cctv, index) => (
|
||||
<Card key={index} withBorder radius="md" p="md">
|
||||
<Group justify="space-between">
|
||||
<Stack gap={0}>
|
||||
<Group gap="xs">
|
||||
<Text fw={500} c={dark ? "dark.0" : "black"}>{cctv.id}</Text>
|
||||
<Badge
|
||||
variant="dot"
|
||||
color={cctv.status === "active" ? "green" : "gray"}
|
||||
>
|
||||
{cctv.status === "active" ? "Online" : "Offline"}
|
||||
</Badge>
|
||||
</Group>
|
||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>{cctv.location}</Text>
|
||||
</Stack>
|
||||
<Group gap="xs">
|
||||
<IconClock size={16} stroke={1.5} />
|
||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>{cctv.lastSeen}</Text>
|
||||
</Group>
|
||||
</Group>
|
||||
</Card>
|
||||
))}
|
||||
</Stack>
|
||||
</Card>
|
||||
</GridCol>
|
||||
|
||||
{/* Daftar Laporan Keamanan */}
|
||||
<GridCol span={{ base: 12, lg: 6 }}>
|
||||
<Card withBorder radius="md" p="lg">
|
||||
<Title order={3} mb="md" c={dark ? "dark.0" : "black"}>Laporan Keamanan Lingkungan</Title>
|
||||
|
||||
<Stack gap="sm">
|
||||
{securityReports.map((report, index) => (
|
||||
<Card key={index} withBorder radius="md" p="md">
|
||||
<Group justify="space-between" mb="sm">
|
||||
<Text fw={500} c={dark ? "dark.0" : "black"}>{report.title}</Text>
|
||||
<Badge
|
||||
variant="light"
|
||||
color={
|
||||
report.status === "baru" ? "red" :
|
||||
report.status === "diproses" ? "yellow" : "green"
|
||||
}
|
||||
>
|
||||
{report.status}
|
||||
</Badge>
|
||||
</Group>
|
||||
|
||||
<Group justify="space-between">
|
||||
<Group gap="xs">
|
||||
<IconMapPin size={16} stroke={1.5} />
|
||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>{report.location}</Text>
|
||||
</Group>
|
||||
<Group gap="xs">
|
||||
<IconClock size={16} stroke={1.5} />
|
||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>{report.reportedAt}</Text>
|
||||
</Group>
|
||||
</Group>
|
||||
|
||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"} mt="sm">{report.date}</Text>
|
||||
</Card>
|
||||
))}
|
||||
</Stack>
|
||||
</Card>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default KeamananPage;
|
||||
@@ -116,10 +116,10 @@ const apbdReport = {
|
||||
|
||||
const KeuanganAnggaran = () => {
|
||||
return (
|
||||
<Box p="md">
|
||||
<Box>
|
||||
<Stack gap="xl">
|
||||
<Group justify="space-between" align="center">
|
||||
<Title order={1} fw={700}>
|
||||
<Title order={2} fw={700}>
|
||||
Keuangan & Anggaran
|
||||
</Title>
|
||||
<Button variant="filled">Export Laporan</Button>
|
||||
|
||||
@@ -1,264 +1,342 @@
|
||||
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button"; // Correct import for Button
|
||||
import {
|
||||
Stack,
|
||||
Grid,
|
||||
GridCol,
|
||||
Group,
|
||||
Text,
|
||||
Title,
|
||||
ActionIcon,
|
||||
Progress as MantineProgress,
|
||||
Box,
|
||||
Badge as MantineBadge,
|
||||
Card,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { Progress } from "@/components/ui/progress";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
useMantineColorScheme,
|
||||
ThemeIcon,
|
||||
List,
|
||||
Divider,
|
||||
Skeleton
|
||||
} from "@mantine/core";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Bar, BarChart, CartesianGrid, XAxis, YAxis, Tooltip, ResponsiveContainer, PieChart, Pie, Cell } from "recharts";
|
||||
|
||||
const KinerjaDivisi = () => {
|
||||
// Sample data for division performance
|
||||
const divisions = [
|
||||
const { colorScheme } = useMantineColorScheme();
|
||||
const dark = colorScheme === 'dark';
|
||||
|
||||
// Data for division progress chart
|
||||
const divisionProgressData = [
|
||||
{ name: "Sekretariat", selesai: 12, berjalan: 5, tertunda: 2 },
|
||||
{ name: "Keuangan", selesai: 8, berjalan: 7, tertunda: 1 },
|
||||
{ name: "Sosial", selesai: 10, berjalan: 3, tertunda: 4 },
|
||||
{ name: "Humas", selesai: 6, berjalan: 9, tertunda: 3 },
|
||||
];
|
||||
|
||||
// Division task summaries
|
||||
const divisionTasks = [
|
||||
{
|
||||
id: 1,
|
||||
name: "Divisi Teknologi",
|
||||
target: 95,
|
||||
achievement: 87,
|
||||
status: "On Track",
|
||||
projects: 12,
|
||||
budget: "Rp 2.5M",
|
||||
lastUpdate: "2 days ago",
|
||||
name: "Sekretariat",
|
||||
tasks: [
|
||||
{ title: "Laporan Bulanan", status: "selesai" },
|
||||
{ title: "Arsip Dokumen", status: "berjalan" },
|
||||
{ title: "Undangan Rapat", status: "tertunda" },
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "Divisi Keuangan",
|
||||
target: 90,
|
||||
achievement: 92,
|
||||
status: "Above Target",
|
||||
projects: 8,
|
||||
budget: "Rp 1.8M",
|
||||
lastUpdate: "1 day ago",
|
||||
name: "Keuangan",
|
||||
tasks: [
|
||||
{ title: "Laporan APBDes", status: "selesai" },
|
||||
{ title: "Verifikasi Dana", status: "tertunda" },
|
||||
{ title: "Pengeluaran Harian", status: "berjalan" },
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: "Divisi SDM",
|
||||
target: 85,
|
||||
achievement: 78,
|
||||
status: "Needs Attention",
|
||||
projects: 6,
|
||||
budget: "Rp 1.2M",
|
||||
lastUpdate: "3 days ago",
|
||||
name: "Sosial",
|
||||
tasks: [
|
||||
{ title: "Program Bantuan", status: "selesai" },
|
||||
{ title: "Kegiatan Posyandu", status: "berjalan" },
|
||||
{ title: "Monitoring Stunting", status: "tertunda" },
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: "Divisi Operasional",
|
||||
target: 92,
|
||||
achievement: 89,
|
||||
status: "On Track",
|
||||
projects: 15,
|
||||
budget: "Rp 3.2M",
|
||||
lastUpdate: "5 hours ago",
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: "Divisi Pemasaran",
|
||||
target: 88,
|
||||
achievement: 91,
|
||||
status: "Above Target",
|
||||
projects: 10,
|
||||
budget: "Rp 2.1M",
|
||||
lastUpdate: "1 day ago",
|
||||
name: "Humas",
|
||||
tasks: [
|
||||
{ title: "Publikasi Kegiatan", status: "selesai" },
|
||||
{ title: "Koordinasi Media", status: "berjalan" },
|
||||
{ title: "Laporan Kegiatan", status: "tertunda" },
|
||||
]
|
||||
},
|
||||
];
|
||||
|
||||
// Archive items
|
||||
const archiveItems = [
|
||||
{ name: "Surat Keputusan", count: 12 },
|
||||
{ name: "Laporan Keuangan", count: 8 },
|
||||
{ name: "Dokumentasi", count: 24 },
|
||||
{ name: "Notulensi Rapat", count: 15 },
|
||||
];
|
||||
|
||||
// Activity progress
|
||||
const activityProgress = [
|
||||
{ name: "Pembangunan Jalan", progress: 75, date: "15 Feb 2026", status: "berjalan" },
|
||||
{ name: "Posyandu Bulanan", progress: 100, date: "10 Feb 2026", status: "selesai" },
|
||||
{ name: "Vaksinasi Massal", progress: 45, date: "20 Feb 2026", status: "berjalan" },
|
||||
{ name: "Festival Budaya", progress: 20, date: "5 Mar 2026", status: "berjalan" },
|
||||
];
|
||||
|
||||
// Document statistics
|
||||
const documentStats = [
|
||||
{ name: "Gambar", value: 42 },
|
||||
{ name: "Dokumen", value: 87 },
|
||||
];
|
||||
|
||||
// Activity progress statistics
|
||||
const activityProgressStats = [
|
||||
{ name: "Selesai", value: 12 },
|
||||
{ name: "Dikerjakan", value: 8 },
|
||||
{ name: "Segera Dikerjakan", value: 5 },
|
||||
{ name: "Dibatalkan", value: 2 },
|
||||
];
|
||||
|
||||
const COLORS = ['#10B981', '#F59E0B', '#EF4444', '#6B7280'];
|
||||
const STATUS_COLORS: Record<string, string> = {
|
||||
selesai: 'green',
|
||||
berjalan: 'blue',
|
||||
tertunda: 'red',
|
||||
proses: 'yellow'
|
||||
};
|
||||
|
||||
// Discussion data
|
||||
const discussions = [
|
||||
{ title: "Pembahasan APBDes 2026", sender: "Kepala Desa", timestamp: "2 jam yang lalu" },
|
||||
{ title: "Kegiatan Posyandu", sender: "Divisi Sosial", timestamp: "5 jam yang lalu" },
|
||||
{ title: "Festival Budaya", sender: "Divisi Humas", timestamp: "1 hari yang lalu" },
|
||||
];
|
||||
|
||||
// Today's agenda
|
||||
const todayAgenda = [
|
||||
{ time: "09:00", event: "Rapat Evaluasi Bulanan" },
|
||||
{ time: "14:00", event: "Koordinasi Program Bantuan" },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="flex justify-between items-center">
|
||||
<h1 className="text-3xl font-bold text-gray-900 dark:text-white">
|
||||
Kinerja Divisi
|
||||
</h1>
|
||||
<div className="flex space-x-4">
|
||||
<Button variant="default">Export Data</Button>
|
||||
<Button variant="outline">Filter</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium dark:text-gray-100">Total Divisi</CardTitle>
|
||||
<div className="h-6 w-6 text-muted-foreground">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
className="h-6 w-6"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">5</div>
|
||||
<p className="text-xs text-muted-foreground">Jumlah divisi aktif</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium dark:text-gray-100">
|
||||
Rata-rata Pencapaian
|
||||
</CardTitle>
|
||||
<div className="h-6 w-6 text-muted-foreground">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
className="h-6 w-6"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">87.4%</div>
|
||||
<p className="text-xs text-muted-foreground">Target tercapai</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium dark:text-gray-100">
|
||||
Divisi Melebihi Target
|
||||
</CardTitle>
|
||||
<div className="h-6 w-6 text-muted-foreground">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
className="h-6 w-6"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M13 7h8m0 0v8m0-8l-8 8-4-4-6 6"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">2</div>
|
||||
<p className="text-xs text-muted-foreground">Dari total 5 divisi</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="dark:text-gray-100">Detail Kinerja Divisi</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="dark:text-white">Nama Divisi</TableHead>
|
||||
<TableHead className="dark:text-white">Target (%)</TableHead>
|
||||
<TableHead className="dark:text-white">Pencapaian (%)</TableHead>
|
||||
<TableHead className="dark:text-white">Status</TableHead>
|
||||
<TableHead className="dark:text-white">Proyek Aktif</TableHead>
|
||||
<TableHead className="dark:text-white">Anggaran</TableHead>
|
||||
<TableHead className="dark:text-white">Terakhir Diperbarui</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{divisions.map((division) => (
|
||||
<TableRow key={division.id}>
|
||||
<TableCell className="font-medium dark:text-white">
|
||||
{division.name}
|
||||
</TableCell>
|
||||
<TableCell className="dark:text-white">
|
||||
{division.target}%
|
||||
</TableCell>
|
||||
<TableCell className="dark:text-white">
|
||||
{division.achievement}%
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex items-center">
|
||||
<Progress
|
||||
value={division.achievement}
|
||||
max={100}
|
||||
className="w-24 mr-2"
|
||||
/>
|
||||
<Badge
|
||||
variant={
|
||||
division.status === "Above Target"
|
||||
? "success"
|
||||
: division.status === "On Track"
|
||||
? "secondary"
|
||||
: "destructive"
|
||||
}
|
||||
>
|
||||
{division.status}
|
||||
</Badge>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="dark:text-white">
|
||||
{division.projects}
|
||||
</TableCell>
|
||||
<TableCell className="dark:text-white">
|
||||
{division.budget}
|
||||
</TableCell>
|
||||
<TableCell className="dark:text-white">
|
||||
{division.lastUpdate}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</CardContent>
|
||||
<Stack gap="lg">
|
||||
{/* Grafik Progres Tugas per Divisi */}
|
||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }} >
|
||||
<Title order={4} mb="md" c={dark ? 'white' : 'darmasaba-navy'}>
|
||||
Grafik Progres Tugas per Divisi
|
||||
</Title>
|
||||
<ResponsiveContainer width="100%" height={300}>
|
||||
<BarChart data={divisionProgressData}>
|
||||
<CartesianGrid strokeDasharray="3 3" vertical={false} stroke={dark ? "#141D34" : "white"} />
|
||||
<XAxis
|
||||
dataKey="name"
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
tick={{ fill: dark ? "var(--mantine-color-text)" : "var(--mantine-color-text)" }}
|
||||
/>
|
||||
<YAxis
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
tick={{ fill: dark ? "var(--mantine-color-text)" : "var(--mantine-color-text)" }}
|
||||
/>
|
||||
<Tooltip
|
||||
contentStyle={dark
|
||||
? { backgroundColor: 'var(--mantine-color-dark-7)', borderColor: 'var(--mantine-color-dark-6)' }
|
||||
: {}}
|
||||
/>
|
||||
<Bar dataKey="selesai" stackId="a" fill="#10B981" name="Selesai" radius={[4, 4, 0, 0]} />
|
||||
<Bar dataKey="berjalan" stackId="a" fill="#3B82F6" name="Berjalan" radius={[4, 4, 0, 0]} />
|
||||
<Bar dataKey="tertunda" stackId="a" fill="#EF4444" name="Tertunda" radius={[4, 4, 0, 0]} />
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</Card>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="dark:text-gray-100">Grafik Pencapaian Divisi</CardTitle>
|
||||
</CardHeader> <CardContent>
|
||||
<div className="h-80 flex items-center justify-center bg-gray-50 dark:bg-gray-700 rounded-lg">
|
||||
<p className="text-gray-500 dark:text-gray-300">
|
||||
Grafik pencapaian akan ditampilkan di sini
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
{/* Ringkasan Tugas per Divisi */}
|
||||
<Grid gutter="md">
|
||||
{divisionTasks.map((division, index) => (
|
||||
<GridCol key={index} span={{ base: 12, md: 6, lg: 3 }}>
|
||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }} h="100%">
|
||||
<Title order={4} mb="sm" c={dark ? 'white' : 'darmasaba-navy'}>
|
||||
{division.name}
|
||||
</Title>
|
||||
<Stack gap="sm">
|
||||
{division.tasks.map((task, taskIndex) => (
|
||||
<Box key={taskIndex}>
|
||||
<Group justify="space-between">
|
||||
<Text size="sm" c={dark ? 'white' : 'darmasaba-navy'}>{task.title}</Text>
|
||||
<MantineBadge
|
||||
color={STATUS_COLORS[task.status] || 'gray'}
|
||||
variant="light"
|
||||
>
|
||||
{task.status}
|
||||
</MantineBadge>
|
||||
</Group>
|
||||
</Box>
|
||||
))}
|
||||
</Stack>
|
||||
</Card>
|
||||
</GridCol>
|
||||
))}
|
||||
</Grid>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="dark:text-gray-100">Distribusi Anggaran Divisi</CardTitle>
|
||||
</CardHeader> <CardContent>
|
||||
<div className="h-80 flex items-center justify-center bg-gray-50 dark:bg-gray-700 rounded-lg">
|
||||
<p className="text-gray-500 dark:text-gray-300">
|
||||
Diagram distribusi anggaran akan ditampilkan di sini
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
{/* Arsip Digital Perangkat Desa */}
|
||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}>
|
||||
<Title order={4} mb="md" c={dark ? 'white' : 'darmasaba-navy'}>
|
||||
Arsip Digital Perangkat Desa
|
||||
</Title>
|
||||
<Grid gutter="md">
|
||||
{archiveItems.map((item, index) => (
|
||||
<GridCol key={index} span={{ base: 12, md: 6, lg: 3 }}>
|
||||
<Card p="md" radius="md" withBorder bg={dark ? "#263852ff" : "#F1F5F9"} style={{ borderColor: dark ? "#263852ff" : "#F1F5F9" }}>
|
||||
<Group justify="space-between">
|
||||
<Text c={dark ? 'white' : 'darmasaba-navy'} fw={500}>{item.name}</Text>
|
||||
<Text c={dark ? 'white' : 'darmasaba-navy'} fw={700}>{item.count}</Text>
|
||||
</Group>
|
||||
</Card>
|
||||
</GridCol>
|
||||
))}
|
||||
</Grid>
|
||||
</Card>
|
||||
|
||||
{/* Kartu Progres Kegiatan */}
|
||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}>
|
||||
<Title order={4} mb="md" c={dark ? 'white' : 'darmasaba-navy'}>
|
||||
Progres Kegiatan / Program
|
||||
</Title>
|
||||
<Stack gap="md">
|
||||
{activityProgress.map((activity, index) => (
|
||||
<Card key={index} p="md" radius="md" withBorder bg={dark ? "#263852ff" : "#F1F5F9"} style={{ borderColor: dark ? "#263852ff" : "#F1F5F9" }}>
|
||||
<Group justify="space-between" mb="sm">
|
||||
<Text c={dark ? 'white' : 'darmasaba-navy'} fw={500}>{activity.name}</Text>
|
||||
<MantineBadge
|
||||
color={STATUS_COLORS[activity.status] || 'gray'}
|
||||
variant="light"
|
||||
>
|
||||
{activity.status}
|
||||
</MantineBadge>
|
||||
</Group>
|
||||
<Group justify="space-between">
|
||||
<MantineProgress
|
||||
value={activity.progress}
|
||||
size="sm"
|
||||
radius="xl"
|
||||
color={activity.progress === 100 ? "green" : "blue"}
|
||||
w="calc(100% - 80px)"
|
||||
/>
|
||||
<Text size="sm" c={dark ? 'white' : 'darmasaba-navy'}>{activity.progress}%</Text>
|
||||
</Group>
|
||||
<Text size="sm" c="dimmed" mt="sm">{activity.date}</Text>
|
||||
</Card>
|
||||
))}
|
||||
</Stack>
|
||||
</Card>
|
||||
|
||||
{/* Statistik Dokumen & Progres Kegiatan */}
|
||||
<Grid gutter="md">
|
||||
<GridCol span={{ base: 12, lg: 6 }}>
|
||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}>
|
||||
<Title order={4} mb="md" c={dark ? 'white' : 'darmasaba-navy'}>
|
||||
Jumlah Dokumen
|
||||
</Title>
|
||||
<ResponsiveContainer width="100%" height={200}>
|
||||
<BarChart data={documentStats}>
|
||||
<CartesianGrid strokeDasharray="3 3" vertical={false} stroke={dark ? "#141D34" : "white"} />
|
||||
<XAxis
|
||||
dataKey="name"
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
tick={{ fill: dark ? "var(--mantine-color-text)" : "var(--mantine-color-text)" }}
|
||||
/>
|
||||
<YAxis
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
tick={{ fill: dark ? "var(--mantine-color-text)" : "var(--mantine-color-text)" }}
|
||||
/>
|
||||
<Tooltip
|
||||
contentStyle={dark
|
||||
? { backgroundColor: 'var(--mantine-color-dark-7)', borderColor: 'var(--mantine-color-dark-6)' }
|
||||
: {}}
|
||||
/>
|
||||
<Bar dataKey="value" fill={dark ? "var(--mantine-color-blue-6)" : "var(--mantine-color-blue-filled)"} radius={[4, 4, 0, 0]} />
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</Card>
|
||||
</GridCol>
|
||||
|
||||
<GridCol span={{ base: 12, lg: 6 }}>
|
||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}>
|
||||
<Title order={4} mb="md" c={dark ? 'white' : 'darmasaba-navy'}>
|
||||
Progres Kegiatan
|
||||
</Title>
|
||||
<ResponsiveContainer width="100%" height={200}>
|
||||
<PieChart>
|
||||
<Pie
|
||||
data={activityProgressStats}
|
||||
cx="50%"
|
||||
cy="50%"
|
||||
labelLine={false}
|
||||
outerRadius={80}
|
||||
fill="#8884d8"
|
||||
dataKey="value"
|
||||
label={({ name, percent }) => `${name}: ${percent ? (percent * 100).toFixed(0) : '0'}%`}
|
||||
>
|
||||
{activityProgressStats.map((entry, index) => (
|
||||
<Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />
|
||||
))}
|
||||
</Pie>
|
||||
<Tooltip
|
||||
contentStyle={dark
|
||||
? { backgroundColor: 'var(--mantine-color-dark-7)', borderColor: 'var(--mantine-color-dark-6)' }
|
||||
: {}}
|
||||
/>
|
||||
</PieChart>
|
||||
</ResponsiveContainer>
|
||||
</Card>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
|
||||
{/* Diskusi Internal */}
|
||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}>
|
||||
<Title order={4} mb="md" c={dark ? 'white' : 'darmasaba-navy'}>
|
||||
Diskusi Internal
|
||||
</Title>
|
||||
<Stack gap="sm">
|
||||
{discussions.map((discussion, index) => (
|
||||
<Card key={index} p="md" radius="md" withBorder bg={dark ? "#263852ff" : "#F1F5F9"} style={{ borderColor: dark ? "#263852ff" : "#F1F5F9" }}>
|
||||
<Group justify="space-between">
|
||||
<Text c={dark ? 'white' : 'darmasaba-navy'} fw={500}>{discussion.title}</Text>
|
||||
<Text size="sm" c="dimmed">{discussion.timestamp}</Text>
|
||||
</Group>
|
||||
<Text size="sm" c="dimmed">{discussion.sender}</Text>
|
||||
</Card>
|
||||
))}
|
||||
</Stack>
|
||||
</Card>
|
||||
|
||||
{/* Agenda / Acara Hari Ini */}
|
||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}>
|
||||
<Title order={4} mb="md" c={dark ? 'white' : 'darmasaba-navy'}>
|
||||
Agenda / Acara Hari Ini
|
||||
</Title>
|
||||
{todayAgenda.length > 0 ? (
|
||||
<Stack gap="sm">
|
||||
{todayAgenda.map((agenda, index) => (
|
||||
<Group key={index} align="flex-start">
|
||||
<Box w={60}>
|
||||
<Text c="dimmed">{agenda.time}</Text>
|
||||
</Box>
|
||||
<Divider orientation="vertical" mx="sm" />
|
||||
<Text c={dark ? 'white' : 'darmasaba-navy'}>{agenda.event}</Text>
|
||||
</Group>
|
||||
))}
|
||||
</Stack>
|
||||
) : (
|
||||
<Text c="dimmed" ta="center" py="md">
|
||||
Tidak ada acara hari ini
|
||||
</Text>
|
||||
)}
|
||||
</Card>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,26 +1,76 @@
|
||||
import type React from "react";
|
||||
import { useState } from "react";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Select } from "@/components/ui/select";
|
||||
import {
|
||||
Grid,
|
||||
GridCol,
|
||||
Group,
|
||||
Text,
|
||||
Title,
|
||||
TextInput,
|
||||
Textarea,
|
||||
Select,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
Badge,
|
||||
Stack,
|
||||
useMantineColorScheme,
|
||||
List,
|
||||
Divider,
|
||||
ActionIcon,
|
||||
Box
|
||||
} from "@mantine/core";
|
||||
import { IconMessage, IconAlertTriangle, IconClock, IconCheck, IconChevronRight } from "@tabler/icons-react";
|
||||
import { Line, LineChart, Bar, BarChart, CartesianGrid, XAxis, YAxis, Tooltip, ResponsiveContainer } from "recharts";
|
||||
|
||||
const PengaduanLayananPublik = () => {
|
||||
const { colorScheme } = useMantineColorScheme();
|
||||
const dark = colorScheme === 'dark';
|
||||
|
||||
// Summary data
|
||||
const summaryData = {
|
||||
total: 42,
|
||||
baru: 14,
|
||||
diproses: 14,
|
||||
selesai: 14
|
||||
};
|
||||
|
||||
// Tren pengaduan data
|
||||
const trenData = [
|
||||
{ bulan: "Jan", jumlah: 30 },
|
||||
{ bulan: "Feb", jumlah: 50 },
|
||||
{ bulan: "Mar", jumlah: 42 },
|
||||
{ bulan: "Apr", jumlah: 38 },
|
||||
{ bulan: "Mei", jumlah: 45 },
|
||||
{ bulan: "Jun", jumlah: 42 }
|
||||
];
|
||||
|
||||
// Surat terbanyak data
|
||||
const suratData = [
|
||||
{ jenis: "KTP", jumlah: 24 },
|
||||
{ jenis: "KK", jumlah: 18 },
|
||||
{ jenis: "Domisili", jumlah: 15 },
|
||||
{ jenis: "Usaha", jumlah: 12 },
|
||||
{ jenis: "Lainnya", jumlah: 8 }
|
||||
];
|
||||
|
||||
// Pengajuan terbaru data
|
||||
const pengajuanTerbaru = [
|
||||
{ nama: "Budi Santoso", jenis: "Ketertiban Umum", waktu: "2 jam yang lalu", status: "baru" },
|
||||
{ nama: "Siti Rahayu", jenis: "Pelayanan Kesehatan", waktu: "5 jam yang lalu", status: "diproses" },
|
||||
{ nama: "Ahmad Fauzi", jenis: "Infrastruktur", waktu: "1 hari yang lalu", status: "selesai" },
|
||||
{ nama: "Dewi Lestari", jenis: "Administrasi", waktu: "1 hari yang lalu", status: "baru" },
|
||||
{ nama: "Joko Widodo", jenis: "Keamanan", waktu: "2 hari yang lalu", status: "diproses" }
|
||||
];
|
||||
|
||||
// Ide inovatif data
|
||||
const ideInovatif = [
|
||||
{ nama: "Andi Prasetyo", judul: "Penerapan Smart Village", kategori: "Teknologi" },
|
||||
{ nama: "Rina Kusuma", judul: "Program Ekowisata Desa", kategori: "Ekonomi" },
|
||||
{ nama: "Bambang Suryono", judul: "Peningkatan Sanitasi", kategori: "Kesehatan" },
|
||||
{ nama: "Lina Marlina", judul: "Pusat Kreatif Anak Muda", kategori: "Pendidikan" }
|
||||
];
|
||||
|
||||
const [activeTab, setActiveTab] = useState<"complaints" | "services">(
|
||||
"complaints",
|
||||
);
|
||||
@@ -133,279 +183,462 @@ const PengaduanLayananPublik = () => {
|
||||
setNewComplaint({ title: "", category: "", description: "" });
|
||||
};
|
||||
|
||||
// Render complaint table rows
|
||||
const complaintRows = complaints.map((complaint) => (
|
||||
<Table.Tr key={complaint.id}>
|
||||
<Table.Td className="font-medium">
|
||||
<Text c={dark ? "white" : "dark.3"}>{complaint.title}</Text>
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<Text c={dark ? "white" : "dark.3"}>{complaint.category}</Text>
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<Badge
|
||||
variant="filled"
|
||||
color={
|
||||
complaint.status === "Resolved"
|
||||
? "green"
|
||||
: complaint.status === "In Progress"
|
||||
? "yellow"
|
||||
: "red"
|
||||
}
|
||||
>
|
||||
{complaint.status}
|
||||
</Badge>
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<Badge
|
||||
variant="filled"
|
||||
color={
|
||||
complaint.priority === "High"
|
||||
? "red"
|
||||
: complaint.priority === "Medium"
|
||||
? "yellow"
|
||||
: "blue"
|
||||
}
|
||||
>
|
||||
{complaint.priority}
|
||||
</Badge>
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<Text c={dark ? "white" : "dark.3"}>{complaint.date}</Text>
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
));
|
||||
|
||||
// Status badge color mapping
|
||||
const getStatusColor = (status: string) => {
|
||||
switch (status) {
|
||||
case 'baru': return 'red';
|
||||
case 'diproses': return 'yellow';
|
||||
case 'selesai': return 'green';
|
||||
default: return 'gray';
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="flex justify-between items-center">
|
||||
<h1 className="text-3xl font-bold text-gray-900 dark:text-white">
|
||||
Pengaduan & Layanan Publik
|
||||
</h1>
|
||||
<div className="flex space-x-4">
|
||||
<Button
|
||||
variant={activeTab === "complaints" ? "default" : "outline"}
|
||||
onClick={() => setActiveTab("complaints")}
|
||||
className="dark:bg-slate-700 dark:hover:bg-slate-600 dark:text-white"
|
||||
>
|
||||
Pengaduan
|
||||
</Button>
|
||||
<Button
|
||||
variant={activeTab === "services" ? "default" : "outline"}
|
||||
onClick={() => setActiveTab("services")}
|
||||
className="dark:bg-slate-700 dark:hover:bg-slate-600 dark:text-white"
|
||||
>
|
||||
Layanan Publik
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Stack gap="lg">
|
||||
{activeTab === "complaints" ? (
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
{/* Complaint Submission Form */}
|
||||
<div className="lg:col-span-1">
|
||||
<Card className="dark:bg-gray-800 dark:border-gray-700">
|
||||
<CardHeader>
|
||||
<CardTitle className="dark:text-white">
|
||||
Ajukan Pengaduan
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<form onSubmit={handleSubmitComplaint} className="space-y-4">
|
||||
<div>
|
||||
<label
|
||||
htmlFor="title"
|
||||
className="block text-sm font-medium mb-1 dark:text-gray-300"
|
||||
>
|
||||
Judul Pengaduan
|
||||
</label>
|
||||
<Input
|
||||
id="title"
|
||||
name="title"
|
||||
value={newComplaint.title}
|
||||
onChange={handleInputChange}
|
||||
placeholder="Masukkan judul pengaduan"
|
||||
className="dark:bg-gray-700 dark:border-gray-600 dark:text-white"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label
|
||||
htmlFor="category"
|
||||
className="block text-sm font-medium mb-1 dark:text-gray-300"
|
||||
>
|
||||
Kategori
|
||||
</label>
|
||||
<Select
|
||||
id="category"
|
||||
name="category"
|
||||
value={newComplaint.category}
|
||||
onChange={handleSelectChange}
|
||||
placeholder="Pilih kategori"
|
||||
data={[
|
||||
{ value: "infrastruktur", label: "Infrastruktur" },
|
||||
{ value: "administrasi", label: "Administrasi" },
|
||||
{ value: "utilitas", label: "Utilitas" },
|
||||
{ value: "sanitasi", label: "Sanitasi" },
|
||||
{ value: "kesehatan", label: "Kesehatan" },
|
||||
{ value: "pendidikan", label: "Pendidikan" },
|
||||
]}
|
||||
className="dark:bg-gray-700 dark:border-gray-600 dark:text-white"
|
||||
clearable
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label
|
||||
htmlFor="description"
|
||||
className="block text-sm font-medium mb-1 dark:text-gray-300"
|
||||
>
|
||||
Deskripsi
|
||||
</label>
|
||||
<Textarea
|
||||
id="description"
|
||||
name="description"
|
||||
value={newComplaint.description}
|
||||
onChange={handleInputChange}
|
||||
placeholder="Jelaskan pengaduan Anda secara detail..."
|
||||
rows={4}
|
||||
className="dark:bg-gray-700 dark:border-gray-600 dark:text-white"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
className="w-full dark:bg-blue-600 dark:hover:bg-blue-700"
|
||||
<>
|
||||
{/* Summary Cards */}
|
||||
<Grid gutter="md">
|
||||
<GridCol span={{ base: 12, md: 6, lg: 3 }}>
|
||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }} h="100%">
|
||||
<Group justify="space-between" align="center">
|
||||
<Stack gap={0}>
|
||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
|
||||
Total Pengaduan
|
||||
</Text>
|
||||
<Text size="xl" fw={700} c={dark ? "dark.0" : "black"}>
|
||||
{summaryData.total}
|
||||
</Text>
|
||||
</Stack>
|
||||
<Badge
|
||||
variant="light"
|
||||
color="darmasaba-blue"
|
||||
p={8}
|
||||
radius="md"
|
||||
>
|
||||
Kirim Pengaduan
|
||||
</Button>
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
<IconMessage size={20} />
|
||||
</Badge>
|
||||
</Group>
|
||||
</Card>
|
||||
</GridCol>
|
||||
|
||||
{/* Complaints List */}
|
||||
<div className="lg:col-span-2">
|
||||
<Card className="dark:bg-gray-800 dark:border-gray-700">
|
||||
<CardHeader>
|
||||
<CardTitle className="dark:text-white">
|
||||
Daftar Pengaduan
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="dark:text-gray-300">
|
||||
Judul
|
||||
</TableHead>
|
||||
<TableHead className="dark:text-gray-300">
|
||||
Kategori
|
||||
</TableHead>
|
||||
<TableHead className="dark:text-gray-300">
|
||||
Status
|
||||
</TableHead>
|
||||
<TableHead className="dark:text-gray-300">
|
||||
Prioritas
|
||||
</TableHead>
|
||||
<TableHead className="dark:text-gray-300">
|
||||
Tanggal
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{complaints.map((complaint) => (
|
||||
<TableRow key={complaint.id}>
|
||||
<TableCell className="font-medium dark:text-white">
|
||||
{complaint.title}
|
||||
</TableCell>
|
||||
<TableCell className="dark:text-gray-300">
|
||||
{complaint.category}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Badge
|
||||
variant={
|
||||
complaint.status === "Resolved"
|
||||
? "success"
|
||||
: complaint.status === "In Progress"
|
||||
? "secondary"
|
||||
: "destructive"
|
||||
}
|
||||
>
|
||||
{complaint.status}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Badge
|
||||
variant={
|
||||
complaint.priority === "High"
|
||||
? "destructive"
|
||||
: complaint.priority === "Medium"
|
||||
? "secondary"
|
||||
: "default"
|
||||
}
|
||||
>
|
||||
{complaint.priority}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="dark:text-gray-300">
|
||||
{complaint.date}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
<GridCol span={{ base: 12, md: 6, lg: 3 }}>
|
||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }} h="100%">
|
||||
<Group justify="space-between" align="center">
|
||||
<Stack gap={0}>
|
||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
|
||||
Baru
|
||||
</Text>
|
||||
<Text size="xl" fw={700} c={dark ? "dark.0" : "black"}>
|
||||
{summaryData.baru}
|
||||
</Text>
|
||||
</Stack>
|
||||
<Badge
|
||||
variant="light"
|
||||
color="red"
|
||||
p={8}
|
||||
radius="md"
|
||||
>
|
||||
<IconAlertTriangle size={20} />
|
||||
</Badge>
|
||||
</Group>
|
||||
</Card>
|
||||
</GridCol>
|
||||
|
||||
<GridCol span={{ base: 12, md: 6, lg: 3 }}>
|
||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }} h="100%">
|
||||
<Group justify="space-between" align="center">
|
||||
<Stack gap={0}>
|
||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
|
||||
Diproses
|
||||
</Text>
|
||||
<Text size="xl" fw={700} c={dark ? "dark.0" : "black"}>
|
||||
{summaryData.diproses}
|
||||
</Text>
|
||||
</Stack>
|
||||
<Badge
|
||||
variant="light"
|
||||
color="yellow"
|
||||
p={8}
|
||||
radius="md"
|
||||
>
|
||||
<IconClock size={20} />
|
||||
</Badge>
|
||||
</Group>
|
||||
</Card>
|
||||
</GridCol>
|
||||
|
||||
<GridCol span={{ base: 12, md: 6, lg: 3 }}>
|
||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }} h="100%">
|
||||
<Group justify="space-between" align="center">
|
||||
<Stack gap={0}>
|
||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
|
||||
Selesai
|
||||
</Text>
|
||||
<Text size="xl" fw={700} c={dark ? "dark.0" : "black"}>
|
||||
{summaryData.selesai}
|
||||
</Text>
|
||||
</Stack>
|
||||
<Badge
|
||||
variant="light"
|
||||
color="green"
|
||||
p={8}
|
||||
radius="md"
|
||||
>
|
||||
<IconCheck size={20} />
|
||||
</Badge>
|
||||
</Group>
|
||||
</Card>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
|
||||
{/* Grafik Tren Pengaduan */}
|
||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }} >
|
||||
<Title order={4} mb="md" c={dark ? "dark.0" : "black"}>
|
||||
Grafik Tren Pengaduan
|
||||
</Title>
|
||||
<ResponsiveContainer width="100%" height={300}>
|
||||
<LineChart data={trenData}>
|
||||
<CartesianGrid
|
||||
strokeDasharray="3 3"
|
||||
vertical={false}
|
||||
stroke={dark ? "var(--mantine-color-gray-7)" : "var(--mantine-color-gray-3)"}
|
||||
/>
|
||||
<XAxis
|
||||
dataKey="bulan"
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
tick={{ fill: dark ? "var(--mantine-color-text)" : "var(--mantine-color-text)" }}
|
||||
/>
|
||||
<YAxis
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
tick={{ fill: dark ? "var(--mantine-color-text)" : "var(--mantine-color-text)" }}
|
||||
/>
|
||||
<Tooltip
|
||||
contentStyle={dark
|
||||
? { backgroundColor: 'var(--mantine-color-dark-7)', borderColor: 'var(--mantine-color-dark-6)' }
|
||||
: {}}
|
||||
/>
|
||||
<Line
|
||||
type="monotone"
|
||||
dataKey="jumlah"
|
||||
stroke={dark ? "var(--mantine-color-blue-6)" : "var(--mantine-color-blue-filled)"}
|
||||
strokeWidth={2}
|
||||
dot={{ stroke: dark ? "var(--mantine-color-blue-6)" : "var(--mantine-color-blue-filled)", strokeWidth: 2, r: 4 }}
|
||||
activeDot={{ r: 6, stroke: '#fff', strokeWidth: 2 }}
|
||||
/>
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</Card>
|
||||
|
||||
{/* Surat Terbanyak & Pengajuan Terbaru & Ide Inovatif */}
|
||||
<Grid gutter="md">
|
||||
{/* Surat Terbanyak */}
|
||||
<GridCol span={{ base: 12, lg: 4 }}>
|
||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }} h="100%">
|
||||
<Title order={4} mb="md" c={dark ? "dark.0" : "black"}>
|
||||
Surat Terbanyak
|
||||
</Title>
|
||||
<ResponsiveContainer width="100%" height={250}>
|
||||
<BarChart data={suratData} layout="horizontal">
|
||||
<CartesianGrid
|
||||
strokeDasharray="3 3"
|
||||
horizontal={false}
|
||||
stroke={dark ? "var(--mantine-color-gray-7)" : "var(--mantine-color-gray-3)"}
|
||||
/>
|
||||
<XAxis
|
||||
dataKey="jumlah"
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
tick={{ fill: dark ? "var(--mantine-color-text)" : "var(--mantine-color-text)" }}
|
||||
/>
|
||||
<YAxis
|
||||
dataKey="jenis"
|
||||
type="category"
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
tick={{ fill: dark ? "var(--mantine-color-text)" : "var(--mantine-color-text)" }}
|
||||
width={80}
|
||||
/>
|
||||
<Tooltip
|
||||
contentStyle={dark
|
||||
? { backgroundColor: 'var(--mantine-color-dark-7)', borderColor: 'var(--mantine-color-dark-6)' }
|
||||
: {}}
|
||||
/>
|
||||
<Bar
|
||||
dataKey="jumlah"
|
||||
fill={dark ? "var(--mantine-color-blue-6)" : "var(--mantine-color-blue-filled)"}
|
||||
radius={[0, 4, 4, 0]}
|
||||
/>
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</Card>
|
||||
</GridCol>
|
||||
|
||||
{/* Pengajuan Terbaru */}
|
||||
<GridCol span={{ base: 12, lg: 4 }}>
|
||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }} h="100%">
|
||||
<Title order={4} mb="md" c={dark ? "dark.0" : "black"}>
|
||||
Pengajuan Terbaru
|
||||
</Title>
|
||||
{pengajuanTerbaru.map((item, index) => (
|
||||
<Box key={index}>
|
||||
<Group justify="space-between">
|
||||
<Stack gap={0}>
|
||||
<Text fw={500} c={dark ? "dark.0" : "black"}>{item.nama}</Text>
|
||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>{item.jenis}</Text>
|
||||
</Stack>
|
||||
<Stack gap={0} align="flex-end">
|
||||
<Badge color={getStatusColor(item.status)} variant="light">
|
||||
{item.status}
|
||||
</Badge>
|
||||
<Text size="xs" c={dark ? "dark.4" : "dimmed"}>{item.waktu}</Text>
|
||||
</Stack>
|
||||
</Group>
|
||||
<Divider my="sm" />
|
||||
</Box>
|
||||
))}
|
||||
</Card>
|
||||
</GridCol>
|
||||
|
||||
{/* Ajuan Ide Inovatif */}
|
||||
<GridCol span={{ base: 12, lg: 4 }}>
|
||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }} h="100%">
|
||||
<Title order={4} mb="md" c={dark ? "dark.0" : "black"}>
|
||||
Ajuan Ide Inovatif
|
||||
</Title>
|
||||
{ideInovatif.map((item, index) => (
|
||||
<Box key={index}>
|
||||
<Group justify="space-between">
|
||||
<Stack gap={0}>
|
||||
<Text fw={500} c={dark ? "dark.0" : "black"}>{item.judul}</Text>
|
||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>{item.nama}</Text>
|
||||
</Stack>
|
||||
<Group>
|
||||
<Badge color="blue" variant="light">
|
||||
{item.kategori}
|
||||
</Badge>
|
||||
<ActionIcon variant="subtle" color="darmasaba-blue">
|
||||
<IconChevronRight size={16} />
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
</Group>
|
||||
<Divider my="sm" />
|
||||
</Box>
|
||||
))}
|
||||
</Card>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
|
||||
{/* Complaint Submission Form and List */}
|
||||
<Grid gutter="md">
|
||||
{/* Complaint Submission Form */}
|
||||
<GridCol span={{ base: 12, lg: 4 }}>
|
||||
<Card p="md" withBorder radius="md" h="100%" bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}>
|
||||
<Card.Section withBorder inheritPadding py="xs">
|
||||
<Title order={3} py="xs">Ajukan Pengaduan</Title>
|
||||
</Card.Section>
|
||||
<Card.Section>
|
||||
<form onSubmit={handleSubmitComplaint}>
|
||||
<Stack gap="md" p={"sm"}>
|
||||
<TextInput
|
||||
label="Judul Pengaduan"
|
||||
id="title"
|
||||
name="title"
|
||||
value={newComplaint.title}
|
||||
onChange={handleInputChange}
|
||||
placeholder="Masukkan judul pengaduan"
|
||||
required
|
||||
withAsterisk
|
||||
/>
|
||||
|
||||
<Select
|
||||
label="Kategori"
|
||||
id="category"
|
||||
name="category"
|
||||
value={newComplaint.category}
|
||||
onChange={handleSelectChange}
|
||||
placeholder="Pilih kategori"
|
||||
data={[
|
||||
{ value: "infrastruktur", label: "Infrastruktur" },
|
||||
{ value: "administrasi", label: "Administrasi" },
|
||||
{ value: "utilitas", label: "Utilitas" },
|
||||
{ value: "sanitasi", label: "Sanitasi" },
|
||||
{ value: "kesehatan", label: "Kesehatan" },
|
||||
{ value: "pendidikan", label: "Pendidikan" },
|
||||
]}
|
||||
clearable
|
||||
/>
|
||||
|
||||
<Textarea
|
||||
label="Deskripsi"
|
||||
id="description"
|
||||
name="description"
|
||||
value={newComplaint.description}
|
||||
onChange={handleInputChange}
|
||||
placeholder="Jelaskan pengaduan Anda secara detail..."
|
||||
minRows={4}
|
||||
required
|
||||
withAsterisk
|
||||
/>
|
||||
|
||||
<Button type="submit" mt="md" color="darmasaba-blue">
|
||||
Kirim Pengaduan
|
||||
</Button>
|
||||
</Stack>
|
||||
</form>
|
||||
</Card.Section>
|
||||
</Card>
|
||||
</GridCol>
|
||||
|
||||
{/* Complaints List */}
|
||||
<GridCol span={{ base: 12, lg: 8 }}>
|
||||
<Card withBorder radius="md" bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}>
|
||||
<Card.Section withBorder inheritPadding py="xs">
|
||||
<Title order={3} py="xs">Daftar Pengaduan</Title>
|
||||
</Card.Section>
|
||||
<Card.Section py="md" px="xs">
|
||||
<Table withColumnBorders>
|
||||
<Table.Thead>
|
||||
<Table.Tr>
|
||||
<Table.Th><Text c={dark ? "white" : "dark.3" }>Judul</Text></Table.Th>
|
||||
<Table.Th><Text c={dark ? "white" : "dark.3" }>Kategori</Text></Table.Th>
|
||||
<Table.Th><Text c={dark ? "white" : "dark.3" }>Status</Text></Table.Th>
|
||||
<Table.Th><Text c={dark ? "white" : "dark.3" }>Prioritas</Text></Table.Th>
|
||||
<Table.Th><Text c={dark ? "white" : "dark.3" }>Tanggal</Text></Table.Th>
|
||||
</Table.Tr>
|
||||
</Table.Thead>
|
||||
<Table.Tbody>
|
||||
{complaintRows}
|
||||
</Table.Tbody>
|
||||
</Table>
|
||||
</Card.Section>
|
||||
</Card>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
</>
|
||||
) : (
|
||||
<div>
|
||||
<Card className="dark:bg-gray-800 dark:border-gray-700">
|
||||
<CardHeader>
|
||||
<CardTitle className="dark:text-white">
|
||||
Layanan Publik Tersedia
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<Stack gap="lg">
|
||||
<Card withBorder radius="md">
|
||||
<Card.Section withBorder inheritPadding py="xs">
|
||||
<Title order={3} py="xs">Layanan Publik Tersedia</Title>
|
||||
</Card.Section>
|
||||
<Card.Section pt="md">
|
||||
<Grid gutter="md">
|
||||
{services.map((service) => (
|
||||
<Card
|
||||
key={service.id}
|
||||
className="dark:bg-gray-700 dark:border-gray-600"
|
||||
>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-lg dark:text-white">
|
||||
{service.name}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-300 mb-3">
|
||||
<GridCol key={service.id} span={{ base: 12, md: 6, lg: 4 }}>
|
||||
<Card withBorder radius="md" h="100%">
|
||||
<Title order={4} mb="sm">{service.name}</Title>
|
||||
<Text size="sm" c={dark ? "white" : "dark.3" } mb="md">
|
||||
{service.description}
|
||||
</p>
|
||||
<div className="flex justify-between items-center">
|
||||
</Text>
|
||||
<Group justify="space-between">
|
||||
<Badge
|
||||
variant={
|
||||
variant="filled"
|
||||
color={
|
||||
service.status === "Available"
|
||||
? "success"
|
||||
? "green"
|
||||
: service.status === "Limited"
|
||||
? "secondary"
|
||||
: "destructive"
|
||||
? "yellow"
|
||||
: "red"
|
||||
}
|
||||
>
|
||||
{service.status}
|
||||
</Badge>
|
||||
<span className="text-xs text-gray-500 dark:text-gray-400">
|
||||
<Text size="sm" c={dark ? "white" : "dark.3" }>
|
||||
{service.category}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mt-2">
|
||||
</Text>
|
||||
</Group>
|
||||
<Text size="xs" c={dark ? "white" : "dark.3" } mt="sm">
|
||||
Terakhir diperbarui: {service.lastUpdated}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Text>
|
||||
</Card>
|
||||
</GridCol>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Grid>
|
||||
</Card.Section>
|
||||
</Card>
|
||||
|
||||
<Card className="mt-6 dark:bg-gray-800 dark:border-gray-700">
|
||||
<CardHeader>
|
||||
<CardTitle className="dark:text-white">
|
||||
Statistik Layanan
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div className="bg-gray-100 dark:bg-gray-700 p-4 rounded-lg">
|
||||
<h3 className="text-lg font-semibold dark:text-white">
|
||||
Jumlah Layanan Tersedia
|
||||
</h3>
|
||||
<p className="text-3xl font-bold text-blue-600 dark:text-blue-400">
|
||||
12
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-gray-100 dark:bg-gray-700 p-4 rounded-lg">
|
||||
<h3 className="text-lg font-semibold dark:text-white">
|
||||
Layanan Terpopuler
|
||||
</h3>
|
||||
<p className="text-3xl font-bold text-green-600 dark:text-green-400">
|
||||
4
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-gray-100 dark:bg-gray-700 p-4 rounded-lg">
|
||||
<h3 className="text-lg font-semibold dark:text-white">
|
||||
Permintaan Baru
|
||||
</h3>
|
||||
<p className="text-3xl font-bold text-purple-600 dark:text-purple-400">
|
||||
23
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
<Card withBorder radius="md">
|
||||
<Card.Section withBorder inheritPadding py="xs">
|
||||
<Title order={3} py="xs">Statistik Layanan</Title>
|
||||
</Card.Section>
|
||||
<Card.Section pt="md">
|
||||
<Grid gutter="md">
|
||||
<GridCol span={{ base: 12, md: 4 }}>
|
||||
<Card p="md" bg={dark ? "dark.7" : "gray.0"} radius="md">
|
||||
<Title order={4} mb="xs">Jumlah Layanan Tersedia</Title>
|
||||
<Text size="xl" fw={700} c="darmasaba-blue">
|
||||
12
|
||||
</Text>
|
||||
</Card>
|
||||
</GridCol>
|
||||
<GridCol span={{ base: 12, md: 4 }}>
|
||||
<Card p="md" bg={dark ? "dark.7" : "gray.0"} radius="md">
|
||||
<Title order={4} mb="xs">Layanan Terpopuler</Title>
|
||||
<Text size="xl" fw={700} c="darmasaba-success">
|
||||
4
|
||||
</Text>
|
||||
</Card>
|
||||
</GridCol>
|
||||
<GridCol span={{ base: 12, md: 4 }}>
|
||||
<Card p="md" bg={dark ? "dark.7" : "gray.0"} radius="md">
|
||||
<Title order={4} mb="xs">Permintaan Baru</Title>
|
||||
<Text size="xl" fw={700} c="darmasaba-warning">
|
||||
23
|
||||
</Text>
|
||||
</Card>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
</Card.Section>
|
||||
</Card>
|
||||
</div>
|
||||
</Stack>
|
||||
)}
|
||||
</div>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
Input,
|
||||
NavLink as MantineNavLink,
|
||||
Box,
|
||||
useMantineColorScheme,
|
||||
} from "@mantine/core";
|
||||
|
||||
interface SidebarProps {
|
||||
@@ -17,6 +18,9 @@ interface SidebarProps {
|
||||
export function Sidebar({ className }: SidebarProps) {
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const { colorScheme } = useMantineColorScheme();
|
||||
const isActiveBg = colorScheme === 'dark' ? "#182949" : "#E6F0FF";
|
||||
const isActiveBorder = colorScheme === 'dark' ? "#00398D" : "#1F41AE";
|
||||
|
||||
// Define menu items with their paths
|
||||
const menuItems = [
|
||||
@@ -75,16 +79,34 @@ export function Sidebar({ className }: SidebarProps) {
|
||||
|
||||
{/* Menu Items */}
|
||||
<Stack gap={0} px="xs" flex={1} style={{ overflowY: "auto" }}>
|
||||
{menuItems.map((item, index) => (
|
||||
<MantineNavLink
|
||||
key={index}
|
||||
onClick={() => navigate({ to: item.path })}
|
||||
label={item.name}
|
||||
active={location.pathname === item.path}
|
||||
variant="subtle"
|
||||
color="blue"
|
||||
/>
|
||||
))}
|
||||
{menuItems.map((item, index) => {
|
||||
const isActive = location.pathname === item.path;
|
||||
return (
|
||||
<MantineNavLink
|
||||
key={index}
|
||||
onClick={() => navigate({ to: item.path })}
|
||||
label={item.name}
|
||||
active={isActive}
|
||||
variant="subtle"
|
||||
color="blue"
|
||||
style={{
|
||||
background: isActive ? isActiveBg : "transparent",
|
||||
fontWeight: isActive ? "bold" : "normal",
|
||||
borderLeft: isActive ? `4px solid ${isActiveBorder}` : "4px solid transparent",
|
||||
borderRadius: "8px",
|
||||
transition: "all 200ms ease",
|
||||
margin: "2px 0",
|
||||
}}
|
||||
styles={{
|
||||
body: {
|
||||
"&:hover": {
|
||||
background: "#F1F5F9",
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
|
||||
296
src/components/sosial-page.tsx
Normal file
296
src/components/sosial-page.tsx
Normal file
@@ -0,0 +1,296 @@
|
||||
import { useState } from "react";
|
||||
import {
|
||||
Card,
|
||||
Grid,
|
||||
GridCol,
|
||||
Group,
|
||||
Text,
|
||||
Title,
|
||||
Progress,
|
||||
Stack,
|
||||
useMantineColorScheme,
|
||||
Badge,
|
||||
List,
|
||||
ThemeIcon
|
||||
} from "@mantine/core";
|
||||
import {
|
||||
IconHeartbeat,
|
||||
IconBabyCarriage,
|
||||
IconStethoscope,
|
||||
IconMedicalCross,
|
||||
IconSchool,
|
||||
IconBook,
|
||||
IconCalendarEvent,
|
||||
IconAward
|
||||
} from "@tabler/icons-react";
|
||||
|
||||
const SosialPage = () => {
|
||||
const { colorScheme } = useMantineColorScheme();
|
||||
const dark = colorScheme === 'dark';
|
||||
|
||||
// Sample data for health statistics
|
||||
const healthStats = {
|
||||
ibuHamil: 87,
|
||||
balita: 342,
|
||||
alertStunting: 12,
|
||||
posyanduAktif: 8,
|
||||
};
|
||||
|
||||
// Sample data for health progress
|
||||
const healthProgress = [
|
||||
{ label: "Imunisasi Lengkap", value: 92, color: "green" },
|
||||
{ label: "Pemeriksaan Rutin", value: 88, color: "blue" },
|
||||
{ label: "Gizi Baik", value: 86, color: "teal" },
|
||||
{ label: "Target Stunting", value: 14, color: "red" },
|
||||
];
|
||||
|
||||
// Sample data for posyandu schedule
|
||||
const posyanduSchedule = [
|
||||
{ nama: "Posyandu Mawar", tanggal: "Senin, 15 Feb 2026", jam: "08:00 - 11:00" },
|
||||
{ nama: "Posyandu Melati", tanggal: "Selasa, 16 Feb 2026", jam: "08:00 - 11:00" },
|
||||
{ nama: "Posyandu Dahlia", tanggal: "Rabu, 17 Feb 2026", jam: "08:00 - 11:00" },
|
||||
{ nama: "Posyandu Anggrek", tanggal: "Kamis, 18 Feb 2026", jam: "08:00 - 11:00" },
|
||||
];
|
||||
|
||||
// Sample data for education stats
|
||||
const educationStats = {
|
||||
siswa: {
|
||||
tk: 125,
|
||||
sd: 480,
|
||||
smp: 210,
|
||||
sma: 150,
|
||||
},
|
||||
sekolah: {
|
||||
jumlah: 8,
|
||||
guru: 42,
|
||||
}
|
||||
};
|
||||
|
||||
// Sample data for scholarships
|
||||
const scholarshipData = {
|
||||
penerima: 45,
|
||||
dana: "Rp 1.200.000.000",
|
||||
tahunAjaran: "2025/2026",
|
||||
};
|
||||
|
||||
// Sample data for cultural events
|
||||
const culturalEvents = [
|
||||
{ nama: "Hari Kesaktian Pancasila", tanggal: "1 Oktober 2025", lokasi: "Balai Desa" },
|
||||
{ nama: "Festival Budaya Desa", tanggal: "20 Mei 2026", lokasi: "Lapangan Desa" },
|
||||
{ nama: "Perayaan HUT Desa", tanggal: "17 Agustus 2026", lokasi: "Balai Desa" },
|
||||
];
|
||||
|
||||
return (
|
||||
<Stack gap="lg">
|
||||
{/* Page Header */}
|
||||
<Group justify="space-between" align="center">
|
||||
<Title order={2} c={dark ? "dark.0" : "black"}>
|
||||
Sosial Desa
|
||||
</Title>
|
||||
</Group>
|
||||
|
||||
{/* Health Statistics Cards */}
|
||||
<Grid gutter="md">
|
||||
<GridCol span={{ base: 12, sm: 6, md: 3 }}>
|
||||
<Card withBorder radius="md" padding="lg">
|
||||
<Group justify="space-between" align="center">
|
||||
<Stack gap={0}>
|
||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
|
||||
Ibu Hamil Aktif
|
||||
</Text>
|
||||
<Text size="xl" fw={700} c={dark ? "dark.0" : "black"}>
|
||||
{healthStats.ibuHamil}
|
||||
</Text>
|
||||
</Stack>
|
||||
<ThemeIcon variant="light" color="darmasaba-blue" size="xl" radius="xl">
|
||||
<IconHeartbeat size={24} />
|
||||
</ThemeIcon>
|
||||
</Group>
|
||||
</Card>
|
||||
</GridCol>
|
||||
|
||||
<GridCol span={{ base: 12, sm: 6, md: 3 }}>
|
||||
<Card withBorder radius="md" padding="lg">
|
||||
<Group justify="space-between" align="center">
|
||||
<Stack gap={0}>
|
||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
|
||||
Balita Terdaftar
|
||||
</Text>
|
||||
<Text size="xl" fw={700} c={dark ? "dark.0" : "black"}>
|
||||
{healthStats.balita}
|
||||
</Text>
|
||||
</Stack>
|
||||
<ThemeIcon variant="light" color="darmasaba-success" size="xl" radius="xl">
|
||||
<IconBabyCarriage size={24} />
|
||||
</ThemeIcon>
|
||||
</Group>
|
||||
</Card>
|
||||
</GridCol>
|
||||
|
||||
<GridCol span={{ base: 12, sm: 6, md: 3 }}>
|
||||
<Card withBorder radius="md" padding="lg">
|
||||
<Group justify="space-between" align="center">
|
||||
<Stack gap={0}>
|
||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
|
||||
Alert Stunting
|
||||
</Text>
|
||||
<Text size="xl" fw={700} c="red">
|
||||
{healthStats.alertStunting}
|
||||
</Text>
|
||||
</Stack>
|
||||
<ThemeIcon variant="light" color="red" size="xl" radius="xl">
|
||||
<IconStethoscope size={24} />
|
||||
</ThemeIcon>
|
||||
</Group>
|
||||
</Card>
|
||||
</GridCol>
|
||||
|
||||
<GridCol span={{ base: 12, sm: 6, md: 3 }}>
|
||||
<Card withBorder radius="md" padding="lg">
|
||||
<Group justify="space-between" align="center">
|
||||
<Stack gap={0}>
|
||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
|
||||
Posyandu Aktif
|
||||
</Text>
|
||||
<Text size="xl" fw={700} c={dark ? "dark.0" : "black"}>
|
||||
{healthStats.posyanduAktif}
|
||||
</Text>
|
||||
</Stack>
|
||||
<ThemeIcon variant="light" color="darmasaba-warning" size="xl" radius="xl">
|
||||
<IconMedicalCross size={24} />
|
||||
</ThemeIcon>
|
||||
</Group>
|
||||
</Card>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
|
||||
{/* Health Progress Bars */}
|
||||
<Card withBorder radius="md" p="lg">
|
||||
<Title order={3} mb="md" c={dark ? "dark.0" : "black"}>Statistik Kesehatan</Title>
|
||||
<Stack gap="md">
|
||||
{healthProgress.map((item, index) => (
|
||||
<div key={index}>
|
||||
<Group justify="space-between" mb={5}>
|
||||
<Text size="sm" fw={500} c={dark ? "dark.0" : "black"}>
|
||||
{item.label}
|
||||
</Text>
|
||||
<Text size="sm" fw={600} c={dark ? "dark.0" : "black"}>
|
||||
{item.value}%
|
||||
</Text>
|
||||
</Group>
|
||||
<Progress
|
||||
value={item.value}
|
||||
size="lg"
|
||||
radius="xl"
|
||||
color={item.color}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</Stack>
|
||||
</Card>
|
||||
|
||||
<Grid gutter="md">
|
||||
{/* Jadwal Posyandu */}
|
||||
<GridCol span={{ base: 12, lg: 6 }}>
|
||||
<Card withBorder radius="md" p="lg">
|
||||
<Title order={3} mb="md" c={dark ? "dark.0" : "black"}>Jadwal Posyandu</Title>
|
||||
<Stack gap="sm">
|
||||
{posyanduSchedule.map((item, index) => (
|
||||
<Card key={index} withBorder radius="md" p="md">
|
||||
<Group justify="space-between">
|
||||
<Stack gap={0}>
|
||||
<Text fw={500} c={dark ? "dark.0" : "black"}>{item.nama}</Text>
|
||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>{item.tanggal}</Text>
|
||||
</Stack>
|
||||
<Badge variant="light" color="darmasaba-blue">
|
||||
{item.jam}
|
||||
</Badge>
|
||||
</Group>
|
||||
</Card>
|
||||
))}
|
||||
</Stack>
|
||||
</Card>
|
||||
</GridCol>
|
||||
|
||||
{/* Pendidikan */}
|
||||
<GridCol span={{ base: 12, lg: 6 }}>
|
||||
<Card withBorder radius="md" p="lg">
|
||||
<Title order={3} mb="md" c={dark ? "dark.0" : "black"}>Pendidikan</Title>
|
||||
<Stack gap="md">
|
||||
<Group justify="space-between">
|
||||
<Text fw={500} c={dark ? "dark.0" : "black"}>TK / PAUD</Text>
|
||||
<Text fw={700} c={dark ? "dark.0" : "black"}>{educationStats.siswa.tk}</Text>
|
||||
</Group>
|
||||
<Group justify="space-between">
|
||||
<Text fw={500} c={dark ? "dark.0" : "black"}>SD</Text>
|
||||
<Text fw={700} c={dark ? "dark.0" : "black"}>{educationStats.siswa.sd}</Text>
|
||||
</Group>
|
||||
<Group justify="space-between">
|
||||
<Text fw={500} c={dark ? "dark.0" : "black"}>SMP</Text>
|
||||
<Text fw={700} c={dark ? "dark.0" : "black"}>{educationStats.siswa.smp}</Text>
|
||||
</Group>
|
||||
<Group justify="space-between">
|
||||
<Text fw={500} c={dark ? "dark.0" : "black"}>SMA</Text>
|
||||
<Text fw={700} c={dark ? "dark.0" : "black"}>{educationStats.siswa.sma}</Text>
|
||||
</Group>
|
||||
|
||||
<Card withBorder radius="md" p="md" mt="md">
|
||||
<Group justify="space-between">
|
||||
<Text fw={500} c={dark ? "dark.0" : "black"}>Jumlah Lembaga Pendidikan</Text>
|
||||
<Text fw={700} c={dark ? "dark.0" : "black"}>{educationStats.sekolah.jumlah}</Text>
|
||||
</Group>
|
||||
<Group justify="space-between" mt="sm">
|
||||
<Text fw={500} c={dark ? "dark.0" : "black"}>Jumlah Tenaga Pengajar</Text>
|
||||
<Text fw={700} c={dark ? "dark.0" : "black"}>{educationStats.sekolah.guru}</Text>
|
||||
</Group>
|
||||
</Card>
|
||||
</Stack>
|
||||
</Card>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
|
||||
<Grid gutter="md">
|
||||
{/* Beasiswa Desa */}
|
||||
<GridCol span={{ base: 12, lg: 6 }}>
|
||||
<Card withBorder radius="md" p="lg" bg={dark ? "dark.8" : "darmasaba-blue.0"}>
|
||||
<Group justify="space-between" align="center">
|
||||
<Stack gap={0}>
|
||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>Beasiswa Desa</Text>
|
||||
<Text size="xl" fw={700} c={dark ? "dark.0" : "black"}>Penerima: {scholarshipData.penerima}</Text>
|
||||
</Stack>
|
||||
<ThemeIcon variant="light" color="darmasaba-success" size="xl" radius="xl">
|
||||
<IconAward size={24} />
|
||||
</ThemeIcon>
|
||||
</Group>
|
||||
<Text mt="md" c={dark ? "dark.0" : "black"}>Dana Tersalurkan: <Text span fw={700}>{scholarshipData.dana}</Text></Text>
|
||||
<Text mt="sm" c={dark ? "dark.3" : "dimmed"}>Tahun Ajaran: {scholarshipData.tahunAjaran}</Text>
|
||||
</Card>
|
||||
</GridCol>
|
||||
|
||||
{/* Kalender Event Budaya */}
|
||||
<GridCol span={{ base: 12, lg: 6 }}>
|
||||
<Card withBorder radius="md" p="lg">
|
||||
<Title order={3} mb="md" c={dark ? "dark.0" : "black"}>Kalender Event Budaya</Title>
|
||||
<List spacing="sm">
|
||||
{culturalEvents.map((event, index) => (
|
||||
<List.Item key={index} icon={
|
||||
<ThemeIcon color="darmasaba-blue" size={24} radius="xl">
|
||||
<IconCalendarEvent size={12} />
|
||||
</ThemeIcon>
|
||||
}>
|
||||
<Group justify="space-between">
|
||||
<Text fw={500} c={dark ? "dark.0" : "black"}>{event.nama}</Text>
|
||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>{event.lokasi}</Text>
|
||||
</Group>
|
||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>{event.tanggal}</Text>
|
||||
</List.Item>
|
||||
))}
|
||||
</List>
|
||||
</Card>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default SosialPage;
|
||||
@@ -20,11 +20,14 @@ import { Route as DashboardIndexRouteImport } from './routes/dashboard/index'
|
||||
import { Route as AdminIndexRouteImport } from './routes/admin/index'
|
||||
import { Route as UsersIdRouteImport } from './routes/users/$id'
|
||||
import { Route as ProfileEditRouteImport } from './routes/profile/edit'
|
||||
import { Route as DashboardSosialRouteImport } from './routes/dashboard/sosial'
|
||||
import { Route as DashboardPengaduanLayananPublikRouteImport } from './routes/dashboard/pengaduan-layanan-publik'
|
||||
import { Route as DashboardKinerjaDivisiRouteImport } from './routes/dashboard/kinerja-divisi'
|
||||
import { Route as DashboardKeuanganAnggaranRouteImport } from './routes/dashboard/keuangan-anggaran'
|
||||
import { Route as DashboardKeamananRouteImport } from './routes/dashboard/keamanan'
|
||||
import { Route as DashboardJennaAnalyticRouteImport } from './routes/dashboard/jenna-analytic'
|
||||
import { Route as DashboardDemografiPekerjaanRouteImport } from './routes/dashboard/demografi-pekerjaan'
|
||||
import { Route as DashboardBumdesRouteImport } from './routes/dashboard/bumdes'
|
||||
import { Route as AdminUsersRouteImport } from './routes/admin/users'
|
||||
import { Route as AdminSettingsRouteImport } from './routes/admin/settings'
|
||||
import { Route as AdminApikeyRouteImport } from './routes/admin/apikey'
|
||||
@@ -84,6 +87,11 @@ const ProfileEditRoute = ProfileEditRouteImport.update({
|
||||
path: '/profile/edit',
|
||||
getParentRoute: () => rootRouteImport,
|
||||
} as any)
|
||||
const DashboardSosialRoute = DashboardSosialRouteImport.update({
|
||||
id: '/sosial',
|
||||
path: '/sosial',
|
||||
getParentRoute: () => DashboardRouteRoute,
|
||||
} as any)
|
||||
const DashboardPengaduanLayananPublikRoute =
|
||||
DashboardPengaduanLayananPublikRouteImport.update({
|
||||
id: '/pengaduan-layanan-publik',
|
||||
@@ -101,6 +109,11 @@ const DashboardKeuanganAnggaranRoute =
|
||||
path: '/keuangan-anggaran',
|
||||
getParentRoute: () => DashboardRouteRoute,
|
||||
} as any)
|
||||
const DashboardKeamananRoute = DashboardKeamananRouteImport.update({
|
||||
id: '/keamanan',
|
||||
path: '/keamanan',
|
||||
getParentRoute: () => DashboardRouteRoute,
|
||||
} as any)
|
||||
const DashboardJennaAnalyticRoute = DashboardJennaAnalyticRouteImport.update({
|
||||
id: '/jenna-analytic',
|
||||
path: '/jenna-analytic',
|
||||
@@ -112,6 +125,11 @@ const DashboardDemografiPekerjaanRoute =
|
||||
path: '/demografi-pekerjaan',
|
||||
getParentRoute: () => DashboardRouteRoute,
|
||||
} as any)
|
||||
const DashboardBumdesRoute = DashboardBumdesRouteImport.update({
|
||||
id: '/bumdes',
|
||||
path: '/bumdes',
|
||||
getParentRoute: () => DashboardRouteRoute,
|
||||
} as any)
|
||||
const AdminUsersRoute = AdminUsersRouteImport.update({
|
||||
id: '/users',
|
||||
path: '/users',
|
||||
@@ -137,11 +155,14 @@ export interface FileRoutesByFullPath {
|
||||
'/admin/apikey': typeof AdminApikeyRoute
|
||||
'/admin/settings': typeof AdminSettingsRoute
|
||||
'/admin/users': typeof AdminUsersRoute
|
||||
'/dashboard/bumdes': typeof DashboardBumdesRoute
|
||||
'/dashboard/demografi-pekerjaan': typeof DashboardDemografiPekerjaanRoute
|
||||
'/dashboard/jenna-analytic': typeof DashboardJennaAnalyticRoute
|
||||
'/dashboard/keamanan': typeof DashboardKeamananRoute
|
||||
'/dashboard/keuangan-anggaran': typeof DashboardKeuanganAnggaranRoute
|
||||
'/dashboard/kinerja-divisi': typeof DashboardKinerjaDivisiRoute
|
||||
'/dashboard/pengaduan-layanan-publik': typeof DashboardPengaduanLayananPublikRoute
|
||||
'/dashboard/sosial': typeof DashboardSosialRoute
|
||||
'/profile/edit': typeof ProfileEditRoute
|
||||
'/users/$id': typeof UsersIdRoute
|
||||
'/admin/': typeof AdminIndexRoute
|
||||
@@ -156,11 +177,14 @@ export interface FileRoutesByTo {
|
||||
'/admin/apikey': typeof AdminApikeyRoute
|
||||
'/admin/settings': typeof AdminSettingsRoute
|
||||
'/admin/users': typeof AdminUsersRoute
|
||||
'/dashboard/bumdes': typeof DashboardBumdesRoute
|
||||
'/dashboard/demografi-pekerjaan': typeof DashboardDemografiPekerjaanRoute
|
||||
'/dashboard/jenna-analytic': typeof DashboardJennaAnalyticRoute
|
||||
'/dashboard/keamanan': typeof DashboardKeamananRoute
|
||||
'/dashboard/keuangan-anggaran': typeof DashboardKeuanganAnggaranRoute
|
||||
'/dashboard/kinerja-divisi': typeof DashboardKinerjaDivisiRoute
|
||||
'/dashboard/pengaduan-layanan-publik': typeof DashboardPengaduanLayananPublikRoute
|
||||
'/dashboard/sosial': typeof DashboardSosialRoute
|
||||
'/profile/edit': typeof ProfileEditRoute
|
||||
'/users/$id': typeof UsersIdRoute
|
||||
'/admin': typeof AdminIndexRoute
|
||||
@@ -178,11 +202,14 @@ export interface FileRoutesById {
|
||||
'/admin/apikey': typeof AdminApikeyRoute
|
||||
'/admin/settings': typeof AdminSettingsRoute
|
||||
'/admin/users': typeof AdminUsersRoute
|
||||
'/dashboard/bumdes': typeof DashboardBumdesRoute
|
||||
'/dashboard/demografi-pekerjaan': typeof DashboardDemografiPekerjaanRoute
|
||||
'/dashboard/jenna-analytic': typeof DashboardJennaAnalyticRoute
|
||||
'/dashboard/keamanan': typeof DashboardKeamananRoute
|
||||
'/dashboard/keuangan-anggaran': typeof DashboardKeuanganAnggaranRoute
|
||||
'/dashboard/kinerja-divisi': typeof DashboardKinerjaDivisiRoute
|
||||
'/dashboard/pengaduan-layanan-publik': typeof DashboardPengaduanLayananPublikRoute
|
||||
'/dashboard/sosial': typeof DashboardSosialRoute
|
||||
'/profile/edit': typeof ProfileEditRoute
|
||||
'/users/$id': typeof UsersIdRoute
|
||||
'/admin/': typeof AdminIndexRoute
|
||||
@@ -201,11 +228,14 @@ export interface FileRouteTypes {
|
||||
| '/admin/apikey'
|
||||
| '/admin/settings'
|
||||
| '/admin/users'
|
||||
| '/dashboard/bumdes'
|
||||
| '/dashboard/demografi-pekerjaan'
|
||||
| '/dashboard/jenna-analytic'
|
||||
| '/dashboard/keamanan'
|
||||
| '/dashboard/keuangan-anggaran'
|
||||
| '/dashboard/kinerja-divisi'
|
||||
| '/dashboard/pengaduan-layanan-publik'
|
||||
| '/dashboard/sosial'
|
||||
| '/profile/edit'
|
||||
| '/users/$id'
|
||||
| '/admin/'
|
||||
@@ -220,11 +250,14 @@ export interface FileRouteTypes {
|
||||
| '/admin/apikey'
|
||||
| '/admin/settings'
|
||||
| '/admin/users'
|
||||
| '/dashboard/bumdes'
|
||||
| '/dashboard/demografi-pekerjaan'
|
||||
| '/dashboard/jenna-analytic'
|
||||
| '/dashboard/keamanan'
|
||||
| '/dashboard/keuangan-anggaran'
|
||||
| '/dashboard/kinerja-divisi'
|
||||
| '/dashboard/pengaduan-layanan-publik'
|
||||
| '/dashboard/sosial'
|
||||
| '/profile/edit'
|
||||
| '/users/$id'
|
||||
| '/admin'
|
||||
@@ -241,11 +274,14 @@ export interface FileRouteTypes {
|
||||
| '/admin/apikey'
|
||||
| '/admin/settings'
|
||||
| '/admin/users'
|
||||
| '/dashboard/bumdes'
|
||||
| '/dashboard/demografi-pekerjaan'
|
||||
| '/dashboard/jenna-analytic'
|
||||
| '/dashboard/keamanan'
|
||||
| '/dashboard/keuangan-anggaran'
|
||||
| '/dashboard/kinerja-divisi'
|
||||
| '/dashboard/pengaduan-layanan-publik'
|
||||
| '/dashboard/sosial'
|
||||
| '/profile/edit'
|
||||
| '/users/$id'
|
||||
| '/admin/'
|
||||
@@ -345,6 +381,13 @@ declare module '@tanstack/react-router' {
|
||||
preLoaderRoute: typeof ProfileEditRouteImport
|
||||
parentRoute: typeof rootRouteImport
|
||||
}
|
||||
'/dashboard/sosial': {
|
||||
id: '/dashboard/sosial'
|
||||
path: '/sosial'
|
||||
fullPath: '/dashboard/sosial'
|
||||
preLoaderRoute: typeof DashboardSosialRouteImport
|
||||
parentRoute: typeof DashboardRouteRoute
|
||||
}
|
||||
'/dashboard/pengaduan-layanan-publik': {
|
||||
id: '/dashboard/pengaduan-layanan-publik'
|
||||
path: '/pengaduan-layanan-publik'
|
||||
@@ -366,6 +409,13 @@ declare module '@tanstack/react-router' {
|
||||
preLoaderRoute: typeof DashboardKeuanganAnggaranRouteImport
|
||||
parentRoute: typeof DashboardRouteRoute
|
||||
}
|
||||
'/dashboard/keamanan': {
|
||||
id: '/dashboard/keamanan'
|
||||
path: '/keamanan'
|
||||
fullPath: '/dashboard/keamanan'
|
||||
preLoaderRoute: typeof DashboardKeamananRouteImport
|
||||
parentRoute: typeof DashboardRouteRoute
|
||||
}
|
||||
'/dashboard/jenna-analytic': {
|
||||
id: '/dashboard/jenna-analytic'
|
||||
path: '/jenna-analytic'
|
||||
@@ -380,6 +430,13 @@ declare module '@tanstack/react-router' {
|
||||
preLoaderRoute: typeof DashboardDemografiPekerjaanRouteImport
|
||||
parentRoute: typeof DashboardRouteRoute
|
||||
}
|
||||
'/dashboard/bumdes': {
|
||||
id: '/dashboard/bumdes'
|
||||
path: '/bumdes'
|
||||
fullPath: '/dashboard/bumdes'
|
||||
preLoaderRoute: typeof DashboardBumdesRouteImport
|
||||
parentRoute: typeof DashboardRouteRoute
|
||||
}
|
||||
'/admin/users': {
|
||||
id: '/admin/users'
|
||||
path: '/users'
|
||||
@@ -423,20 +480,26 @@ const AdminRouteRouteWithChildren = AdminRouteRoute._addFileChildren(
|
||||
)
|
||||
|
||||
interface DashboardRouteRouteChildren {
|
||||
DashboardBumdesRoute: typeof DashboardBumdesRoute
|
||||
DashboardDemografiPekerjaanRoute: typeof DashboardDemografiPekerjaanRoute
|
||||
DashboardJennaAnalyticRoute: typeof DashboardJennaAnalyticRoute
|
||||
DashboardKeamananRoute: typeof DashboardKeamananRoute
|
||||
DashboardKeuanganAnggaranRoute: typeof DashboardKeuanganAnggaranRoute
|
||||
DashboardKinerjaDivisiRoute: typeof DashboardKinerjaDivisiRoute
|
||||
DashboardPengaduanLayananPublikRoute: typeof DashboardPengaduanLayananPublikRoute
|
||||
DashboardSosialRoute: typeof DashboardSosialRoute
|
||||
DashboardIndexRoute: typeof DashboardIndexRoute
|
||||
}
|
||||
|
||||
const DashboardRouteRouteChildren: DashboardRouteRouteChildren = {
|
||||
DashboardBumdesRoute: DashboardBumdesRoute,
|
||||
DashboardDemografiPekerjaanRoute: DashboardDemografiPekerjaanRoute,
|
||||
DashboardJennaAnalyticRoute: DashboardJennaAnalyticRoute,
|
||||
DashboardKeamananRoute: DashboardKeamananRoute,
|
||||
DashboardKeuanganAnggaranRoute: DashboardKeuanganAnggaranRoute,
|
||||
DashboardKinerjaDivisiRoute: DashboardKinerjaDivisiRoute,
|
||||
DashboardPengaduanLayananPublikRoute: DashboardPengaduanLayananPublikRoute,
|
||||
DashboardSosialRoute: DashboardSosialRoute,
|
||||
DashboardIndexRoute: DashboardIndexRoute,
|
||||
}
|
||||
|
||||
|
||||
7
src/routes/dashboard/bumdes.ts
Normal file
7
src/routes/dashboard/bumdes.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import BumdesPage from '@/components/bumdes-page'
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
|
||||
export const Route = createFileRoute('/dashboard/bumdes')({
|
||||
component: BumdesPage,
|
||||
})
|
||||
|
||||
7
src/routes/dashboard/keamanan.ts
Normal file
7
src/routes/dashboard/keamanan.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import KeamananPage from '@/components/keamanan-page'
|
||||
|
||||
export const Route = createFileRoute('/dashboard/keamanan')({
|
||||
component: KeamananPage,
|
||||
})
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { createFileRoute, Outlet } from "@tanstack/react-router";
|
||||
import { Header } from "@/components/header";
|
||||
import { Sidebar } from "@/components/sidebar";
|
||||
import { AppShell, Burger, Group } from "@mantine/core";
|
||||
import { AppShell, Burger, Group, useMantineColorScheme } from "@mantine/core";
|
||||
import { useDisclosure } from "@mantine/hooks";
|
||||
|
||||
export const Route = createFileRoute("/dashboard")({
|
||||
@@ -10,7 +10,10 @@ export const Route = createFileRoute("/dashboard")({
|
||||
|
||||
function RouteComponent() {
|
||||
const [opened, { toggle }] = useDisclosure();
|
||||
|
||||
const { colorScheme } = useMantineColorScheme();
|
||||
const headerBgColor = colorScheme === 'dark' ? "#11192D" : "#19355E";
|
||||
const navbarBgColor = colorScheme === 'dark' ? "#11192D" : "white";
|
||||
const mainBgColor = colorScheme === 'dark' ? "#11192D" : "#edf3f8ff";
|
||||
return (
|
||||
<AppShell
|
||||
header={{ height: 60 }}
|
||||
@@ -21,18 +24,18 @@ function RouteComponent() {
|
||||
}}
|
||||
padding="md"
|
||||
>
|
||||
<AppShell.Header>
|
||||
<AppShell.Header bg={headerBgColor}>
|
||||
<Group h="100%" px="md">
|
||||
<Burger opened={opened} onClick={toggle} hiddenFrom="sm" size="sm" />
|
||||
<Header />
|
||||
</Group>
|
||||
</AppShell.Header>
|
||||
|
||||
<AppShell.Navbar p="md">
|
||||
<AppShell.Navbar p="md" bg={navbarBgColor}>
|
||||
<Sidebar />
|
||||
</AppShell.Navbar>
|
||||
|
||||
<AppShell.Main>
|
||||
<AppShell.Main bg={mainBgColor}>
|
||||
<Outlet />
|
||||
</AppShell.Main>
|
||||
</AppShell>
|
||||
|
||||
8
src/routes/dashboard/sosial.ts
Normal file
8
src/routes/dashboard/sosial.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import SocialPage from '@/components/sosial-page'
|
||||
|
||||
|
||||
export const Route = createFileRoute('/dashboard/sosial')({
|
||||
component: SocialPage,
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user