Compare commits
5 Commits
nico/17-ma
...
nico/18-ma
| Author | SHA1 | Date | |
|---|---|---|---|
| d714c09efc | |||
| 0a97e31416 | |||
| 158a2db435 | |||
| 2d68d4dc06 | |||
| 97e6caa332 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -37,6 +37,9 @@ report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
||||
# Dashboard-MD
|
||||
Dashboard-MD
|
||||
|
||||
# md
|
||||
*.md
|
||||
|
||||
# Playwright artifacts
|
||||
test-results/
|
||||
playwright-report/
|
||||
|
||||
168
Pengaduan-New.md
Normal file
168
Pengaduan-New.md
Normal file
@@ -0,0 +1,168 @@
|
||||
Create a modern analytics dashboard UI for a village complaint system (Pengaduan Dashboard).
|
||||
|
||||
Tech stack:
|
||||
- React 19 + Vite (Bun runtime)
|
||||
- Mantine UI (core components)
|
||||
- TailwindCSS (layout & spacing only)
|
||||
- Recharts (charts)
|
||||
- TanStack Router
|
||||
- Icons: lucide-react
|
||||
- State: Valtio
|
||||
- Date: dayjs
|
||||
|
||||
---
|
||||
|
||||
## 🎨 DESIGN STYLE
|
||||
|
||||
- Clean, minimal, and soft dashboard
|
||||
- Background: light gray (#f3f4f6)
|
||||
- Card: white with subtle shadow
|
||||
- Border radius: 16px–24px (rounded-2xl)
|
||||
- Typography: medium contrast (not too bold)
|
||||
- Primary color: navy blue (#1E3A5F)
|
||||
- Accent: soft blue + neutral gray
|
||||
- Icons inside circular solid background
|
||||
|
||||
Spacing:
|
||||
- Use gap-6 consistently
|
||||
- Internal padding: p-5 or p-6
|
||||
- Layout must feel breathable (no clutter)
|
||||
|
||||
---
|
||||
|
||||
## 🧱 LAYOUT STRUCTURE
|
||||
|
||||
### 🔹 TOP SECTION (4 STAT CARDS - GRID)
|
||||
Grid: 4 columns (responsive → 2 / 1)
|
||||
|
||||
Each card contains:
|
||||
- Title (small, muted)
|
||||
- Big number (bold, large)
|
||||
- Subtitle (small gray text)
|
||||
- Right side: circular icon container
|
||||
|
||||
Example:
|
||||
- Total Pengaduan → 42 → "Bulan ini"
|
||||
- Baru → 14 → "Belum diproses"
|
||||
- Diproses → 14 → "Sedang ditangani"
|
||||
- Selesai → 14 → "Terselesaikan"
|
||||
|
||||
Use:
|
||||
- Mantine Card
|
||||
- Group justify="space-between"
|
||||
- Icon inside circle (bg navy, icon white)
|
||||
|
||||
---
|
||||
|
||||
## 📈 MAIN CHART (FULL WIDTH)
|
||||
Title: "Tren Pengaduan"
|
||||
|
||||
- Use Recharts LineChart
|
||||
- Smooth line (monotone)
|
||||
- Show dots on each point
|
||||
- Data: Apr → Okt
|
||||
- Value range: 30–60
|
||||
|
||||
Style:
|
||||
- Minimal grid (light dashed)
|
||||
- No heavy colors (use gray/blue line)
|
||||
- Rounded container card
|
||||
- Add small top-right icon (expand)
|
||||
|
||||
---
|
||||
|
||||
## 📊 BOTTOM SECTION (3 COLUMN GRID)
|
||||
|
||||
### 🔹 LEFT: "Surat Terbanyak"
|
||||
- Horizontal bar chart (Recharts)
|
||||
- Categories:
|
||||
- KTP
|
||||
- KK
|
||||
- Domisili
|
||||
- Usaha
|
||||
- Lainnya
|
||||
|
||||
Style:
|
||||
- Dark blue bars
|
||||
- Rounded edges
|
||||
- Clean axis
|
||||
|
||||
---
|
||||
|
||||
### 🔹 CENTER: "Pengajuan Terbaru"
|
||||
List of activity cards:
|
||||
|
||||
Each item:
|
||||
- Name (bold)
|
||||
- Subtitle (jenis surat)
|
||||
- Time (small text)
|
||||
- Status badge (kanan)
|
||||
|
||||
Status:
|
||||
- baru → red
|
||||
- proses → blue
|
||||
- selesai → green
|
||||
|
||||
Style:
|
||||
- Card per item
|
||||
- Soft border
|
||||
- Rounded
|
||||
- Compact spacing
|
||||
|
||||
---
|
||||
|
||||
### 🔹 RIGHT: "Ajuan Ide Inovatif"
|
||||
List mirip dengan pengajuan terbaru:
|
||||
|
||||
Each item:
|
||||
- Nama
|
||||
- Judul ide
|
||||
- Waktu
|
||||
- Button kecil "Detail"
|
||||
|
||||
Style:
|
||||
- Right-aligned action button
|
||||
- Light border
|
||||
- Clean spacing
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ COMPONENT STRUCTURE
|
||||
|
||||
components/
|
||||
- StatCard.tsx
|
||||
- LineChartCard.tsx
|
||||
- BarChartCard.tsx
|
||||
- ActivityList.tsx
|
||||
- IdeaList.tsx
|
||||
|
||||
routes/
|
||||
- dashboard.tsx
|
||||
|
||||
---
|
||||
|
||||
## ✨ INTERACTIONS (IMPORTANT)
|
||||
|
||||
- Hover card → scale(1.02)
|
||||
- Transition: 150ms ease
|
||||
- Icon circle slightly pop on hover
|
||||
- List item hover → subtle bg change
|
||||
|
||||
---
|
||||
|
||||
## 🎯 UX DETAILS
|
||||
|
||||
- Numbers must be visually dominant
|
||||
- Icons must balance layout (not too big)
|
||||
- Avoid heavy borders
|
||||
- Keep everything aligned perfectly
|
||||
- No clutter
|
||||
|
||||
---
|
||||
|
||||
## 🚀 OUTPUT
|
||||
|
||||
- Modular React components (NOT one file)
|
||||
- Clean code (production-ready)
|
||||
- Use Mantine properly (no hacky inline styles unless needed)
|
||||
- Use Tailwind only for layout/grid/spacing
|
||||
@@ -1,302 +0,0 @@
|
||||
Buat halaman dashboard admin modern untuk sistem pemerintahan desa bernama **Darmasaba Dashboard NOC**.
|
||||
|
||||
Gunakan stack berikut:
|
||||
|
||||
Frontend:
|
||||
|
||||
* React 19
|
||||
* Bun runtime
|
||||
* Vite
|
||||
* TailwindCSS
|
||||
* Mantine UI
|
||||
* Mantine Charts atau Recharts
|
||||
* Tabler Icons
|
||||
* TanStack Router
|
||||
* Dayjs
|
||||
|
||||
UI harus modular dengan reusable components.
|
||||
|
||||
Gunakan **TailwindCSS sebagai styling utama** dengan warna dari konfigurasi berikut:
|
||||
|
||||
Primary:
|
||||
darmasaba-navy (#1E3A5F)
|
||||
|
||||
Secondary:
|
||||
darmasaba-blue (#3B82F6)
|
||||
|
||||
Success:
|
||||
#22C55E
|
||||
|
||||
Warning:
|
||||
#FACC15
|
||||
|
||||
Danger:
|
||||
#EF4444
|
||||
|
||||
Background:
|
||||
#F5F8FB
|
||||
|
||||
Dashboard harus memiliki **Light Mode dan Dark Mode**.
|
||||
|
||||
Dark Mode Color Rules:
|
||||
background: #0F172A
|
||||
card: #1E293B
|
||||
border: #334155
|
||||
text: #E2E8F0
|
||||
|
||||
Card style:
|
||||
|
||||
* rounded-xl
|
||||
* soft shadow
|
||||
* padding besar
|
||||
* border subtle
|
||||
* smooth hover animation
|
||||
|
||||
Gunakan grid layout responsive.
|
||||
|
||||
---
|
||||
|
||||
SECTION 1 — PROGRAM KEGIATAN
|
||||
|
||||
Buat 4 card horizontal di bagian atas yang menampilkan kegiatan desa.
|
||||
|
||||
Setiap card memiliki:
|
||||
|
||||
* header biru
|
||||
* progress bar kegiatan
|
||||
* tanggal kegiatan
|
||||
* badge status
|
||||
|
||||
Data card:
|
||||
|
||||
1.
|
||||
|
||||
Judul: Rakor 2025
|
||||
Tanggal: 3 Juli 2025
|
||||
Progress: 90%
|
||||
Status: selesai
|
||||
|
||||
2.
|
||||
|
||||
Judul: Pemutakhiran Indeks Desa
|
||||
Tanggal: 3 Juli 2025
|
||||
Progress: 85%
|
||||
Status: selesai
|
||||
|
||||
3.
|
||||
|
||||
Judul: Mengurus Akta Cerai Warga
|
||||
Tanggal: 3 Juli 2025
|
||||
Progress: 80%
|
||||
Status: selesai
|
||||
|
||||
4.
|
||||
|
||||
Judul: Pasek 7 Desa Adat
|
||||
Tanggal: 3 Juli 2025
|
||||
Progress: 92%
|
||||
Status: selesai
|
||||
|
||||
Progress bar:
|
||||
|
||||
* rounded
|
||||
* warna warning
|
||||
* animasi smooth
|
||||
|
||||
Status badge:
|
||||
|
||||
* success color
|
||||
|
||||
---
|
||||
|
||||
SECTION 2 — GRID DASHBOARD
|
||||
|
||||
Layout:
|
||||
|
||||
3 column grid.
|
||||
|
||||
Left column (sidebar style):
|
||||
Divisi Teraktif
|
||||
|
||||
List item card dengan arrow icon.
|
||||
|
||||
Data:
|
||||
|
||||
Kesejahteraan — 37 kegiatan
|
||||
Pemerintahan — 26 kegiatan
|
||||
Keuangan — 17 kegiatan
|
||||
Sekretaris Desa — 15 kegiatan
|
||||
Tata Usaha TK — 14 kegiatan
|
||||
Perangkat Kewilayahan — 12 kegiatan
|
||||
Pelayanan — 10 kegiatan
|
||||
Perencanaan — 9 kegiatan
|
||||
Tata Usaha & Umum — 7 kegiatan
|
||||
|
||||
Setiap item:
|
||||
|
||||
* rounded
|
||||
* hover effect
|
||||
* arrow icon kanan
|
||||
|
||||
---
|
||||
|
||||
Middle column:
|
||||
|
||||
Jumlah Dokumen
|
||||
|
||||
Gunakan **Bar Chart**.
|
||||
|
||||
Kategori:
|
||||
|
||||
* Gambar
|
||||
* Dokumen
|
||||
|
||||
Nilai:
|
||||
|
||||
* Gambar: 300
|
||||
* Dokumen: 310
|
||||
|
||||
Gunakan:
|
||||
Recharts atau Mantine Charts.
|
||||
|
||||
---
|
||||
|
||||
Right column:
|
||||
|
||||
Progres Kegiatan
|
||||
|
||||
Gunakan **Pie Chart**.
|
||||
|
||||
Data:
|
||||
|
||||
Selesai — 83.33%
|
||||
Dikerjakan — 16.67%
|
||||
Segera Dikerjakan — 0%
|
||||
Dibatalkan — 0%
|
||||
|
||||
Legend harus berwarna.
|
||||
|
||||
---
|
||||
|
||||
SECTION 3 — DISCUSSION PANEL
|
||||
|
||||
Judul: Diskusi
|
||||
|
||||
Tampilkan list diskusi internal staf.
|
||||
|
||||
Item card memiliki:
|
||||
|
||||
* icon chat
|
||||
* judul pesan
|
||||
* nama pengirim
|
||||
* tanggal
|
||||
|
||||
Contoh data:
|
||||
|
||||
"Kepada Pelayanan, mohon di cek..."
|
||||
Pengirim: I.B Surya Prabhawa Manu
|
||||
Tanggal: 12 Apr 2025
|
||||
|
||||
"Kepada staf perencanaan @suar..."
|
||||
Pengirim: Ni Nyoman Yuliani
|
||||
Tanggal: 14 Jun 2025
|
||||
|
||||
"ijin atau mohon kepada KBD sar..."
|
||||
Pengirim: Ni Wayan Martini
|
||||
Tanggal: 12 Apr 2025
|
||||
|
||||
---
|
||||
|
||||
SECTION 4 — ACARA HARI INI
|
||||
|
||||
Card sederhana.
|
||||
|
||||
Jika tidak ada acara tampilkan:
|
||||
|
||||
"Tidak ada acara hari ini"
|
||||
|
||||
---
|
||||
|
||||
SECTION 5 — ARSIP DIGITAL PERANGKAT DESA
|
||||
|
||||
Grid 2 column.
|
||||
|
||||
Menu arsip:
|
||||
|
||||
Surat Keputusan
|
||||
Dokumentasi
|
||||
Laporan Keuangan
|
||||
Notulensi Rapat
|
||||
|
||||
Setiap item berupa card clickable dengan:
|
||||
|
||||
* icon dokumen
|
||||
* border
|
||||
* hover effect
|
||||
|
||||
---
|
||||
|
||||
DESIGN STYLE
|
||||
|
||||
Gunakan gaya:
|
||||
|
||||
Modern Government Dashboard
|
||||
Clean UI
|
||||
Soft shadow
|
||||
Rounded-xl
|
||||
Spacing besar
|
||||
Minimalistic
|
||||
|
||||
---
|
||||
|
||||
RESPONSIVE RULES
|
||||
|
||||
Desktop:
|
||||
12 column grid
|
||||
|
||||
Tablet:
|
||||
6 column grid
|
||||
|
||||
Mobile:
|
||||
single column stack
|
||||
|
||||
---
|
||||
|
||||
COMPONENT STRUCTURE
|
||||
|
||||
src/components/dashboard
|
||||
|
||||
activity-card.tsx
|
||||
division-list.tsx
|
||||
document-chart.tsx
|
||||
progress-chart.tsx
|
||||
discussion-panel.tsx
|
||||
event-card.tsx
|
||||
archive-card.tsx
|
||||
|
||||
src/pages
|
||||
|
||||
dashboard.tsx
|
||||
|
||||
---
|
||||
|
||||
CODE QUALITY
|
||||
|
||||
Gunakan:
|
||||
|
||||
* React hooks
|
||||
* reusable components
|
||||
* Mantine components jika perlu
|
||||
* Tailwind utility classes
|
||||
* dark mode support
|
||||
* responsive layout
|
||||
* clean TypeScript
|
||||
|
||||
---
|
||||
|
||||
Output:
|
||||
|
||||
* Halaman dashboard lengkap
|
||||
* Semua komponen reusable
|
||||
* Chart sudah bekerja
|
||||
* Layout identik dengan desain dashboard modern pemerintahan
|
||||
BIN
public/light-mode.png
Normal file
BIN
public/light-mode.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 90 KiB |
BIN
public/white.png
Normal file
BIN
public/white.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 77 KiB |
@@ -36,6 +36,7 @@ export function ActivityList() {
|
||||
? "0 1px 3px 0 rgb(0 0 0 / 0.1)"
|
||||
: "0 1px 3px 0 rgb(0 0 0 / 0.1)",
|
||||
}}
|
||||
h="100%"
|
||||
>
|
||||
<Group gap="xs" mb="lg">
|
||||
<Calendar
|
||||
|
||||
@@ -39,6 +39,7 @@ export function ChartAPBDes() {
|
||||
? "0 1px 3px 0 rgb(0 0 0 / 0.1)"
|
||||
: "0 1px 3px 0 rgb(0 0 0 / 0.1)",
|
||||
}}
|
||||
h="100%"
|
||||
>
|
||||
<Title order={4} c={dark ? "white" : "gray.9"} mb="lg">
|
||||
Grafik APBDes
|
||||
|
||||
@@ -42,6 +42,7 @@ export function ChartSurat() {
|
||||
? "0 1px 3px 0 rgb(0 0 0 / 0.1)"
|
||||
: "0 1px 3px 0 rgb(0 0 0 / 0.1)",
|
||||
}}
|
||||
h="100%"
|
||||
>
|
||||
<Group justify="space-between" mb="md">
|
||||
<Box>
|
||||
|
||||
@@ -39,6 +39,7 @@ export function DivisionProgress() {
|
||||
? "0 1px 3px 0 rgb(0 0 0 / 0.1)"
|
||||
: "0 1px 3px 0 rgb(0 0 0 / 0.1)",
|
||||
}}
|
||||
h="100%"
|
||||
>
|
||||
<Title order={4} c={dark ? "white" : "gray.9"} mb="lg">
|
||||
Divisi Teraktif
|
||||
|
||||
@@ -32,6 +32,7 @@ export function SatisfactionChart() {
|
||||
? "0 1px 3px 0 rgb(0 0 0 / 0.1)"
|
||||
: "0 1px 3px 0 rgb(0 0 0 / 0.1)",
|
||||
}}
|
||||
h="100%"
|
||||
>
|
||||
<Title order={4} c={dark ? "white" : "gray.9"} mb={5}>
|
||||
Tingkat Kepuasan
|
||||
|
||||
@@ -1,116 +1,73 @@
|
||||
import { BarChart, PieChart } from "@mantine/charts";
|
||||
import {
|
||||
Badge,
|
||||
Box,
|
||||
Card,
|
||||
Grid,
|
||||
GridCol,
|
||||
Group,
|
||||
Progress,
|
||||
Stack,
|
||||
Table,
|
||||
Text,
|
||||
ThemeIcon,
|
||||
Title,
|
||||
useMantineColorScheme,
|
||||
} from "@mantine/core";
|
||||
import {
|
||||
IconArrowDown,
|
||||
IconArrowUp,
|
||||
IconBabyCarriage,
|
||||
IconSkull,
|
||||
} from "@tabler/icons-react";
|
||||
import React from "react";
|
||||
Users,
|
||||
Home,
|
||||
Baby,
|
||||
TrendingDown,
|
||||
BarChart3,
|
||||
PieChart as PieChartIcon,
|
||||
Building2,
|
||||
} from "lucide-react";
|
||||
import {
|
||||
Bar,
|
||||
BarChart,
|
||||
CartesianGrid,
|
||||
Cell,
|
||||
Pie,
|
||||
PieChart,
|
||||
ResponsiveContainer,
|
||||
Tooltip,
|
||||
XAxis,
|
||||
YAxis,
|
||||
} from "recharts";
|
||||
|
||||
// Sample Data
|
||||
// KPI Data
|
||||
const kpiData = [
|
||||
{
|
||||
id: 1,
|
||||
title: "Total Penduduk",
|
||||
value: "5.634",
|
||||
sub: "Aktif terdaftar",
|
||||
icon: (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth={1.5}
|
||||
stroke="currentColor"
|
||||
className="h-6 w-6 text-muted-foreground"
|
||||
role="img"
|
||||
aria-label="Icon penduduk"
|
||||
>
|
||||
<title>Total Penduduk</title>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M15 19.128a9.38 9.38 0 0 0 2.625.372 9.337 9.337 0 0 0 4.121-.952 4.125 4.125 0 0 0-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 0 1 8.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0 1 11.964-3.07M12 6.375a3.375 3.375 0 1 1-6.75 0 3.375 3.375 0 0 1 6.75 0Zm8.25 2.25a2.625 2.625 0 1 1-5.25 0 2.625 2.625 0 0 1 5.25 0Z"
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
subtitle: "Aktif terdaftar",
|
||||
icon: Users,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: "Kepala Keluarga",
|
||||
value: "1.354",
|
||||
sub: "Total KK",
|
||||
icon: (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth={1.5}
|
||||
stroke="currentColor"
|
||||
className="h-6 w-6 text-muted-foreground"
|
||||
role="img"
|
||||
aria-label="Icon kepala keluarga"
|
||||
>
|
||||
<title>Kepala Keluarga</title>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M2.25 12l8.954-8.955c.44-.439 1.152-.439 1.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25"
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
subtitle: "Total KK",
|
||||
icon: Home,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: "Kelahiran",
|
||||
value: "23",
|
||||
sub: "Tahun ini",
|
||||
icon: (
|
||||
<IconBabyCarriage
|
||||
className="h-6 w-6 text-muted-foreground"
|
||||
role="img"
|
||||
aria-label="Icon kelahiran"
|
||||
/>
|
||||
),
|
||||
subtitle: "Tahun ini",
|
||||
icon: Baby,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: "Kemiskinan",
|
||||
value: "324",
|
||||
delta: "-10% dari tahun lalu",
|
||||
deltaType: "positive",
|
||||
icon: (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth={1.5}
|
||||
stroke="currentColor"
|
||||
className="h-6 w-6 text-muted-foreground"
|
||||
role="img"
|
||||
aria-label="Icon kemiskinan"
|
||||
>
|
||||
<title>Kemiskinan</title>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126ZM12 15.75h.007v.008H12v-.008Z"
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
subtitle: "-10% dari tahun lalu",
|
||||
trend: "positive",
|
||||
icon: TrendingDown,
|
||||
},
|
||||
];
|
||||
|
||||
// Age Distribution Data
|
||||
const ageDistributionData = [
|
||||
{ ageRange: "17-25", total: 850 },
|
||||
{ ageRange: "26-35", total: 1200 },
|
||||
@@ -120,6 +77,7 @@ const ageDistributionData = [
|
||||
{ ageRange: "65+", total: 484 },
|
||||
];
|
||||
|
||||
// Job Distribution Data
|
||||
const jobDistributionData = [
|
||||
{ job: "Sipil", total: 1200 },
|
||||
{ job: "Guru", total: 850 },
|
||||
@@ -128,283 +86,597 @@ const jobDistributionData = [
|
||||
{ job: "Wiraswasta", total: 984 },
|
||||
];
|
||||
|
||||
// Religion Data
|
||||
const religionData = [
|
||||
{ religion: "Hindu", total: 4234, color: "red" },
|
||||
{ religion: "Islam", total: 856, color: "blue" },
|
||||
{ religion: "Kristen", total: 412, color: "green" },
|
||||
{ religion: "Buddha", total: 202, color: "yellow" },
|
||||
{ name: "Hindu", value: 4234, color: "#EF4444" },
|
||||
{ name: "Islam", value: 856, color: "#3B82F6" },
|
||||
{ name: "Kristen", value: 412, color: "#22C55E" },
|
||||
{ name: "Buddha", value: 202, color: "#FACC15" },
|
||||
];
|
||||
|
||||
// Banjar Data
|
||||
const banjarData = [
|
||||
{ banjar: "Banjar Darmasaba", population: 1200, kk: 300, poor: 45 },
|
||||
{ banjar: "Banjar Manesa", population: 950, kk: 240, poor: 32 },
|
||||
{ banjar: "Banjar Cabe", population: 800, kk: 200, poor: 28 },
|
||||
{ banjar: "Banjar Penenjoan", population: 1100, kk: 280, poor: 38 },
|
||||
{ banjar: "Banjar Baler Pasar", population: 984, kk: 250, poor: 42 },
|
||||
{ banjar: "Banjar Bucu", population: 600, kk: 184, poor: 25 },
|
||||
{ banjar: "Darmasaba", population: 1200, kk: 300, poor: 45 },
|
||||
{ banjar: "Manesa", population: 950, kk: 240, poor: 32 },
|
||||
{ banjar: "Cabe", population: 800, kk: 200, poor: 28 },
|
||||
{ banjar: "Penenjoan", population: 1100, kk: 280, poor: 38 },
|
||||
{ banjar: "Baler Pasar", population: 984, kk: 250, poor: 42 },
|
||||
{ banjar: "Bucu", population: 600, kk: 184, poor: 25 },
|
||||
];
|
||||
|
||||
// Dynamic Stats Data
|
||||
const dynamicStats = [
|
||||
{
|
||||
title: "Kelahiran",
|
||||
value: "23",
|
||||
icon: <IconBabyCarriage size={16} />,
|
||||
color: "green",
|
||||
icon: Baby,
|
||||
color: "#22C55E",
|
||||
},
|
||||
{
|
||||
title: "Kematian",
|
||||
value: "12",
|
||||
icon: <IconSkull size={16} />,
|
||||
color: "red",
|
||||
icon: TrendingDown,
|
||||
color: "#EF4444",
|
||||
},
|
||||
{
|
||||
title: "Pindah Masuk",
|
||||
value: "45",
|
||||
icon: <IconArrowDown size={16} />,
|
||||
color: "blue",
|
||||
icon: Users,
|
||||
color: "#3B82F6",
|
||||
},
|
||||
{
|
||||
title: "Pindah Keluar",
|
||||
value: "32",
|
||||
icon: <IconArrowUp size={16} />,
|
||||
color: "orange",
|
||||
icon: Users,
|
||||
color: "#3B82F6",
|
||||
},
|
||||
];
|
||||
|
||||
// Sektor Unggulan Data
|
||||
const sektorUnggulanData = [
|
||||
{ sektor: "Pertanian", value: 65 },
|
||||
{ sektor: "Perdagangan", value: 45 },
|
||||
{ sektor: "Industri", value: 38 },
|
||||
{ sektor: "Jasa", value: 52 },
|
||||
];
|
||||
|
||||
const DemografiPekerjaan = () => {
|
||||
const { colorScheme } = useMantineColorScheme();
|
||||
const dark = colorScheme === "dark";
|
||||
return (
|
||||
<Box className="space-y-6">
|
||||
<Stack gap="xl">
|
||||
{/* KPI Cards */}
|
||||
<Grid gutter="lg">
|
||||
{kpiData.map((kpi) => (
|
||||
<Grid.Col key={kpi.id} span={{ base: 12, md: 6, lg: 3 }}>
|
||||
<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={dark ? "dark.3" : "dimmed"}>
|
||||
{kpi.title}
|
||||
</Text>
|
||||
{React.cloneElement(kpi.icon, {
|
||||
className: "h-6 w-6",
|
||||
color: dark
|
||||
? "var(--mantine-color-dark-3)"
|
||||
: "var(--mantine-color-dimmed)",
|
||||
})}
|
||||
</Group>
|
||||
<Title order={3} fw={700} c={dark ? "dark.0" : "black"} mt="xs">
|
||||
{kpi.value}
|
||||
</Title>
|
||||
{kpi.delta && (
|
||||
<Text
|
||||
size="xs"
|
||||
c={
|
||||
kpi.deltaType === "positive"
|
||||
? "green"
|
||||
: kpi.deltaType === "negative"
|
||||
? "red"
|
||||
: dark
|
||||
? "dark.3"
|
||||
: "dimmed"
|
||||
}
|
||||
mt={4}
|
||||
>
|
||||
{kpi.delta}
|
||||
</Text>
|
||||
)}
|
||||
{kpi.sub && (
|
||||
<Text size="xs" c={dark ? "dark.3" : "dimmed"} mt={2}>
|
||||
{kpi.sub}
|
||||
</Text>
|
||||
)}
|
||||
</Card>
|
||||
</Grid.Col>
|
||||
))}
|
||||
</Grid>
|
||||
|
||||
{/* Charts Section */}
|
||||
<Grid gutter="lg">
|
||||
{/* Grafik Pengelompokan Umur */}
|
||||
<Grid.Col span={{ base: 12, lg: 6 }}>
|
||||
return (
|
||||
<Stack gap="lg">
|
||||
{/* TOP SECTION - 4 STAT CARDS */}
|
||||
<Grid gutter="md">
|
||||
{kpiData.map((item) => (
|
||||
<Grid.Col key={item.id} span={{ base: 12, sm: 6, lg: 3 }}>
|
||||
<Card
|
||||
p="md"
|
||||
radius="md"
|
||||
radius="xl"
|
||||
withBorder
|
||||
bg={dark ? "#141D34" : "white"}
|
||||
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||
bg={dark ? "#1E293B" : "white"}
|
||||
style={{
|
||||
borderColor: dark ? "#334155" : "white",
|
||||
boxShadow: "0 1px 3px 0 rgb(0 0 0 / 0.1)",
|
||||
transition: "transform 0.15s ease, box-shadow 0.15s ease",
|
||||
}}
|
||||
h="100%"
|
||||
>
|
||||
<Title order={3} fw={500} mb="md">
|
||||
Grafik Pengelompokan Umur
|
||||
</Title>
|
||||
<BarChart
|
||||
h={300}
|
||||
data={ageDistributionData}
|
||||
dataKey="ageRange"
|
||||
series={[{ name: "total", color: "darmasaba-navy" }]}
|
||||
withLegend
|
||||
/>
|
||||
<Group justify="space-between" align="flex-start" w="100%">
|
||||
<Stack gap={2}>
|
||||
<Text size="sm" c="dimmed">
|
||||
{item.title}
|
||||
</Text>
|
||||
<Text size="xl" fw={700} c={dark ? "white" : "gray.9"}>
|
||||
{item.value}
|
||||
</Text>
|
||||
<Group gap={4} align="flex-start">
|
||||
{item.trend === "positive" && (
|
||||
<TrendingDown size={14} color="#22C55E" />
|
||||
)}
|
||||
<Text
|
||||
size="xs"
|
||||
c={
|
||||
item.trend === "positive"
|
||||
? "green"
|
||||
: dark
|
||||
? "gray.4"
|
||||
: "gray.5"
|
||||
}
|
||||
>
|
||||
{item.subtitle}
|
||||
</Text>
|
||||
</Group>
|
||||
</Stack>
|
||||
<ThemeIcon
|
||||
color="#1E3A5F"
|
||||
variant="filled"
|
||||
size="lg"
|
||||
radius="xl"
|
||||
>
|
||||
<item.icon style={{ width: "60%", height: "60%" }} />
|
||||
</ThemeIcon>
|
||||
</Group>
|
||||
</Card>
|
||||
</Grid.Col>
|
||||
))}
|
||||
</Grid>
|
||||
|
||||
{/* Demografi Pekerjaan */}
|
||||
<Grid.Col span={{ base: 12, lg: 6 }}>
|
||||
<Card
|
||||
p="md"
|
||||
radius="md"
|
||||
withBorder
|
||||
bg={dark ? "#141D34" : "white"}
|
||||
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||
>
|
||||
<Title order={3} fw={500} mb="md">
|
||||
{/* ROW 2 - 3 COLUMNS */}
|
||||
<Grid gutter="lg">
|
||||
{/* LEFT: PENGELOMPOKAN UMUR */}
|
||||
<Grid.Col span={{ base: 12, lg: 4 }}>
|
||||
<Card
|
||||
p="md"
|
||||
radius="xl"
|
||||
withBorder
|
||||
bg={dark ? "#1E293B" : "white"}
|
||||
style={{
|
||||
borderColor: dark ? "#334155" : "white",
|
||||
boxShadow: "0 1px 3px 0 rgb(0 0 0 / 0.1)",
|
||||
}}
|
||||
h="100%"
|
||||
>
|
||||
<Group gap="xs" mb="md">
|
||||
<ThemeIcon color="#1E3A5F" variant="filled" size="sm" radius="sm">
|
||||
<BarChart3 size={14} />
|
||||
</ThemeIcon>
|
||||
<Title order={4} c={dark ? "white" : "gray.9"}>
|
||||
Pengelompokan Umur
|
||||
</Title>
|
||||
</Group>
|
||||
<ResponsiveContainer width="100%" height={250}>
|
||||
<BarChart data={ageDistributionData}>
|
||||
<CartesianGrid
|
||||
strokeDasharray="3 3"
|
||||
vertical={false}
|
||||
stroke={dark ? "#334155" : "#e5e7eb"}
|
||||
/>
|
||||
<XAxis
|
||||
dataKey="ageRange"
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
tick={{
|
||||
fill: dark ? "#E2E8F0" : "#374151",
|
||||
fontSize: 12,
|
||||
}}
|
||||
/>
|
||||
<YAxis
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
tick={{
|
||||
fill: dark ? "#E2E8F0" : "#374151",
|
||||
fontSize: 12,
|
||||
}}
|
||||
/>
|
||||
<Tooltip
|
||||
contentStyle={{
|
||||
backgroundColor: dark ? "#1E293B" : "white",
|
||||
borderColor: dark ? "#334155" : "#e5e7eb",
|
||||
borderRadius: "8px",
|
||||
}}
|
||||
labelStyle={{ color: dark ? "#E2E8F0" : "#374151" }}
|
||||
/>
|
||||
<Bar
|
||||
dataKey="total"
|
||||
fill="#1E3A5F"
|
||||
radius={[8, 8, 0, 0]}
|
||||
maxBarSize={40}
|
||||
/>
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</Card>
|
||||
</Grid.Col>
|
||||
|
||||
{/* CENTER: DEMOGRAFI PEKERJAAN */}
|
||||
<Grid.Col span={{ base: 12, lg: 4 }}>
|
||||
<Card
|
||||
p="md"
|
||||
radius="xl"
|
||||
withBorder
|
||||
bg={dark ? "#1E293B" : "white"}
|
||||
style={{
|
||||
borderColor: dark ? "#334155" : "white",
|
||||
boxShadow: "0 1px 3px 0 rgb(0 0 0 / 0.1)",
|
||||
}}
|
||||
h="100%"
|
||||
>
|
||||
<Group gap="xs" mb="md">
|
||||
<ThemeIcon color="#1E3A5F" variant="filled" size="sm" radius="sm">
|
||||
<Building2 size={14} />
|
||||
</ThemeIcon>
|
||||
<Title order={4} c={dark ? "white" : "gray.9"}>
|
||||
Demografi Pekerjaan
|
||||
</Title>
|
||||
<BarChart
|
||||
h={300}
|
||||
data={jobDistributionData}
|
||||
dataKey="job"
|
||||
series={[{ name: "total", color: "darmasaba-navy" }]}
|
||||
withLegend
|
||||
/>
|
||||
</Card>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</Group>
|
||||
<ResponsiveContainer width="100%" height={250}>
|
||||
<BarChart data={jobDistributionData} layout="vertical">
|
||||
<CartesianGrid
|
||||
strokeDasharray="3 3"
|
||||
horizontal={false}
|
||||
stroke={dark ? "#334155" : "#e5e7eb"}
|
||||
/>
|
||||
<XAxis
|
||||
type="number"
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
tick={{
|
||||
fill: dark ? "#E2E8F0" : "#374151",
|
||||
fontSize: 12,
|
||||
}}
|
||||
/>
|
||||
<YAxis
|
||||
type="category"
|
||||
dataKey="job"
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
tick={{
|
||||
fill: dark ? "#E2E8F0" : "#374151",
|
||||
fontSize: 12,
|
||||
}}
|
||||
width={90}
|
||||
/>
|
||||
<Tooltip
|
||||
contentStyle={{
|
||||
backgroundColor: dark ? "#1E293B" : "white",
|
||||
borderColor: dark ? "#334155" : "#e5e7eb",
|
||||
borderRadius: "8px",
|
||||
}}
|
||||
/>
|
||||
<Bar
|
||||
dataKey="total"
|
||||
fill="#1E3A5F"
|
||||
radius={[0, 8, 8, 0]}
|
||||
maxBarSize={30}
|
||||
/>
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</Card>
|
||||
</Grid.Col>
|
||||
|
||||
{/* Agama & Data per Banjar */}
|
||||
<Grid gutter="lg">
|
||||
{/* Distribusi Agama */}
|
||||
<Grid.Col span={{ base: 12, lg: 6 }}>
|
||||
<Card
|
||||
p="md"
|
||||
radius="md"
|
||||
withBorder
|
||||
bg={dark ? "#141D34" : "white"}
|
||||
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||
>
|
||||
<Title order={3} fw={500} mb="md">
|
||||
Distribusi Agama
|
||||
{/* RIGHT: STATISTIK DINAMIKA PENDUDUK */}
|
||||
<Grid.Col span={{ base: 12, lg: 4 }}>
|
||||
<Card
|
||||
p="md"
|
||||
radius="xl"
|
||||
withBorder
|
||||
bg={dark ? "#1E293B" : "white"}
|
||||
style={{
|
||||
borderColor: dark ? "#334155" : "white",
|
||||
boxShadow: "0 1px 3px 0 rgb(0 0 0 / 0.1)",
|
||||
}}
|
||||
h="100%"
|
||||
>
|
||||
<Group gap="xs" mb="md">
|
||||
<ThemeIcon color="#1E3A5F" variant="filled" size="sm" radius="sm">
|
||||
<BarChart3 size={14} />
|
||||
</ThemeIcon>
|
||||
<Title order={4} c={dark ? "white" : "gray.9"}>
|
||||
Dinamika Penduduk
|
||||
</Title>
|
||||
<PieChart
|
||||
h={300}
|
||||
data={religionData.map((item) => ({
|
||||
name: item.religion,
|
||||
value: item.total,
|
||||
color: item.color,
|
||||
}))}
|
||||
withLabels
|
||||
withLabelsLine
|
||||
labelsPosition="outside"
|
||||
labelsType="percent"
|
||||
/>
|
||||
</Card>
|
||||
</Grid.Col>
|
||||
|
||||
{/* Data per Banjar */}
|
||||
<Grid.Col span={{ base: 12, lg: 6 }}>
|
||||
<Card
|
||||
p="md"
|
||||
radius="md"
|
||||
withBorder
|
||||
bg={dark ? "#141D34" : "white"}
|
||||
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||
>
|
||||
<Title order={3} fw={500} c={dark ? "dark.0" : "black"} mb="md">
|
||||
Data per Banjar
|
||||
</Title>
|
||||
<Table striped highlightOnHover>
|
||||
<Table.Thead>
|
||||
<Table.Tr>
|
||||
<Table.Th>
|
||||
<Text c={dark ? "dark.0" : "black"}>Banjar</Text>
|
||||
</Table.Th>
|
||||
<Table.Th>
|
||||
<Text c={dark ? "dark.0" : "black"}>Penduduk</Text>
|
||||
</Table.Th>
|
||||
<Table.Th>
|
||||
<Text c={dark ? "dark.0" : "black"}>KK</Text>
|
||||
</Table.Th>
|
||||
<Table.Th>
|
||||
<Text c={dark ? "dark.0" : "black"}>Miskin</Text>
|
||||
</Table.Th>
|
||||
</Table.Tr>
|
||||
</Table.Thead>
|
||||
<Table.Tbody>
|
||||
{banjarData.map((item, index) => (
|
||||
<Table.Tr key={`${item.banjar}-${index}`}>
|
||||
<Table.Td>
|
||||
<Text c={dark ? "dark.0" : "black"}>{item.banjar}</Text>
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<Text c={dark ? "dark.0" : "black"}>
|
||||
{item.population.toLocaleString()}
|
||||
</Text>
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<Text c={dark ? "dark.0" : "black"}>
|
||||
{item.kk.toLocaleString()}
|
||||
</Text>
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<Text c={dark ? "red.4" : "red"}>
|
||||
{item.poor.toLocaleString()}
|
||||
</Text>
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
))}
|
||||
</Table.Tbody>
|
||||
</Table>
|
||||
</Card>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
|
||||
{/* Statistik Dinamika Penduduk */}
|
||||
<Card
|
||||
p="md"
|
||||
radius="md"
|
||||
withBorder
|
||||
bg={dark ? "#141D34" : "white"}
|
||||
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||
>
|
||||
<Title order={3} fw={500} c={dark ? "dark.0" : "black"} mb="md">
|
||||
Statistik Dinamika Penduduk
|
||||
</Title>
|
||||
<Grid gutter="md">
|
||||
{dynamicStats.map((stat, index) => (
|
||||
<Grid.Col
|
||||
key={`${stat.title}-${index}`}
|
||||
span={{ base: 12, md: 3 }}
|
||||
>
|
||||
<Card
|
||||
p="md"
|
||||
radius="md"
|
||||
withBorder
|
||||
bg={dark ? "#141D34" : "white"}
|
||||
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||
>
|
||||
<Group justify="space-between" align="center">
|
||||
<Box>
|
||||
<Text size="sm" fw={500} c={dark ? "dark.3" : "dimmed"}>
|
||||
</Group>
|
||||
<Grid gutter="sm">
|
||||
{dynamicStats.map((stat, index) => (
|
||||
<Grid.Col key={index} span={6}>
|
||||
<Card
|
||||
p="sm"
|
||||
radius="lg"
|
||||
bg={dark ? "#334155" : "#F1F5F9"}
|
||||
style={{
|
||||
transition: "transform 0.15s ease",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
>
|
||||
<Stack gap={2} align="center">
|
||||
<ThemeIcon
|
||||
color={stat.color}
|
||||
variant="filled"
|
||||
size="md"
|
||||
radius="lg"
|
||||
>
|
||||
<stat.icon size={14} />
|
||||
</ThemeIcon>
|
||||
<Text size="xs" c="dimmed" ta="center">
|
||||
{stat.title}
|
||||
</Text>
|
||||
<Title order={4} fw={700} c={stat.color}>
|
||||
<Text
|
||||
size="lg"
|
||||
fw={700}
|
||||
c={stat.color}
|
||||
style={{ lineHeight: 1 }}
|
||||
>
|
||||
{stat.value}
|
||||
</Title>
|
||||
</Box>
|
||||
<Box c={stat.color}>{stat.icon}</Box>
|
||||
</Text>
|
||||
</Stack>
|
||||
</Card>
|
||||
</Grid.Col>
|
||||
))}
|
||||
</Grid>
|
||||
</Card>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
|
||||
{/* ROW 3 - 3 COLUMNS */}
|
||||
<Grid gutter="lg">
|
||||
{/* LEFT: DISTRIBUSI AGAMA */}
|
||||
<Grid.Col span={{ base: 12, lg: 4 }}>
|
||||
<Card
|
||||
p="md"
|
||||
radius="xl"
|
||||
withBorder
|
||||
bg={dark ? "#1E293B" : "white"}
|
||||
style={{
|
||||
borderColor: dark ? "#334155" : "white",
|
||||
boxShadow: "0 1px 3px 0 rgb(0 0 0 / 0.1)",
|
||||
}}
|
||||
h="100%"
|
||||
>
|
||||
<Group gap="xs" mb="md">
|
||||
<ThemeIcon color="#1E3A5F" variant="filled" size="sm" radius="sm">
|
||||
<PieChartIcon size={14} />
|
||||
</ThemeIcon>
|
||||
<Title order={4} c={dark ? "white" : "gray.9"}>
|
||||
Distribusi Agama
|
||||
</Title>
|
||||
</Group>
|
||||
<ResponsiveContainer width="100%" height={250}>
|
||||
<PieChart>
|
||||
<Pie
|
||||
data={religionData}
|
||||
cx="50%"
|
||||
cy="50%"
|
||||
innerRadius={60}
|
||||
outerRadius={90}
|
||||
paddingAngle={2}
|
||||
dataKey="value"
|
||||
>
|
||||
{religionData.map((entry, index) => (
|
||||
<Cell key={`cell-${index}`} fill={entry.color} />
|
||||
))}
|
||||
</Pie>
|
||||
<Tooltip
|
||||
contentStyle={{
|
||||
backgroundColor: dark ? "#1E293B" : "white",
|
||||
borderColor: dark ? "#334155" : "#e5e7eb",
|
||||
borderRadius: "8px",
|
||||
}}
|
||||
/>
|
||||
</PieChart>
|
||||
</ResponsiveContainer>
|
||||
<Stack gap="xs" mt="md">
|
||||
{religionData.map((item, index) => (
|
||||
<Group key={index} justify="space-between">
|
||||
<Group gap="xs">
|
||||
<Box
|
||||
w={10}
|
||||
h={10}
|
||||
style={{
|
||||
backgroundColor: item.color,
|
||||
borderRadius: 2,
|
||||
}}
|
||||
/>
|
||||
<Text size="sm" c={dark ? "white" : "gray.7"}>
|
||||
{item.name}
|
||||
</Text>
|
||||
</Group>
|
||||
</Card>
|
||||
</Grid.Col>
|
||||
))}
|
||||
</Grid>
|
||||
</Card>
|
||||
</Stack>
|
||||
</Box>
|
||||
<Text size="sm" fw={600} c={dark ? "white" : "gray.9"}>
|
||||
{item.value.toLocaleString()}
|
||||
</Text>
|
||||
</Group>
|
||||
))}
|
||||
</Stack>
|
||||
</Card>
|
||||
</Grid.Col>
|
||||
|
||||
{/* CENTER: DATA PER BANJAR */}
|
||||
<Grid.Col span={{ base: 12, lg: 4 }}>
|
||||
<Card
|
||||
p="md"
|
||||
radius="xl"
|
||||
withBorder
|
||||
bg={dark ? "#1E293B" : "white"}
|
||||
style={{
|
||||
borderColor: dark ? "#334155" : "white",
|
||||
boxShadow: "0 1px 3px 0 rgb(0 0 0 / 0.1)",
|
||||
}}
|
||||
h="100%"
|
||||
>
|
||||
<Group gap="xs" mb="md">
|
||||
<ThemeIcon color="#1E3A5F" variant="filled" size="sm" radius="sm">
|
||||
<Users size={14} />
|
||||
</ThemeIcon>
|
||||
<Title order={4} c={dark ? "white" : "gray.9"}>
|
||||
Data per Banjar
|
||||
</Title>
|
||||
</Group>
|
||||
<Box style={{ overflowX: "auto" }}>
|
||||
<table style={{ width: "100%", borderCollapse: "collapse" }}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th
|
||||
style={{
|
||||
textAlign: "left",
|
||||
padding: "8px",
|
||||
fontSize: "12px",
|
||||
fontWeight: 600,
|
||||
color: dark ? "#94A3B8" : "#64748B",
|
||||
borderBottom: `1px solid ${dark ? "#334155" : "#e5e7eb"}`,
|
||||
}}
|
||||
>
|
||||
Banjar
|
||||
</th>
|
||||
<th
|
||||
style={{
|
||||
textAlign: "right",
|
||||
padding: "8px",
|
||||
fontSize: "12px",
|
||||
fontWeight: 600,
|
||||
color: dark ? "#94A3B8" : "#64748B",
|
||||
borderBottom: `1px solid ${dark ? "#334155" : "#e5e7eb"}`,
|
||||
}}
|
||||
>
|
||||
Penduduk
|
||||
</th>
|
||||
<th
|
||||
style={{
|
||||
textAlign: "right",
|
||||
padding: "8px",
|
||||
fontSize: "12px",
|
||||
fontWeight: 600,
|
||||
color: dark ? "#94A3B8" : "#64748B",
|
||||
borderBottom: `1px solid ${dark ? "#334155" : "#e5e7eb"}`,
|
||||
}}
|
||||
>
|
||||
KK
|
||||
</th>
|
||||
<th
|
||||
style={{
|
||||
textAlign: "right",
|
||||
padding: "8px",
|
||||
fontSize: "12px",
|
||||
fontWeight: 600,
|
||||
color: dark ? "#94A3B8" : "#64748B",
|
||||
borderBottom: `1px solid ${dark ? "#334155" : "#e5e7eb"}`,
|
||||
}}
|
||||
>
|
||||
Miskin
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{banjarData.map((item, index) => (
|
||||
<tr
|
||||
key={index}
|
||||
style={{
|
||||
backgroundColor:
|
||||
index % 2 === 0
|
||||
? dark
|
||||
? "#334155"
|
||||
: "#F8FAFC"
|
||||
: "transparent",
|
||||
transition: "background-color 0.15s ease",
|
||||
}}
|
||||
>
|
||||
<td
|
||||
style={{
|
||||
padding: "10px 8px",
|
||||
fontSize: "13px",
|
||||
fontWeight: 500,
|
||||
color: dark ? "#E2E8F0" : "#1E293B",
|
||||
}}
|
||||
>
|
||||
{item.banjar}
|
||||
</td>
|
||||
<td
|
||||
style={{
|
||||
padding: "10px 8px",
|
||||
textAlign: "right",
|
||||
fontSize: "13px",
|
||||
color: dark ? "#E2E8F0" : "#1E293B",
|
||||
}}
|
||||
>
|
||||
{item.population.toLocaleString()}
|
||||
</td>
|
||||
<td
|
||||
style={{
|
||||
padding: "10px 8px",
|
||||
textAlign: "right",
|
||||
fontSize: "13px",
|
||||
color: dark ? "#E2E8F0" : "#1E293B",
|
||||
}}
|
||||
>
|
||||
{item.kk.toLocaleString()}
|
||||
</td>
|
||||
<td
|
||||
style={{
|
||||
padding: "10px 8px",
|
||||
textAlign: "right",
|
||||
fontSize: "13px",
|
||||
color: "#EF4444",
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
{item.poor.toLocaleString()}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</Box>
|
||||
</Card>
|
||||
</Grid.Col>
|
||||
|
||||
{/* RIGHT: STATISTIK SEKTOR UNGGULAN */}
|
||||
<Grid.Col span={{ base: 12, lg: 4 }}>
|
||||
<Card
|
||||
p="md"
|
||||
radius="xl"
|
||||
withBorder
|
||||
bg={dark ? "#1E293B" : "white"}
|
||||
style={{
|
||||
borderColor: dark ? "#334155" : "white",
|
||||
boxShadow: "0 1px 3px 0 rgb(0 0 0 / 0.1)",
|
||||
}}
|
||||
h="100%"
|
||||
>
|
||||
<Group gap="xs" mb="md">
|
||||
<ThemeIcon color="#1E3A5F" variant="filled" size="sm" radius="sm">
|
||||
<BarChart3 size={14} />
|
||||
</ThemeIcon>
|
||||
<Title order={4} c={dark ? "white" : "gray.9"}>
|
||||
Sektor Unggulan
|
||||
</Title>
|
||||
</Group>
|
||||
<ResponsiveContainer width="100%" height={250}>
|
||||
<BarChart data={sektorUnggulanData} layout="vertical">
|
||||
<CartesianGrid
|
||||
strokeDasharray="3 3"
|
||||
horizontal={false}
|
||||
stroke={dark ? "#334155" : "#e5e7eb"}
|
||||
/>
|
||||
<XAxis
|
||||
type="number"
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
tick={{
|
||||
fill: dark ? "#E2E8F0" : "#374151",
|
||||
fontSize: 12,
|
||||
}}
|
||||
/>
|
||||
<YAxis
|
||||
type="category"
|
||||
dataKey="sektor"
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
tick={{
|
||||
fill: dark ? "#E2E8F0" : "#374151",
|
||||
fontSize: 12,
|
||||
}}
|
||||
width={90}
|
||||
/>
|
||||
<Tooltip
|
||||
contentStyle={{
|
||||
backgroundColor: dark ? "#1E293B" : "white",
|
||||
borderColor: dark ? "#334155" : "#e5e7eb",
|
||||
borderRadius: "8px",
|
||||
}}
|
||||
/>
|
||||
<Bar
|
||||
dataKey="value"
|
||||
fill="#1E3A5F"
|
||||
radius={[0, 8, 8, 0]}
|
||||
maxBarSize={40}
|
||||
>
|
||||
{sektorUnggulanData.map((entry, index) => (
|
||||
<Cell key={`cell-${index}`} fill="#1E3A5F" />
|
||||
))}
|
||||
</Bar>
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</Card>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,123 +1,79 @@
|
||||
import { BarChart } from "@mantine/charts";
|
||||
import {
|
||||
Badge,
|
||||
Box,
|
||||
Button,
|
||||
Card,
|
||||
Grid,
|
||||
GridCol,
|
||||
Group,
|
||||
Progress,
|
||||
Stack,
|
||||
Text,
|
||||
ThemeIcon,
|
||||
Title,
|
||||
useMantineColorScheme,
|
||||
} from "@mantine/core";
|
||||
import React from "react";
|
||||
import {
|
||||
MessageCircle,
|
||||
CheckCircle,
|
||||
AlertTriangle,
|
||||
Clock,
|
||||
TrendingUp,
|
||||
} from "lucide-react";
|
||||
import {
|
||||
Bar,
|
||||
BarChart,
|
||||
CartesianGrid,
|
||||
ResponsiveContainer,
|
||||
Tooltip,
|
||||
XAxis,
|
||||
YAxis,
|
||||
} from "recharts";
|
||||
|
||||
// Sample Data
|
||||
// KPI Data
|
||||
const kpiData = [
|
||||
{
|
||||
id: 1,
|
||||
title: "Interaksi Hari Ini",
|
||||
value: "61",
|
||||
delta: "+15% dari kemarin",
|
||||
deltaType: "positive",
|
||||
icon: (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth={1.5}
|
||||
stroke="currentColor"
|
||||
className="h-6 w-6 text-muted-foreground"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M8.625 12a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H8.25m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H12m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H16.5m-13.5 3h15a2.25 2.25 0 0 0 2.25-2.25V6.75A2.25 2.25 0 0 0 19.5 4.5h-15a2.25 2.25 0 0 0-2.25 2.25v10.5A2.25 2.25 0 0 0 4.5 19.5Z"
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
subtitle: "+15% dari kemarin",
|
||||
trend: "positive",
|
||||
icon: MessageCircle,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: "Jawaban Otomatis",
|
||||
value: "87%",
|
||||
sub: "53 dari 61 interaksi",
|
||||
icon: (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth={1.5}
|
||||
stroke="currentColor"
|
||||
className="h-6 w-6 text-muted-foreground"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M9 12.75 11.25 15 15 9.75M21 12c0 1.268-.63 2.473-1.688 3.342-.48.485-.926.97-1.378 1.44c-1.472 1.58-2.306 2.787-2.91 3.514-.15.18-.207.33-.207.33A.75.75 0 0 1 15 21h-3c-1.104 0-2.08-.542-2.657-1.455-.139-.201-.264-.406-.38-.614l-.014-.025C8.85 18.067 8.156 17.2 7.5 16.325.728 12.56.728 7.44 7.5 3.675c3.04-.482 5.584.47 7.042 1.956.674.672 1.228 1.462 1.696 2.307.426.786.793 1.582 1.113 2.392h.001Z"
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
subtitle: "53 dari 61 interaksi",
|
||||
icon: CheckCircle,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: "Belum Ditindak",
|
||||
value: "8",
|
||||
sub: "Perlu respon manual",
|
||||
deltaType: "negative",
|
||||
icon: (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth={1.5}
|
||||
stroke="currentColor"
|
||||
className="h-6 w-6 text-muted-foreground"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126ZM12 15.75h.007v.008H12v-.008Z"
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
subtitle: "Perlu respon manual",
|
||||
icon: AlertTriangle,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: "Waktu Respon",
|
||||
value: "2.3 sec",
|
||||
sub: "Rata-rata",
|
||||
icon: (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth={1.5}
|
||||
stroke="currentColor"
|
||||
className="h-6 w-6 text-muted-foreground"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M12 6v6h4.5m4.5 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
subtitle: "Rata-rata",
|
||||
icon: Clock,
|
||||
},
|
||||
];
|
||||
|
||||
// Chart Data
|
||||
const chartData = [
|
||||
{ day: "Sen", total: 100 },
|
||||
{ day: "Sel", total: 120 },
|
||||
{ day: "Rab", total: 90 },
|
||||
{ day: "Kam", total: 150 },
|
||||
{ day: "Jum", total: 110 },
|
||||
{ day: "Sab", total: 80 },
|
||||
{ day: "Min", total: 130 },
|
||||
{ day: "Sen", total: 45 },
|
||||
{ day: "Sel", total: 62 },
|
||||
{ day: "Rab", total: 38 },
|
||||
{ day: "Kam", total: 75 },
|
||||
{ day: "Jum", total: 58 },
|
||||
{ day: "Sab", total: 32 },
|
||||
{ day: "Min", total: 51 },
|
||||
];
|
||||
|
||||
// Top Topics Data
|
||||
const topTopics = [
|
||||
{ topic: "Cara mengurus KTP", count: 89 },
|
||||
{ topic: "Syarat Kartu Keluarga", count: 76 },
|
||||
@@ -126,6 +82,7 @@ const topTopics = [
|
||||
{ topic: "Info program bansos", count: 48 },
|
||||
];
|
||||
|
||||
// Busy Hours Data
|
||||
const busyHours = [
|
||||
{ period: "Pagi (08–12)", percentage: 30 },
|
||||
{ period: "Siang (12–16)", percentage: 40 },
|
||||
@@ -138,146 +95,206 @@ const JennaAnalytic = () => {
|
||||
const dark = colorScheme === "dark";
|
||||
|
||||
return (
|
||||
<Box className="space-y-6">
|
||||
<Stack gap="xl">
|
||||
{/* KPI Cards */}
|
||||
<Grid gutter="lg">
|
||||
{kpiData.map((kpi) => (
|
||||
<Grid.Col key={kpi.id} span={{ base: 12, md: 6, lg: 3 }}>
|
||||
<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}
|
||||
</Text>
|
||||
{React.cloneElement(kpi.icon, {
|
||||
className: "h-6 w-6", // Keeping classes for now, can be replaced by Mantine Icon component if available or styled with sx prop
|
||||
color: "var(--mantine-color-dimmed)", // Set color via prop
|
||||
})}
|
||||
</Group>
|
||||
<Title order={3} fw={700} mt="xs">
|
||||
{kpi.value}
|
||||
</Title>
|
||||
{kpi.delta && (
|
||||
<Text
|
||||
size="xs"
|
||||
c={
|
||||
kpi.deltaType === "positive"
|
||||
? "green"
|
||||
: kpi.deltaType === "negative"
|
||||
? "red"
|
||||
: "dimmed"
|
||||
}
|
||||
mt={4}
|
||||
>
|
||||
{kpi.delta}
|
||||
</Text>
|
||||
)}
|
||||
{kpi.sub && (
|
||||
<Text size="xs" c="dimmed" mt={2}>
|
||||
{kpi.sub}
|
||||
</Text>
|
||||
)}
|
||||
</Card>
|
||||
</Grid.Col>
|
||||
))}
|
||||
</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 }}>
|
||||
<Stack gap="lg">
|
||||
{/* TOP SECTION - 4 STAT CARDS */}
|
||||
<Grid gutter="md">
|
||||
{kpiData.map((item) => (
|
||||
<Grid.Col key={item.id} span={{ base: 12, sm: 6, lg: 3 }}>
|
||||
<Card
|
||||
p="md"
|
||||
radius="md"
|
||||
radius="xl"
|
||||
withBorder
|
||||
bg={dark ? "#141D34" : "white"}
|
||||
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||
bg={dark ? "#1E293B" : "white"}
|
||||
style={{
|
||||
borderColor: dark ? "#334155" : "white",
|
||||
boxShadow: "0 1px 3px 0 rgb(0 0 0 / 0.1)",
|
||||
transition: "transform 0.15s ease, box-shadow 0.15s ease",
|
||||
}}
|
||||
h="100%"
|
||||
>
|
||||
<Title order={3} fw={500} mb="md">
|
||||
Jam Tersibuk
|
||||
</Title>
|
||||
<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>
|
||||
<Group justify="space-between" align="flex-start" w="100%">
|
||||
<Stack gap={2}>
|
||||
<Text size="sm" c="dimmed">
|
||||
{item.title}
|
||||
</Text>
|
||||
<Text size="xl" fw={700} c={dark ? "white" : "gray.9"}>
|
||||
{item.value}
|
||||
</Text>
|
||||
<Group gap={4} align="flex-start">
|
||||
{item.trend === "positive" && (
|
||||
<TrendingUp size={14} color="#22C55E" />
|
||||
)}
|
||||
<Text
|
||||
size="xs"
|
||||
c={
|
||||
item.trend === "positive"
|
||||
? "green"
|
||||
: dark
|
||||
? "gray.4"
|
||||
: "gray.5"
|
||||
}
|
||||
>
|
||||
{item.subtitle}
|
||||
</Text>
|
||||
</Group>
|
||||
</Stack>
|
||||
<ThemeIcon
|
||||
color="#1E3A5F"
|
||||
variant="filled"
|
||||
size="lg"
|
||||
radius="xl"
|
||||
>
|
||||
<item.icon style={{ width: "60%", height: "60%" }} />
|
||||
</ThemeIcon>
|
||||
</Group>
|
||||
</Card>
|
||||
</Grid.Col>
|
||||
))}
|
||||
</Grid>
|
||||
|
||||
{/* Topik Pertanyaan Terbanyak & Jam Tersibuk */}
|
||||
<Grid.Col span={{ base: 12, lg: 6 }}>
|
||||
<Stack gap="lg">
|
||||
{/* Topik Pertanyaan Terbanyak */}
|
||||
<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>
|
||||
<Stack gap="xs">
|
||||
{topTopics.map((item, index) => (
|
||||
<Group
|
||||
key={index}
|
||||
justify="space-between"
|
||||
align="center"
|
||||
p="xs"
|
||||
{/* MAIN CHART - INTERAKSI CHATBOT */}
|
||||
<Card
|
||||
p="md"
|
||||
radius="xl"
|
||||
withBorder
|
||||
bg={dark ? "#1E293B" : "white"}
|
||||
style={{
|
||||
borderColor: dark ? "#334155" : "white",
|
||||
boxShadow: "0 1px 3px 0 rgb(0 0 0 / 0.1)",
|
||||
}}
|
||||
>
|
||||
<Group justify="space-between" mb="md">
|
||||
<Title order={4} c={dark ? "white" : "gray.9"}>
|
||||
Interaksi Chatbot
|
||||
</Title>
|
||||
</Group>
|
||||
<ResponsiveContainer width="100%" height={300}>
|
||||
<BarChart data={chartData}>
|
||||
<CartesianGrid
|
||||
strokeDasharray="3 3"
|
||||
vertical={false}
|
||||
stroke={dark ? "#334155" : "#e5e7eb"}
|
||||
/>
|
||||
<XAxis
|
||||
dataKey="day"
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
tick={{ fill: dark ? "#E2E8F0" : "#374151" }}
|
||||
/>
|
||||
<YAxis
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
tick={{ fill: dark ? "#E2E8F0" : "#374151" }}
|
||||
/>
|
||||
<Tooltip
|
||||
contentStyle={{
|
||||
backgroundColor: dark ? "#1E293B" : "white",
|
||||
borderColor: dark ? "#334155" : "#e5e7eb",
|
||||
borderRadius: "8px",
|
||||
}}
|
||||
labelStyle={{ color: dark ? "#E2E8F0" : "#374151" }}
|
||||
cursor={{ fill: dark ? "#334155" : "#f3f4f6" }}
|
||||
/>
|
||||
<Bar
|
||||
dataKey="total"
|
||||
fill="#1E3A5F"
|
||||
radius={[8, 8, 0, 0]}
|
||||
maxBarSize={60}
|
||||
/>
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</Card>
|
||||
|
||||
{/* BOTTOM SECTION - 2 COLUMNS */}
|
||||
<Grid gutter="lg">
|
||||
{/* LEFT: TOPIK PERTANYAAN TERBANYAK */}
|
||||
<Grid.Col span={{ base: 12, lg: 6 }}>
|
||||
<Card
|
||||
p="md"
|
||||
radius="xl"
|
||||
withBorder
|
||||
bg={dark ? "#1E293B" : "white"}
|
||||
style={{
|
||||
borderColor: dark ? "#334155" : "white",
|
||||
boxShadow: "0 1px 3px 0 rgb(0 0 0 / 0.1)",
|
||||
}}
|
||||
h="100%"
|
||||
>
|
||||
<Title order={4} c={dark ? "white" : "gray.9"} mb="md">
|
||||
Topik Pertanyaan Terbanyak
|
||||
</Title>
|
||||
<Stack gap="xs">
|
||||
{topTopics.map((item, index) => (
|
||||
<Box
|
||||
key={index}
|
||||
p="sm"
|
||||
bg={dark ? "#334155" : "#F1F5F9"}
|
||||
style={{
|
||||
transition: "background-color 0.15s ease",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
>
|
||||
<Group justify="space-between">
|
||||
<Text size="sm" fw={500} c={dark ? "white" : "gray.9"}>
|
||||
{item.topic}
|
||||
</Text>
|
||||
<Badge
|
||||
variant="light"
|
||||
color="darmasaba-blue"
|
||||
radius="sm"
|
||||
fw={600}
|
||||
>
|
||||
<Text size="sm" fw={500}>
|
||||
{item.topic}
|
||||
</Text>
|
||||
<Badge variant="light" color="gray">
|
||||
{item.count}x
|
||||
</Badge>
|
||||
</Group>
|
||||
))}
|
||||
</Stack>
|
||||
</Card>
|
||||
|
||||
{/* Jam Tersibuk */}
|
||||
{item.count}x
|
||||
</Badge>
|
||||
</Group>
|
||||
</Box>
|
||||
))}
|
||||
</Stack>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Card>
|
||||
</Grid.Col>
|
||||
|
||||
{/* RIGHT: JAM TERSIBUK */}
|
||||
<Grid.Col span={{ base: 12, lg: 6 }}>
|
||||
<Card
|
||||
p="md"
|
||||
radius="xl"
|
||||
withBorder
|
||||
bg={dark ? "#1E293B" : "white"}
|
||||
style={{
|
||||
borderColor: dark ? "#334155" : "white",
|
||||
boxShadow: "0 1px 3px 0 rgb(0 0 0 / 0.1)",
|
||||
}}
|
||||
h="100%"
|
||||
>
|
||||
<Title order={4} c={dark ? "white" : "gray.9"} mb="md">
|
||||
Jam Tersibuk
|
||||
</Title>
|
||||
<Stack gap="md">
|
||||
{busyHours.map((item, index) => (
|
||||
<Box key={index}>
|
||||
<Group justify="space-between" mb={5}>
|
||||
<Text size="sm" fw={500} c={dark ? "white" : "gray.9"}>
|
||||
{item.period}
|
||||
</Text>
|
||||
<Text size="sm" fw={600} c={dark ? "white" : "gray.9"}>
|
||||
{item.percentage}%
|
||||
</Text>
|
||||
</Group>
|
||||
<Progress
|
||||
value={item.percentage}
|
||||
size="lg"
|
||||
radius="xl"
|
||||
color="#1E3A5F"
|
||||
animated
|
||||
/>
|
||||
</Box>
|
||||
))}
|
||||
</Stack>
|
||||
</Card>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default JennaAnalytic;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Grid, Stack } from "@mantine/core";
|
||||
import { ActivityCard, } from "./kinerja-divisi/activity-card";
|
||||
import { ActivityCard } from "./kinerja-divisi/activity-card";
|
||||
import { DivisionList } from "./kinerja-divisi/division-list";
|
||||
import { DocumentChart } from "./kinerja-divisi/document-chart";
|
||||
import { ProgressChart } from "./kinerja-divisi/progress-chart";
|
||||
@@ -14,25 +14,25 @@ const programKegiatanData = [
|
||||
title: "Rakor 2025",
|
||||
date: "3 Juli 2025",
|
||||
progress: 90,
|
||||
status: "selesai" as const,
|
||||
status: "Selesai" as const,
|
||||
},
|
||||
{
|
||||
title: "Pemutakhiran Indeks Desa",
|
||||
date: "3 Juli 2025",
|
||||
progress: 85,
|
||||
status: "selesai" as const,
|
||||
status: "Selesai" as const,
|
||||
},
|
||||
{
|
||||
title: "Mengurus Akta Cerai Warga",
|
||||
date: "3 Juli 2025",
|
||||
progress: 80,
|
||||
status: "selesai" as const,
|
||||
status: "Selesai" as const,
|
||||
},
|
||||
{
|
||||
title: "Pasek 7 Desa Adat",
|
||||
date: "3 Juli 2025",
|
||||
progress: 92,
|
||||
status: "selesai" as const,
|
||||
status: "Selesai" as const,
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -1,17 +1,10 @@
|
||||
import {
|
||||
Box,
|
||||
Card,
|
||||
Group,
|
||||
Progress,
|
||||
Text,
|
||||
useMantineColorScheme,
|
||||
} from "@mantine/core";
|
||||
import { Card, Text, Progress, Group, Box } from "@mantine/core";
|
||||
|
||||
interface ActivityCardProps {
|
||||
title: string;
|
||||
date: string;
|
||||
progress: number;
|
||||
status: "selesai" | "berjalan" | "tertunda";
|
||||
status: "Selesai" | "Berjalan" | "Tertunda";
|
||||
}
|
||||
|
||||
export function ActivityCard({
|
||||
@@ -20,16 +13,13 @@ export function ActivityCard({
|
||||
progress,
|
||||
status,
|
||||
}: ActivityCardProps) {
|
||||
const { colorScheme } = useMantineColorScheme();
|
||||
const dark = colorScheme === "dark";
|
||||
|
||||
const getStatusColor = (s: string) => {
|
||||
switch (s) {
|
||||
case "selesai":
|
||||
const getStatusColor = () => {
|
||||
switch (status) {
|
||||
case "Selesai":
|
||||
return "#22C55E";
|
||||
case "berjalan":
|
||||
case "Berjalan":
|
||||
return "#3B82F6";
|
||||
case "tertunda":
|
||||
case "Tertunda":
|
||||
return "#EF4444";
|
||||
default:
|
||||
return "#9CA3AF";
|
||||
@@ -38,58 +28,62 @@ export function ActivityCard({
|
||||
|
||||
return (
|
||||
<Card
|
||||
p="md"
|
||||
radius="xl"
|
||||
withBorder
|
||||
bg={dark ? "#1E293B" : "white"}
|
||||
p={0}
|
||||
withBorder={false}
|
||||
style={{
|
||||
borderColor: dark ? "#334155" : "white",
|
||||
boxShadow: dark
|
||||
? "0 1px 3px 0 rgb(0 0 0 / 0.1)"
|
||||
: "0 1px 3px 0 rgb(0 0 0 / 0.1)",
|
||||
backgroundColor: "#F3F4F6",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
{/* 🔵 HEADER */}
|
||||
<Box
|
||||
style={{
|
||||
borderLeft: `4px solid #3B82F6`,
|
||||
paddingLeft: 12,
|
||||
marginBottom: 12,
|
||||
backgroundColor: "#1E3A5F",
|
||||
padding: "16px",
|
||||
textAlign: "center",
|
||||
}}
|
||||
>
|
||||
<Text size="sm" fw={600} c={dark ? "white" : "#1E3A5F"}>
|
||||
<Text c="white" fw={700} size="md">
|
||||
{title}
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
<Group justify="space-between" mb="xs">
|
||||
<Text size="xs" c="dimmed">
|
||||
{date}
|
||||
</Text>
|
||||
<Box
|
||||
style={{
|
||||
backgroundColor: getStatusColor(status),
|
||||
color: "white",
|
||||
padding: "2px 8px",
|
||||
borderRadius: 4,
|
||||
fontSize: 11,
|
||||
fontWeight: 600,
|
||||
{/* CONTENT */}
|
||||
<Box p="md">
|
||||
{/* PROGRESS */}
|
||||
<Progress
|
||||
value={progress}
|
||||
radius="xl"
|
||||
size="lg"
|
||||
color="orange"
|
||||
styles={{
|
||||
root: {
|
||||
height: 16,
|
||||
},
|
||||
}}
|
||||
>
|
||||
{status.toUpperCase()}
|
||||
</Box>
|
||||
</Group>
|
||||
/>
|
||||
|
||||
<Progress
|
||||
value={progress}
|
||||
size="sm"
|
||||
radius="xl"
|
||||
color={progress === 100 ? "green" : "yellow"}
|
||||
animated={progress < 100}
|
||||
/>
|
||||
{/* FOOTER */}
|
||||
<Group justify="space-between" mt="md">
|
||||
<Text size="sm" fw={500}>
|
||||
{date}
|
||||
</Text>
|
||||
|
||||
<Text size="xs" c="dimmed" mt="xs" ta="right">
|
||||
{progress}%
|
||||
</Text>
|
||||
<Box
|
||||
style={{
|
||||
backgroundColor: getStatusColor(),
|
||||
color: "white",
|
||||
padding: "4px 12px",
|
||||
borderRadius: 999,
|
||||
fontSize: 12,
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
{status}
|
||||
</Box>
|
||||
</Group>
|
||||
</Box>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@ export function ArchiveCard({ item, onClick }: ArchiveCardProps) {
|
||||
cursor: "pointer",
|
||||
transition: "transform 0.2s, box-shadow 0.2s",
|
||||
}}
|
||||
h="100%"
|
||||
onClick={onClick}
|
||||
>
|
||||
<Group gap="md">
|
||||
|
||||
@@ -48,6 +48,7 @@ export function DiscussionPanel() {
|
||||
? "0 1px 3px 0 rgb(0 0 0 / 0.1)"
|
||||
: "0 1px 3px 0 rgb(0 0 0 / 0.1)",
|
||||
}}
|
||||
h="100%"
|
||||
>
|
||||
<Group gap="xs" mb="md">
|
||||
<MessageCircle size={20} color={dark ? "#E2E8F0" : "#1E3A5F"} />
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
Bar,
|
||||
BarChart,
|
||||
CartesianGrid,
|
||||
Cell,
|
||||
ResponsiveContainer,
|
||||
Tooltip,
|
||||
XAxis,
|
||||
@@ -10,8 +11,8 @@ import {
|
||||
} from "recharts";
|
||||
|
||||
const documentData = [
|
||||
{ name: "Gambar", value: 300 },
|
||||
{ name: "Dokumen", value: 310 },
|
||||
{ name: "Gambar", jumlah: 300, color: "#FACC15" },
|
||||
{ name: "Dokumen", jumlah: 310, color: "#22C55E" },
|
||||
];
|
||||
|
||||
export function DocumentChart() {
|
||||
@@ -61,7 +62,11 @@ export function DocumentChart() {
|
||||
}}
|
||||
labelStyle={{ color: dark ? "#E2E8F0" : "#374151" }}
|
||||
/>
|
||||
<Bar dataKey="value" fill="#3B82F6" radius={[4, 4, 0, 0]} />
|
||||
<Bar dataKey="jumlah" radius={[4, 4, 0, 0]}>
|
||||
{documentData.map((entry, index) => (
|
||||
<Cell key={`cell-${index}`} fill={entry.color} />
|
||||
))}
|
||||
</Bar>
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</Card>
|
||||
|
||||
@@ -33,6 +33,7 @@ export function EventCard({ agendas = [] }: EventCardProps) {
|
||||
? "0 1px 3px 0 rgb(0 0 0 / 0.1)"
|
||||
: "0 1px 3px 0 rgb(0 0 0 / 0.1)",
|
||||
}}
|
||||
h="100%"
|
||||
>
|
||||
<Group gap="xs" mb="md">
|
||||
<Calendar size={20} color={dark ? "#E2E8F0" : "#1E3A5F"} />
|
||||
|
||||
@@ -10,7 +10,7 @@ import { Cell, Pie, PieChart, ResponsiveContainer, Tooltip } from "recharts";
|
||||
|
||||
const progressData = [
|
||||
{ name: "Selesai", value: 83.33, color: "#22C55E" },
|
||||
{ name: "Dikerjakan", value: 16.67, color: "#FACC15" },
|
||||
{ name: "Dikerjakan", value: 16.67, color: "#F59E0B" },
|
||||
{ name: "Segera Dikerjakan", value: 0, color: "#3B82F6" },
|
||||
{ name: "Dibatalkan", value: 0, color: "#EF4444" },
|
||||
];
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -61,7 +61,10 @@ export function Sidebar({ className }: SidebarProps) {
|
||||
return (
|
||||
<Box className={className}>
|
||||
{/* Logo */}
|
||||
<Image src="/logo-desa-plus.png" alt="Logo" />
|
||||
<Image
|
||||
src={dark ? "/white.png" : "/light-mode.png"}
|
||||
alt="Logo"
|
||||
/>
|
||||
|
||||
{/* Search */}
|
||||
<Box p="md">
|
||||
|
||||
@@ -1,9 +1,51 @@
|
||||
import { AppShell, Burger, Group, useMantineColorScheme } from "@mantine/core";
|
||||
import { useDisclosure } from "@mantine/hooks";
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { Header } from "@/components/header";
|
||||
import JennaAnalytic from "@/components/jenna-analytic";
|
||||
import { Sidebar } from "@/components/sidebar";
|
||||
|
||||
export const Route = createFileRoute("/jenna-analytic")({
|
||||
component: RouteComponent,
|
||||
component: JennaAnalyticPage,
|
||||
});
|
||||
|
||||
function RouteComponent() {
|
||||
return <div>Hello "/jenna-analytic"!</div>;
|
||||
function JennaAnalyticPage() {
|
||||
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 }}
|
||||
navbar={{
|
||||
width: 300,
|
||||
breakpoint: "sm",
|
||||
collapsed: { mobile: !opened },
|
||||
}}
|
||||
padding="md"
|
||||
>
|
||||
<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"
|
||||
bg={navbarBgColor}
|
||||
style={{ display: "flex", flexDirection: "column" }}
|
||||
>
|
||||
<div style={{ flex: 1, overflowY: "auto" }}>
|
||||
<Sidebar />
|
||||
</div>
|
||||
</AppShell.Navbar>
|
||||
|
||||
<AppShell.Main bg={mainBgColor}>
|
||||
<JennaAnalytic />
|
||||
</AppShell.Main>
|
||||
</AppShell>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user