Files
dashboard-desaplus-noc/src/components/kinerja-divisi.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

357 lines
9.1 KiB
TypeScript

import {
Bar,
BarChart,
CartesianGrid,
Cell,
Pie,
PieChart,
ResponsiveContainer,
Tooltip,
XAxis,
YAxis,
} from "recharts";
import { useMantineColorScheme } from "@mantine/core";
const KinerjaDivisi = () => {
const { colorScheme } = useMantineColorScheme();
const dark = colorScheme === "dark";
// Top row - 4 activity cards
const activities = [
{
title: "Rakor 2025",
progress: 100,
date: "15 Jan 2025",
},
{
title: "Pemutakhiran Indeks Desa",
progress: 100,
date: "20 Feb 2025",
},
{
title: "Mengurus akta cerai warga",
progress: 100,
date: "5 Mar 2025",
},
{
title: "Pasek 7 desa adat",
progress: 100,
date: "10 Mar 2025",
},
];
// Document statistics
const documentStats = [
{ name: "Gambar", value: 300, color: "#FAC858" },
{ name: "Dokumen", value: 310, color: "#92CC76" },
];
// Activity progress statistics
const activityProgressStats = [
{ name: "Selesai", value: 83.33, fill: "#92CC76" },
{ name: "Dikerjakan", value: 16.67, fill: "#FAC858" },
{ name: "Segera Dikerjakan", value: 0, fill: "#5470C6" },
{ name: "Dibatalkan", value: 0, fill: "#EE6767" },
];
// Discussion data
const discussions = [
{
title: "Pembahasan APBDes 2026",
sender: "Kepala Desa",
date: "10 Mar 2025",
},
{
title: "Kegiatan Posyandu",
sender: "Divisi Sosial",
date: "9 Mar 2025",
},
{
title: "Festival Budaya",
sender: "Divisi Humas",
date: "8 Mar 2025",
},
];
return (
<div
className="min-h-screen"
style={{ backgroundColor: dark ? "#10192D" : "#F3F4F6" }}
>
{/* Top Row - 4 Activity Cards */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-6">
{activities.map((activity, index) => (
<div
key={index}
className="rounded-xl shadow-sm p-5"
style={{
backgroundColor: dark ? "#141D34" : "white",
border: `1px solid ${dark ? "#141D34" : "white"}`,
}}
>
{/* Dark blue title bar */}
<div
className="text-white px-3 py-2 rounded-t-lg -mx-5 -mt-5 mb-4"
style={{ backgroundColor: "#1E3A5F" }}
>
<h3 className="text-sm font-semibold">{activity.title}</h3>
</div>
{/* Orange progress bar */}
<div
className="w-full rounded-full h-2 mb-3"
style={{ backgroundColor: dark ? "#2d3748" : "#E5E7EB" }}
>
<div
className="bg-orange-500 h-2 rounded-full"
style={{ width: `${activity.progress}%` }}
/>
</div>
{/* Date and badge */}
<div className="flex justify-between items-center">
<span
className="text-xs"
style={{ color: dark ? "#9CA3AF" : "#6B7280" }}
>
{activity.date}
</span>
<span className="text-xs bg-green-100 text-green-700 px-2 py-1 rounded-full font-medium">
Selesai
</span>
</div>
</div>
))}
</div>
{/* Second Row - Charts */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
{/* Left Card - Jumlah Dokumen (Bar Chart) */}
<div
className="rounded-xl shadow-sm p-5"
style={{
backgroundColor: dark ? "#141D34" : "white",
border: `1px solid ${dark ? "#141D34" : "white"}`,
}}
>
<h3
className="text-lg font-semibold mb-4"
style={{ color: dark ? "white" : "#1F2937" }}
>
Jumlah Dokumen
</h3>
<ResponsiveContainer width="100%" height={250}>
<BarChart data={documentStats}>
<CartesianGrid
strokeDasharray="3 3"
vertical={false}
stroke={dark ? "#2d3748" : "#E5E7EB"}
/>
<XAxis
dataKey="name"
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="value" radius={[4, 4, 0, 0]}>
{documentStats.map((entry, index) => (
<Cell key={`cell-${index}`} fill={entry.color} />
))}
</Bar>
</BarChart>
</ResponsiveContainer>
</div>
{/* Right Card - Progres Kegiatan (Pie Chart) */}
<div
className="rounded-xl shadow-sm p-5"
style={{
backgroundColor: dark ? "#141D34" : "white",
border: `1px solid ${dark ? "#141D34" : "white"}`,
}}
>
<h3
className="text-lg font-semibold mb-4"
style={{ color: dark ? "white" : "#1F2937" }}
>
Progres Kegiatan
</h3>
<ResponsiveContainer width="100%" height={250}>
<PieChart>
<Pie
data={activityProgressStats.filter(item => item.value > 0)}
cx="50%"
cy="50%"
outerRadius={80}
dataKey="value"
label={({ name, percent }) =>
`${name}: ${percent ? (percent * 100).toFixed(0) : 0}%`
}
>
{activityProgressStats
.filter(item => item.value > 0)
.map((entry, index) => (
<Cell key={`cell-${index}`} fill={entry.fill} />
))}
</Pie>
<Tooltip
contentStyle={{
backgroundColor: dark ? "#1F2937" : "white",
border: `1px solid ${dark ? "#374151" : "#E5E7EB"}`,
borderRadius: "8px",
color: dark ? "white" : "#1F2937",
}}
/>
</PieChart>
</ResponsiveContainer>
{/* Legend */}
<div className="mt-4 space-y-2">
<div className="flex items-center gap-2">
<div className="w-3 h-3 rounded-full bg-blue-500"></div>
<span
className="text-sm"
style={{ color: dark ? "#9CA3AF" : "#4B5563" }}
>
Segera Dikerjakan
</span>
</div>
<div className="flex items-center gap-2">
<div className="w-3 h-3 rounded-full bg-yellow-500"></div>
<span
className="text-sm"
style={{ color: dark ? "#9CA3AF" : "#4B5563" }}
>
Dikerjakan
</span>
</div>
<div className="flex items-center gap-2">
<div className="w-3 h-3 rounded-full bg-green-500"></div>
<span
className="text-sm"
style={{ color: dark ? "#9CA3AF" : "#4B5563" }}
>
Selesai
</span>
</div>
<div className="flex items-center gap-2">
<div className="w-3 h-3 rounded-full bg-red-500"></div>
<span
className="text-sm"
style={{ color: dark ? "#9CA3AF" : "#4B5563" }}
>
Dibatalkan
</span>
</div>
</div>
</div>
</div>
{/* Bottom Row */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Left Card - Diskusi */}
<div
className="rounded-xl shadow-sm p-5"
style={{
backgroundColor: dark ? "#141D34" : "white",
border: `1px solid ${dark ? "#141D34" : "white"}`,
}}
>
<h3
className="text-lg font-semibold mb-4"
style={{ color: dark ? "white" : "#1F2937" }}
>
Diskusi
</h3>
<div className="space-y-3">
{discussions.map((discussion, index) => (
<div
key={index}
className="flex items-start gap-3 p-3 rounded-lg transition-colors"
style={{
backgroundColor: dark ? "#1F2937" : "#F9FAFB",
}}
>
<div className="flex-shrink-0">
<div
className="w-8 h-8 rounded-full flex items-center justify-center"
style={{ backgroundColor: "#DBEAFE" }}
>
<svg
className="w-4 h-4"
style={{ color: "#1E3A5F" }}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"
/>
</svg>
</div>
</div>
<div className="flex-1">
<h4
className="text-sm font-medium"
style={{ color: dark ? "white" : "#1F2937" }}
>
{discussion.title}
</h4>
<p
className="text-xs mt-1"
style={{ color: dark ? "#9CA3AF" : "#6B7280" }}
>
{discussion.sender} {discussion.date}
</p>
</div>
</div>
))}
</div>
</div>
{/* Right Card - Acara Hari Ini */}
<div
className="rounded-xl shadow-sm p-5"
style={{
backgroundColor: dark ? "#141D34" : "white",
border: `1px solid ${dark ? "#141D34" : "white"}`,
}}
>
<h3
className="text-lg font-semibold mb-4"
style={{ color: dark ? "white" : "#1F2937" }}
>
Acara Hari Ini
</h3>
<div className="flex items-center justify-center h-32">
<p
className="text-sm"
style={{ color: dark ? "#9CA3AF" : "#6B7280" }}
>
Tidak ada acara hari ini
</p>
</div>
</div>
</div>
</div>
);
};
export default KinerjaDivisi;