Files
dashboard-desaplus-noc/src/components/demografi-pekerjaan.tsx
nico 66d207c081 feat: refactor UI components to TailwindCSS with dark mode support
- Convert Mantine-based components to TailwindCSS + Recharts
- Add dark mode support for all dashboard pages
- Update routing to allow public dashboard access
- Components refactored:
  - kinreja-divisi.tsx: Village performance dashboard
  - pengaduan-layanan-publik.tsx: Public complaint management
  - jenna-analytic.tsx: Chatbot analytics dashboard
  - demografi-pekerjaan.tsx: Demographic analytics
  - keuangan-anggaran.tsx: APBDes financial dashboard
  - bumdes-page.tsx: UMKM sales monitoring
  - sosial-page.tsx: Village social monitoring
- Remove landing page, redirect / to /dashboard
- Update auth middleware for public dashboard access

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-03-11 15:26:16 +08:00

471 lines
11 KiB
TypeScript

import {
IconArrowDown,
IconArrowUp,
IconBabyCarriage,
IconSkull,
IconUsers,
IconHome,
IconExclamationCircle,
} from "@tabler/icons-react";
import { useMantineColorScheme } from "@mantine/core";
import {
Bar,
BarChart,
CartesianGrid,
Cell,
Pie,
PieChart,
ResponsiveContainer,
Tooltip,
XAxis,
YAxis,
} from "recharts";
const DemografiPekerjaan = () => {
const { colorScheme } = useMantineColorScheme();
const dark = colorScheme === "dark";
// KPI Data
const kpiData = [
{
id: 1,
title: "Total Penduduk",
value: "5.634",
subtitle: "Aktif terdaftar",
icon: IconUsers,
},
{
id: 2,
title: "Kepala Keluarga",
value: "1.354",
subtitle: "Total KK",
icon: IconHome,
},
{
id: 3,
title: "Kelahiran",
value: "23",
subtitle: "Tahun ini",
icon: IconBabyCarriage,
},
{
id: 4,
title: "Kemiskinan",
value: "324",
subtitle: "-10% dari tahun lalu",
icon: IconExclamationCircle,
},
];
// Age distribution data
const ageDistributionData = [
{ ageRange: "17-25", total: 850 },
{ ageRange: "26-35", total: 1200 },
{ ageRange: "36-45", total: 1100 },
{ ageRange: "46-55", total: 950 },
{ ageRange: "56-65", total: 750 },
{ ageRange: "65+", total: 484 },
];
// Job distribution data
const jobDistributionData = [
{ job: "Sipil", total: 1200 },
{ job: "Guru", total: 850 },
{ job: "Petani", total: 950 },
{ job: "Pedagang", total: 750 },
{ job: "Wiraswasta", total: 984 },
];
// Religion data
const religionData = [
{ religion: "Hindu", total: 4234, color: "#EF4444" },
{ religion: "Islam", total: 856, color: "#3B82F6" },
{ religion: "Kristen", total: 412, color: "#10B981" },
{ religion: "Buddha", total: 202, color: "#F59E0B" },
];
// 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 },
];
// Dynamic stats
const dynamicStats = [
{
title: "Kelahiran",
value: "23",
icon: IconBabyCarriage,
color: "#10B981",
},
{
title: "Kematian",
value: "12",
icon: IconSkull,
color: "#EF4444",
},
{
title: "Pindah Masuk",
value: "45",
icon: IconArrowDown,
color: "#3B82F6",
},
{
title: "Pindah Keluar",
value: "32",
icon: IconArrowUp,
color: "#F59E0B",
},
];
const COLORS = ["#1E3A5F", "#3B82F6", "#60A5FA", "#93C5FD", "#DBEAFE"];
const cardStyle = {
backgroundColor: dark ? "#141D34" : "white",
border: `1px solid ${dark ? "#141D34" : "white"}`,
};
const textStyle = {
color: dark ? "white" : "#1F2937",
};
const subtitleStyle = {
color: dark ? "#9CA3AF" : "#6B7280",
};
return (
<div
className="min-h-screen"
style={{ backgroundColor: dark ? "#10192D" : "#F3F4F6" }}
>
<div className="max-w-7xl mx-auto">
{/* Row 1: 4 Statistic Cards */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-6">
{kpiData.map((kpi) => (
<div
key={kpi.id}
className="rounded-xl shadow-sm p-6"
style={cardStyle}
>
<div className="flex items-center justify-between">
<div className="flex-1">
<h3
className="text-sm font-medium mb-1"
style={subtitleStyle}
>
{kpi.title}
</h3>
<p
className="text-3xl font-bold mb-1"
style={textStyle}
>
{kpi.value}
</p>
<p
className="text-xs"
style={subtitleStyle}
>
{kpi.subtitle}
</p>
</div>
<div className="flex-shrink-0 ml-4">
<div
className="w-12 h-12 rounded-full flex items-center justify-center text-white"
style={{ backgroundColor: "#1E3A5F" }}
>
<kpi.icon size={24} />
</div>
</div>
</div>
</div>
))}
</div>
{/* Row 2: 2 Chart Cards */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
{/* Age Distribution Bar Chart */}
<div
className="rounded-xl shadow-sm p-6"
style={cardStyle}
>
<h3
className="text-lg font-semibold mb-4"
style={textStyle}
>
Grafik Pengelompokan Umur
</h3>
<ResponsiveContainer width="100%" height={300}>
<BarChart data={ageDistributionData}>
<CartesianGrid
strokeDasharray="3 3"
vertical={false}
stroke={dark ? "#2d3748" : "#E5E7EB"}
/>
<XAxis
dataKey="ageRange"
axisLine={false}
tickLine={false}
tick={{ fill: dark ? "#9CA3AF" : "#6B7280" }}
/>
<YAxis
axisLine={false}
tickLine={false}
tick={{ fill: dark ? "#9CA3AF" : "#6B7280" }}
/>
<Tooltip
contentStyle={{
backgroundColor: dark ? "#1F2937" : "white",
border: `1px solid ${dark ? "#374151" : "#E5E7EB"}`,
borderRadius: "8px",
color: dark ? "white" : "#1F2937",
}}
/>
<Bar dataKey="total" radius={[4, 4, 0, 0]}>
{ageDistributionData.map((entry, index) => (
<Cell
key={`cell-${index}`}
fill={COLORS[index % COLORS.length]}
/>
))}
</Bar>
</BarChart>
</ResponsiveContainer>
</div>
{/* Job Distribution Bar Chart */}
<div
className="rounded-xl shadow-sm p-6"
style={cardStyle}
>
<h3
className="text-lg font-semibold mb-4"
style={textStyle}
>
Demografi Pekerjaan
</h3>
<ResponsiveContainer width="100%" height={300}>
<BarChart data={jobDistributionData}>
<CartesianGrid
strokeDasharray="3 3"
vertical={false}
stroke={dark ? "#2d3748" : "#E5E7EB"}
/>
<XAxis
dataKey="job"
axisLine={false}
tickLine={false}
tick={{ fill: dark ? "#9CA3AF" : "#6B7280" }}
/>
<YAxis
axisLine={false}
tickLine={false}
tick={{ fill: dark ? "#9CA3AF" : "#6B7280" }}
/>
<Tooltip
contentStyle={{
backgroundColor: dark ? "#1F2937" : "white",
border: `1px solid ${dark ? "#374151" : "#E5E7EB"}`,
borderRadius: "8px",
color: dark ? "white" : "#1F2937",
}}
/>
<Bar dataKey="total" radius={[4, 4, 0, 0]}>
{jobDistributionData.map((entry, index) => (
<Cell
key={`cell-${index}`}
fill={COLORS[index % COLORS.length]}
/>
))}
</Bar>
</BarChart>
</ResponsiveContainer>
</div>
</div>
{/* Row 3: 3 Insight Cards */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-6">
{/* Religion Distribution Pie Chart */}
<div
className="rounded-xl shadow-sm p-6"
style={cardStyle}
>
<h3
className="text-lg font-semibold mb-4"
style={textStyle}
>
Distribusi Agama
</h3>
<ResponsiveContainer width="100%" height={250}>
<PieChart>
<Pie
data={religionData}
cx="50%"
cy="50%"
outerRadius={80}
dataKey="total"
nameKey="religion"
label={({ name, percent }) =>
`${name}: ${percent ? (percent * 100).toFixed(0) : 0}%`
}
>
{religionData.map((entry, index) => (
<Cell key={`cell-${index}`} fill={entry.color} />
))}
</Pie>
<Tooltip
contentStyle={{
backgroundColor: dark ? "#1F2937" : "white",
border: `1px solid ${dark ? "#374151" : "#E5E7EB"}`,
borderRadius: "8px",
color: dark ? "white" : "#1F2937",
}}
/>
</PieChart>
</ResponsiveContainer>
</div>
{/* Population per Banjar Table */}
<div
className="rounded-xl shadow-sm p-6 lg:col-span-2"
style={cardStyle}
>
<h3
className="text-lg font-semibold mb-4"
style={textStyle}
>
Data per Banjar
</h3>
<div className="overflow-x-auto">
<table className="w-full">
<thead>
<tr
style={{
borderBottom: `1px solid ${dark ? "#2d3748" : "#E5E7EB"}`,
}}
>
<th
className="text-left py-3 px-4 text-sm font-medium"
style={subtitleStyle}
>
Banjar
</th>
<th
className="text-left py-3 px-4 text-sm font-medium"
style={subtitleStyle}
>
Penduduk
</th>
<th
className="text-left py-3 px-4 text-sm font-medium"
style={subtitleStyle}
>
KK
</th>
<th
className="text-left py-3 px-4 text-sm font-medium"
style={subtitleStyle}
>
Miskin
</th>
</tr>
</thead>
<tbody>
{banjarData.map((item, index) => (
<tr
key={index}
style={{
borderBottom: `1px solid ${dark ? "#2d3748" : "#F3F4F6"}`,
}}
>
<td
className="py-3 px-4 text-sm"
style={textStyle}
>
{item.banjar}
</td>
<td
className="py-3 px-4 text-sm"
style={textStyle}
>
{item.population.toLocaleString()}
</td>
<td
className="py-3 px-4 text-sm"
style={textStyle}
>
{item.kk.toLocaleString()}
</td>
<td
className="py-3 px-4 text-sm"
style={{ color: "#EF4444" }}
>
{item.poor.toLocaleString()}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
{/* Population Dynamics Stats */}
<div
className="rounded-xl shadow-sm p-6"
style={cardStyle}
>
<h3
className="text-lg font-semibold mb-4"
style={textStyle}
>
Statistik Dinamika Penduduk
</h3>
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
{dynamicStats.map((stat, index) => (
<div
key={index}
className="p-4 rounded-lg"
style={{
backgroundColor: dark ? "#1F2937" : "#F9FAFB",
}}
>
<div className="flex items-center justify-between">
<div>
<p
className="text-sm font-medium mb-1"
style={subtitleStyle}
>
{stat.title}
</p>
<p
className="text-2xl font-bold"
style={{ color: stat.color }}
>
{stat.value}
</p>
</div>
<div
className="w-10 h-10 rounded-full flex items-center justify-center"
style={{
backgroundColor: `${stat.color}20`,
color: stat.color,
}}
>
<stat.icon size={20} />
</div>
</div>
</div>
))}
</div>
</div>
</div>
</div>
);
};
export default DemografiPekerjaan;