Compare commits

...

5 Commits

Author SHA1 Message Date
d714c09efc Fix New UI Pengaduan 2026-03-18 00:43:44 +07:00
0a97e31416 Fix New UI Pengaduan 2026-03-18 00:34:53 +07:00
158a2db435 Fix New UI Pengaduan 2026-03-17 21:41:03 +07:00
2d68d4dc06 Fix New UI Kinerja Divisi 2026-03-17 21:19:10 +07:00
97e6caa332 Fix UI Beranda 2026-03-17 21:03:36 +07:00
22 changed files with 1471 additions and 1673 deletions

3
.gitignore vendored
View File

@@ -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
View 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: 16px24px (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: 3060
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

View File

@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

BIN
public/white.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

View File

@@ -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

View File

@@ -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

View File

@@ -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>

View File

@@ -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

View File

@@ -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

View File

@@ -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>
);
};

View File

@@ -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 (0812)", percentage: 30 },
{ period: "Siang (1216)", 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;

View File

@@ -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,
},
];

View File

@@ -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>
);
}
}

View File

@@ -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">

View File

@@ -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"} />

View File

@@ -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>

View File

@@ -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"} />

View File

@@ -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

View File

@@ -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">

View File

@@ -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>
);
}